Django: User vs UserProfile - django

I'm building a site where we ask users several personal details (birth date, phone number, address, marital status etc. many more).
Option 1: User model only. Put these personal fields in class User(AbstractUser) model
class User(AbstractUser):
birth_date = ...
phone_number = ...
Option 2: User + UserProfile models: separate login-related data (User) from personal data (UserProfile) like:
class User(AbstractUser):
pass
class UserProfile(models.Model):
user = models.OneToOneField(
User,
to_field="id",
primary_key=True,
on_delete=models.CASCADE,
related_name="user_profile",
)
birth_date = ...
phone_number = ...
Which one is the best practice?

This is subjective, based on each ones opinion/needs. But will go over the technical differences:
Option 1 is easier to use in your code and more efficient as you don't have to do JOINs between tables in order to get all information
Option 2 will make the model look cleaner, with only the most important fields present. This is helpful for data engineers or data analyst who work a lot on SQL visualisation tools
Unless UserProfile has 20+ fields, I would personally go with Option 1.

As always the answer will be "it depends". If your users might have different types of profiles/roles that may require additional set of fields I would go with the second option because it would allow you to have one user model that can combine traits and functionalities of several users. Depending on situation you can call specific method of profile (look for Delegation pattern for more examples).

Related

Django One(Many)-To-Many Relation in reusable app

I have a model named Exam. each Exam has a set of users called participants. The only way I found to keep such set in Django is to add a field in User model. But I'd prefer to write this model to be as independent as possible so if later I want to use it again I can do it without changing my User model. So How can I handle having such set without manually modifying the User model fields?
Regarding your comment here is what you could do something like this:
class Exam(models.Model):
participants = models.ManyToMany(settings.AUTH_USER_MODEL, through='Participation')
class Participation(models.Model)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
exam = models.ForeignKey('Exam')
active = models.BooleanField(default=False)
Another option would be to use Django's limit_coices_to. It's not transaction-save, but might do the job. You would just limit to choices to all non-related objects.

django guardian, permissions and extending django auth groups to 'organization' models

