Django: multiple models in one template using forms [closed]
Asked Answered
M

7

126

I'm building a support ticket tracking app and have a few models I'd like to create from one page. Tickets belong to a Customer via a ForeignKey. Notes belong to Tickets via a ForeignKey as well. I'd like to have the option of selecting a Customer (that's a whole separate project) OR creating a new Customer, then creating a Ticket and finally creating a Note assigned to the new ticket.

Since I'm fairly new to Django, I tend to work iteratively, trying out new features each time. I've played with ModelForms but I want to hide some of the fields and do some complex validation. It seems like the level of control I'm looking for either requires formsets or doing everything by hand, complete with a tedious, hand-coded template page, which I'm trying to avoid.

Is there some lovely feature I'm missing? Does someone have a good reference or example for using formsets? I spent a whole weekend on the API docs for them and I'm still clueless. Is it a design issue if I break down and hand-code everything?

Museology answered 20/2, 2009 at 12:50 Comment(1)
at first you should validate your customer form and if it was valid, create a copy from request.POST ( new_data=request.POST.copy() ).and then get customer id ( from validated customer form ) and with updating new_data, make customer id a value to foreignkey field(maybe customer in your model).And finally consider new_data for validate your second form(Tickets)Aquilar
S
97

This really isn't too hard to implement with ModelForms. So lets say you have Forms A, B, and C. You print out each of the forms and the page and now you need to handle the POST.

if request.POST():
    a_valid = formA.is_valid()
    b_valid = formB.is_valid()
    c_valid = formC.is_valid()
    # we do this since 'and' short circuits and we want to check to whole page for form errors
    if a_valid and b_valid and c_valid:
        a = formA.save()
        b = formB.save(commit=False)
        c = formC.save(commit=False)
        b.foreignkeytoA = a
        b.save()
        c.foreignkeytoB = b
        c.save()

Here are the docs for custom validation.

Sandhog answered 22/2, 2009 at 16:9 Comment(2)
btw, I don't think formsets are good solution to the problem you described. I always used them to represent multiple instances of a model. E.g. you have a applicant form and you want 3 references to you make a formset that has 3 instances of the Reference model.Sandhog
note that, with the way you do it, the .is_valid() call is not short circuited. If you want to short circuit it, you'll need to delay calling the .is_valid() function until the 'and'.Fabri
F
71

I just was in about the same situation a day ago, and here are my 2 cents:

1) I found arguably the shortest and most concise demonstration of multiple model entry in single form here: http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/ .

In a nutshell: Make a form for each model, submit them both to template in a single <form>, using prefix keyarg and have the view handle validation. If there is dependency, just make sure you save the "parent" model before dependant, and use parent's ID for foreign key before commiting save of "child" model. The link has the demo.

2) Maybe formsets can be beaten into doing this, but as far as I delved in, formsets are primarily for entering multiples of the same model, which may be optionally tied to another model/models by foreign keys. However, there seem to be no default option for entering more than one model's data and that's not what formset seems to be meant for.

Foil answered 12/6, 2009 at 9:50 Comment(0)
A
27

I very recently had the some problem and just figured out how to do this. Assuming you have three classes, Primary, B, C and that B,C have a foreign key to primary

    class PrimaryForm(ModelForm):
        class Meta:
            model = Primary

    class BForm(ModelForm):
        class Meta:
            model = B
            exclude = ('primary',)

    class CForm(ModelForm):
         class Meta:
            model = C
            exclude = ('primary',)

    def generateView(request):
        if request.method == 'POST': # If the form has been submitted...
            primary_form = PrimaryForm(request.POST, prefix = "primary")
            b_form = BForm(request.POST, prefix = "b")
            c_form = CForm(request.POST, prefix = "c")
            if primary_form.is_valid() and b_form.is_valid() and c_form.is_valid(): # All validation rules pass
                    print "all validation passed"
                    primary = primary_form.save()
                    b_form.cleaned_data["primary"] = primary
                    b = b_form.save()
                    c_form.cleaned_data["primary"] = primary
                    c = c_form.save()
                    return HttpResponseRedirect("/viewer/%s/" % (primary.name))
            else:
                    print "failed"

        else:
            primary_form = PrimaryForm(prefix = "primary")
            b_form = BForm(prefix = "b")
            c_form = Form(prefix = "c")
     return render_to_response('multi_model.html', {
     'primary_form': primary_form,
     'b_form': b_form,
     'c_form': c_form,
      })

This method should allow you to do whatever validation you require, as well as generating all three objects on the same page. I have also used javascript and hidden fields to allow the generation of multiple B,C objects on the same page.

