I have a model that has a unique generic foreign key relationship:
class Contact(models.Model):
...
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
class Meta:
unique_together = ('content_type', 'object_id',)
meaning that a Contact
can only ever belong to one object. Usually, when I want to reverse the relationship I can do
class Person(models.Model):
...
contacts = generic.GenericRelation(Contact)
and calling person.contacts.all()
will give me all the objects. Because only one Contact
will ever be returned in my situation, is there a better way of accessing this object in reverse?
p.s. I could write person.contact.all()[0]
but there must be a cleaner approach
Well, looking at the question in Chris's comment helped, and I wrote a mixin to return the object using a quick lookup (which should be cached):
class ContactMixin(object):
@property
def contactcard(self):
ctype = ContentType.objects.get_for_model(self.__class__)
try:
contact = Contact.objects.get(content_type__pk = ctype.id, object_id=self.id)
except Contact.DoesNotExist:
return None
return contact
and the Person:
class Person(ContactMixin, models.Model):
...
now I can just call
myperson.contactcard.phone_number
I won't accept just yet, as there might be other suggestions
if a queryset contains exactly one element you can do qs.get()
, or in your case
person.contact.get()
(you may still need to catch the DoesNotExists
exception)
The accepted answer above implicitly assumes that the contact field is nullable whereas in the original post this is not the case. Assuming that you do want the key to be nullable I would do this:
class Contact(models.Model):
...
content_type = models.ForeignKey(
ContentType,
blank=True,
null=True,
on_delete=models.SET_NULL
)
object_id = models.PositiveIntegerField(blank=True, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
unique_together = ('content_type', 'object_id',)
class Person(models.Model):
...
contacts = generic.GenericRelation(Contact)
@property
def contact(self):
return self.contacts.first()
# Or if you want to generalise the same thing to a mixin.
class ContactableModel(models.Model):
contacts = generic.GenericRelation(Contact)
class Meta:
abstract = True
@property
def contact(self):
return self.contacts.first()
If there is no contact then .first()
will return None
. Simple and pythonic. There's no need for all the generic introspection mechanics of the accepted answer, the references are already right at our fingertips.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With