sagittarian
sagittarian

Reputation: 1094

Admin inline editing of a many-to-many relationship in django

I have a somewhat complicated situation with the models I'm developing in Django. The basic idea is that users on the site can give awards to people, but I also want to track the relationship between a user and an award recipient. A user can give multiple awards to the same person, but there is only one relationship between them. The models look something like this (User is the standard auth_user model):

class Recipient(models.Model):
    name = models.CharField(max_length=255)
    senders = models.ManyToManyField(User, through='Relationship')

class Relationship(models.Model):
    user = models.ForeignKey(User)
    recipient = models.ForeignKey(Recipient)
    relationship = models.CharField(max_length=255, blank=True, default='')
    class Meta:
        unique_together = (('user', 'recipient'),)

class Award(models.Model):
    recipient = models.ForeignKey(Recipient)
    user = models.ForeignKey(User)
    relationship = models.ForeignKey(Relationship)
    award_name = models.CharField(max_length=255)

In the admin I have something like

class RelationshipInline(admin.StackedInline):
    model = Relationship

class AwardAdmin(admin.ModelAdmin):
    inlines = [RelationshipInline]
    exclude = ['relationship']

admin.site.register(Recipient)
admin.site.register(Award, AwardAdmin)

When I try to add an award in the admin I get the error "<class 'Relationship'> has no ForeignKey to <class 'Award'>". I realize that this is because when I edit Award, Django doesn't know which Relationship should be edited inline, but in fact it's obvious (to a human) that it's the one with the same user/recipient pair. I can't specify that the user/recipient in Relationship are a key because Django doesn't support multicolumn keys (so all I can do is to specify them as unique_together). Likewise I can't specify that user/recipient in Award are a multicolumn foreign key to Relationship.

Is there any way to get what I'm trying to get at here? It seems a huge inconvenience to have to edit Relationship separately in the admin when there's a Many-to-One relationship to it from Award. Is there a better way to organize the models?

Upvotes: 1

Views: 1607

Answers (1)

Chris Forrette
Chris Forrette

Reputation: 3214

The basic problem causing the error you described is because you can't have an inline for 'Relationship' in 'Award', it should be the other way around to work properly. This makes sense because inlines are meant to provide a means of creating an arbitrary number of related records (in this case, Relationships) bound to the parent record you're editing (an Award), but Award can only be related to one Relationship so using an inline doesn't work.

In any case, I'm going to suggest a bit of simplification...

I would suggest dropping the Recipient model entirely and switching to something like this:

class Relationship(models.Model):
    user1 = models.ForeignKey(User)
    user2 = models.ForeignKey(User)
    relationship = models.CharField(max_length=255, blank=True, default='')
    class Meta:
        unique_together = (('user1', 'user2'),)

class Award(models.Model):
    recipient = models.ForeignKey(User)
    user = models.ForeignKey(User)
    relationship = models.ForeignKey(Relationship)
    award_name = models.CharField(max_length=255)

And, rather than specifically managing the 'relationship' value on the Award model, I would override the save() method to get or create the relationship of the 2 users and save it. That would look something like this (not tested):

...
from django.db.models import Q
...
class Award(models.Model):
    ...
    def save(self, *args, **kwargs):
        try:
            # Query for an existing relationship between the 2 users in question...
            relationship = Relationship.objects.get(
                (Q(user1=self.recipient) & Q(user2=self.user)) | (Q(user1=self.user) & Q(user2=self.recipient))
            )
        except:
            relationship = Relationship.objects.create(user1=self.recipient, user2=self.user, relationship='Pals')
        self.relationship = relationship
        super(Award, self).save(*args, **kwargs)

Upvotes: 1

Related Questions