Addis answered 3/3, 2009 at 13:5 Comment(2)
In this example, how are you setting the foreign keys for models B and C to point to the Primary model?Finback
I only have two models that I want to show on the same form. But I don't get the exclude = ('primary',) statement. What is primary? If have 2 models CustomerConfig and Contract. Contract has the foreign key to CustomerConfig. Such as customer_config=models.ForeignKey('CustomerPartnerConfiguration') What is 'primary'?Lauer
E
13

The MultiModelForm from django-betterforms is a convenient wrapper to do what is described in Gnudiff's answer. It wraps regular ModelForms in a single class which is transparently (at least for basic usage) used as a single form. I've copied an example from their docs below.

# forms.py
from django import forms
from django.contrib.auth import get_user_model
from betterforms.multiform import MultiModelForm
from .models import UserProfile

User = get_user_model()

class UserEditForm(forms.ModelForm):
    class Meta:
        fields = ('email',)

class UserProfileForm(forms.ModelForm):
    class Meta:
        fields = ('favorite_color',)

class UserEditMultiForm(MultiModelForm):
    form_classes = {
        'user': UserEditForm,
        'profile': UserProfileForm,
    }

# views.py
from django.views.generic import UpdateView
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import redirect
from django.contrib.auth import get_user_model
from .forms import UserEditMultiForm

User = get_user_model()

class UserSignupView(UpdateView):
    model = User
    form_class = UserEditMultiForm
    success_url = reverse_lazy('home')

    def get_form_kwargs(self):
        kwargs = super(UserSignupView, self).get_form_kwargs()
        kwargs.update(instance={
            'user': self.object,
            'profile': self.object.profile,
        })
        return kwargs
Enlistment answered 19/10, 2015 at 12:5 Comment(3)
Just saw django-betterforms and its MultiModelForm class before coming across your answer. Their solution looks real good but it seems it hasn't been updated in a while. Are you still using this @jozxyqk? Any problems?Luben
@Luben it's been a few years. Back then I found it convenient and one of the better options around. If you don't get too fancy with it it saves some time. I can imagine when you want to start customizing and doing non-trivial forms it would be easier to roll your own. Easily mixing forms and contexts in views is the first feature I really think I missed in django.Enlistment
Thanks for the reply man. I'm considering just forking it and maybe update a few things along the way. From what I've seen so far, it's working just fine. You're right, it's a huge time saver.Luben
M
5

I currently have a workaround functional (it passes my unit tests). It is a good solution to my opinion when you only want to add a limited number of fields from other models.

Am I missing something here ?

class UserProfileForm(ModelForm):
    def __init__(self, instance=None, *args, **kwargs):
        # Add these fields from the user object
        _fields = ('first_name', 'last_name', 'email',)
        # Retrieve initial (current) data from the user object
        _initial = model_to_dict(instance.user, _fields) if instance is not None else {}
        # Pass the initial data to the base
        super(UserProfileForm, self).__init__(initial=_initial, instance=instance, *args, **kwargs)
        # Retrieve the fields from the user model and update the fields with it
        self.fields.update(fields_for_model(User, _fields))

    class Meta:
        model = UserProfile
        exclude = ('user',)

    def save(self, *args, **kwargs):
        u = self.instance.user
        u.first_name = self.cleaned_data['first_name']
        u.last_name = self.cleaned_data['last_name']
        u.email = self.cleaned_data['email']
        u.save()
        profile = super(UserProfileForm, self).save(*args,**kwargs)
        return profile
Monoicous answered 18/4, 2012 at 20:53 Comment(0)
J
3

"I want to hide some of the fields and do some complex validation."

I start with the built-in admin interface.

  1. Build the ModelForm to show the desired fields.

  2. Extend the Form with the validation rules within the form. Usually this is a clean method.

    Be sure this part works reasonably well.

Once this is done, you can move away from the built-in admin interface.

Then you can fool around with multiple, partially related forms on a single web page. This is a bunch of template stuff to present all the forms on a single page.

Then you have to write the view function to read and validated the various form things and do the various object saves().

"Is it a design issue if I break down and hand-code everything?" No, it's just a lot of time for not much benefit.

Jeniferjeniffer answered 20/2, 2009 at 14:38 Comment(2)
I don't know how, therefore don't do itDollfuss
@orokusaki: What more would you like? That seems to describe a solution. What more should be said? The question is vague, so it's hard to provide actual code. Rather than complain, please provide a suggestion for improvement. What do you suggest?Jeniferjeniffer
T
3

According to Django documentation, inline formsets are for this purpose: "Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key".

See https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#inline-formsets

Tort answered 26/5, 2012 at 0:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.