django: exclude certain form elements based on a condition

Question:

I have some form fields that I want to include/exclude based on whether or not a certain condition is met. I know how to include and exclude form elements, but I am having difficulty doing it when I want it elements to show based on the outcome of a function.

Here is my form:

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

What I am trying to accomplish is, based on the standings of total points, a team should not be able to change their driver until their specified day. As in, the last team in the standings can add/drop a driver on Monday, second to last team can add/drop on Tuesday, and so on…

So the first problem — how do I get the Team instance inside the form itself from the id that was passed in. And, how do I include/exclude based on the result of draft_unlocked().

Or perhaps there is a better way to do all of this?

Thanks a lot everyone.

Asked By: apexdodge

||

Answers:

This is actually fairly straightforward (conditional field settings) – here’s a quick example:

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

So, key points from this:

  • self.instance represents the bound object, if the form is bound. I believe it is passed in as a named argument, therefore in kwargs, which the parent constructor uses to create self.instance.
  • You can modify the field properties after you’ve called the parent constructor.
  • widgets are how forms are displayed. HiddenInput basically means <input type="hidden" .../>.

There is one limitation; I can tamper with the input to change a value if I modify the submitted POST/GET data. If you don’t want this to happen, something to consider is overriding the form’s validation (clean()) method. Remember, everything in Django is just objects, which means you can actually modify class objects and add data to them at random (it won’t be persisted though). So in your __init__ you could:

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

Then in your clean method for said form:

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.
Answered By: user257111

You can do what you need by adding your own init where you can pass in the id when you instantiate the form class:

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

Hope that helps you out.

Answered By: Brandon Taylor
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.