I ran into a problem
I am making a job search site on Django, I have the following logic:
Authorization and authentication of ordinary job seekers using Django's built-in model - User
Also separate authorization and authentication for users who provide work, i.e. employers,
which are placed in my own model Employer
Here is my Employer model
class Employer(AbstractUser):
full_name = models.CharField(max_length=150, verbose_name="Ім'я")
main_office_city = models.ForeignKey(City, on_delete=models.CASCADE,
verbose_name='Місто головного офісу')
phone_number = models.ForeignKey(Phone, on_delete=models.CASCADE)
email = models.CharField(max_length=50, unique=True, verbose_name='Email')
hashed_password = models.CharField(max_length=120, default='')
date_joined = models.DateTimeField(verbose_name='Дата реєстрації',
default=timezone.now)
def __str__(self):
return self.full_name
class Meta:
verbose_name = 'Роботодавець'
verbose_name_plural = 'Роботодавці'
I read in the documentation that to create your own authentication system you can use the imitation from the AbstractUser class
But in my case this is not the best choice, because AbstractModel adds its own fields by default.
That is, I think that I need to either somehow make it so that the AbstractUser class does not add its fields, or think of some other authentication logic using another technology
Maybe someone has some ideas how it can be done?
Im facing a problem in Django with authorization permissions (a bit new to Django).
I have a teacher, student and manager models.
When a teacher sends a request to my API they should get different permissions than a student (ie, a student will see all of his own test grades, while a teacher can see all of its own class's students, and a manager can see everything).
My questions are as follows:
How do I make all of my models valid system users? I've tried adding
models.OneToOneField(User, on_delete=models.CASCADE)
But this requires creating a user, and then assigning it to the teacher. What I want is for the actual teacher "instance" to be the used user.
How do I check which "type" is my user ? if they are a teacher, student or manager? do I need to go over all 3 tables every time a user sends a request, and figure out which they belong to ? doesnt sound right.
I thought about creating a global 'user' table with a "type" column, but then I wont be able to add specific columns to my models (ie a student should have an avg grade while a teacher shouldn't) .
Would appreciate any pointers in the right direction.
When you need multiple user types, for example, in your case multiple roles are needed like a student, teacher, manager, etc… then you need a different role for all the persons to categorize.
To have these roles you need to extend AbstractUser(for simple case) in your models.py for your User model also You can specify permissions in your models. Attaching permissions is done on the model's class Meta using the permissions field. You will be able to specify as many permissions as you need, but it must be in a tuple like below:
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.db.models.fields.related import ForeignKey
from django.utils.translation import gettext as _
class Role(models.Model):
STUDENT = 1
TEACHER = 2
MANAGER = 3
ROLE_CHOICES = (
(STUDENT, 'student'),
(TEACHER, 'teacher'),
(MANAGER, 'manager'),
)
id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)
def __str__(self):
return self.get_id_display()
class User(AbstractUser):
roles = models.ManyToManyField(Role)
username = models.CharField(max_length = 50, blank = True, null = True, unique = True)
email = models.EmailField(_('email address'), unique = True)
native_name = models.CharField(max_length = 5)
phone_no = models.CharField(max_length = 10)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
def __str__(self):
return "{}".format(self.email)
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='students')
sample_field_name = models.CharField(max_length = 50, blank = True, null = True)
class Meta:
permissions = (("sample_permission", "can change sth of sth"),)
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='teachers')
sample_field_name = models.CharField(max_length = 50, blank = True, null = True)
class Meta:
permissions = (("sample_permission", "can change sth in sth"),)
class Manager(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='managers')
sample_field_name = models.CharField(max_length = 50, blank = True, null = True)
class Meta:
permissions = (("sample_permission", "can change sth in sth"),)
After that you should have your permissions for your views and Adding permissions to restrict a function to only users that have that particular permission can be done by using a Django built-in decorator, permission_required for function-based views::
from django.contrib.auth.decorators import permission_required
#permission_required('students.sample_permission')
def student_sample_view(request):
"""Raise permission denied exception or redirect user"""
And if you are using a class-based view, you just need to use a mixin, PermissionRequiredMixin:
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView
class SampleListView(PermissionRequiredMixin, ListView):
permission_required = 'students.sample_permission'
# Or multiple permissions
permission_required = ('students.sample_permission', 'teachers.other_sample_permission')
This was one way you can manage multiple roles in your Django project,
you can also find more ways in below blogs and references:
How to Implement Multiple User Types with Django
Managing User Permissions in Django
Supporting Multiple Roles Using Django’s User Model
Django Roles, Groups and Permissions Introduction
django-multiple-user-types-example GitHub repository
I have been using Django for quite a while but never have I thought of this until now.
Currently, I have a project that contains different user levels. Usually, in my past experience, I only developed systems using Django with only two user levels which are superuser and normal/regular user. So my question is what are the effective ways to present these different user levels in the model/database? Here, I'm going to use a school system as an example and also provide some of my initial thoughts on implementing it.
User levels:
Admin (superuser & staff)
Principal
Teacher
Students
Method #1: Add new tables based on each user level
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
user = models.CharfieldField(max_length = 10, unique = True)
class Admin(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
class Pricipal(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
Method #2: Add additional user types attributes in the User model
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
user = models.CharfieldField(max_length = 10, unique = True)
is_superuser = models.BooleanField(default = False)
is_staff = models.BooleanField(default = False)
is_principal = models.BooleanField(default = False)
is_teacher = models.BooleanField(default = False)
is_student = models.BooleanField(default = False
'''
User table in DB:
user | is_superuser | is_staff | is_principal | is_teacher | is_student
'''
My thoughts:
In Method #1, as the built-in User model has two fields, is_staff and is_superuser, Is it possible to implement/change the fields into a SuperUser/Admin table as in the example above? This means that when I create an admin/superuser, I want it to add a new row into the Admin table, instead of adding a new user and updating the user's is_superuser and is_staff fields into 1 in the built-in User model.
In Method #2, the problem with it is that tables with different access privileges are directly connected to it. For example, Salary model (which cannot be accessed by Student user) has a direct link with the User model (contains Student user).
I hope I am able to get some insights and also a proper effective way of implementing this so that to prevent any implementation inconvenience and mistakes in the future. Thank you very much.
I think you are in the right path with method #2. It is lighter, and more straightforward.
I would not use a custom "user-like" model for each permission level. Over-complicated, does not scale, and multiply the number of queries, with no very benefit for your problem. Not your UML schema but its content must guarantee your permission requirements.
If the permission levels are not mutual-exclusive :
from django.db import models
from django.contrib.postgres.fields import ArrayField
class User(AbstractUser):
ADMIN = 0
PRINCIPLE = 1
TEACHER = 2
STUDENT = 3
USER_LEVEL_CHOICES = (
(ADMIN, "Admin"),
(PRINCIPLE, "Principle"),
(TEACHER, "Teacher"),
(STUDENT, "Student"),
)
status = ArrayField(
models.IntegerField(choices=USER_LEVEL_CHOICES, blank=True, default=STUDENT),
)
But you need to have a wider reflexion.
I think you are talking about two separate problems : polymorphism, and permissions
Polymorphism :
Polymorphism is the ability of an object to take on many forms. For a Django model, it can be done with many strategies : OneToOneField -as you mentioned- multi-table inheritance, abstract models, or proxy-models.
Very good resources : this article, and Django doc about model inheritance
This very complex problem all refer to : how much your several forms of a same entity are similar, or different. And which operations are particularly similar or different (data shape, querying, permission, ...etc)
Permissions design :
You can choose among several patterns
Model-oriented permission : A user is granted "add", "view", "edit" or "delete" permission to a Model. This is done in Django with the built-in Permission model, that have a ForeignKey to ContentType
Object-oriented permission : A user is granted "add", "view", "edit" or "delete" permission for each Model instance. Some packages provides this ability, django-guardian for example.
Rule-oriented permission : A user is granted permission to a Model instance through custom logic instead of M2M table. The django rules package provide this kind of architecture.
You can create from AbstractUser (a full User model, complete with fields, including is_superuser and is_staff) a Profile and then, once you have the profile, give the chance of users to create other type of profile (Student, Teacher or Principle) which could have functionalities of its own.
For instances, in your models.py
class Profiles(AbstractUser):
date_of_birth = models.DateField(max_length=128, blank=True, null=True, default=None, verbose_name=_(u'Date of birth'))
principle = models.OneToOneField(Principles, null=True, blank=True, verbose_name=_(u'Principles'), on_delete=models.CASCADE)
teacher = models.OneToOneField(Teachers, null=True, blank=True, verbose_name=_(u'Teachers'), on_delete=models.CASCADE)
student = models.OneToOneField(Students, null=True, blank=True, verbose_name=_(u'Students'), on_delete=models.CASCADE)
class Meta:
db_table = 'profiles'
verbose_name = _('Profile')
verbose_name_plural = _('Profiles')
To that model you can add class methods, such as
def is_teacher(self):
if self.teacher:
return True
else:
return False
Then, your Teachers model could look like this
class Teachers(models.Model):
image = models.FileField(upload_to=UploadToPathAndRename(settings.TEACHERS_IMAGES_DIR), blank=True, null=True, verbose_name=_('Teacher logo'))
name = models.CharField(blank=False, null=False, default=None, max_length=255, validators=[MaxLengthValidator(255)], verbose_name=_('Name'))
street = models.CharField( max_length=128, blank=False, null=True, default=None, verbose_name=_('Street'))
created_by = models.ForeignKey('Profiles', null=True, blank=True, on_delete=models.SET_NULL)
One of the methods that I used in several projects is this (pseudo code):
class User(AbstractUser):
ADMIN = 0
PRINCIPLE = 1
TEACHER = 2
STUDENT = 3
USER_LEVEL_CHOICES = (
(ADMIN, "Admin"),
(PRINCIPLE, "Principle"),
(TEACHER, "Teacher"),
(STUDENT, "Student"),
)
user_level = models.IntgerField(choices=USER_LEVEL_CHOICES)
def lvl_decorator():
def check_lvl(func):
def function_wrapper(self, actor, action_on, *args, **kwargs):
if 'action_lvl' not in action_on: # then action_on is user
if actor.user_lvl < action_on.user_lvl:
return True
return False
else: # then action_on is action of some kind for that user (you can add action_lvl to ... and pas them to this wapper)
if actor.user_lvl < action_on.action_lvl:
return True
return False
return function_wrapper
return check_lvl
Then you can write wrapper function with this logic for any action check if action level is bigger than user level e.g.: if someone wants to change superuser password he/she should be logged-in with level-0-user but for changing normal user's password he/she should be level 0, 1. This logic also can be applied to class, functions, etc actions.
Create base class and then add lvl_decorator to it then inherent from it => this keeps your code super clean and prevents further copy paste.
example of what i mean:
def lvl_decorator():
def check_lvl(func):
def function_wrapper(self, actor, action_on, *args, **kwargs):
if 'action_lvl' not in action_on: # then action_on is user
if actor.user_lvl < action_on.user_lvl:
return True
return False
else:
if actor.user_lvl < action_on.action_lvl:
return True
return False
return function_wrapper
return check_lvl
class BaseClass(type):
def __new__(cls, name, bases, local):
for attr in local:
value = local[attr]
if callable(value):
local[attr] = lvl_decorator()
return type.__new__(cls, name, bases, local)
# in other locations like views.py use this sample
class FooViewDjango(object, ApiView): # don't remove object or this won't work, you can use any Django stuff you need to inherent.
__metaclass__ = BaseClass
def baz(self):
print('hora hora')
Use this base class in any where you want.
I have been getting my head around these basics but I am not getting it right. I am trying to associate my view to my user model using team which is a foreign key. When I try to create of a gps, I get an error saying "team is a required field" but instead it should be read only. The team attribute should be filled automatically with the id of the currentUser
Model
class User(models.Model):
first_name = models.CharField(max_length=200,blank=False)
last_name = models.CharField(max_length=200, blank=False)
class Gps(models.Model):
location = models.CharField(max_length=200,blank=False)
team= models.ForeignKey(User, on_delete=models.CASCADE)
serializers
class GpsSerializer(serializers.ModelSerializer):
class Meta:
model = Gps
fields = ('id','location','team')
view
class Gps_list(generics.ListCreateAPIView):
queryset = Gps.objects.all()
serializer_class = GpsSerializer
team = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
There are two changes needed. First, team field definition should be moved to serializer class instead of view. Second, you should use Django's contrib.auth.User model instead of your definition of User, as because serializers.CurrentUserDefault() will bring request.user only. So you should remove your User definition and import that to your models.py:
from django.contrib.auth.models import User
Further steps would be to replace read_only=True with queryset=User.objects.all() to allow create.
I'm working on an existing Django project in Django 1.4. I want to upgrade to Django 1.5 for implementing a custom user model. I have to ensure that my existing information is not deleted from the database(Postgres 9.1).
This post explains the steps to be done for data migration using South but only for AbstractUser. I have to use AbstractBaseUser. Is there any way?
My existing model is like :
class Individual(models.Model):
user = models.ForeignKey(User, unique=True, blank=True, null=True)
parent = models.ForeignKey('self', unique=True, blank=True, null=True)
....(some many to many and foreign key)
I want to change it to something like this.
class User(AbstractBaseUser, Individual, PermissionsMixin):
email = models.EmailFeild(max_length=50, unique=True)
is_admin = models.BooleanField(default=False)
username = models.CharField(max_length=50,unique=True)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
The individual class will contain all previous fields except the user. How can I go about doing this?
Document how to migrate from a built-in User model to a custom User model thread has some workaround discussions.