Search code examples
pythondjangodjango-modelsdjango-adminone-to-one

Generic one-to-one relation in Django


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


Solution

  • 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