Question

What I'm trying to do is create a dynamic ModelForm that generates extra fields based on one of its class-attributes to use in a ModelAdmin. Something like:

class MyModelForm(forms.ModelForm):
    config_fields = ('book_type', 'is_featured', 'current_price__is_sale')

class MyModelAdmin(admin.ModelAdmin):
    form = MyModelForm

In this case, MyModelForm would generate fields based on the config_fields attribute by performing some introspection. My approach so far looks something like this (based on this answer https://stackoverflow.com/a/6581949/677985):

class ConfigForm(type):
    def __new__(cls, name, bases, attrs):
        if 'config_fields' in attrs:
            for config_field in attrs['config_fields']:

                # ... (removed for clarity)

                attrs.update(fields)
        return type(name, bases, attrs)

class MyModelForm(forms.ModelForm):
    __metaclass__ = ConfigForm
    config_fields = ('book_type', 'is_featured', 'current_price__is_sale')

This approach works well enough, but I'm not quite happy with it for several reasons:

  1. The validation doesn't seem to work, but this is a minor concern for now
  2. I'm not quite sure why the "if config_field in attrs:"-condition is needed, but it is
  3. I would prefer for MyModelForm to inherit instead of setting the __metaclass__ attribute, the base-class could then be easily reused and would allow me to easily override the clean- and __init__-methods.

I tried implementing the third item, the result being that the extra-fields did not show up in the admin-form. I'd be grateful if someone could help me figure this out, or at least point me in the right direction.

I am aware that using a metaclass for this probably overkill, and would guess that part of the problem is that ModelForm already has one or two metaclasses in its inheritance-chain. So if anyone has an alternate solution that accomplishes the same, that would make me just as happy.

Was it helpful?

Solution

I believe that the ModelForm already has a metaclass, but you're overwriting it by setting your own. That's why you're not getting validation or any of the other built in goodness of modelforms.

Instead, you should be able to use type directly to create your ModelForm, which will describe the type you want, but still cause the ModelForms metaclass to do its thing.

Example:

    config_fields = ('book_type', 'is_featured', 'current_price__is_sale')
    # the below is an example, you need more work to construct the proper attrs
    attrs = dict((f, forms.SomeField) for f in config_fields)
    ConfigModelForm = type('DynamicModelForm', (forms.ModelForm,), attrs)

    class MyModelAdmin(admin.ModelAdmin):
        form = ConfigModelForm

You can wrap the first part up in a function if need be, and invoke it for your form attribute in your ModelAdmin.

See my answer here for links and discussion on using type.

OTHER TIPS

How about this,

Basically any form that extends your StepForm will also have the metaclass you wanted in the case below it's StepFormMetaclass, please note that if you have the form defined in some form.py file, you will need to import the form in the ___init___.py so that it will execute it during django starting sequence.

from django.forms.forms import DeclarativeFieldsMetaclass


class StepFormMetaclass(DeclarativeFieldsMetaclass):
    .......
    def __new__(meta_class, name, bases, attributes):
        .....
        return DeclarativeFieldsMetaclass.__new__(meta_class, name, bases, attributes)

class StepForm(six.with_metaclass(StepFormMetaclass, forms.Form, StepFormMixin)):
    def __init__(self, *args, **kwargs):

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


    def as_p(self):
        return ......
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top