BiG-Up
BiG-Up

Reputation: 31

django multiple inheritance

I'm currently working on a django project where i need to do multiple legacy. The project in it self has an admin with multiple websites. In my admin part i've created a Member class containing all mandatory info for a member. Then all individual sites have an MemberExtra class created from the Member class from the admin where i add all complementary information. When i launch my server (python manage.py runserver...) i've that error :

Error: One or more models did not validate:
programsite.memberextra: Accessor for field 'member_ptr' clashes with related field 'Member.memberextra'. Add a related_name argument to the definition for 'member_ptr'.
programsite.memberextra: Reverse query name for field 'member_ptr' clashes with related field 'Member.memberextra'. Add a related_name argument to the definition for 'member_ptr'.
gourmandiz.memberextra: Accessor for field 'member_ptr' clashes with related field 'Member.memberextra'. Add a related_name argument to the definition for 'member_ptr'.
gourmandiz.memberextra: Reverse query name for field 'member_ptr' clashes with related field 'Member.memberextra'. Add a related_name argument to the definition for 'member_ptr'.

admin/models.py:

class Member(models.Model):
    prog = models.ForeignKey(Program, verbose_name=_("Program"))
    status = models.CharField(_("Status"), m    status = models.CharField(_("Status"), max_length=1, choices=STATUS_CHOICE\
S)
    points_avai = models.BigIntegerField(_("
Current Points"), null=True)
    points_notavai = models.BigIntegerField(_("Future Points"), null=True)
    cn = models.CharField(_("Company name"), max_length=250)
    full_name = models.CharField(_("Full name"), max_length=250)
    b_add = models.CharField(_("Billing address"), max_length=250)
    b_city = models.CharField(_("Billing City"), max_length=250)
    b_zip = models.CharField(_("Billing ZIP code"), max_length=250)
    b_country = models.CharField(_("Billing country"), max_length=250)
    prog_start_date = models.DateField(_("Program start date"), null=True)
    prog_end_date = models.DateField(_("Program end date"), null=True)
    member_id = models.CharField(_("Member ID"), max_length=250, primary_key=T\
rue)
    client_id = models.CharField(_("Client ID"), max_length=250, help_text="Nu\
méro de client.")
    user = models.OneToOneField(User)

    def __unicode__(self):
        return self.full_name + " (" + str(self.member_id) + ")"

    class Meta:                                                     
        verbose_name = _("Member")
        verbose_name_plural = _("Members")

programsite/models.py:

class MemberExtra(Member):
 email = models.EmailField(_("Email"), max_length=100, null=True)
 tel = models.CharField(_("Tel"), max_length=100, null=True)
 patrick = models.CharField(_("Patrick"), max_length=100, null=True)
 test42 = models.CharField(_("Test42"), max_length=100, null=True)

gourmandiz/models.py:

class MemberExtra(Member):
     email = models.EmailField(_("Email"), max_length=100, null=True)

Upvotes: 3

Views: 2123

Answers (2)

Thierry J
Thierry J

Reputation: 2189

The problem here is that you inherit your model twice, and both the child models have the same name. This results in having twice the same related_name, which is a problem for Django.

Your solution to add

member = models.OneToOneField(Member, related_name="%(app_label)s_%(class)s_related")"

in your MemberExtra model works, but you loose the implicit inheritance magic that Django do to let you access both your models in one:

With your solution you have to do:

from programsite.models import MemberExtra
m = MemberExtra.objects.get(member__full_name = "Foobar")
m.email # -> returns the email of your MemberExtra instance
m.member.b_add # -> returns the address of the inherited member instance

Where, with the Django native inheritance, you can do:

from programsite.models import MemberExtra
m = MemberExtra.objects.get(full_name = "Foobar")
m.email # -> returns the email of your MemberExtra instance
m.b_add # -> returns the address of the inherited member instance

Which is much cleaner in my opinion.

To manage the inheritance, Django actually creates a OneToOneField (https://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance). This field is called <parentclass>_ptr, member_ptr in you case.

If you manually create a OneToOneField named <parentclass>_ptr, and give it a related_name, Django is still able to find the parent model, and will not complain about identical related_names.

In your case, just add

member_ptr = models.OneToOneField(Member, related_name="%(app_label)s_%(class)s_related")"

in both your MemberExtra model definitions.

This solution works, but is not how it should be done. Django provides a flag parent_link that, when set to true, will tell Django that this is the field that will be used to access the parent class.

So you can add a field

member = models.OneToOneField(Member, parent_link=True, related_name="%(app_label)s_%(class)s_related")"

which will still work if, for some reason, Django needs to rename the default pointer to the parent.

Upvotes: 2

Chris Pratt
Chris Pratt

Reputation: 239250

The related_name for a FK has to be unique. When you have an FK with a default related_name (unspecified), that is inherited by multiple other models, all of the models end up with the same related_name. See the section in the Django docs entitled Be careful with related_name.

The solution is to set the related_name argument of the FK to something like:

prog = models.ForeignKey(Program, verbose_name=_("Program"), related_name="%(app_label)s_%(class)s_related")

Django will then sub the app label and module name into to the string, making the related_name unique for each of the subclasses.

Upvotes: 1

Related Questions