Database design of users and contacts

Hi,
Im struggling designing my django database relationships.

My project contains multiple Locations defined by the model Location.
In each Location, a user is able to add multiple Contacts to the Location.

A user can also add himself, or other users, as Contacts to the Location.

My struggle comes as i want to avoid to have 3 Locations where the same individual exists as 3 non-related contact-objects.

Currently i have a basic setup with fields:
Location(name)
Person (name, email)
User (AbstractUser custom user model with an added person_id)
Contact (person_id, location_id)

This way the Person becomes the “core” identifier of an individual, and when a user adds a Contact to a Location, the user is able to add a pre-existing Person as a Contact, if the Person exists.
If the Person doesnt exist, the User is able to create a new Person which can then be added as a Contact.
This setup also means that when a user signs up to the site, a Person-object is created through signals.

However, as i dont have much experience in database design, im wondering if there is a better way of doing this. It also bugs me that an email field exists in both the Person and User model, but i dont see any workaround of this.

Any help or redirection to sources are appreciated.

I’d make the following initial suggestions:

  • Since you’re defining a custom user, I don’t understand the need for a separate Person object. But, assuming there is a reason for this, I recommend that you define the OneToOneField in Person instead of User

  • I definitely would not bother using a signal to create the Person. Create the Person directly in the same view as you’re creating the User.

  • If there is a OneToOne relationship between Person and User, there is no need or value to having an email field in both. Given either object, you have direct access to the related object and so the email field only needs to exist in one of them.

  • Effectively, what you are creating is a many-to-many relationship between Person and Location. I suggest you define it that way. If there are additional attributes needing to be stored associated with a Contact, then define it as a through model for that relationship.

Thank you for the valuable input Ken!

I think i need the separate Person object as both Users and Contacts needs it.
The person model allows me to ensure that a “real life individual” only exists in one place in my project, and this Person can then be featured both as a User, and as a Contact in one or more Locations.
If i dropped the Person model, i have no way to relate the non-user Contacts to a single identifier.
that said, if you see something fishy here, please let me know.

I will drop the signal and do as suggested.

Contact will indeed have many more attributes. I cant seem to find any info in the django-docs regarding through models, can you point me in the right direction of some ressource?

Yes, you can relate them to User.

See:

I dont understand this, maybe we misunderstood each other.
Most contacts in my project have no relation to any User.

A Person can have a user, but many Persons dont, and is only featured in the project as Contacts.

Example: User MJay manages Location “Djangoforum”, and adds KenW as a Contact.
When adding KenW as contact, a Person object of KenW is also created, and the Contact object have a foreignkey to the Person object.
Now if user MJay also wants to add KenW to Location “Stackoverflow”, he adds a new contact with a relation to the KenW Person.
All this occurs without KenW having a User on the site.

Ok, that makes sense and wasn’t clear to me from what was posted earlier.

However, that doesn’t (necessarily) fundamentally change the underlying data model. While a “User” is an authenticatable entity, there’s no requirement that it must be such. You can create “User” objects that do not (and cannot) log on to the site.

If the Person object has no data fields unique to it (i.e., it only contains fields that would also be in the User object), then I would still recommend that you eliminate the Person model. If there are fields in Person that won’t exist in User, I’d still recommend using the appropriate fields in User instead of replicating that data in Person.

The Person object have no unique fields, so now im considering shredding it as per your suggestion.

In practice, how would you separate authenticable / non-authenticable users?
Simply a “authenticable” booleanfield in the User model? Or how would you arrange it?

The User model already have a is_active field, that when set to False unables any authentication.
And for the password, there’s the set_unusable_password method.
So with these two you can create a user account that won’t be able to authenticate.

@leandrodesouzadev That way i wont be able to distinguish actual users which is no longer active, and users who are not really users.

If you need to distinguish between these accounts, then all you need to do is create your own boolean field to create this distinction.

you can set up a logic that assigns activity scores to your users so that you can classify them in order of activity according to your criteria.

Sure you can.

I think you might be misinterpreting the function and purpose of the is_active field.

Depending upon your precise requirements in that area, you have a couple of options - but we would need to know exactly what it is you’re trying to determine by this to make a solid recommendation.

We use the is_active field to seperate active and in-active users, as our “true” users needs to login every 6th month, or their user will be deemed in-active (setting is_active = 0).
Does the field have more functions?

Do i have any other options than dropping table Person, and instead expanding the User model with User.is_authenticable (bool), where 1 indicates true users, and 0 indicates the non-users?

I camt seem to find it somewhat fishy to have non-users of the site to be listed in the User model though, but that might just be me? :slight_smile:

a user who is not a user of your application can be considered anonymous. did you think of that?

An anonymous user is one with no entry in the User model - that’s not going to work for this.

You’ve got at least two options here.

A User that has never logged in has a last_login==None.

You can use Group membership to identify “real” vs “virtual” users. (Create two groups and assign User accounts to one or the other.)

Yes, it’s just you.

You have features like the is_active field and set_unusable_password to be able to manage a User account for people not authorized to log in.

What you may have, potentially and in theory, a need to convert a virtual user to a real user. Maintaining separate information between the two creates the possibility of a conflict when the status of one changes.

Thank you Ken, you have given me a lot to think about.