Django Multi-Table Inheritance VS Specifying Explicit OneToOne Relationship in Models - django

Hope all this makes sense :) I'll clarify via comments if necessary. Also, I am experimenting using bold text in this question, and will edit it out if I (or you) find it distracting. With that out of the way...
Using django.contrib.auth gives us User and Group, among other useful things that I can't do without (like basic messaging).
In my app I have several different types of users. A user can be of only one type. That would easily be handled by groups, with a little extra care. However, these different users are related to each other in hierarchies / relationships.
Let's take a look at these users: -
Principals - "top level" users
Administrators - each administrator reports to a Principal
Coordinators - each coordinator reports to an Administrator
Apart from these there are other user types that are not directly related, but may get related later on. For example, "Company" is another type of user, and can have various "Products", and products may be supervised by a "Coordinator". "Buyer" is another kind of user that may buy products.
Now all these users have various other attributes, some of which are common to all types of users and some of which are distinct only to one user type. For example, all types of users have to have an address. On the other hand, only the Principal user belongs to a "BranchOffice".
Another point, which was stated above, is that a User can only ever be of one type.
The app also needs to keep track of who created and/or modified Principals, Administrators, Coordinators, Companies, Products etc. (So that's two more links to the User model.)
In this scenario, is it a good idea to use Django's multi-table inheritance as follows: -
from django.contrib.auth.models import User
class Principal(User):
#
#
#
branchoffice = models.ForeignKey(BranchOffice)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
#
#
#
Or should I go about doing it like this: -
class Principal(models.Model):
#
#
#
user = models.OneToOneField(User, blank=True)
branchoffice = models.ForeignKey(BranchOffice)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
#
#
#
Please keep in mind that there are other user types that are related via foreign keys, for example: -
class Administrator(models.Model):
#
#
#
principal = models.ForeignKey(Principal, help_text="The supervising principal for this Administrator")
user = models.OneToOneField(User, blank=True)
province = models.ForeignKey( Province)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratorcreator")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratormodifier")
I am aware that Django does use a one-to-one relationship for multi-table inheritance behind the scenes. I am just not qualified enough to decide which is a more sound approach.

I'd like to expand on the solution by #thornomad.
Extending Django's User class directly can cause all kinds of trouble with the internal django.auth mechanisms. What I've done in a similar situation is precisely what #thornomad suggests - I made my own UserProfile model linked one-to-one with the Django User model, in which I held additional user data and from which I inherited models for different types of users.
Something to fit what you described:
class UserProfile(models.Model):
user = models.OneToOneField(User, blank=True, related_name='profile')
class Meta:
abstract = True
class PositionHolderUserProfile(UserProfile):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="created_users")
modified_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="modified_users")
class Principal(PositionHolderUserProfile):
branchoffice = models.ForeignKey(BranchOffice)
class Administrator(PositionHolderUserProfile):
superior = models.ForeignKey(Principal, related_name="subordinates")
province = models.ForeignKey(Province)
class Coordinator(PositionHolderUserProfile):
superior = models.ForeignKey(Administrator, related_name="subordinates")
class Company(UserProfile):
name = models.CharField(max_length=50)
class Product(models.Model):
name = models.CharField(max_length=50)
produced_by = models.ForeignKey(Company)
class Buyer(UserProfile):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
products_bought = models.ManyToManyField(Product)

I recently switched over to using models that inherit off of contrib.auto.models.User. My general observation is that in theory they're great, but sometimes they don't get auto-magically handled like they're supposed to.
I think your decision regarding inheritance vs. OneToOne comes down to this:
Do I want to have Django automatically do something right 95% of the time, and need to debug that other 5%
-OR-
Do I want to do something manually myself 100% of the time
If you haven't seen it, the Scott Barham blog has a great post about inheriting off of User, and also building a custom back end to make sure that your custom object is being returned -- Extending the Django User.
Additionally of interest would be the AutoOneToOne field provided by django-annoying. It's sort of a hybrid of the two approaches here -- there's no inheritance taking place, but Django is taking care of creating the matching OneToOneField if it's not present.
Also, thornomad does make a good point about the redundancy in your models. You could easily implement an abstract class to clean that up as so (assuming you're doing manual OneToOne):
class BaseExtendedUser(models.Model):
user = models.OneToOneField(User, blank=True, related_name='profile')
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="created_users")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="modified_users")
class Meta:
abstract = True
class Administrator(BaseExtendedUser):
province = models.ForeignKey(Province)
class Principal(BaseExtendedUser):
branchoffice = models.ForeignKey(BranchOffice)

