Django - Extend User Twice Using AbstractUser - django

I'd like to have both Teacher and Student Accounts in my Django WebApp. Now I have extended the base user using Student(AbstractUser) and AUTH_USER_MODEL='mainApp.Student'.
Now I'd like to implement a Teacher Account, using the same approach. But before I wreck my project I wanted to ask for input. Do you think this can work or should I use a OneToOneField or maybe just a is_teacher / is_student boolean?
I'd like to avoid using a OneToOneField, as that creates the need to first create a User and then also create a Student / Teacher.

Related

Django - Why should I extend User as opposed to creating my one user class

All,
I'm having some difficulty understanding what is the advantage of extending the user class in Django? I feel it's more advantageous to overwrite it and create my own user class, with all the files that I want.
I'm asking this because all the tutorials that I see always refer to create a profile class and from there extend the user class.
I would see the advantage if I wanted to extend the user class multiple times, but I feel that this never happens.
You need to extend the AbstractUser class because you will want to use the functionality that django has already provided. This includes authentication, group, permissions, etc.
You will not want to write all from scratch. Even if you plan to write all on your own because of unique needs of your application, it is still advisable to extend the AbstractUser class. May be you will need to use some of its feature in future.
Contrary to your case, if someone needs only functionality of User model already provided by Django, it is still advisable to create new model that extends AbstractUser class. This is because, once you have set AUTH_USER_MODEL in your settings.py, you can't change it (or will be very difficult) to change in future. Doing this, will always give you freedom to use all the functionality that django has provided and implement the functionality you may need to implement.
So, the ideal thing is to always do this in the beginning of any project (before any migrations):
users.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
phone = models.CharField(max_length=25, blank=True,unique=True, null=True)
def __str__(self):
return self.username
And in your settings.py.
AUTH_USER_MODEL = 'users.User'
Here's the source code for Django's AbstractUser class where you extend your custom user model from. It said this in the first few lines:
An abstract base class implementing a fully-featured User model with
admin-compliant permissions.
If you don't include what Django's default classes come with, errors may occur. Take the one above, if you didn't add the permissions, you might not be able to access Django's admin site. Fields like is_active, is_staff, etc. provide more flexibility for you to customize your authentication later on.
Digging deeper, there are additional configurations:
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
Unless you're absolutely positive that you can include all these in your model, I will simply suggest you to utilize what's already given.

Why to extend Django User model? Is it bettet than using One-To-One Link With a User Model (Profile)?

this is my first question here, nevertheless S.O. is my main point of reference and support while developing my coding skills with Django.
Thanks on advance to all people who is part of SO!
My main question is: Under your experience, in which situations you needed to extend User Model? is it a common thing to do? ... all tutorials and blogs I have read says ... "for example, if you want to use an email insted a username". But this doesn't sound like a real reason to me, I haven't found other reasons than this.
If I want to use email as username I can create a user-creation form with a field to get username and allowing only emails. This will perfecly solve the problem right?
In my case I followed Django official recommendation:
it’s highly recommended to set up a custom user model, even if the
default User model is sufficient for you. This model behaves
identically to the default user model, but you’ll be able to customize
it in the future if the need arises.
So I did this:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
I'm ready, but ready for what? Please enlight me.
The second part of my question is: Let's imagine in the future I want to register more information about the users of my blog (country, gender, etc.) ... is it possible to create a profile table and point it with 1-to-1 relationship to my extended User model (which does nothing but is ready for the future)? Should I do it now that I have already dropped and created the database again?
Thanks a lot and sorry if I wrote too much.
Best regards,
goka.
If your website need to collect more user information, like, company, telephone, zip code. Also, maybe you need to classify user, like, level_1, level_2, something haven't be define in User model. Then, it's better to create a profile model.
If you already have some user registered, then you add new profile model, it may cause your future coding very messy, like, one year later, you or other software engineer may assume all User have 'profile', and directly use user.profile.something without checking whether profile exists or not or a user, that will throw exception.

implementing multiple profiles with django-all-auth

