Django - 在模板中手动单独呈现CheckboxSelectMultiple()小部件

32

我有这两个模型:

models.py

class App(models.Model):
    app_name = models.SlugField(max_length=50)
    options_loaded = models.ManyToManyField(Option)
    created_by = models.ForeignKey(User)

    def __unicode__(self):
        return self.name

class Option(models.Model):
    option_name = models.SlugField(max_length=50)
    condition = models.BooleanField('Enable condition')
    option = models.BooleanField('Enable option1')
    created_by = models.ForeignKey(User)

    def __unicode__(self):
        return self.option_name

我希望呈现一个表格,如下所示。其中复选框来自不同的模型(第一列使用 CheckboxSelectMultiple() 组件的 M2M 字段),而选项名称可以是 <a href="/link/">Option_name</a>

enter image description here

这种方式可行吗?


3
非常好,我从未想过那个。 - catherine
尝试设置悬赏以吸引他人回答。我无法提供悬赏,也没有链接。 - catherine
我可以设置赏金,但无法提供大额奖励... - Below the Radar
尝试提供更高的赏金。我会在你的问题中给你点赞,这样你就可以获得更多的声望。 - catherine
1
您有一个基本的数据建模问题。选项的布尔字段只能填写一次。为了使用布尔字段进行建模,布尔字段需要在一个通过模型中与应用程序相关联的选项中。 - Francis Yaconiello
3个回答

39

这是我的简单解决方案:在模板中手动渲染CheckboxSelectMultiple()

<table>
<thead>
  <tr>
    <td>&nbsp;</td>
    <td>V</td>
    <td>S</td>
  </tr>
</thead>    
{% for pk, choice in form.options.field.widget.choices %}
<tr>
  <td><a href="/link/{{ choice }}">{{ choice }}</a></td>
  <td>
    <label for="id_options_{{ forloop.counter0 }}">
      <input {% for m2moption in model.m2moptions.all %}{% if option.pk == pk %}checked="checked"{% endif %}{% endfor %} type="checkbox" id="id_options_{{ forloop.counter0 }}" value="{{ pk }}" name="options" />
    </label>
  </td>
</tr>
{% endfor %}                
</table>

我知道这是一篇旧帖子,但我遇到了类似的问题,你的解决方案让我挽救了今天。我有一个问题想问你,我在想你能不能帮助我。我有一个表单用来编辑模型,但是使用你的解决方案后,所有的复选框都未被选中,与模型中的选项不符。你有什么想法吗?谢谢。 - user2466766
也许您正在使用与此解决方案不完全兼容的新Django版本。在上面的代码中,管理复选框选中状态的部分是:{% for option in app.options.all %}{% if option.pk == pk %}checked="checked"{% endif %}{% endfor %} - Below the Radar
1
谢谢你的时间。我已经让它工作了。我误解了 ´{% for option in app.options.all %}´。也许应该是 ´{% for m2moption in model.m2moptions.all %}´。再次感谢。你的解决方案很棒。 - user2466766
好主意,有了你的建议会更清晰。 - Below the Radar
感谢您提供这个解决方案。对于那些在此寻找手动呈现CheckboxSelectMultiple()的标准方式的人,我在这里添加了另一个答案:https://dev59.com/a2Up5IYBdhLWcg3wXGsZ#42152304 ,以此方式来实现(使用 HTML 列表——Django 呈现的方式)。 - Anupam

13

http://dev.yaconiello.com/playground/example/one/

首先,我会像这样重新构建你的模型。按照你目前的设置,选项/应用复选框关系会表现得很差。每个选项只能具有一个布尔值,它与所有应用对象共享。

模型

from django.db import models
from django.utils.translation import ugettext as _

class Option(models.Model):
    condition = models.CharField(
        verbose_name = _(u'Condition Text'),
        max_length = 255,
    )
    option = models.CharField(
        verbose_name = _(u'Option Text'),
        max_length = 255,
    )

    def __unicode__(self):
        return self.condition


class App(models.Model):
    title = models.CharField(
        verbose_name = _(u'App Name'), 
        max_length = 255
    )
    slug = models.SlugField(
        max_length = 50,
        unique = True
    )
    activated = models.BooleanField(
        verbose_name = _(u'Activated'),
        default = False,
    )
    options = models.ManyToManyField(
        Option,
        through="AppOption"
    )

    def __unicode__(self):
        return self.title