I don't think I would inherit the User model, rather use a custom UserProfile - leaving the contrib.auth model alone. With the custom UserProfile model, you could setup a base user profile model that can be a part of all your different user types.
Just looking at it quickly, too, I would look carefully at any models that repeat all the same fields (like your last two Principle and Administrator models). Combining the built in group functionality with the user profile idea may do what you are looking for.

Please consider what happens in the data model when a Coordinator gets promoted to a Principal. I would not use inheritance in this case at all. Please reconsider the previous poster's suggestion "Combining the built in group functionality with the user profile idea may do what you are looking for."

Do you need objects of your user classes to act like an auth.User anywhere? That would be the most obvious reason to use inheritance over OneToOne. One pro of the OneToOne approach would be the ease in which you can change over to another User model, if that's a concern.
The real issue I see with what you have above (by either method) is that there doesn't appear to be anything stopping you from having a Principal object and an Administrator object share the same User. OneToOneField can only guarantee a one-to-one mapping between any two relations.

Related

Django access manytomany field from related_name in a view

I have what i think is a simple question but I am struggling to find out how it works. I get how related name works for foreign keys but with many to many fields it seems to break my brain.
I have two 3 models at play here. A User, TeamMember and Team Model as seen below.
User model is the built in django model.
#TeamMember Model
class TeamMember(models.Model):
member = models.ForeignKey(User, on_delete=models.SET(get_default_team_member), verbose_name='Member Name', related_name="team_members")
...
#Team Model
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="managers", null=True, blank=True)
team_lead = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tls", null=True, blank=True)
tps = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tps", null=True, blank=True)
members = models.ManyToManyField(TeamMember, blank=True, related_name="members")
...
Now in a view i want to access a specific users team. I thought i could do this by doing something like this:
member = TeamMember.objects.get(pk=1)
member_team = member.members.name
However if I print member_name than it prints nothing. If I try to access any of the other fields on that model like member.members.team_lead.first_name it fails to find the team_lead field. I understand that this has a .all() attribute but i thought it was tied to the team object through the members field. So if that member matches the team it would give me the team. So I thought it might be an issue if the same member was linked to more than one team (which is possible) so i tired something like this member.members.all().first().name and i get an error that states it cannot get name from NoneType.
Is there an easy way to get the team name from a relationship like this or am i better off just doing a team query with the user?
Thanks,
jAC
First of all, I would like to point out that you are not using the related_name (and related_query_name parameters in a proper way). I think this SO post will help you to understand the concept in a better way.
So, I would change the related_name (and related_query_name) values in the Team model as below,
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
team_lead = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
tps = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
members = models.ManyToManyField(
TeamMember, blank=True, related_name="teams", related_query_name="team"
)
...
Now in a view i want to access a specific user's team.
Since the Team and TeamMember models are connected via ManyToManyField, you may have "zero or more" Teams associated with a single TeamMember
So, the following query will get you all the teams associated with a particular TeamMemeber
team_member = TeamMember.objects.get(pk=1)
all_teams = team_member.teams.all()
You can also iterate over the QuerySet as,
team_member = TeamMember.objects.get(pk=1)
for team in team_member.teams.all():
print(team.name)
For anyone wondering what I did based on JPG's advice was the for loop option
team_member = TeamMember.objects.get(pk=1)
teams = [t.name for t in team_member.members.all()]
I personally do not care which team i get as my need in this case is just to pass a team through even if it is none. So i just use team = team[0] if teams.count() > 0 else "No team"

