15

I'm designing a new Django app and due to several possibilities, I'm not sure which would be the best, thus I'd like opinions, and hopefully improve what I got so far.

This question comes close but not quite. This one touches the flat/nested subject which is helpful, while still not answering the question. There are many others on the same subject, and yet none tell me what I want to know.

Background

The models have each unique properties with some shared attributes, and I need to reference them in another model, optimally with a single entry point rather than having a field for each possible model.

I want to be able to do complex Django ORM queries involving the Base class and filter by SubClass when needed. E.g Event.objects.all() to return all events. I'm aware of Django model utils Inheritance Manager and intend to use it if possible.

Also, I'll be using django admin to create and manage the objects, so an easy integration is a must. I want to be able to create a new SubEvent directly, without having first to create a Event instance.

Example

To illustrate, let's say I have the following models for app A.

class Event(models.Model):
    commom_field = models.BooleanField()

    class Meta:
        abstract = True

class SubEventA(Event):
    email = models.EmailField(unique=True)

class SubEventB(Event):
    title = models.TextField()

class SubEventC(Event):
    number = models.IntegerField(default=10)

# and so on

And also an app B, where I want to be able to reference a event which can be of any type, like:

class OtherModel(models.Model):
    event = models.ForeignKey('A.Event')

# This won't work, because `A.Event` is abstract.

Possible solutions

  1. Use a GenericForeignKey.

    # B.models.py
    class OtherModel(models.Model):
        content_type = models.ForeignKey(ContentType)
        object_id = models.PositiveIntegerField()
        event = GenericForeignKey('content_type', 'object_id')
    

    What I don't like about this is that I'll lose the querying capabilities Django ORM has, and I might need to do additional fiddling to get it working on admin. Not sure, never dealt with this before

  2. Flatten Event

    I can bring it all up to the base class and have flags or checks outside the model definition, something like:

    class Event(models.Model):
        commom_field = models.BooleanField()
        email = models.EmailField(blank=True)
        title = models.TextField(blank=True)
        number = models.IntegerField(default=10)
    

    This might seem like the best idea at first, but of course there are other kind of fields, and that forces me to allow nulls/blanks for most of them (like the email field), losing the db level integrity check.

  3. OneToOne relationships

    Rather than abstract like on 1 or flatten on 2 it is possible to have a db table for each, where the models will look like:

    class Event(models.Model):
        commom_field = models.BooleanField()
    
    class SubEventA(models.Model):
        event = models.OneToOneField(Event)
        email = models.EmailField(unique=True)
    
    class SubEventB(models.Model):
        event = models.OneToOneField(Event)
        title = models.TextField(blank=True)
    
    class SubEventC(models.Model):
        event = models.OneToOneField(Event)
        number = models.IntegerField(default=10)
    

    So far it solved the two initial problems, but now when I get to the admin interface, I'll have to customize each form to create the base Event before saving a SubEvent instance.

Questions

  1. Is there a better approach?

  2. Can any of the choices I present be improved in any direction (ORM query, DB constraints, admin interface)?

Community
  • 1
  • 1
alfetopito
  • 1,453
  • 2
  • 18
  • 27

3 Answers3

12

I've pondered about both answers and came up with something based off of those suggestions. Thus I'm adding this answer of my own.

I've chosen to use django-polymorphic, quite nice tool suggested by @professorDante. Since this is a multi-table inheritance, @albar's answer is also somewhat correct.

tl;dr

django-polymorphic attends the 3 main requirements:

  1. Allow django ORM querying style
  2. Keep db level constraints by having a multi-table inheritance and one table for each sub class
  3. Easy django admin integration

Longer version

Django-polymorphic allows me to query all different event instances from the base class, like:

# assuming the objects where previously created
>>> Event.objects.all()
[<SubEventA object>, <SubEventB object>, <SubEventC object>]

It also has great django admin integration, allowing seamless objects creation and editing.

The models using django-polymorphic would look like:

