【问题标题】:Django: Same form multiple times in one viewDjango:在一个视图中多次使用相同的表单
【发布时间】:2014-02-18 12:37:00
【问题描述】:

我有三个模型 User (django.contrib.auth)、ScreeningUser_ScreeningUser_Screening 是一个 m2m 表,带有额外字段 status

#models.py
from django.db import models
from django.contrib.auth.models import User

class Screening(models.Model):
    title = models.CharField(max_length=255)
    start = models.DateTimeField()
    user_relation = models.ManyToManyField(User, blank=True,
        through='User_Status')

class User_Status(models.Model):
    ATTENDING = 'c'
    NOT_ATTENDING = 'n'
    PROJECTION = 'p'
    STATUS_CHOICES = (
        (ATTENDING, 'attending'),
        (NOT_ATTENDING, 'not attending'),
        (PROJECTING, 'projecting'),
    )
    screening = models.ForeignKey(Screening)
    user = models.ForeignKey(User)
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

现在我想制作一个视图,显示所有即将上映的电影。到目前为止,很容易:

#views.py
@login_required()
def index(request):
    current_screenings = Screening.objects.filter(start__gte=timezone.now())
    context = {'current_screenings': current_screenings}
    return render(request, 'schedule/index.html', context)

在此视图中,登录用户应该能够更新他们的status(来自User_Screening 表)。也可能是用户还没有此筛选的记录,因此应该创建一个。

