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

123 views
Skip to first unread message

Zameer Ahmed

unread,
Feb 3, 2020, 6:53:11 PM2/3/20
to Django users
Hi, 
I've also posted this question on StackoverFlow and haven't got any response surprisingly. Here is the link.

I am new to Django only 1 week 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.

1. Both can have similar extended models like addresses below.
2. 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)# Can I extend it like this or do i need some other approach?
 ....
 
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):
 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:
 
 class Salary(models.Model):
   staff = models.OneToOneField(User, on_delete=models.CASCADE, validators=[some-validator])


Thank you in advance for your kind help for this.

Best Regards,

Zaheer

Mike Dewhirst

unread,
Feb 3, 2020, 10:00:11 PM2/3/20
to django...@googlegroups.com
On 4/02/2020 10:41 am, Zameer Ahmed wrote:
> Hi,
> I've also posted this question on StackoverFlow and haven't got any
> response surprisingly. Here is the link
> <https://stackoverflow.com/questions/60048405/django-extending-models-with-multi-role-users-and-multiple-requirement-types>.
>
> I am new to Django only 1 week 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.
>
> 1. Both can have similar extended models like addresses below.
> 2. 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)

is_staff already exists in AbstractUser for purposes of controlling
whether the user may login to the Admin site. If you really need the
concept it may be a good idea to think of a different name for your
field to avoid possible confusion later.

> 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)

Having such a relationship makes  User.is_student  redundant. I wouldn't
have is_student at all. Likewise is_staff.

> class CurrentCourse(models.Model):
>  student = models.OneToOneField(Student, on_delete=model.CASCADE)# Can
> I extend it like this or do i need some other approach?

Not sure what you mean by CurrentCourse. If there are many students and
many courses you probably want a ManyToManyField between (I presume)
Course and User. The name CurrentCourse indicates to me that a student
can be doing only one course (of many) at a time. Without knowing your
intentions it is difficult to say much more.


>  ....
>
> class Staff(models.Model):
>  ROLES = (('Teacher', 'Teacher'), ('Professor', 'Professor'),
> ('Administration', 'Administration'))

You have omitted 'Tutor' and that might merge student and staff.
However, you have the option to connect both Student and Staff to User
so you retain flexibility to let students also join the staff.

>    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.

Just think about the real world. Do people share addresses? Do people
have multiple addresses?  Are students and staff both Users?

Students and staff are both users so on the Address model you might want
a ManyToManyField pointing to User.

I quite like ManyToManyField because the through table which is
automatically created can be adjusted to contain additional information
which describes the relationship. For example, a student might have a
local residential and a separate postal address as well as a more
distant parental address. The enhanced through table would be useful for
differentiating between them.

If a User can have only one address then you need a ForeignKey on User
which points to an address.

>
> class Address(models.Model):
>  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:
>
>  class Salary(models.Model):
>    staff = models.OneToOneField(User, on_delete=models.CASCADE,
> validators=[some-validator])
>
>
> Thank you in advance for your kind help for this.
>
> Best Regards,
>
> Zaheer
> --
> 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
> <mailto:django-users...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-users/51552bea-877b-4c66-8a7a-42a68ca761e5%40googlegroups.com
> <https://groups.google.com/d/msgid/django-users/51552bea-877b-4c66-8a7a-42a68ca761e5%40googlegroups.com?utm_medium=email&utm_source=footer>.

Integr@te System

unread,
Feb 3, 2020, 11:26:33 PM2/3/20
to django...@googlegroups.com

To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/84e69a79-d0bc-7fe4-8a6b-e757352c27ee%40dewhirst.com.au.

Zameer Ahmed

unread,
Feb 4, 2020, 6:17:57 PM2/4/20
to django...@googlegroups.com
Thanks Mike for taking time off to read long question and response is much appreciated.
In all the discussion you just told me what not to do for laying down my model structure.
Now I have idea somewhat not to do.
Can you kindly laydown minimal structure where I can achieve multiple user roles?

I just need to extend user properly and remaining part I got figured out from docs for many2many and many2one relations.

Best regards, 
Zaheer

To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/84e69a79-d0bc-7fe4-8a6b-e757352c27ee%40dewhirst.com.au.

Mike Dewhirst