Django noob here - I was recently pointed to django-all-auth for registering/handling users, which I'm finding awesome in its near instant setup.
However I'm stumbling at trying to implement multiple user profile models. In reading other answers I've found this to be the closest answer thus far, but not really what I need.
When coding my own basic registration the user would select an account type (basic, pro, elite for example - each being their own profile model). Depending on the link selected the signup form would display both the generic User registration form as well as the profile form of the type chosen by the user.
I know I can go so far as to completely customize all-auth and make something like this work, but I'm hoping to be pointed in a direction that involves less wrecking of the original app. I've thought about having user redirected after signup to choose a profile type, but that seems to be a lot of extra steps.
Thanks for the help!
To extend the basic user class, just subclass AbstractUser. You can find that in the docs. With this you can add the fields your basic user is missing.
Now, you want several types of profiles with different fields, or perhaps the same fields but adding new fields every time.
You can create something like:
class ProfileBase(models.Model):
user=models.OneToOneField(User)
class ProfilePro(ProfileBase):
pro_field=models.SomeField(Foo)
#You can extend ProfilePro here if you want it to include the pro_field
class ProfileElite(ProfileBase):
elite_field=models.someField(Bar)
Once you have these models creating the forms should be easy.
Be aware, when you subclass this way django creates 1 table per model, including on the subclass table only the new fields. This makes necessary a join per level of inheritance so try not to abuse that.
There is a second way to use inheritance:
class ProfileBase(models.Model):
user=models.OneToOneField(User)
class Meta:
abstract=True
If you define the base class as abstract you won't have a table for it, so you can't create objects for that model, but each of your subclasses will be on it's own table. If you do it this way you may need extra logic to handle cases when user changes of type of profile (i.e. went from pro to elite).
To keep this separated from django-allauth, just finish the registration (create a form and in your settings define ACCOUNT_SIGNUP_FORM_CLASS to override all-auth default with your basic info + pick a profile type form) and once your user is logged in, redirect them to their profile to finish the process.

what type of model design should I use to add roles to Users in such a way that it works in Django and in the Django admin

