views:

350

answers:

2

heya,

I think my understanding of Django's FK and admin is a bit faulty, so I'd value any input on how to model the below case.

Firstly, we have generic Address objects. Then, we have User's, who each have a UserProfile. Through this, Users belong to departments, as well as having addresses.

Departments themselves can also have multiple addresses, as well as a head of department. So it might be something like (this is something I'm just hacking up now):

class Address(models.Model):
    street_address = models.CharField(max_length=20)
    etc...

class Department(models.Model):
    name = models.CharField(max_lenght=20)
    head_of_department = models.OneToOneField(User)
    address = models.ForeignKey(Address)

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    address = models.ForeignKey(Address)
    department = models.OneToOneField(Department)

Anyhow, firstly, is that the right way of setting up the relationships?

And secondly, I'd like it to appear in the admin that you can edit a department, and on that page, it'd have an inline list of all the addresses to also edit. I've tried setting up an AddressInline class, and attaching it as an inline to Department.

class AddressInline(admin.TabularInline):
    model = Address

class DepartmentAdmin(admin.ModelAdmin):
    inlines = [AddressInline]

However, when I try to display that, I get:

Exception at /admin/people/department/1/
<class 'people.models.Address'> has no ForeignKey to <class 'people.models.Department'>

Cheers, Victor

A: 

I read your models what you want from your models as:

A department has one to many addresses

A department has one and only one user (as head of department)

A user (through his profile) belongs to one to many departments

A user (through his profile) has one to many addresses

If that was your intent, meaning that there is no case where a user will NOT have an address or a department, and no case where a department will not have an address or a head of department; then I would say your models are OK should read:

class Department(models.Model):
    name = models.CharField(max_lenght=20)
    head_of_department = models.OneToOneField(User)
    address = models.ForeignKey(Address)

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    address = models.ForeignKey(Address)
    department = models.OneToOneField(Department)

class Address(models.Model):
    street_address = models.CharField(max_length=20)
    ...

    class Meta:
        abstract = True

class UserAddress(Address):
    user_profile = models.ForeignKey(UserProfile)

class DepartmentAddress(Address):
    department = models.ForeignKey(Department)

Read more about abstract classes.

What your models are not contemplating are the possibilities that two Users will have the same address, and/or that two departments will have the same address. Since you are not specifying a unique constraint on address (that I can see), I assume that you are OK with a real-world address showing up more than once in your Address table.

If that is OK with you; fine.

The error message you are getting is stating a fact: there is no foreign key in Address to Department. You will have to revert that relationship for the inline to work. Meaning, when editing an address you can edit any departments associated with it; but not the reverse. With the models I suggest above you should not see this error.

See the example from the docs. Notice how an Author has many Books and how the many side of the relationship is the one that can be inline.

celopes
This answer would make sense, except that it appears to be ignoring the actual model code posted ;-) In which the ForeignKeys to Address are on UserProfile and Department, meaning that each of those can have only one Address, but an Address may be owned by many entities.
Carl Meyer
Dang my poor speed reading skills... Carl - as usual - is right. I'll correct my answer. Deserved -1, thanks.
celopes
Fast edit, -1 retracted ;-)
Carl Meyer
+2  A: 

Since it seems you want a UserProfile or Department to have potentially many addresses, your ForeignKeys are backward. A single ForeignKey can only point to one model instance, whereas there is no limit on the number of ForeignKeys that can point to a single model instance. So your ForeignKey should be on Address (in which case your inline would work as-is).

The complicating factor is that you have a single Address model and you want to relate it to two other models; a single ForeignKey on Address can't point to both UserProfile and Department. One solution is to have two address models (DepartmentAddress, with a ForeignKey to Department, and UserAddress, with a ForeignKey to UserProfile). You could reduce duplication in your code by having these both inherit from an abstract base class containing all the data fields, but you still end up with two mostly-identical tables in your database.

The other option is to have a GenericForeignKey on Address, which can point to an instance of any model. Your inline would then need to become a GenericInlineModelAdmin. This violates pure database normalization and doesn't allow your database to do proper integrity checking. If you had potentially more models in the future that would also have addresses, I'd consider this; if it's likely to be limited to only the current two, I might go with the above option instead.

Carl Meyer
Ah... On my edit I used approach one.
celopes
heya,Coolies, thanks for that. So you're saying the FK field should be on Address, not UserProfile/Department. Have to try and get my head around that - is that the way it would normally be done in DB theory, if they were SQL tables? How would you handle this particularly case with a generic Address table in SQL theory?I think I might try the abstract base class approach, seems less work than GenericRelations...lol. It would all then work nicely with the admin interface, right, because those two Address subclasses would essentially be different objects?Cheers,Victor
victorhooi
Victor: yes, in straight SQL you would still put the foreign key field on the "one" side of a many-to-one relationship. And yes, the abstract base class solution will work nicely with the admin inlines (either one will, really).
Carl Meyer