harto
harto

Reputation: 90513

How to get Django form field from model field?

I'd like to create a form that includes fields from two separate models, along with some other regular (non-model) fields. The form will create an instance of each model. I don't think I can use inline formsets for this, since I don't want to include all the fields from both models.

I'd like to create the form field without hard-coding the type of the model fields.

I know I can get a form field from a model field using model_field.formfield(). But how can I get the specific model field?

My first solution:

def get_fields(model_class):
    fields = {}
    for f in model_class._meta.fields:
        fields[f.name] = f

class MyForm(forms.Form):
    foo_name = get_fields(Foo)['name'].formfield()
    bar_name = get_fields(Bar)['name'].formfield()
    other_field = ...

Is there an equivalent of get_fields already? Is this a bad idea? I'm uncomfortable relying on the model _meta attribute. Or am I going about this the completely wrong way?

Upvotes: 22

Views: 13905

Answers (5)

seddonym
seddonym

Reputation: 17259

There is now a documented API for getting the model field from a model class:

my_model_field = MyModel._meta.get_field('my_model_field_name')

Although it's not officially documented until Django 1.8, this should work with earlier Django versions too.

Once you have this, you can get the form field like so:

form_field = my_model_field.formfield()

Upvotes: 13

ruohola
ruohola

Reputation: 24107

It's still undocumented as far as I know, but since Django 3.0 you can make a form field from a single model field called field_name with:

MyModel.field_name.field.formfield()

If your model is a parler.models.TranslatableModel, and the field_name is inside translations, you can use:

MyModel.translations.field.model.field_name.field.formfield()

Or, if you prefer a bit more verbose, but better documented ways, you can use:

MyModel._meta.get_field("field_name").formfield()

and

MyModel._parler_meta.get_model_by_related_name("translations")._meta.get_field("field_name").formfield()

See:
Django get_field
django-parler get_model_by_related_name

Upvotes: 0

naw
naw

Reputation: 1516

You also can take a look at django.forms.models.fields_for_model. That should give you a dictionary of fields, and then you can add the fields of the form

Upvotes: 19

Paul Bormans
Paul Bormans

Reputation: 1312

Another solution can be to create one 'uber'-form that aggregates the concrete modelforms. The form supports the methods that a form normally provides and it forward them to all the child forms. Some will be simple, other complicated. The big advantage of that approach is that no code beyond the form is affected (client validation code and alike). The concept isn't really revolutionary but i guess complicated to add afterwards. Paul

Upvotes: 2

Casey W. Stark
Casey W. Stark

Reputation: 757

You should never have to build the fields yourself unless you want some special behavior.

This should be as simple as using two ModelForms and an extra Form inside one <form> tag in your template with one submit button.

in forms.py:

class Model1Form(forms.ModelForm):
    class Meta:
        model = Model1
        fields = ('fields', 'you', 'want')

class Model2Form(forms.ModelForm):
    class Meta:
        model = Model2
        fields = ('fields', 'you', 'want')

class ExtraFieldsForm(forms.Form):
    extra_field = form.TextField() # or whatever field you're looking for

in views.py:

form1 = Model1Form(request.POST or None)
form2 = Model2Form(request.POST or None)
form3 = ExtraFieldsForm(request.POST or None)

if form1.is_valid() and form2.is_valid() and form3.is_valid():
    form1.save()
    form2.save()
    form3.save()

    ...do other stuff like a redirect...

and in the template:

<form method="POST" action="">{% csrf_token %}
    <fieldset>
        {{ form1|as_uni_form }}
        {{ form2|as_uni_form }}
        {{ form3|as_uni_form }}
        <div class="form_block">
            <input type="submit" value="Save both models"/>
        </div>
    </fieldset>
</form>

I'm used to using django-uni-form, but you can render the form fields however you like. Good luck with your site.

Upvotes: 13

Related Questions