unread,
Feb 4, 2020, 9:56:24 PM2/4/20
to django...@googlegroups.com
On 5/02/2020 10:14 am, Zameer Ahmed wrote:
> Thanks Mike for taking time off to read long question and response is
> much appreciated.
> In all the discussion you just told me what not to do for laying down
> my model structure.
> Now I have idea somewhat not to do.
> Can you kindly laydown minimal structure where I can achieve multiple
> user roles?

There are as many different ways to do that as there are use-cases.

For my purposes (not necessarily anyone else's) I have a single user
with a user_profile to store 1:1 information such as preferences when
using the software or cellphone for MFA or (in my case) their company.
That is interesting because I decided early that for security reasons a
single login would only ever work for a single company. My software is
tenant-based with multiple companies. Users who work for multiple
companies must have multiple logins.

My basis for this is it seems to me to be the simplest and most flexible
for my purposes. Also, it means I can lift that entire section of my
code and use it virtually unchanged in other projects.

Everyone who uses the software is a human and all that is different
is(are) the role(s) they inhabit. In my software people have defined
roles, they can switch to have other roles and they can have multiple
roles at the same time.

The roles are named django_auth_groups which each get appropriate
permissions. A user needs to be in multiple groups if they need more
permissions than are available in a single group.

I have no users with their own permissions. They can only get their
permissions from group membership. I have omitted user individual
permissions entirely from the project. They are evil.

Then in a utils module I have a bunch of easy to remember functions like
this ...

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
def is_admin(user, name="admin"):
    return is_member(user, name)

def is_author(user, name="author"):
    return is_member(user, name)

def is_authority(user, name="authority"):
    return is_member(user, name)

def is_consumer(user, name="consumer"):
    return is_member(user, name)

def is_editor(user, name="editor"):
    return is_member(user, name)

def is_manager(user, name="manager"):
    return is_member(user, name)
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
def is_member(user, name):
    if user and name and user.is_active and user.is_staff:
        return user.groups.filter(name=name).exists()
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

I also have similar methods on the user model itself which return
booleans. Also one which returns a string of all the groups a user is a
member of.

So my experience may not suit your use-case but it suits me because it
is simple and flexible.

I have not extended the custome user much beyond extra methods. No extra
fields. I think a user_profile in a 1:1 relationship offers all that you
probably need for the stuff which is exclusively human-user-related.

Different user profile tables for different roles might be exactly what
you need or they might be overkill.

Maybe the way to think about it is to abstract the commonalities into a
single (ie simpler) user_profile table and then really examine the
differences and decide how best to deal with them in the context of what
you want the software to do.

That might be roles the way I do it. It might be multiple profile tables
which inherit from the common one.

I sometimes wonder how I might explain my project design to someone who
is taking it over and that usually stops all my complex ideas and forces
me to look for something closer to the real world which will be easier
to explain and if you code with real world language it will be easier to
hand over. And to understand the next time you look at it yourself!

Good luck

Mike
> <mailto:django-users%2Bunsu...@googlegroups.com>
> > <mailto:django-users...@googlegroups.com
> <mailto:django-users%2Bunsu...@googlegroups.com>>.
> > To view this discussion on the web visit
> >
> https://groups.google.com/d/msgid/django-users/51552bea-877b-4c66-8a7a-42a68ca761e5%40googlegroups.com
>
> >
> <https://groups.google.com/d/msgid/django-users/51552bea-877b-4c66-8a7a-42a68ca761e5%40googlegroups.com?utm_medium=email&utm_source=footer>.
>
> --
> 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
> <mailto:django-users%2Bunsu...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-users/84e69a79-d0bc-7fe4-8a6b-e757352c27ee%40dewhirst.com.au.
>
> --
> 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
> <mailto:django-users...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/django-users/CAAUw0KoP_uMPyMv7aXrA1iwW0ESCwFqn2f8RHnvQR1k9W9%3DMQw%40mail.gmail.com
> <https://groups.google.com/d/msgid/django-users/CAAUw0KoP_uMPyMv7aXrA1iwW0ESCwFqn2f8RHnvQR1k9W9%3DMQw%40mail.gmail.com?utm_medium=email&utm_source=footer>.

Reply all
Reply to author
Forward
0 new messages