Django - 自定义 ModelMultipleChoiceField 无法根据父模型对选择进行分类

Posted

技术标签:

【中文标题】Django - 自定义 ModelMultipleChoiceField 无法根据父模型对选择进行分类【英文标题】:Django - Custom ModelMultipleChoiceField can't categorize choices based on their parent model 【发布时间】:2019-06-14 07:14:27 【问题描述】:

以下可用编辑!

我的目标:

类别 1

----选项1

----选项2

--选项3

类别2

----选项1

----选项2

等等

我有一个父模型 (Venue) 和一个子模型 (Amenity)。一个场地可以有很多便利设施。

在配置我的初始数据并使用 form.as_p 呈现时,一切正常。

但是当我尝试渲染自己的自定义表单以便应用循环时,它不会预先填充它们。

这是我的模板:

<form method="POST" class="ui form">
    % csrf_token %
    % for category in categories %
    <h4 class="ui horizontal divider header">
        <i class="list icon"></i>
        category.category
    </h4>
    <p class="ui center aligned text"><u>category.description</u></p>

    % for amenity in category.amenity_set.all %

    <div class="inline field">
        <label for="choices_amenity.id"></label>
        <div class="ui checkbox">
            <input id="choices_amenity.id" type="checkbox" value="amenity.id" name="choices">

            <label><span data-tooltip="amenity.description" data-position="top left">amenity</span></label>
        </div>

    </div>
    % endfor %
    % endfor %
    <button type="submit" name="submit" class="ui button primary">Next</button>
</form>

我的模型表单:

class AmenitiesForm(ModelForm):
    class Meta:
        model = Venue
        fields = ('choices',)


    choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.CheckboxSelectMultiple,)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if kwargs.get('instance'):
            initial = kwargs.setdefault('initial', )
            initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]

        forms.ModelForm.__init__(self, *args, **kwargs)

    def save(self, commit=True):
        instance = forms.ModelForm.save(self)
        instance.amenity_set.clear()
        instance.amenity_set.add(*self.cleaned_data['choices'])
        return instance

和我的views.py:

class AddAmenitiesView(LoginRequiredMixin, CreateView):
    """
    AddAmenitiesView is the view that prompts the user to select the amenities of their venue.
    """
    model = Venue
    form_class = AmenitiesForm
    template_name = 'venues/add_amenities.html'

    def parent_venue(self):
        """
        returns the parent_venue based on the kwargs
        :return:
        """
        parent_venue = Venue.objects.get(id=self.kwargs["venue_id"])
        return parent_venue

    def get_initial(self):
        initial = super().get_initial()
        initial['choices'] = self.parent_venue().amenity_set.all()
        return initial

    def form_valid(self, form):
        venue = Venue.objects.get(id=self.kwargs['venue_id'])
        form.instance = venue
        # form.instance.owner = self.request.user
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["parent_venue"] = self.parent_venue()
        context["categories"] = AmenitiesCategory.objects.all()
        return context

    def get_success_url(self):
        return reverse('add-amenities', kwargs='venue_id': self.object.id,)

我想这与我的模板有关,因为正常渲染表单,它确实预先填充了模型。

感谢您抽出宝贵时间!

编辑: 通过下面 Raydel Miranda 的回答,我设法编辑了表单的呈现方式:

forms.py:

class CustomAmenitiesSelectMultiple(CheckboxSelectMultiple):
    """
    CheckboxSelectMultiple Parent: https://docs.djangoproject.com/en/2.1/_modules/django/forms/widgets/#CheckboxSelectMultiple
    checkbox_select.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_select.html
    multiple_input.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/multiple_input.html
    checkbox_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_option.html
    input_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/input_option.html

    """
    template_name = "forms/widgets/custom_checkbox_select.html"
    option_template_name = 'forms/widgets/custom_checkbox_option.html'


class AmenitiesForm(ModelForm):
    class Meta:
        model = Venue
        fields = ('choices',)

    choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=CustomAmenitiesSelectMultiple,)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if kwargs.get('instance'):
            initial = kwargs.setdefault('initial', )
            initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]

        forms.ModelForm.__init__(self, *args, **kwargs)

    def save(self, commit=True):
        instance = forms.ModelForm.save(self)
        instance.amenity_set.clear()
        instance.amenity_set.add(*self.cleaned_data['choices'])
        return instance

