Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pendant to inline formsets for many-to-many relations

Tags:

python

django

Following Kevin Dias instructions in this article, I try to generate one form for two related models. This seems to work for one-to-many relations, however I run into problems using many-to-many relations.

Here is some example code for a user-role management:

#models.py
from django.db import models

class Role(models.Model): # for each role there can be multiple users
    role_name=models.CharField(max_length=20)

class User(models.Model): # each user can have multiple roles
    name=models.CharField(max_length=20)
    role=models.ManyToManyField(Role, through='UserRole')

class UserRole(models.Model): # table to store which user has which roles
    role=models.ForeignKey(Role)
    user=models.ForeignKey(User)

# forms.py
from django.forms import ModelForm
from django.forms.models import inlineformset_factory

from rightmanagement.models import Role, User

class UserForm(ModelForm):
    class Meta:
        model = User

RoleFormSet = inlineformset_factory(User, Role) # this is probably the line that causes the problem

# views.py
from django.http import HttpResponseRedirect
from rightmanagement.models import User
from rightmanagement.forms import RoleFormSet, UserForm

# Create view
from django.views.generic import CreateView
class UserCreate(CreateView):
    model = User
    form_class = UserForm

    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)
        role_form = RoleFormSet()
        return self.render_to_response(
            self.get_context_data(form=form,
                                  role_form=role_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)
        role_form = RoleFormSet(self.request.POST)
        if (form.is_valid() and role_form.is_valid()):
            return self.form_valid(form, role_form)
        else:
            return self.form_invalid(form, role_form)

    def form_valid(self, form, role_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()
        role_form.instance = self.object
        role_form.save()
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form, role_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,
                                  role_form=role_form))

These settings lead to the error message <class 'rightmanagement.models.Role'> has no ForeignKey to <class 'rightmanagement.models.User'>.

Doing some research I found this: Django inlineformset_factory and ManyToMany fields. It seems like inline formsets are only for ForeignKey but not for ManyToManyField. Also the docs can be interpreted like this.

However, I think in this particular case a foreign key instead of a many-to-many relation wouldn't make any sense. How would a pendant to Django's built-in inline formset look like for many-to-many relations? The aim would be to build a form that allows either the assignment of the user to roles that already exist or create new roles and assign them to the user if they do not exist yet.

like image 872
speendo Avatar asked Mar 06 '14 23:03

speendo


1 Answers

As you're probably aware, you can't edit many-to-many relationships with inline formsets. You can, however, edit the through model. So for your inline formset, you just need to set the model to the through model, like so:

RoleFormSet = inlineformset_factory(UserRole, User.role.through)
like image 169
Drewness Avatar answered Sep 20 '22 17:09

Drewness