Wednesday, August 28, 2013

How to require selection of a M2M with intermediate table/model during admin record creation?

I have been beating my head against the wall trying to figure this out.  I came up with a simplified example to illustrate what I'm trying to do.

Posit a hypothetical university application; say, a system where students, faculty and administration can all participate.  There are different privilege levels and different areas of functionality, so students will never see the screens that professors use for tracking assignments, etc.  The various permissions will be designated by somebody's Position, which includes student (undergrad), student (graduate), TA, professor, and dean.  (This is a contrived example, to try to illustrate what I'm trying to accomplish.)  As you can see, a User can have multiple Positions:  grad student and TA, or professor and dean.  The areas of functionality are also accessible by overlapping Positions, with the Position determining how much they can do in that screen.  As I said, students will not even know of the assignment tracking screen, but TAs and professors will, and professors will be able to do more in it.  Professors will not see the administration functions, unless they are also a dean.

I plan to have a single User and login per person.  Users with multiple Positions will be prompted which one to use per session.  Here is the models.py:

from django.db import models

# Create your models here.
class Position (models.Model):
  CHOICES = ( ('pub', 'Publish'), ('die', 'Perish') )
  title = models.CharField (max_length=32)
  privileges = models.CharField (max_length=3, choices=CHOICES)

class User (models.Model):
  login = models.EmailField (unique=True)
  name = models.CharField (max_length=32)
  password = models.CharField (max_length=32)
  positions = models.ManyToManyField (Position, through='UserPosition')

  def __str__ (self): return self.name

class UserPosition (models.Model):
  user = models.ForeignKey (User)
  position = models.ForeignKey (Position)
  effectDate = models.DateField()

So there's an intermediate model for the M2M relationship so I can store extra information per User/Position.

Here's the stock admin.py:

from django import forms
from django.contrib import admin
from forum.models import *

admin.site.register (User)
admin.site.register (Position)

This is the view.  As you can see, it doesn't accommodate hooking up the new User with any Position(s).

So I wrote a UserAdmin and a form to go with it.  The new admin.py:

# imports

class UserPositionAdmin (admin.TabularInline):
  model = UserPosition
  extra = 1

class UserCreationForm (forms.ModelForm):
 
  class Meta:
    model = User

class UserAdmin (admin.ModelAdmin):

  form = UserCreationForm
  inlines = (UserPositionAdmin,)

admin.site.register (User, UserAdmin)
admin.site.register (Position)

Now the input form looks like this:

Now my problem is that I require each User to have at least one Position, but I can't figure out how to get the form to fail validation (i.e., in the clean() call) if no Position is selected in the inline.  I've tried waiting until the User's save() function is reached, and tried checking in the UserAdmin's save_model(), but so far have been unable to find out at those points if a Position was selected before form submission or not.  So I think the UserCreationForm is the right place to check for it.

The problem seems to be that the field isn't technically part of the User class, so the form validation doesn't look for a clean_userpositions() function.  I tried creating a function by that name, but it never got called if the Position was blank (I didn't test with a valid Position selection).

It seems to me this can't possibly be something nobody's ever done before, but I haven't been able to find the answers even in the voluminous Django 1.5 documentation.  I would greatly appreciate direction on how to validate that a Position is selected.

Thanks in advance.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
For more options, visit https://groups.google.com/groups/opt_out.

No comments:

Post a Comment