django guardian https://github.com/lukaszb/django-guardian is a really well written object-level permissions app; and I have actually read up on and used quite a number of other django object level permissions app in various django projects.
In a recent project that I am working on, I decided to use django guardian but I have a model design question relating to the pros and cons of two possible approaches and their respective implications on sql query performance:-
using django.contrib.auth.models.Group and extending that to my custom organization app's models; or
using django.contrib.auth.models.User instead and creating an m2m field for each of the organization type in my organization app.
Approach #1
# Organisation app's models.py
from django.contrib.auth.models import Group
class StudentClass(models.Model):
name = models.CharField('Class Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
size = models.IntegerField('Class Size', blank=True)
class SpecialInterestGroup(models.Model):
name = models.CharField('Interest Group Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
description = models.TextField('What our group does!', blank=True)
class TeachingTeam(models.Model):
name = models.CharField('Teacher Team Name', max_length=255)
groups = models.ManyToManyField(Group, blank=True)
specialization = models.TextField('Specialty subject matter', blank=True)
In this approach, when a user is added to a group (django group) for the first time, the group object is created and also assigned to one of these 3 classes, if that group object does not yet belong to the class it is added into.
This means that each StudentClass object, sc_A, sc_B etc, can possibly contain a lot of groups.
What that means is that for me to ascertain whether or not a specific user (say myuser) belongs to a particular organization, I have to query for all the groups that the user belong to, via groups_myuser_belongto = myuser.groups and then query for all the groups that are associated to the organization I am interested in, via groups_studentclass = sc_A.groups.all() and since I now have 2 lists that I need to compare, I can do set(groups_myuser_belongto) && set(groups_studentclass), which will return a new set which may contain 1 or more groups that intersect. If there are 1 or more groups, myuser is indeed a member of sc_A.
This model design therefore implies that I have to go through a lot of trouble (and extra queries) just to find out if a user belongs to an organization.
And the reason why I am using m2m to groups is so as to make use of the Group level permissions functionality that django guardian provides for.
Is such a model design practical?
Or am I better off going with a different model design like that...
Approach #2
# Organisation app's models.py
from django.contrib.auth.models import User
class StudentClass(models.Model):
name = models.CharField('Class Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
size = models.IntegerField('Class Size', blank=True)
class SpecialInterestGroup(models.Model):
name = models.CharField('Interest Group Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
description = models.TextField('What our group does!', blank=True)
class TeachingTeam(models.Model):
name = models.CharField('Teacher Team Name', max_length=255)
users = models.ManyToManyField(User, blank=True)
specialization = models.TextField('Specialty subject matter', blank=True)
Obviously, this model design makes it really easy for me to check if a user object belongs to a particular organization or not. All I need to do to find out if user john is part of a TeachingTeam maths_teachers or not is to check:
user = User.objects.get(username='john')
maths_teachers = TeachingTeam.objects.get(name='Maths teachers')
if user in maths_teachers.users.all():
print "Yes, this user is in the Maths teachers organization!"
But what this model design implies is that when I add a user object to a group (recall that I want to use django guardian's Group permissions functionality), I have to make sure that the save() call adds the user object into a "Maths Teachers" group in django.contrib.auth.models.Group AND into my custom TeachingTeam class's "Maths Teachers" object. And that doesn't feel very DRY, not to mention that I have to somehow ensure that the save calls into both the models are done in a single transaction.
Is there a better way to design my models given this use case/requirement - use django groups and yet provide a way to "extend" the django's native group functionality (almost like how we extend django's user model with a "user profile app")?
My take on this (having developed django apps for a long time) is that you should stick with the natural approach (so a StudentClass has Users rather than Groups). Here "natural" means that it correspond to the actual semantics of the involved objects.
If belonging to a specific StudentClass must imply some automatic group (in addition to those granted to the user), add a groups m2m to the StudentClass model, and create a new authentication backend (extending the default one), which provides a custom get_all_permissions(self, user_obj, obj=None) method. It will be hooked by https://github.com/django/django/blob/master/django/contrib/auth/models.py#L201
In this method query for any group associated to any Organization the user belongs to. And you don't need to do 1+N queries, correct use of the ORM will navigate through two *-to-many at once.
The current ModelBackend method in https://github.com/django/django/blob/master/django/contrib/auth/backends.py#L37 queries get_group_permissions(user_obj) and adds them to the perms the user has assigned. You could add similar behavior by adding (cached) get_student_class_permission and other corresponding methods.
(edited for clearer prologue)
Obs: There is another approach which is to use generic relationships, in this approach you would have the User model instance pointing to it's resources through the contenttypes framework. There is a nice question here on SE explaining this approach.
About the performance: There are some clues that a single select with JOIN is cheaper than many simple select (1,2,3). In this case opition 2 would be better.
About the usability: The first approach is hard to explain, hard to understand. IMHO go for no. 2. Or try the generic relations.

How to use the 'reverse' of a Django ManyToMany relationship?

I'm coming from a Rails background, and am having a bit of trouble making use of the "Association Methods" provided in Django. I have two models (which have been simplified for the sake of brevity), like so:
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
companies = models.ManyToManyField('Company', blank=True)
class Company(models.Model):
name = models.CharField(max_length=255)
According to the Django documentation:
"It doesn't matter which model has the ManyToManyField, but you should only put it in one of the models -- not both.".
So I understand that if I have an instance of a User, called user, I can do:
user.companies
My question is how do I do the reverse? How do I get all users that belong to a Company instance, let's say Company:
company.users # This doesn't work!
What's the convention to do this? The documentation that I've read doesn't really cover this. I need the association to work both ways, so I can't simply move it from one model to the other.
company.user_set.all()
will return a QuerySet of User objects that belong to a particular company. By default you use modelname_set to reverse the relationship, but you can override this be providing a related_name as a parameter when defining the model, i.e.
class User(models.Model):
companies = models.ManyToManyField(Company, ..., related_name="users")
> company.users.all()
here is the relevant documentation

How do you decide on creating a new model vs a field in django?

I'm creating a user profile class for my new django website, and I am trying to decide how to represent a user's physical address in my models.
Is it better practice to create a new subclass of model and reference it with a OneToOne key like
class UserProfile(models.Model):
...
address = models.OneToOneField(AddressModel)
...
class AddressModel(models.Model)
street_address = models.CharField(max_length=30)
city = models.CharField(max_length=15)
....
or is it better to create a new address field like
class UserProfile(models.Model):
...
address = AddressField(location_dict)
...
class AddressField(models.Field)
# details go here
...
I generally find it useful to have separate models if the entries might be created independently. For example, if you might end up with a collection of addresses AND a collection of users, not all of which will be linked immediately, then I'd keep them separate.
However, if all addresses in your database will always and immediately be associated with a user, I'd simply add a new field to the model.
Note: some people will tell you that it's wrong and evil to have nullable database columns, and that you should therefore have a separate model if any of your addresses will ever be None. I disagree; while there are often many great reasons to avoid nullable columns, in cases like this I don't find the inconvenience of checking for a null address any more onerous than checking whether the one-to-one model entry exists.
Like Eli said, it's a question of independence. For this particular example, I would make the address a field of UserProfile, but only if you expect to have one address per user. If each user might have multiple addresses (a home address and a vacation address, for example), then I would recommend setting up a model using ForeignKey, which models a Many-To-One relationship.
class UserProfile(models.Model):
...
class AddressModel(models.Model)
user = models.ForeignKey(UserProfile)
street_address = models.CharField(max_length=30)
city = models.CharField(max_length=15)
location = models.CharField(max_length=15) #"Home," "work," "vacation," etc.
Then many AddressModel objects can be created and associated with each UserProfile.
To answer your question, I'd say in general it's probably better to separate out the address as mentioned by other users.
I think the more you learn about database normalization the easier this question is to answer.
This article, Using MySQL, Normalisation, should help you figure out the basics of the "forms" of normalization. BTW, even though it's titled MySQL, it's really very generic for relational databases.
While you don't always need to go through all the normal-forms for all projects, learning about it really helps.

Django, need some specified groups of users

I need let's say Instructors, Students and so on.
Both groups are users, could login etc.
But for example Instructor need many to many relationship with model Subjects.
How to achieve that?
Create a class Instructors that would inherit from Users. Within this class provide the many-to-many relationship. You could also use the profile module to identify the separation.
There are good examples of both here.
EDIT: There is also a good post by James Bennett here.
I wouldn't use inheritors here. Just create models that point to User:
class Instructor(models.Model):
user = models.OneToOneField(User)
# other fields
subjects = models.ManyToManyField('Subject')
class Student(models.Model):
# other fields
user = models.OneToOneField(User)
class Subject(models.Model):
name = models.CharField(max_length=40)
This has the benefit of keeping the common user functionality separate from the Instructor and Student functions. There's really no reason to actually treat Instructors or Students as Users.
You can't point Subject to User-who-is-Instructor model, it can't be expresed in SQL.
What you can do is e.g. to point Subject to User model, making sure in your code that you only create Subject instances for User-s that are members of Instructors group.