Dynamically update ModelForm's Meta class
Asked Answered
T

4

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 = []
Thekla answered 17/11, 2008 at 23:44 Comment(0)
B
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'))
Bibliophage answered 18/11, 2008 at 0:38 Comment(7)
great example thanks. possible to also have the model set dynamically ? form = getModelForm( Passenger, ('field1', 'field2') )Bateau
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.Bibliophage
You'll need to replace return MyPassengerForm() with return MyPassengerForm(*args, **kwargs) if, for example, you need to pass an instance to the form.Gilemette
@Erve1879: no, MyPassengerForm has a closure over args and kwargs so that's not necessaryBibliophage
@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)Gilemette
@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 methodHaydenhaydn
@Gilemette 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.Haydenhaydn
P
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'])
Pangermanism answered 1/4, 2009 at 2:54 Comment(2)
You should probably use {}.pop() instead of setting it then deleting it. exclude_list = kwargs.pop('exclude_list') Saves a line of code. shrugNonobjective
as @JustinAbrahms said i hope you can use kwargs.pop('exclude_list', default_value) some thing like exclude_list=kwargs.pop('exclude_list', '')Balakirev
C
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)
Carilla answered 1/10, 2010 at 16:2 Comment(0)
D
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
Debora answered 13/11, 2015 at 2:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.