我不明白,我如何为每个筛选存档一个表单下拉字段,用户可以在其中选择他的状态。 (? 如果尚未设置状态,attendingnot attendingprojection

据我了解,我需要多种表格,这些表格都知道它们与哪些筛查相关。

另外,Formsets 似乎不起作用,因为我不能总是用初始数据填写表格,因为部分或全部放映可能缺少记录。此外,我不知道哪种形式属于哪种筛选对象。

更新: 我想在 HTML 中得到的结果是这样的:

<form>
  <h1>Current Screening 1</h1>
    <select onchange="submit()" name="screening_user" id="s1">
      <option value="att">Attending</option>
      <option value="not_att">Not Attending</option>
      <option selected="selected" value="pro">Projection</option>
    </select>
  <h1>Current Screening 2</h1>
    <select onchange="submit()" name="screening_user" id="s2">
      <!-- The 'Please Select' option is only visible, if the user does not
        have a relation in 'User_Screening' for this screening -->
      <option selected="selected" value="none">Please Select</option>
      <option value="att">Attending</option>
      <option value="not_att">Not Attending</option>
      <option value="pro">Projection</option>
    </select>
  <!-- More Screenings -->
  <h1>Current Screening n</h1>
    <!-- select for screening n -->
</form>

因此,需要根据登录用户从具有预加载数据的相同表单中更改表单数量。

【问题讨论】:

    标签: django django-1.6


    【解决方案1】:

    如果筛选与用户有 m2m 关系,则参加的用户可以在该列表中。如果不参加……好吧,比他们不参加!这有意义吗?

    class Screening(models.Model):
        title = models.CharField(max_length=255)
        date = models.DateTimeField()
        attending = models.ManyToManyField(User)
    

    表格:

    class ScreeningForm(ModelForm):
        class Meta:
            model = Screening
            fieds = ['attending', ]
    

    表单集:

    ScreeningFormSet = modelformset_factory(Screenig, max_num=1)
    formset = ScreeningFormSet(Screening=Screening.objects.filter(date__gte=now))
    

    【讨论】:

    • 好吧,我可能需要澄清一下,不仅仅是status'来'和'不来'。我为这个问题缩短了它们。另外:如果我与状态“不来”有关系,我知道用户主动决定不来,这是我确实需要的,出于电子邮件提醒的目的。
    【解决方案2】:

    一方面,您可以通过 ajax 请求发送表单数据。在该请求中,您只需发送一个表格并处理数据。您不需要任何表单集。根据您的用例,这可能会给您的服务器增加不必要的流量。

    另一个解决方案是添加另一个STATUS_CHOICE,例如“未选择任何内容”作为默认值,如果数据库中没有筛选用户组合的条目,则使用该表单。在您的视图的 POST 处理程序中,您可以检查表单数据是否设置为此值。在这种情况下,您只需忽略该表单。如果它是另一个值,则相应地设置 db 条目。

    【讨论】:

    • 我真正想要归档的是在一个视图中多次使用相同的表单(如标题所示)。我更新了我的问题以澄清我期望的 html 输出。
    • 我没有发现问题。只需在您的视图中创建一个表单列表并创建一个模板来迭代该表单列表并呈现它们。当表单更改时调用 onChange() 并将包含表单 id 的表单数据提交到相应的视图并在那里进行处理。
    【解决方案3】:

    在feenode 上#django 的帮助下,我解决了我的问题。最后,我坚持使用表单集。

    考虑到我的问题中的models.py,我不得不稍微更改User_Status,如果筛选尚不存在关系,则为Select-Widget 添加NO_STATUS 选项。请注意,NO_STATUS 不是model.CharField 的选择!

    #models.py
    class User_Status(models.Model):
    NO_STATUS = '?'
    PROJECTIONIST = 'p'
    ATTENDING = 'c'
    NOT_ATTENDING = 'n'
    STATUS_CHOICES = [
        (ATTENDING, 'Anwesend'),
        (NOT_ATTENDING, 'Nicht anwesend'),
        (PROJECTIONIST, 'Vorführer'),
    ]
    STATUS_CHOICES_AND_EMPTY = [(NO_STATUS, 'Please choose')] + STATUS_CHOICES
    screening = models.ForeignKey(Screening)
    user = models.ForeignKey(User)
    status = models.CharField(max_length=1, choices=STATUS_CHOICES,
        default=ATTENDING)
    

    接下来,表格。修改后的__init__ 需要注意,“请选择”只是一个有效的选择,如果它被设置为status 的初始值。否则,选择不会显示。

    #forms.py
    class ScreeningUserStatusForm(forms.Form):
        screening_id = forms.IntegerField(min_value=1)
        status = forms.ChoiceField(choices=User_Status.STATUS_CHOICES_AND_EMPTY, 
            widget=forms.Select(attrs={"onChange":'submit()'}))
    
        def __init__(self, *args, **kwargs):
            super(ScreeningUserStatusForm, self).__init__(*args, **kwargs)
            if self['status'].value() != User_Status.NO_STATUS:
                #Once, a status is selected, the status should not be unset.
                self.fields['status'].choices=User_Status.STATUS_CHOICES
    

    最后是视图,它使用表单集将所有当前放映的内容放入其中。

    def update_user_status(screening, user, status):
        #Get old status, if already exists.
        new_status = User_Status.objects.get_or_create(screening=screening,
            user=user)
    
        # Add to selected status
        new_status.status = status 
        new_status.save()
    
    @login_required()
    def index(request):
        """
        displays all upcoming screenings
        """
    
        # Get current screenings
        current_screening_set = Screening.objects.filter(start__gte=timezone.now() - datetime.timedelta(hours=24)).order_by('start')
        current_screening_list = current_screening_set.values('id')
    
        ScreeningFormSet = formset_factory(ScreeningUserStatusForm, extra=0)
    
        if request.method == 'POST':
            #Get a formset bound to data from POST
            formset = ScreeningFormSet(request.POST, request.FILES)
            if formset.is_valid():
                for form in formset.cleaned_data:
                    s = get_object_or_404(Screening, pk=form['screening_id'])
                    if form['status'] != User_Status.NO_STATUS:
                        update_user_status(screening=s, user=request.user, status=form['status'])
        else:
            #create a fresh formset
            for form_data in current_screening_list:
                screening = Screening.objects.get(pk=form_data['id'])
                status = User_Status.objects.filter(user=request.user, screening=screening)
                if status.count() != 1:
                    form_data['status'] = u'?'
                else:
                    form_data['status'] = status.first().status
                form_data['screening_id'] = form_data['id']
    
            formset = ScreeningFormSet(initial=current_screening_list)
    
        forms_and_curr_screenings = zip(formset.forms, current_screening_set)
    
        context = {'formset' : formset, 'current_screenings' : forms_and_curr_screenings}
        return render(request, 'schedule/index.html', context)
    

    formset.formscurrent_screening_set 一起压缩,为每个来源提供额外数据。 formset 被额外提供给management_form 的模板。

    模板可能如下所示

    <!-- index.html -->
    {% if current_screenings %}
        <form method="post">
        {{ formset.management_form }}
        {% csrf_token %}
        <table>
          <thead>
            <tr>
              <th>Screening</th>
              <th>My Status</th>
            </tr>
          </thead>
          <tbody>
          {% for form, screening in current_screenings %}
            <tr>
              <td>{{ screening }}</a></td>
              <td>
                {{ form.screening_id.as_hidden }}
                {{ form.status }}
              </td>
            </tr>
          {% endfor %}
          </tbody>
        </table>
      </form>
    {% endif %}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-04
      • 2020-11-03
      • 2018-01-25
      • 2020-05-16
      • 2021-04-14
      • 1970-01-01
      • 2020-06-24
      • 2011-09-22
      相关资源
      最近更新 更多