custom_checkbox_select.html:

% with id=widget.attrs.id %
<div class="inline field">
    <div % if id % id=" id " % endif %% if widget.attrs.class % class=" widget.attrs.class " % endif %>
        % for group, options, index in widget.optgroups %% if group %
        <div>
             group 
            <div>
                % if id % id=" id _ index " % endif %>% endif %% for option in options %
                <div class="checkbox">% include option.template_name with widget=option %</div>
                % endfor %% if group %
            </div>

        </div>
        % endif %% endfor %
    </div>
</div>

% endwith %

custom_checkbox_option.html:

<label% if widget.attrs.id % for=" widget.attrs.id "% endif %>% endif %% include "django/forms/widgets/input.html" %% if widget.wrap_label %  widget.label </label>

根据要求,还有我的models.py:

class TimeStampedModel(models.Model):
    """
    An abstract base class model that provides self-updating
    "created" and "modified" fields.
    """

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class VenueType(TimeStampedModel):
    type = models.CharField(max_length=250)
    description = models.TextField()

    def __str__(self):
        return self.type


class Venue(TimeStampedModel):
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=250)
    type = models.ForeignKey(VenueType, on_delete=models.CASCADE)
    total_capacity = models.PositiveIntegerField(default=0)
    description = models.TextField(blank=False)
    contact_number = PhoneNumberField(blank=True)
    contact_email = models.EmailField(blank=True)
    published = models.BooleanField(default=False)

    def __str__(self):
        return self.name

class AmenitiesCategory(TimeStampedModel):
    category = models.CharField(max_length=250)
    description = models.TextField()

    def __str__(self):
        return self.category


class Amenity(TimeStampedModel):
    category = models.ForeignKey(AmenitiesCategory, on_delete=models.CASCADE)
    venues = models.ManyToManyField(Venue, blank=True)
    space = models.ManyToManyField(Space, blank=True)
    name = models.CharField(max_length=250)
    description = models.TextField()

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['category']

【问题讨论】:

【参考方案1】:

您说在配置我的初始数据并使用 form.as_p 呈现它时,一切都按预期工作,如果是这样,请使用 form.choices 来呈现该字段.

<form method="POST" class="ui form">
    % csrf_token %  
    form.choices
   <button type="submit" name="submit" class="ui button primary">Next</button>
</form>

然后,您需要有一个带有自己模板的自定义CheckboxSelectMultiple(以防您想要向用户展示自定义演示文稿),并在您的表单中使用它:

自定义CheckboxSelectMultiple 可以是:

class MyCustomCheckboxSelectMultiple(CheckboxSelectMultiple):
    template_name = "project/template/custom/my_checkbox_select_multiple.html"

形式如下:

class AmenitiesForm(ModelForm):
    # ... 
    choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.MyCustomCheckboxSelectMultiple)
    # ...

如何实现模板my_checkbox_select_multiple.html,由你决定。

如果您使用的是 1.11 之前的 Django,请访问此链接以了解您为自定义小部件模板而要做的其他事情。

Django widget override template

希望对您有所帮助!

【讨论】:

谢谢你,我现在试试看,会告诉你的! @TonyKyriakidis,没什么,根据我的经验,覆盖小部件模板总是更好,它不仅是您可以告诉如何渲染的数据,还有错误。另一方面,自定义小部件变成了一段更可重用的代码。 我还没有设法让它工作。遍历类别并展示他们的便利设施是欺骗我的部分 我想看看你的模型。

以上是关于Django - 自定义 ModelMultipleChoiceField 无法根据父模型对选择进行分类的主要内容,如果未能解决你的问题,请参考以下文章

python/django models中自定义用户认证及django admin自定义

Django 自定义用户认证

Django:如何查看已定义的自定义标签?

在自定义模板标签中解析 Django 自定义模板标签

django-自定义文件上传存储类

自定义 Django 标签和 jQuery