Django - Extending models with multi role users and multiple requirement types

I am new to Django only 2 weeks and I am stuck with scenario I need help with.
This is kind of core concept and I apologize in advance for my lack of knowledge.
I've extended the base user model like below with multiple roles. Now each role has distinct requirements for their profiles.
I need help to understand that how to extend Students or Staff further. There are two scenario I need to extend them.
Both can have similar extended models like addresses below.
Both can have different extended models like Students have CurrentCourse and Staff does not. Staff has Salary model and Students does not.
class User(AbstractUser):
is_student = models.BooleanField(default=True)
is_staff = models.BooleanField(default=True)
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
dob = models.CharField(max_length=30, blank=True)
location = models.CharField(max_length=30, blank=True)
class CurrentCourse(models.Model):
student = models.OneToOneField(Student,
on_delete=model.CASCADE)
....
class Staff(models.Model):
ROLES = (('Teacher', 'Teacher'), ('Professor', 'Professor'), ('Administration', 'Administration'))
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
role = models.CharField(max_length=100, choices=ROLES)
website = models.CharField(max_length=100, blank=True)
Both types of Users can have multiple addresses and both Students and Staff needs addressess, not sure how to extend this with ForeignKey.
class Address(models.Model):
user = ?
street = models.CharField(max_length=200)
city = models.CharField(max_length=50)
country = models.CharField(max_length=50)
class Salary(models.Model):
staff = models.OneToOneField(Staff, on_delete=models.CASCADE)
current_salary = models.DecimalField(max_digits=10, decimal_places=2)
Finally please let me know how can I apply validators on each model for instance I did sub-classed 'User' to all models instead of Student or Staff. How to apply a validator on OneToOneField like below? I thought that if I apply a validator like:
staff_validator(value):
I can't call user in validator function and not sure if it will be global or within model its applied on with indent.
class Salary(models.Model):
staff = models.OneToOneField(User, on_delete=models.CASCADE, validators=[staff_validator])
Thank you in advance for your kind help for this.
Imagine the real world scenario where your Users are People and People can have different roles with the time. Like if I am a Student today I belong to "People" category and if I will be Professor tomorrow still I will belong to People category.
So People can wear different hats or adapt different roles but they still will be People. Similarly your User model will always be part of all the roles you need to define in database. So you need to extend your User model for not only all the roles in fact for models you believe User can be part of and it should be like below:
class User(AbstractUser):
is_student = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
.....
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='foo')
.....
class Courses(models.Model):
user = models.ManyToMany(User)
You need to consult official docs with three types of models. Abstract models, Proxy models and Inherited models for more clarity. Also for creating view for your application you can create checks for your users with decorators. Here is a good example as per your question.

How to create a shared model in Django?