class AppOption(models.Model):
    app = models.ForeignKey(
        App,
        verbose_name = _(u'App'), 
    )
    option = models.ForeignKey(
        Option,
        verbose_name = _(u'Option'), 
    )
    condition_activated = models.BooleanField(
        verbose_name = _(u'Condition Activated'),
        default = False,
    )
    option_activated = models.BooleanField(
        verbose_name = _(u'Option Activated'),
        default = False,
    )

    class Meta:
        unique_together = (("app", "option"),)

    def __unicode__(self):
        return "%s %s (%s | %s | %s)" % (self.app, self.option, self.app.activated, self.option_activated, self.condition_activated)

其次,您应该使用带有自定义逻辑的模型表单集和模型表单...

表单

from django.forms.models import modelformset_factory
from django import forms

class AppOptionForm(forms.ModelForm):
    class Meta:
        model = AppOption
        fields = ("app", "option", "condition_activated", "option_activated")

AppOptionFormSet = modelformset_factory(AppOption, form=AppOptionForm)

class AppForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(AppForm, self).__init__(*args, **kwargs)

        if self.instance:
            self.appoptions_prefix = "appoptions-%s"%self.instance.pk
            self.appoptions_formset = AppOptionFormSet(prefix=self.appoptions_prefix, 
                queryset=AppOption.objects.filter(app=self.instance).order_by('option'))

    class Meta:
        model = App
        fields = ("id", "activated",)

AppFormSet = modelformset_factory(App, form=AppForm)

好的,发生的事情是我们创建了一个AppOption模型表单,然后将其转换为模型表单集。

接下来,我们创建了一个App模型表单,该表单具有重写的init方法,该方法为App模型表单的实例实例化了一个AppOption表单集。

最后,我们使用App模型表单创建了一个模型表单集。

这是一个视图,用于保存所有应用程序和应用程序选项

def one(request):
    if request.method == 'POST':
        formset = AppFormSet(request.POST, prefix="apps") # do some magic to ALSO apply POST to inner formsets
        if formset.is_valid(): # do some magic to ALSO validate inner formsets
            for form in formset.forms:
                # saved App Instances
                form.save()
                for innerform in form.appoptions_formset:
                    # saved AppOption instances
                    innerform.save()
    else:
        formset = AppFormSet(prefix="apps")

    options = Option.objects.all()

    return render(
        request,
        "playground/example/one.html",
        {
            'formset' : formset,
            'options' : options,
        }
    )

模板

this is a test
<style>
thead td {
    width: 50px;
    height: 100px;
}
.vertical {
    -webkit-transform: rotate(-90deg);
    -moz-transform: rotate(-90deg);
    -ms-transform: rotate(-90deg);
    -o-transform: rotate(-90deg);
    filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
}
</style>
<form>
<table>
<thead>
    <tr>
        <td>&nbsp;</td>
        <td><p class="vertical">Activate App</p></td>
        {% for option in options %}
        <td><p class="vertical">{{ option.condition }}</p></td>
        <td><p class="vertical">{{ option.option }}</p></td>
        {% endfor %}
    </tr>
</thead>
{% for form in formset.forms %}
    {% if form.instance.pk %}
    <tr>
        <td align="center">{{ form.instance.title }}{{ form.id.as_hidden }}</td>
        <td align="center">{{ form.activated }}</td>
        {% for optionform in form.appoptions_formset.forms %}
        {% if optionform.instance.pk %}
        <td align="center">
            {{ optionform.app.as_hidden }}
            {{ optionform.app.as_hidden }}
            {{ optionform.condition_activated }}
        </td>
        <td align="center">{{ optionform.option_activated }}</td>
        {% endif %}
        {% endfor %}
    </tr>
    {% endif %}
{% endfor %}
</table>
</form>

注意:在我写“# do some magic...”的地方,您需要编写代码来完成这些操作,否则提交此示例将无法正常工作。我希望我已经让您走得足够远,以便您可以完成这个任务。 - Francis Yaconiello

8

如果你想手动渲染CheckBoxMultipleSelect,但是要按照标准方式(Django使用HTML列表的方式),以下是我总结的方法(@below-the-radar的解决方案帮助我实现了它)

<ul id="id_{{field.name}}">
  {% for pk, choice in field.field.widget.choices %}
    <li>
      <label for="id_{{field.name}}_{{ forloop.counter0 }}">
      <input id="id_{{field.name}}_{{ forloop.counter0 }}" name="{{field.name}}" type="checkbox" value="{{pk}}" />
      {{ choice }}
      </label>
    </li>
  {% endfor %}
</ul>

5
虽然这并没有回答提问者的问题,但这正是我在寻找的。 - Aidan
你能详细解释一下吗?我不太理解 View 到底应该返回什么,也无法复现它... - swasher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接