Generic one-to-one relation in Django

Question:

I need to set up one-to-one relation which must also be generic. May be you can advice me a better design. So far I came up to the following models

class Event(models.Model):
    # skip event related fields...
    content_type      = models.ForeignKey(ContentType)
    object_id         = models.PositiveIntegerField()
    content_object    = generic.GenericForeignKey('content_type', 'object_id')

    class Meta:
        unique_together   = ('content_type', 'object_id')

class Action1(models.Model):
    # skip action1 related fields...
    events = generic.GenericRelation(Event, content_type_field='content_type', object_id_field='object_id')

    @property
    def event(self):
        return self.events.get() # <<<<<< Is this reasonable?

class Action2(models.Model):...

In Django Admin in event list I want to collect all actions, and from there I want go to admin pages for actions. Is it possible to avoid creating event property in the action models? Is there a better solution? It would be nice to combine the field events and the property event in a single definition. The project I am working with uses Django 1.1

Asked By: Andrei

||

Answers:

I recently came across this problem. What you have done is fine, but you can generalise it a little bit more by creating a mixin that reverses the relationship transparently:

class Event(models.Model):
    content_type      = models.ForeignKey(ContentType)
    object_id         = models.PositiveIntegerField()
    content_object    = generic.GenericForeignKey('content_type', 'object_id')

    class Meta:
        unique_together   = ('content_type', 'object_id')

class EventMixin(object):
     @property
     def get_event(self):
         ctype = ContentType.objects.get_for_model(self.__class__)
         try:
             event = Event.objects.get(content_type__pk = ctype.id, object_id=self.id)
         except:
            return None 
         return event

class Action1(EventMixin, models.Model):
    # Don't need to mess up the models fields (make sure the mixing it placed before models.Model)
    ...

and

action = Action1.object.get(id=1)
event = action.get_event

You might want to add caching to the reverse relationship too

Answered By: Timmy O'Mahony

GenericRelation is a class for representing a Generic Many to One and adding a first_event property allow to represent a Generic One to One.

class Event(models.Model):
    content_type      = models.ForeignKey(ContentType)
    object_id         = models.PositiveIntegerField()
    content_object    = generic.GenericForeignKey('content_type', 'object_id')

    class Meta:
        unique_together   = ('content_type', 'object_id') # Important

class Action1(models.Model):
    events = generic.GenericRelation(Event)

    @property
    def first_event(self):
        return self.events.first()

In addition, I recommend using .first() inside Action1.first_event instead of .get() due to, it returning None if the event doesn’t exist. .get() raises an exception if the event doesn’t exist and this behavior can be unwanted.

Answered By: Leonardo Ramírez