How to store a key to a something that can be either a person or an organization?

40 views
Skip to first unread message

Antonis Christofides

unread,
Oct 20, 2017, 5:59:35 AM10/20/17
to django...@googlegroups.com
Hello,

Two real examples that I've faced:

class MeteorologicalStation(models.Model):
    ...
     owner = models.ForeignKey(to a person or organization)


class Document(models.Model):
     ...
     authors = models.ManyToManyKey(to persons or organizations)

What I do is I create a superclass that I call Lentity (short for "legal
entity", despite the fact that it could refer to a group of people and is not
necessary legal) and the two subclasses Person and Organization, with
multi-table inheritance.

But since I guess that this is a relatively common problem, I was wondering
about what other people are doing.

Thanks!

Antonis

--
Antonis Christofides
http://djangodeployment.com


Jani Tiainen

unread,
Oct 20, 2017, 7:16:20 AM10/20/17
to django...@googlegroups.com
Hi.

I've resolved such a case with two nullable fkeys and a discriminator field to tell which one fkey is used.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/043cc4db-f1a9-3beb-cf44-f63c401114ac%40djangodeployment.com.
For more options, visit https://groups.google.com/d/optout.

Ruben Alves

unread,
Oct 20, 2017, 12:55:48 PM10/20/17
to Django users

Jani Tiainen

unread,
Oct 20, 2017, 3:25:51 PM10/20/17
to django...@googlegroups.com
I would be a bit cautious with generic foreignkeys since they don’t provide database integrity checks.

IOW, you can break your data very easily.

-- 
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.

To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.

Matthew Pava

unread,
Oct 20, 2017, 3:48:10 PM10/20/17
to django...@googlegroups.com

In my project, I have a contacts model.

I have separate models, such as Person, Institution, Supplier, or Shipper that all have a foreign key to contacts.

So it looks like you’re doing it the way that I would do it.

James Schneider

unread,
Oct 21, 2017, 3:06:28 AM10/21/17
to django...@googlegroups.com


On Oct 20, 2017 4:15 AM, "Jani Tiainen" <red...@gmail.com> wrote:
Hi.

I've resolved such a case with two nullable fkeys and a discriminator field to tell which one fkey is used.

Another option that is slightly safer is to override the save() method to set the opposing FK to None every time the model is saved. Your 'discriminator' is then based on which field is populated. It helps avoid situations where a cascade deletion of the foreign object leaves your model with no FK associations, but the model is still marked with a discriminator, and the lack of an FK value can incur a bug. It's one less field to set with the same net result. 

There are use cases for a discriminator field though (ie calling functions dynamically, or a status that can't be determined by presence or lack of data, etc.), so you may still have a good solution. I just prefer to minimize the fields that I'm managing.

-James

James Schneider

unread,
Oct 21, 2017, 3:30:42 AM10/21/17
to django...@googlegroups.com

What I do is I create a superclass that I call Lentity (short for "legal
entity", despite the fact that it could refer to a group of people and is not
necessary legal) and the two subclasses Person and Organization, with
multi-table inheritance.

Seems silly to name a model as such given that it can easily be nonrepresentative of the data in the table.

Is it an abstract class? If yes, why not just call it Entity? A legal status can easily be represented by a field in the model.

If a Person and Organization are treated the same, you can also use just an Entity table with a field designating the type of Entity, such as 'person' or 'organization'. Querying all Person and Organization objects would then constitute only a single query, rather than two queries, one for each model type. If you add other similar models, you also increase the number of queries required to pull all of the objects, whereas a field designation allows you to pull some or all objects regardless of type.

There are a ton of reasons to keep them separate, though, so your design may still be correct.

-James

Antonis Christofides

unread,
Oct 21, 2017, 5:06:48 AM10/21/17
to django...@googlegroups.com

Hello James,

You are right that the correct term in everyday language for the superclass of organization and person is "entity". However, I didn't want to name it "entity" in the code, because, really, "entity" is a different thing in programming, it's a term like "object". It would be very confusing. The reason I didn't name it LegalEntity is exactly that it might not be legal. I chose to invent a new term, "Lentity", which ensures that someone will not (mis)understand it just by looking at the name and will necessarily look it up.

I think that my design is "correct" from a theoretical point of view. Inheritance is the way to solve such a problem in OOP, and multi-table inheritance is the way to solve this problem in relational databases. That's what the books say anyway.

In theory, theory and practice are the same. In practice they differ. :-)

My design generally works and I'm quite satisfied with it. This doesn't mean it's without problems. So, for example, the original definition of Lentity in an app of mine was this:

class Lentity(models.Model):

    def __str__(self):
        try:
            return str(self.person)
        except Person.DoesNotExist:
            return str(self.organization)

    class Meta:
         verbose_name_plural = 'Lentities'