I have a couple of models that have need of a common set of fields. It is basically a set of various different types of contacts:
# models I would like to share
class Address(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
street1 = models.CharField(_('street1'), max_length=125, blank=True)
street2 = models.CharField(_('street2'), max_length=125, blank=True)
city = models.CharField(_('city'), max_length=50, blank=True)
state = models.CharField(_('state'), max_length=2, blank=True)
zip_code = models.CharField(_('zip_code'), max_length=10, blank=True)
class Phone(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
phone = models.CharField(_('phone'), max_length=50, blank=True)
# these are the models that I would like to have addresses and phone numbers
class Contact(models.Model):
name = models.CharField(_('name'), max_length=50, blank=False)
class Organization(models.Model):
name = models.CharField(_('name'), max_length=255, blank=False)
email = models.CharField(_('email'), max_length=100, blank=True)
website = models.CharField(_('website'), max_length=255, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
photo = models.CharField(_('photo'), max_length=255, blank=True)
I would like to share the Phone and Address models with the Contact, Organization and UserProfile models. My first attempt was to add ForeignKey on each of Contact, Organization and UserProfile but after more research I believe this is backwards, so I moved the ForeignKey to Address and Phone but then discovered that ForeignKey can belong to one and only one model. In addition to sharing this data structure between multiple different contact types, I would like the ability to add more than one address or phone number to a contact. A contact could have a home address, work address, mobile number, and work number for example. So I have basically 2 questions:
1) Is sharing a model in this way a reasonable thing to do?
2) How would I go about setting up the models?
If I've understood correctly; you want your Contact, Organization, and UserProfile models to all have fields for Address and Phone. Moreover, they can each have more than one address/phone.
Is this reasonable? Sounds so to me.
How could you go about setting up the models? Generic Relations spring to my mind.
Consider just Contact and Address for now. Your second attempt is correct: we have a Many-to-one relationship (one contact, many addresses), so we need to make Contact a ForeignKey field in the Address model, like so:
class Address(models.Model):
<other_fields>
contact = models.ForeignKey('Contact', on_delete=models.CASCADE)
This allows you to assign multiple addresses to each contact.
Moving on, you essentially want your Address model to have multiple foreign keys: Contact, Organization, and UserProfile. One way of achieving this is to use Generic Relations. This makes use of Django's built-in "contenttypes" framework, and allows you to create GenericForeignKey fields that point to more than one model. I encourage you to read the docs linked, since generic relations aren't so trivial. In the end, you'll have something like:
class Address(models.Model):
label = models.CharField(max_length=50, blank=True)
street_1 = models.CharField(max_length=125, blank=True)
street_2 = models.CharField(max_length=125, blank=True)
etc...
models_with_address = models.Q(app_label='app_name', model='contact') | \
models.Q(app_label='app_name', model='organization') | \
models.Q(app_label='app_name', model='userprofile')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=models_with_address)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
With this, you can create several addresses for each of the models specified in the models_with_address query. To be able to query the addresses for a given contact/organization/etc., you'll need a reverse generic relation. Setting this up involves adding the line address = GenericRelation(Address) to the respective models.
For further generalisation, you could create a ContactableModel (or whatever) class:
class ContactableModel(models.Model):
address = GenericRelation('Address')
phone = GenericRelation('Phone')
Any model with an address and phone number (Contact, Organization, etc.) could then inherit this so that you don't have to repeatedly include those two fields. You could also improve the models_with_address limit, so that we have something like limit_choices_to=<subclasses_of_ContactableModel>.
Hope this helps!
A simple solution would be to add ManyToManyField(Address|Phone) (i.e. two M2M fields)
to Contact, Organization and UserProfile.
But that would mean different contacts/organizations/users could share addresses.
While this looks tempting, the problem with such a solution is that if someone
edits an address of a contact, they also change the address for all the remaining objects in the system!
Another possible solution which avoids the above problem
and doesn't require M2M fields or generic relationships would be to use
multi-table inheritance:
class Address(models.Model):
entity = models.ForeignKey(Entity)
...
class Entity(models.Model):
pass
class Contact(Entity):
...
class Organization(Entity):
...
(Under the hood Django creates implicit OneToOneField-s
that point from Contact & Organization to Entity.)

Django update model without UpdateView

