【问题标题】:Django - MultipleCheckBoxSelector with m2m field - How to add object instead of save_m2m()Django - 带有 m2m 字段的 MultipleCheckBoxSelector - 如何添加对象而不是 save_m2m()
【发布时间】:2013-08-11 19:35:31
【问题描述】:

我使用带有自定义表单选项的 inlineformset_factory 来更改查询集和 m2m 字段的小部件,即:ezMap。我希望表单让用户可以选择使用 CheckBoxSelectMultiple 小部件将当前selected_map 添加或删除到 m2m 字段。但是,我不想让用户能够删除已经存在的其他对象。问题是当我使用formset.save_m2m() 保存表单集时,它会覆盖该字段并删除所有已保存的对象。

我怎样才能只添加一个新对象而不删除其他对象?


模型:(删除了一些不必要的字段)

class Shapefile(models.Model):
    filename = models.CharField(max_length=255)

class EzMap(models.Model):
    map_name = models.SlugField(max_length=50)
    layers = models.ManyToManyField(Shapefile, verbose_name='Layers to display', null=True, blank=True)

class LayerStyle(models.Model):
    styleName = models.SlugField(max_length=50)
    layer = models.ForeignKey(Shapefile)
    ezMap = models.ManyToManyField(EzMap)

表格:

class polygonLayerStyleFormset(forms.ModelForm):
    add_to_map = forms.BooleanField(required=False)
    def __init__(self, *args, **kwargs):
        self.map_selected =  kwargs.pop("map_selected", None)
        super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
        self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
        self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
        self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
        self.fields['ezMap'].help_text =""

    class Meta:
        model = LayerStyle

    def save(self, *args, **kwargs):
        instance = super(polygonLayerStyleFormset, self).save(*args, **kwargs)
        instance.add_to_map = self.cleaned_data['add_to_map']
        return instance


ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, can_delete=True, extra=1, max_num=5,
                fields = ['styleName', 'conditionStyle', 'fillColor', 'fillOpacity', 'strokeColor', 'strokeWeight', 'ezMap'], form=polygonLayerStyleFormset)

观看次数:

def setLayerStyle(request, map_name, layer_id):
    map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
    layer_selected = Shapefile.objects.get(id=layer_id)
    layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
    styleFormset = ftlStylePolygonFormset
    if request.POST:
        formset = styleFormset(request.POST, instance=layer_selected)
        if formset.is_valid():
            instances = formset.save()
            for instance in instances:
                if instance.add_to_map:
                    instance.ezMap.add(map_selecte)
                else:
                    instance.ezMap.remove(map_selected)
            save_link = u"/ezmapping/map/%s" % (map_name)
            return HttpResponseRedirect(save_link)
    else:
        formset = styleFormset(instance=layer_selected)
        #set initial data for add_to_map
        for form in formset:
            if form.instance.pk:
                if map_selected in form.instance.ezMap.all():
                    form.fields['add_to_map'].initial = {'add_to_map': True}

【问题讨论】:

    标签: django django-forms


    【解决方案1】:

    我对您使用 ezMap 表单域所做的事情感到困惑。您将其查询集设置为单元素列表,然后为其使用 CheckboxSelectMultiple 小部件。您是否设置为让用户取消选择该匹配地图,但不添加新地图?

    要在初始化时执行此操作,您需要定义一个自定义基 formset 类并将其作为 formset 参数传递给您的工厂。

    from django.forms.models import BaseInlineFormSet
    
    class polygonLayerStyleForm(forms.ModelForm):
        def __init__(self, *args, **kwargs):
            self.map_selected =  kwargs.pop("map_selected", None)
            super(polygonLayerStyleForm, self).__init__(*args, **kwargs)
            self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
            self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
            self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
            self.fields['ezMap'].help_text =""
    
        class Meta:
            model = LayerStyle
    
    class polygonLayerStyleFormset(BaseInlineFormSet):
        def __init__(self, *args, **kwargs):
            self.map_selected =  kwargs.pop("map_selected", None)
            super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
    
        def _construct_form(self, i, **kwargs):
            kwargs['map_selected'] = self.map_selected
            return super(polygonLayerStyleFormset, self)._construct_form(i, **kwargs)
    
    ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, formset=polygonLayerStyleFormset, form=polygonLaterStyleForm, # and other arguments as above
                                                   )
    

    在您的视图中创建字段后,通过表单集表单并直接更改字段的查询集可能更简单:

        formset = ftlStylePolygonFormset(instance=layer_selected)
        for form in formset.forms:
            form.fields['ezMap'].queryset = EzMap.objects.filter(id=map_selected.id)
    

    说到这里,通常的约定是在视图中拆分 POST 和 GET 的情况:

    from django.shortcuts import render
    
    def setLayerStyle(request, map_name, layer_id):
        map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
        layer_selected = Shapefile.objects.get(id=layer_id)
        layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
        if request.method == 'POST':
            formset = ftlStylePolygonFormset(request.POST, instance=layer_selected, map_selected=map_selected)
            if formset.is_valid():
                instances = formset.save()
                save_link = u"/ezmapping/map/%s" % (map_name)
                return HttpResponseRedirect(save_link)
        else:
            formset = ftlStylePolygonFormset(instance=layer_selected, map_selected=map_selected)
        return render(request, "ezmapping/manage_layerStyle.html", {'layer_style': layerStyle_selected, 'layerStyleformset': formset, 'layer': layer_selected})
    

    最好使用redirect 快捷方式来反向查找成功重定向的视图,而不是对目标 URL 进行硬编码。并且在基于 URL 参数访问对象时使用 get_object_or_404 或其他等效项 - 现在虚假 URL 将触发异常并给用户一个 500 状态错误,这是不可取的。

    有条件地添加到ezMap 关系:

    class polygonLayerStyleForm(forms.ModelForm):
        add_to_map = forms.BooleanField()
    
        def save(self, *args, **kwargs):
            instance = super(polygonLayerStyleForm, self).save(*args, **kwargs)
            instance.add_to_map = self.cleaned_data['add_to-map']
            return instance
    

    然后在视图中:

    instances = formset.save()
    for instance in instances:
        if instance.add_to_map:
            instance.ezMap.add(map_selected)
    

    您也可以在 save 方法中调用 add,但是您必须在之前的某个时间将地图设置为成员数据 - 更重要的是,处理 commit=False 的情况。

    【讨论】:

    • 非常感谢,您是对的,ezmap 表单字段允许用户仅将当前 selected_map 添加或删除到 m2m 字段
    • 你的所有解释都很完美,除了get_object_or_404,我不确定。你的意思是我应该使用get_object_or_404 而不是layer_selected = Shapefile.objects.get(id=layer_id) 和其他人?
    • 我就是这样做的。基本上,想想如果用户编辑 url 以包含错误的地图名称,您希望发生什么。或者,更天真地,为稍后重命名的地图添加书签 - 在后一种情况下,设置永久重定向会很礼貌,但即使是 404 也比 500 好。
    • 您的解决方案正在运行(我使用了更简单的解决方案),但是当我使用 formset.save_m2m() 保存表单集时,它会覆盖 m2m 字段,而不仅仅是向其中添加新对象。你知道我该怎么做吗? (见更新的问题)
    • 确保在 POST 路径中也这样做。表单不会直接在两个请求之间持续存在,实际上是 POST 案例将提交的值与self.initial 进行比较。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    • 2013-02-05
    • 2011-01-27
    • 1970-01-01
    • 1970-01-01
    • 2017-10-27
    相关资源
    最近更新 更多