19

I need to display one form, with multiple fields from 2 different models. Form will contain only part of fields from models, and layout will be made using the crispy forms.

My models:

class Company(BaseModel):
    title = models.CharField(_('Company'), max_length=128)
    domain = models.CharField(_('Domain'), max_length=128)
class Account(BaseModel):
    company = models.ForeignKey(Company)
    user = models.OneToOneField(User)
    role = models.CharField(_('Role'), choices=ROLES, default='member', max_length=32)

Fields which I want to show in form: company title, user first name, user last name, user email

Is it even possible? How can I do this?

dease
  • 2,975
  • 13
  • 39
  • 75
  • You can pass both the forms in the views – Kakar Jan 15 '15 at 16:43
  • 1
    Make a form manually. `class MyCustomForm(forms.Form)`. You can include any fields you'd like there, not limited to a data model. – Yuji 'Tomita' Tomita Jan 15 '15 at 18:46
  • 1
    @Kakar is closest here. The other answers on this page involve throwing away the benefits of using ModelForms. Whilst this is sometimes the best approach - it's probably more 'Djangonic' to create two model forms (which are rendered as a single HTML form) - I'll try and write up a proper answer soon. – Andy Baker Dec 20 '16 at 17:59

2 Answers2

31

The other answers on this page involve tossing away the benefits of model forms and possibly needing to duplicate some of the functionality you get for free.

The real key is to remember that one html form != one django form. You can have multiple forms wrapped in a single html form tag.

So you can just create two model forms and render them both in your template. Django will handle working out which POST parameters belong to each unless some field names clash - in which case give each form a unique prefix when you instantiate it.

Forms:

class CompanyForm(forms.ModelForm):
    class Meta:
        fields = [...]
        model = Company

class AccountForm(forms.ModelForm):
    class Meta:
        fields = [...]
        model = Account

View:

if request.method == 'POST':

    company_form = CompanyForm(request.POST)
    account_form = AccountForm(request.POST)

    if company_form.is_valid() and account_form.is_valid():

        company_form.save()
        account_form.save()
        return HttpResponseRedirect('/success')        

    else:
        context = {
            'company_form': company_form,
            'account_form': account_form,
        }

else:
    context = {
        'company_form': CompanyForm(),
        'account_form': AccountForm(),
    }

return TemplateResponse(request, 'your_template.html', context)

Template:

<form action="." method="POST">
    {% csrf_token %}
    {{ company_form.as_p }}
    {{ account_form.as_p }}
    <button type="submit">
</form>
Andy Baker
  • 21,158
  • 12
  • 58
  • 71
  • +1 for your cleaned answer. It's the Django way of doing things. The only problem is that you cannot intercalate fields from the different models. I'd appreciate, If you could expand on how to intercalate fields from different models in the template rendering. – Omar Gonzales Nov 23 '17 at 16:34
  • 3
    @OmarGonzales To intercalate we'll need to render each field separately (and not use something like `{{company_form.as_p}}` ) – Anupam Jun 15 '18 at 09:14
  • Is there a way to implement this in a class-based view? – Pavel Shlepnev Jan 23 '20 at 14:05
  • @PavelShlepnev what do you mean by a way in class-based view. The logic would be the same be it a functional or class view. You can write a get and post method in your class view and the rest would same – Mohammad Faisal Oct 28 '20 at 06:16
1

In your forms.py

from django import forms


class YourForm(forms.Form):
    title = forms.CharField()
    first_name = forms.CharField()
    last_name = ...

In your views.py

from forms import YourForm
from django import views
from models import Company, Account

class YourFormView(views.FormView)
    template_name = 'some_template.html'
    form_class = YourForm
    success_url = '/thanks/'

    def form_valid(self, form):
        title = form.cleaned_data['title']
        ...
        # do your processing here using Company and Account
        # i.e. company = Company.objects.create(title=title, ...)
        #      account = Account.objects.get_or_create(
        #      user=..., company=company ...)
        #      ... more processing
        #
        # Call company.save() and account.save() after adding
        # your processed details to the relevant instances
        #  and return a HttpResponseRedirect(self.success_url)

    def is_valid(self):
        # don't forget to validate your fields if need be here

As usual the docs are pretty helpful. https://docs.djangoproject.com/en/1.7/topics/forms/

zsoobhan
  • 1,484
  • 15
  • 18