I have a scenario where in my users profile they have an associated organisation. I need to be able to allow the users to select and set this organisation (user_organization), however I would like to do it without allowing them to just see a list (drop down menu) of all the organisations within the application. My work around for this was to issue each organisation with a unique code (org_code) and allow users to enter that code into a form and have the related organisation applied to their profile. I can easily understand the suedocode logic behind this, however I am unsure how to implement it within my views and forms. If anyone can advise me the best way to do this or point me in the correct direction to learn how? It would be appreciated. See my models below for clarification on how things fit together.
Profile:
class Profile(models.Model):
Super_Admin = "Super_Admin"
Admin = "Admin"
Manager = "Manager"
Developer = "Developer"
ROLE_CHOICES = (
(Super_Admin, 'Super_Admin'),
(Admin, 'Admin'),
(Manager, 'Manager'),
(Developer, 'Developer'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_group = models.OneToOneField(Group, on_delete=models.CASCADE, blank=True, null=True)
user_organization = models.OneToOneField(Organization, on_delete=models.CASCADE, blank=True, null=True)
role = models.CharField(choices=ROLE_CHOICES, default="Developer", max_length=12)
activation_key = models.CharField(max_length=120, blank=True, null=True)
activated = models.BooleanField(default=False)
def __str__(self):
return self.user.username
Organizations:
class Organization(models.Model):
org_name = models.CharField(max_length=120, blank=False, unique=True)
org_code = models.CharField(max_length=120, blank=False, unique=True, default=GenerateOrganozationCode)
Answering using the extra info from the comments you've made above:
"I want them to be able to input a code into a text field which when submitted, if it matches the code (org_code) in the organization model it will then populate the (user_organization) in their Profile with the correct (org_name)"
Within the logic for the view, you need to extract the org_code. You should also make org_code of Organization the primary key of the object (you dont have to, but it would be easier if the pk was the org code). From here, you can map the org_code with the primary key value of Organization.
Organization.objects.get(pk=the_entered_org_code)
If you'd rather not assign org code as the primary key of the object, you can just filter for the org code.
Organization.objects.filter(org_code=the_entered_org_code)
This should get you started.

Django Multiple User Profiles with Class based views (Best Practices)

I have a website where there are two kinds of users (say) : students and tutors.
Both types have common login functionality, age, gender etc but have distinct attributes such as report cards for students and degree certificates for tutors.
I read this question : Django - Multiple User Profiles and setup my profiles as shown below:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, related_name='profile')
mobile = models.CharField(max_length=10, blank=False, null=True)
picture = models.ImageField(
upload_to='images/', default='images/newuser.png')
age = models.IntegerField(null=True)
slug = models.SlugField()
...
And two other models that link to the above. Eg:
class StudentProfile(models.Model):
profile = models.ForeignKey(UserProfile, related_name="user_profile")
#more custom attributes
class TutorProfile(models.Model):
profile = models.ForeignKey(UserProfile, related_name="doctor_profile")
#more custom attributes
Now my questions:
1) SlugField is defined on the UserProfile attribute but will ideally use the User.username field. This means a join between these two tables will happen each time. Is this to be expected?
2) Assuming I am using class based views, editing/viewing the profile will depend on the UserProfile in question. But I want the user to be able to edit/view all his details on the same page. Thus, I will have to fetch TutorProfile / StudentProfile too and add custom logic to ensure updates happen to them too.
It seems to me that there should be a proper way of handling these situations (Since a lot of websites have similar requirements). What are the best practices to be followed in such a situation?
While searching for answers, I came across a solution which I think might suit my needs (posting here to welcome critique and help out others who might be looking for answers).
Taken from Django Design patterns and Best Practices
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, related_name='profile')
mobile = models.CharField(max_length=10, blank=False, null=True)
picture = models.ImageField(
upload_to='images/', default='images/newuser.png')
age = models.IntegerField(null=True)
gender = models.IntegerField(null=True)
user_type = models.CharField(max_length=20, choices=UserTypes.CHOICES)
slg = models.SlugField()
class Meta(object):
abstract = True
class StudentProfile(models.Model):
report_card = models.FileField(upload_to="/temp")
house = models.CharField()
class Meta(object):
abstract = True
class TutorProfile(models.Model):
performance = models.IntegerField(default=0)
salary = models.IntegerField(default=0)
class Meta(object):
abstract = True
One base abstract class and two specific classes which cover the various user profiles. Keeping them separate like this makes it easy for us to reason about the various fields present in each user type.
Finally,
class Profile(UserProfile, StudentProfile, TutorProfile):
pass
This is the model used as the settings.AUTH_USER_MODEL.
Overall, the advantages I see:
Single DB call on user edit/view page.
Easier to think about overall.
Disadvantage : Lots of wasted space.
Anyone has any better suggestions?