I am using Djangos default authentication system (django.contrib.auth) and I would like to add 'roles' to my users in such a way that Django Admin can work with it.
An example:
A user can be a staffmember, teacher, student and/or parent
If the user has a role assigned, he will gain permissions (eg. staffmembers may sign in to the Django admin)
Some roles might have some extra fields (eg. parent has a relation with at least one student and each student has a field with it's classgroup
Not every role has extra fields
A parent can be a teacher or staffmember and vise versa
A student can not have another role
There are all sorts of (conventional) ways to accomplish the above within a model. Django supports a lot of them, but the Django admin does not. The Django admin has a lot of good features so I would like to use it (but I am getting more and more afraid that it will not be possible).
The following model is what I thought of at first:
class ExtendedUser(contrib.auth.models.User):
"""
For the ease of use I inherit from User. I might
want to add methods later
"""
pass
class StaffMember(models.Model):
"""
A staffmember is a co-worker of the organisation
and has permissions to make changes to (parts of)
the system.
For now the staffmember only has methods
"""
user = models.OneToOneField(ExtendedUser, related_name="staff_member")
class Student(models.Model):
"""
A student can participate in some courses
"""
user = models.OneToOneField(ExtendedUser, related_name="student")
class Teacher(models.Model):
user = models.OneToOneField(ExtendedUser, related_name="teacher")
subjects = models.ManyToManyField(..)
class Parent(models.Model):
user = models.OneToOneField(ExtendedUser, related_name="parent")
children = models.ManyToManyField(Student, related_name=parents")
This works in Django (and in a lot of other MVC-based frameworks). But I can't find a proper way to display the above in the admin.
Ideally I would like to add a User and then within the User-changeview add different roles. At first I thought I could use Inlines:
class StudentInlineAdmin(admin.StackedInline):
model = Student
extra = 0
template = 'accounts/admin/role.html'
I then make some slight changes to the inline template to present the editing user button with a caption 'Add Student role'. Once we hit the button, we display the form and a User role is added. Not ideal, but it works.
Too bad, for Staffmembers there are no fields to add to the inline form. This way it is not possible to trigger the 'has_changed' property for inlines forms. This results in the new role not being saved to the database.
To solve this last problem, I hacked around a bit and added a 'dummy' formfield to the empty user-roles and then hide this field using JS. This did trigger the has_changed.
Still this would not work for somehow none of my inline-models are saved during some tests later on.
So I think I am just doing it the wrong way. I did a lot of Googling and found a lot of people hassling with the same sorts of problems. The one that suited my situation the most was http://www.mail-archive.com/django-users#googlegroups.com/msg52867.html. But still this solution does not give me an easy way to implement the admin.
I also thought about using the built-in groups but in that case I have no idea how I should add the different fields.
Another thing I thought of was trying to 'Add a student' instead of adding a User and assigning a role to him. This works pretty well in the admin if you just inherit the user:
class StudentUser(auth.models.User):
pass
But two problems here. At first it is not possible to be a staffmember and a teacher. Second it is not really working in the rest of Django for the request object return a User object for which it is impossible to request the Student, Parent, Staffmember object. The only way to get one of these is to instantiate a new Student object bases on the User object.
So here is the question: what type of model design should I use to add roles to Users in such a way that it works in Django and in the Django admin?
Friendly Regards,
Wout
I'm assuming in the following that you do not want to alter the admin, or make a copy of django.contrib.admin and hack it as desired.
A user can be a staffmember, teacher, student and/or parent
You could store this in a user profile. Inheriting from User will work, too, but instead of using User.get_profile(), you'll need to manually map from User to ExtendedUser.
If the user has a role assigned, he will gain permissions (eg. staffmembers may sign in to the Django admin)
In that specific case, you can't use automatic role-based assignment. Whether or not a person can access the admin is determined by the is_staff field in their User record.
The most automatic way I can think of is to create an "Update Permissions" admin command, which will update admin fields like is_staff and the permissions based on the role set in the user's profile. BTW, even though this is not fully "automatic", it is a denormalization that can improve performance.
Some roles might have some extra fields (eg. parent has a relation with at least one student and each student has a field with it's classgroup
Not every role has extra fields
A parent can be a teacher or staffmember and vise versa
A student can not have another role
Read up on form validation. That's where you can enforce these rules.
In your model, I'd recommend that you alter the related names of your one-to-one fields:
class StaffMember(models.Model):
user = models.OneToOneField(ExtendedUser, related_name="as_staff_member")
class Student(models.Model):
user = models.OneToOneField(ExtendedUser, related_name="as_student")
class Teacher(models.Model):
user = models.OneToOneField(ExtendedUser, related_name="as_teacher")
class Parent(models.Model):
user = models.OneToOneField(ExtendedUser, related_name="as_parent")
Since theUser.as_teacher is a lot clearer than theUser.teacher (which I would read as "the user's teacher").
This works in Django (and in a lot of other MVC-based frameworks). But I can't find a proper way to display the above in the admin.
You're going to have one table in the admin per role. There's no fancy "bottom half of the edit page will redraw itself when you change roles" feature. If you want that, you will need to write your own admin.
Django's admin is great, but it's not trying to be everything to everyone. I have a role-based setup like yours (except the roles themselves are stored in a table), and the admin works fine if a little clunky. The general idea is that if the admin isn't good enough, then you should be writing your own views.
I also thought about using the built-in groups but in that case I have no idea how I should add the different fields.
The built-in groups are not what you're looking for.
Another thing I thought of was trying to 'Add a student' instead of adding a User and assigning a role to him. [...] At first it is not possible to be a staffmember and a teacher.
"Subclassing" is a more restrictive one-to-one. I think your initial model is better.
Second it is not really working in the rest of Django for the request object return a User object for which it is impossible to request the Student, Parent, Staffmember object. The only way to get one of these is to instantiate a new Student object bases on the User object.
No, you instead find the Student object using the auto-generated id from the User object:
try:
theStudent = Student.objects.get(user_ptr_id=theUser.id)
except Student.DoesNotExist:
# That user isn't a student; deal with it here.
If you're going to use the admin, I think you're going to have to live with a two-step process of adding an ExtendedUser, then adding Student or whatever entries for them.
So it comes down to a tradeoff: a little extra work using the built-in admin, or writing your own user management views. WHich route is best really depends on how much this interface will be used: If it's just you, then the admin should be fine, even with its warts. If a lot of people will be using it on a routine basis, then you should just write your own views to handle things.

Django: Using custom AUTH system, why is the User model always still needed?

I'm developing an SAAS and having the hardest time wrapping my brain around why I need to use "User" for anything other than myself. I don't know why but it makes me queezy to think that I, as the developer/admin of the entire software, with full Django Admin access (like the Eye of Sauron), have the same type of User object as an "Account" holder's "UserProfile" has. Please help me understand why this is necessary.
Example:
class Account(models.Model): # represents copporate customer
admin = models.ForeignKey(User)
# other fields ...
class UserProfile(models.Model):
user = models.ForeignKey(User)
account = models.ForeignKey(Account)
It feels like I'm mingling the builtin Admin functionality with my account holders' users' functionality. Is this just for purposes of reusing elements like request.user, etc.?
Well, reuse of code and functionality might be a happy side-effect, but fundamentally I don't think this is broken.
A User represents someone using your website. At the base level it doesn't matter who that person is or what features or functionality they need - just that they make requests and can be identified in some way.
Further functionality can be added in different layers, either through built in components like Groups or Permissions, or through something else you build on top yourself as you are doing in your example.