django:根据条件排除某些表单元素

6 投票
2 回答
6075 浏览
提问于 2025-04-16 18:05

我有一些表单字段,想根据某个条件来决定是否显示这些字段。我知道怎么添加和删除表单元素,但当我想根据一个函数的结果来显示这些元素时,就遇到困难了。

这是我的表单:

class ProfileForm(ModelForm):
    # this_team = get Team instance from team.id passed in
    # how?

    def draft_unlocked(self):
        teams = Team.objects.order_by('total_points')
        count = 0
        for team in teams:
            if team.pk == this_team.pk:
                break
            count += 1

        now = datetime.datetime.now().weekday()
        if now >= count:
            # show driver_one, driver_two, driver_three
        else:
            # do not show driver_one, driver_two, driver_three

class Meta:
    model = Team

我想实现的目标是,根据总积分的排名,某个队伍在指定的日期之前不能更换他们的司机。比如,排名最后的队伍可以在星期一添加或删除司机,倒数第二的队伍可以在星期二进行同样的操作,依此类推……

所以第一个问题是——我怎么才能从传入的ID中获取到表单里的Team实例?还有,我该如何根据draft_unlocked()的结果来决定是否显示这些字段。

或者,也许还有更好的方法来实现这一切?

非常感谢大家。

2 个回答

2

你可以通过添加自己的初始化方法来实现你需要的功能,这样在创建表单类的时候就可以传入ID了:

class ProfileForm(ModelForm):
    def __init__(self, team_id, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)

        this_team = Team.objects.get(pk=team_id)

        teams = Team.objects.order_by('total_points')
        count = 0
        for team in teams:
            if team.pk == this_team.pk:
                break
            count += 1

        now = datetime.datetime.now().weekday()
        if now >= count:
            # show driver_one, driver_two, driver_three
        else:
            # do not show driver_one, driver_two, driver_three

class Meta:
    model = Team

#views.py
def my_view(request, team_id):
    profile_form = ProfileForm(team_id, request.POST or None)
    #more code here

希望这对你有帮助。

8

其实这很简单(条件字段设置)——这里有个快速的例子:

from django.forms import Modelform
from django.forms.widgets import HiddenInput

class SomeForm(ModelForm):

    def __init__(self, *args, **kwargs):
        # call constructor to set up the fields. If you don't do this 
        # first you can't modify fields.
        super(SomeForm, self).__init__(*args, **kwargs)

        try:
            # make somefunc return something True
            # if you can change the driver.
            # might make sense in a model?
            can_change_driver = self.instance.somefunc()                          
        except AttributeError:
            # unbound form, what do you want to do here?
            can_change_driver = True # for example?

        # if the driver can't be changed, use a input=hidden
        # input field.
        if not can_change_driver:
            self.fields["Drivers"].widget = HiddenInput()
        
    class Meta:
        model = SomeModel

所以,以下是一些关键点:

  • self.instance 代表了绑定的对象,如果表单是绑定的。我认为它是作为一个命名参数传入的,因此在 kwargs 中,父类构造函数用它来创建 self.instance
  • 在调用父类构造函数之后,你可以修改字段的属性。
  • widgets 是表单的显示方式。HiddenInput 基本上意味着 <input type="hidden" .../>

有一个限制;如果我修改提交的 POST/GET 数据,我可以篡改输入来改变值。如果你不想让这种情况发生,可以考虑重写表单的验证(clean())方法。记住,在 Django 中,一切都是对象,这意味着你实际上可以随机修改类对象并向它们添加数据(不过这些数据不会被保存)。所以在你的 __init__ 方法中,你可以:

self.instance.olddrivers = instance.drivers.all()

然后在你所说的表单的 clean 方法中:

def clean(self):
    # validate parent. Do this first because this method
    # will transform field values into model field values.
    # i.e. instance will reflect the form changes.
    super(SomeForm, self).clean()

    # can we modify drivers?
    can_change_driver = self.instance.somefunc() 

    # either we can change the driver, or if not, we require 
    # that the two lists are, when sorted, equal (to allow for 
    # potential non equal ordering of identical elements).

    # Wrapped code here for niceness
    if (can_change_driver or 
                   (sorted(self.instance.drivers.all()) == 
                    sorted(self.instance.olddrivers))):  
        return True
    else:
        raise ValidationError() # customise this to your liking.

撰写回答