Then, when I had too many persons and organizations, I had to use raw_id_fields in the admin, and for this I had to make this change:

class Lentity(models.Model):
    # descr is a redundant field; see save() for more information.
    descr = models.CharField(max_length=111, blank=True, editable=False)

    def __str__(self):
        try:
            return str(self.person)
        except Person.DoesNotExist:
            return str(self.organization)

    def save(self, *args, **kwargs):
        # descr is a redundant, calculated field that is automatically filled
        # in during save(). We introduce this redundancy because there is
        # currently no other way in which the Django admin can sort lentities,
        # i.e. we can use descr as a field in "ordering" or "get_ordering", but
        # it's not possible to use an expression. (We need the Django admin to
        # manage lentities in general, as well as persons and organizations, in
        # order to make it possible to use raw_id_fields in AuthorInline.)
        self.descr = str(self)
        super(Lentity, self).save(*args, **kwargs)

    class Meta:
        verbose_name_plural = 'Lentities'
So I had to put Lentity in the admin, ensuring users have no permissions to edit or delete or create "Lentity" directly, and I told users to just ignore that. (I'm can't tell whether these problems are worse than having two fields plus a discriminator field in the admin).

Regards,

Antonis
Antonis Christofides
http://djangodeployment.com
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.

James Schneider

unread,
Oct 23, 2017, 5:37:43 AM10/23/17
to django...@googlegroups.com


On Oct 21, 2017 2:06 AM, "Antonis Christofides" <ant...@djangodeployment.com> wrote:

Hello James,

You are right that the correct term in everyday language for the superclass of organization and person is "entity". However, I didn't want to name it "entity" in the code, because, really, "entity" is a different thing in programming, it's a term like "object". It would be very confusing. The reason I didn't name it LegalEntity is exactly that it might not be legal. I chose to invent a new term, "Lentity", which ensures that someone will not (mis)understand it just by looking at the name and will necessarily look it up.

IMO if you name things in such a complicated fashion that others have to look them up, they probably aren't very good names. Even worse that you've arbitrarily shortened the name non-intuitively. I don't like it, but if it works for you, more power to you.

I think that my design is "correct" from a theoretical point of view. Inheritance is the way to solve such a problem in OOP, and multi-table inheritance is the way to solve this problem in relational databases. That's what the books say anyway.

Usually, but in this case, your OOP design also influences your DB schema, which has different implications and philosophy to consider. RDB's don't support the concept of inheritance AFAIK.

In theory, theory and practice are the same. In practice they differ. :-)

Always.

My design generally works and I'm quite satisfied with it. This doesn't mean it's without problems. So, for example, the original definition of Lentity in an app of mine was this:

class Lentity(models.Model):

    def __str__(self):
        try:
            return str(self.person)
        except Person.DoesNotExist:
            return str(self.organization)
This code makes no sense. You should not be referring to models directly. Everything should be relative to self. It should be as simple as return self.name, with a property called name on each child model that returns the correct field.
Or don't bother overriding it at all at this level and override it properly in each child model.

    class Meta:
         verbose_name_plural = 'Lentities'

Then, when I had too many persons and organizations, I had to use raw_id_fields in the admin, and for this I had to make this change:

Not sure why having a larger list prompted the use of raw ID fields. Usually it's the opposite.

class Lentity(models.Model):
    # descr is a redundant field; see save() for more information.
    descr = models.CharField(max_length=111, blank=True, editable=False)

    def __str__(self):
        try:
            return str(self.person)
        except Person.DoesNotExist:
            return str(self.organization)

    def save(self, *args, **kwargs):
        # descr is a redundant, calculated field that is automatically filled
        # in during save(). We introduce this redundancy because there is
        # currently no other way in which the Django admin can sort lentities,
        # i.e. we can use descr as a field in "ordering" or "get_ordering", but
        # it's not possible to use an expression. (We need the Django admin to
        # manage lentities in general, as well as persons and organizations, in
        # order to make it possible to use raw_id_fields in AuthorInline.)
        self.descr = str(self)
        super(Lentity, self).save(*args, **kwargs)

    class Meta:
        verbose_name_plural = 'Lentities'
So I had to put Lentity in the admin, ensuring users have no permissions to edit or delete or create "Lentity" directly, and I told users to just ignore that. (I'm can't tell whether these problems are worse than having two fields plus a discriminator field in the admin).

There are some weird design issues here that I don't have time or context to comment on. Seeing the words 'redundant' and 'field' in the same sentence worries me as far as database schema design goes. But if it is working for you...

-James


Reply all
Reply to author
Forward
0 new messages