# A.models.py
from polymorphic import PolymorphicModel

class Event(PolymorphicModel):
    commom_field = models.BooleanField()

    # no longer abstract

class SubEventA(Event):
    email = models.EmailField(unique=True)

class SubEventB(Event):
    title = models.TextField()

class SubEventC(Event):
    number = models.IntegerField(default=10)



# B.models.py

# it doesnt have to be polymorphic to reference polymorphic models
class OtherModel(models.Model):
    event = models.ForeignKey('A.Event')

Besides, I can reference only the base model from another class and I can assign any of the subclasses directly, such as:

>>> sub_event_b = SubEventB.objects.create(title='what a lovely day')
>>> other_model = OtherModel()
>>> other_model.event = sub_event_b
alfetopito
  • 1,453
  • 2
  • 18
  • 27
4

My .2c on this. Not sure about your design in #3. Each SubEvent subclasses Event, and has a one-to-one to Event? Isn't that the same thing?

Your proposal on the Generic Key is exactly what it is designed for.

Another possibility - Polymorphism with Mixins. Use something like Django-polymorphic, so querying returns you the subclass you want. I use this all the time and its super useful. Then make Mixins for attributes that will be reused across many classes. So a simple example, making an email Mixin

class EmailMixin(models.Model):

    email = models.EmailField(unique=True)

    class Meta:
        abstract = True

Then use it

class MySubEvent(EmailMixin, models.Model):

    <do stuff>

This way you dont have redundant attributes on subclasses, as you would if they were all in the parent.

Luca Angioloni
  • 2,243
  • 2
  • 19
  • 28
professorDante
  • 2,290
  • 16
  • 26
  • Copy paste mistake. Updated the 3rd case inheritance. – alfetopito Oct 13 '15 at 16:12
  • I think I get your point on Mixins, but then what changes regarding the reference of the parent class in another model? – alfetopito Oct 13 '15 at 16:22
  • Thats a separate issue - just dont make Event abstract, and have a GFK to Event in the other model. – professorDante Oct 13 '15 at 16:31
  • If I use GFK I end up on the 1st solution, but I don't get why Event should not be abstract for this case. – alfetopito Oct 13 '15 at 16:37
  • It can be abstract of course, that's just a design choice. Its not right or wrong, but if you do go abstract, you cant have the GFK. – professorDante Oct 13 '15 at 16:43
  • The point in going for GFK was to keep Event abstract. Of course, I would not link to the event class itself. I'll update the question to make that clear. – alfetopito Oct 13 '15 at 16:52
  • But thats the point - you cant have both! Why have Event abstract, when it sounds like you'll need it to be concrete everywhere? You have to match asthetics with practicality, and realise that Objects and ORM sometimes aren't the perfect match. – professorDante Oct 13 '15 at 16:56
2

Why not a multi-table inheritance?

class Event(models.Model):
    commom_field = models.BooleanField()

class SubEventA(Event):
    email = models.EmailField(unique=True)

class SubEventB(Event):
    title = models.TextField(blank=True)

class SubEventC(Event):
    number = models.IntegerField(default=10)
whirish
  • 450
  • 4
  • 18
albar
  • 3,020
  • 1
  • 14
  • 27
  • How is that different from the 3rd case, given that "The inheritance relationship introduces links between the child model and each of its parents (via an automatically-created OneToOneField)"? – alfetopito Oct 13 '15 at 16:31
  • It is simplier as django does the job of maintaining the OneToOne relationship for you, so I think the problem you mentionned (" to customize each form to create the base Event before saving a SubEvent instance") no more exists. – albar Oct 14 '15 at 10:17
  • @alfetopito: Could you explain why the multi-table inheritance does not fit your need? – albar Oct 16 '15 at 08:02
  • Multi-table inheritance by itself doesn't help much regarding ORM querying and admin integration. Yes, it works, but I was looking for an easier way to do both. I added an answer explaining my final choice. – alfetopito Oct 17 '15 at 12:30