mloch mloch - 27 days ago 27
Python Question

How to save inline formset user field in Django using views

I've been using this great post http://kevindias.com/writing/django-class-based-views-multiple-inline-formsets/ to setup my site. I was wondering how to save the user field automatically to an inline formset in views (I used the blockquote for changes to the original). The RecipeForm in (see also below for context)

self.object = form.save(commit=False)
self.object.owner = self.request.user
self.object.save()


saves nicely automatically but not the

ingredient_form.owner= self.request.user


I know Django suggests using BaseInlineFormSet, but most people suggest saving user field in views.py and not forms or models for many different reasons. I would appreciate any suggestions or answers. Here's the full code:

models.py

from django.db import models


class Recipe(models.Model):
owner = models.ForeignKey(User)
title = models.CharField(max_length=255)
description = models.TextField()


class Ingredient(models.Model):
owner = models.ForeignKey(User)
recipe = models.ForeignKey(Recipe)
description = models.CharField(max_length=255)


class Instruction(models.Model):
recipe = models.ForeignKey(Recipe)
number = models.PositiveSmallIntegerField()
description = models.TextField()


forms.py

from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from .models import Recipe, Ingredient, Instruction


class RecipeForm(ModelForm):
class Meta:
model = Recipe
IngredientFormSet = inlineformset_factory(Recipe, Ingredient)
InstructionFormSet = inlineformset_factory(Recipe, Instruction)


views.py

from django.http import HttpResponseRedirect
from django.views.generic import CreateView
from .forms import IngredientFormSet, InstructionFormSet, RecipeForm
from .models import Recipe


class RecipeCreateView(CreateView):
template_name = 'recipe_add.html'
model = Recipe
form_class = RecipeForm
success_url = 'success/'

def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates blank versions of the form
and its inline formsets.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet()
instruction_form = InstructionFormSet()
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
instruction_form=instruction_form))

def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance and its inline
formsets with the passed POST variables and then checking them for
validity.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet(self.request.POST)
instruction_form = InstructionFormSet(self.request.POST)
if (form.is_valid() and ingredient_form.is_valid() and
instruction_form.is_valid()):
return self.form_valid(form, ingredient_form, instruction_form)
else:
return self.form_invalid(form, ingredient_form, instruction_form)

def form_valid(self, form, ingredient_form, instruction_form):
"""
Called if all forms are valid. Creates a Recipe instance along with
associated Ingredients and Instructions and then redirects to a
success page.
"""
self.object = form.save(commit=False)
self.object.owner = self.request.user
self.object.save()
ingredient_form.instance = self.object
ingredient_form.owner= self.request.user
ingredient_form.save()
instruction_form.instance = self.object
instruction_form.save()
return HttpResponseRedirect(self.get_success_url())

def form_invalid(self, form, ingredient_form, instruction_form):
"""
Called if a form is invalid. Re-renders the context data with the
data-filled forms and errors.
"""
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
instruction_form=instruction_form))

Answer

I did some more research and the solution looks somewhat complex following this guide of how to add custom formset saving but modified for BaseInlineFormset as mentioned above. I realized it will be simpler just to make ModelForms for each Model and then linking them in a view, since I only need one child form at a time in the add a new recipe view and can reuse the ModelForm code.

here's the new code that works great! Feel free to contact if you need more info.

forms.py

from django.forms import ModelForm
from .models import Recipe, Ingredient, Instruction


class RecipeForm(ModelForm):

    class Meta:
        model = Recipe
        exclude = ['owner',]

class IngredientForm(ModelForm):

    class Meta:
        model = Ingredient
        exclude = ['owner','recipe',]

class InstructionForm(ModelForm):

    class Meta:
        model = Instruction
        exclude = ['recipe',]

views.py

from .forms import IngredientForm, InstructionForm, RecipeForm


def add_new_value(request):
    rform = RecipeForm(request.POST or None)
    iform = IngredientForm(request.POST or None)
    cform = InstructionForm(request.POST or None)
    if rform.is_valid() and iform.is_valid() and cform.is_valid():
        rinstance = rform.save(commit=False)
        iinstance = iform.save(commit=False)
        cinstance = cform.save(commit=False)
        user = request.user
        rinstance.owner = user
        rinstance.save()
        iinstance.owner = user
        cinstance.owner = user
        iinstance.recipe_id = rinstance.id
        cinstance.recipe_id = rinstance.id
        iinstance.save()
        cinstance.save()
        return HttpResponseRedirect('/admin/')
    context = {
        'rform' : rform,
        'iform' : iform,
        'cform' : cform,
    }
    return render(request, "add_new_recipe.html", context)

template: add_new_recipe.html

<!DOCTYPE html>
<html>
<head>
    <title>Add Recipe</title>
</head>

<body>
    <div>
        <h1>Add Recipe</h1>
        <form action="" method="post">
            {% csrf_token %}
            <div>
                {{ rform.as_p }}
                {{ iform.as_p }}
                {{ cform.as_p }}
            </div>
            <input type="submit" value="Add recipe" class="submit" />
        </form>
    </div>
</body>
</html>