23

I am hoping to dynamically update a ModelForm's inline Meta class from my view. Although this code seems to update the exclude list in the Meta class, the output from as_p(), as_ul(), etc does not reflect the updated Meta exclude.

I assume then that the html is generated when the ModelForm is created not when the as_*() is called. Is there a way to force the update of the HTML?

Is this even the best way to do it? I just assumed this should work.

Thoughts?

from django.forms import ModelForm

from testprogram.online_bookings.models import Passenger

class PassengerInfoForm(ModelForm):

    def set_form_excludes(self, exclude_list):
        self.Meta.exclude = excludes_list

    class Meta:
        model = Passenger
        exclude = []
Daniel Naab
  • 22,690
  • 8
  • 54
  • 55
ashchristopher
  • 25,143
  • 18
  • 48
  • 49

4 Answers4

59

The Meta class is used to dynamically construct the form definition - so by the time you've created the ModelForm instance, the fields not in the exclude have already been added as the new object's attributes.

The normal way to do it would be to just have multiple class definitions for each possible exclude list. But if you want the form itself to be dynamic, you'll have to create a class definition on the fly. Something like:

def get_form(exclude_list):
    class MyForm(ModelForm):
        class Meta:
            model = Passenger
            exclude = exclude_list
    return MyForm

form_class = get_form(('field1', 'field2'))
form = form_class()

UPDATE: I just revisited this post and thought I'd post a little more idiomatic way to handle a dynamic class:

def PassengerForm(exclude_list, *args, **kwargs):
    class MyPassengerForm(ModelForm):
        class Meta:
            model = Passenger
            exclude = exclude_list

        def __init__(self):
            super(MyPassengerForm, self).__init__(*args, **kwargs)

    return MyPassengerForm()

form = PassengerForm(('field1', 'field2'))
Daniel Naab
  • 22,690
  • 8
  • 54
  • 55
  • great example thanks. possible to also have the model set dynamically ? form = getModelForm( Passenger, ('field1', 'field2') ) – jujule Feb 25 '10 at 15:15
  • 2
    jujule: sure, just add a positional argument to the form function: "def PassengerForm(exclude_list, model, *args, **kwargs)", and set the Meta.model field in the same way Meta.exclude is set. – Daniel Naab Feb 26 '10 at 01:28
  • 1
    You'll need to replace `return MyPassengerForm()` with `return MyPassengerForm(*args, **kwargs)` if, for example, you need to pass an instance to the form. – Erve1879 Feb 06 '15 at 12:25
  • @Erve1879: no, `MyPassengerForm` has a closure over `args` and `kwargs` so that's not necessary – Daniel Naab Feb 06 '15 at 14:30
  • 2
    @DanielNaab - interesting. I did wonder if I was on the wrong track, but I had to add `*args, **kwargs` in order to get editing an existing instance to work; passing `instance=object` to the form does not work without `*args, **kwargs` (for me, anyway) – Erve1879 Feb 06 '15 at 14:45
  • 1
    @DanielNaab perhaps `get_passenger_form` is a better name than `PassengerForm` to conform to styling of the rest of the code? `PassengerForm` looks like its a Class but its actually a method – Anupam Oct 07 '18 at 10:43
  • 1
    @Erve1879 For me also, it works only when returning `MyPassengerForm(*args, **kwargs)` in case of an instance passed. I think the reason is that `args`, `kwargs` are assigned outside the scope of the `MyPassengerForm` above, so when its instantiated as `MyPassengerForm()`, `args` and `kwargs` do not contain anything. – Anupam Oct 07 '18 at 10:51
13

Another way:

class PassengerInfoForm(ModelForm):
    def __init__(self, *args, **kwargs):
        exclude_list=kwargs.pop('exclude_list', '')

        super(PassengerInfoForm, self).__init__(*args, **kwargs)

        for field in exclude_list:
            del self.fields[field]

    class Meta:
        model = Passenger

form = PassengerInfoForm(exclude_list=['field1', 'field2'])
suhailvs
  • 20,182
  • 14
  • 100
  • 98
user85461
  • 6,510
  • 2
  • 34
  • 40
  • 8
    You should probably use {}.pop() instead of setting it then deleting it. exclude_list = kwargs.pop('exclude_list') Saves a line of code. *shrug* – Justin Abrahms Aug 06 '09 at 15:26
  • as @JustinAbrahms said i hope you can use **kwargs.pop('exclude_list', default_value)** some thing like `exclude_list=kwargs.pop('exclude_list', '')` – suhailvs Oct 14 '14 at 00:51
4

Similar approach, somewhat different goal (generic ModelForm for arbitrary models):

from django.contrib.admin.widgets import AdminDateWidget
from django.forms import ModelForm
from django.db import models

def ModelFormFactory(some_model, *args, **kwargs):
    """
    Create a ModelForm for some_model
    """
    widdict = {}
    # set some widgets for special fields
    for field in some_model._meta.local_fields:
        if type(field) is models.DateField:
            widdict[field.name] = AdminDateWidget()

    class MyModelForm(ModelForm): # I use my personal BaseModelForm as parent
        class Meta:
            model = some_model
            widgets = widdict

    return MyModelForm(*args, **kwargs)
sneawo
  • 3,543
  • 1
  • 26
  • 31
Hraban
  • 545
  • 5
  • 10
3

Use modelform_factory (doc):

from django.forms.models import modelform_factory

from testprogram.online_bookings.models import Passenger

exclude = ('field1', 'field2')
CustomForm = modelform_factory(model=Passenger, exclude=exclude)  # generates ModelForm dynamically
custom_form = CustomForm(data=request.POST, ...)  # form instance
keisuke
  • 2,123
  • 4
  • 20
  • 31