How to implement different permission in Django? - django

I have a model Payment with fields: date_created, comment, description, amount. Each payment belongs to User.
class Payment(models.Model):
author = models.ForeignKey('auth.User')
amount = models.FloatField(max_length=10)
description = models.CharField(max_length=128)
created_date = models.DateTimeField(
default=datetime.now())
comment = models.TextField(max_length=256)
def __str__(self):
return self.description
Now I need to implement different roles for User: regular (can CRUD his payments), manager (can CRUD users), admin (can CRUD everything). So, the main question is how to implement this roles for users during registration (further it can't be changed). I also need to be able to set this role during registration via api (django rest framework).

Finally decided to user Django built-in Permissions
#e.g.
content_type = ContentType.objects.get_for_model(User)
permission = Permission.objects.get(codename='admin_can_manage_users')
user.user_permissions.add(permission)

Related

Is there a way to optimize the creation and retrieval of users (that are also part of another model) in Django with just one request?

My app has Users that can be Doctors/Patients/Secretaries. To create a Doctor, therefore, I perform two POST requests: one for the creation of a User and one for a Doctor. The way I do this, the User has to be created first so that I can later create the Doctor (Doctor requires a 'User' field). I am using Django Rest Framework to create the API.
class User(AbstractUser):
# defined roles so when I retrieve user, I know to perform a
# request to api/doctors/ or api/secretaries/ etc depending on role.
ROLES = (
('d', 'Doctor'),
('s', 'Secretary'),
('p', 'Patient'),
)
role = models.CharField(
max_length=1, choices=ROLES, blank=True, default='p', help_text='Role')
class Doctor(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
national_id = models.CharField(max_length=32, blank=False)
...
Since I'm new to Django, I don't know if two requests is the standard/best way of creating this User/Doctor.
This comes to mind as I am also thinking of the GET methods which will be performed later on (two GET requests when a Doctor logs in if I want to retrieve all of their info (User/Doctor)?)
I read about subclassing, which would be something like Doctor(User), then the only necessary request would be a single POST to create a Doctor (which would alongside create the User). I am, however, skeptical of subclassing the User as I read at least 3 SO answers stating it could cause problems in the future.
have a look at this good tutorial https://simpleisbetterthancomplex.com/tutorial/2018/01/18/how-to-implement-multiple-user-types-with-django.html which explain 2 different approches
extend AbstractUser with flags is_doctor, is_secretary, is_patient
class User(AbstractUser):
is_doctor = models.BooleanField('Doctor status', default=False)
is_secretary = models.BooleanField('Secretary status', default=False)
is_patient = models.BooleanField('Patient status', default=False)
using roles which suites your case:
class Role(models.Model):
'''
The Role entries are managed by the system,
automatically created via a Django data migration.
'''
ROLE_CHOICES = (
('d', 'Doctor'),
('s', 'Secretary'),
('p', 'Patient'),
)
id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)
def __str__(self):
return self.get_id_display()
class User(AbstractUser):
roles = models.ManyToManyField(Role)
Problem
Determine best practices for handling multiple user types and adding attributes to a user in Django.
Solution
The following is are recommendations based on design patterns that have been commonly used in Django since version 0.96. These recommendations are present in Django’s documentation (see: References).
Roles
Use Django’s built in permissions module for role and group management instead of rolling your own role and group management.
Instead of a model for each type, create a single UserProfile model, relegating user types to being managed by permissions model.
I recommend using a data migration to add groups so that default groups are automatically seeded on initial migrate call—this reduces overhead for anyone setting up your project for the first time.
OneToOne
Use Django OneToOne field instead of ForeignKey.
UserProfile and Signals
Create a signal that creates a UserProfile on User create.
Example
class UserProfile(models.Model):
user = models.OneToOne(“User”, on_delete=models.CASCADE)
national_id = models.CharField(max_length=32,
def create_profile(sender, **kwargs):
user = kwargs["instance"]
if kwargs["created"]:
user_profile = UserProfile(user=user)
user_profile.save()
post_save.connect(create_profile, sender=User)
References
Django permissions (groups): https://docs.djangoproject.com/en/3.1/topics/auth/default/#groups
Django Data Migrations: https://docs.djangoproject.com/en/3.1/topics/migrations/#data-migrations
Django extending User model recommendation: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-django-s-default-user
Django post_save signal: https://docs.djangoproject.com/en/3.1/ref/signals/#post-save
in your case (the model you made) you can create the doctor and the user in one post request to the doctor creation by overriding the create function for the
from rest_framework.generics import CreateAPIView, ListAPIView
class CreateDoctorViewSet(CreateAPIView, ListAPIView):
def create(self, request, *args, **kwargs):
data = self.request.data
user_dict_keys = ["username", "email", "first_name", "last_name"]
user_dict = {key: data.pop(key, None) for key in user_dict_keys}
user_dict['role'] = "d"
user_serializer = UserSerializer(data=user_dict)
# if it's not valid it will return the exception details for the requester
user_serializer.is_valid(raise_exception=True)
user = user_serializer.create(user_dict)
user.set_password(data['password'])
data.pop("password", None)
user.save()
response = super().create(request, *args, **kwargs)
if response.statu_code == 201:
return response
# if an error happened while in the doctor model (model error or serializer error) >> delete the created user
user.delete()
return response
or made a little more DRY :
from rest_framework.generics import CreateAPIView, ListAPIView
def create_user(self):
data = self.request.data
user_dict_keys = ["username", "email", "first_name", "last_name"]
user_dict = {key: data.pop(key, None) for key in user_dict_keys}
user_dict['role'] = "d"
user_serializer = UserSerializer(data=user_dict)
# if it's not valid it will return the exception details for the requester
user_serializer.is_valid(raise_exception=True)
user = user_serializer.create(user_dict)
user.set_password(data['password'])
data.pop("password", None)
user.save()
return user
class CreateDoctorViewSet(CreateAPIView, ListAPIView):
def create(self, request, *args, **kwargs):
user = create_user(request)
response = super().create(request, *args, **kwargs)
if response.statu_code == 201:
return response
# if an error happened while in the doctor model (model error or serializer error) >> delete the created user
user.delete()
return response
Personal Advices:
in the case you're providing the User model has one role so it's better to make the user field in the Doctor class OneToOne instead of ForeignKey.
Of course if you have cases where there are people for example converting from Doctor to Secretary and you want them to switch between roles on the same account you can keep the ForeignKey on Doctor model but you have to make multiple roles possible in the user model.
What you mean by subclassing it is called Multi table inheritance. And there is no problem in using it, no side effects and it is perfectly compatible with Django Rest Framework (which you have tagged). This is the way it works:
class User(AbstractUser):
# Your common fields for all user types.
class Doctor(User):
national_id = models.CharField(max_length=32, blank=False)
class Secretary(User):
# Your specific fields for secretary model
class Patient(User):
# Your specifict fields for patient model
In background, it uses a OneToOne relationship for each subtype.
Advantajes of using Multi table inheritance:
It is simple and elegant: you don't have to take care of different tables, queries, etc; Django does it for you.
It also unsures a good and formalized structure of your database: different tables for common and specific data in OneToOne relationship.
It is suitable for your needs? That depends.
If each subtype has its own specific fields -> use multi table inheritance without doubt.
If each subtype has the same set of fields but different behaviours (different class/model methods, code, etc) -> use proxy models.
If all the subtypes have the same set of fields and the same behaviour (same class/model methods, code, etc) -> use role based approach (one field identifying the role).
(extra) If you have dozens of subtypes, each one of them has different fields and you don't care too much about database formalization -> Don't use Multi table inheritance. In this case, you can use a mix of role based approach with JSON fields (for storing all the specifict fields) and proxy models (for handling different behaviours)**.

Django restrict user access to related objects

I am writing an application which allows a user to authenticate and view objects only within their organisation. For a generic.ListView, I can restrict access with the code below:
models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
organisation = models.ForeignKey('Organisation', null=True, on_delete=models.CASCADE)
class Organisation(models.Model):
name = models.CharField(max_length=255, unique=True, null=False, verbose_name="Name")
views.py
class OrganisationList(LoginRequiredMixin, generic.ListView):
model = Organisation
def get_queryset(self):
return Organisation.objects.filter(id=self.request.user.organisation.id)
In addition to this view the user will access forms, an API and the django admin interface that require this restriction.
For example, user Brett belongs to Kids Incorporated. When he logs in to the admin panel he can currently also see ACME Corporation but should not be able to do so.
I have looked at ModelManager interface but I am not sure how to get the user request and override
Is there a way to run write one query for all views (DRY) that so that a user will only see their own organisation?
There is a way.
Install django-crequest package: https://github.com/Alir3z4/django-crequest#installing
Create a model.Manager within models.py and override the get_queryset method
models.py
...
from crequest.middleware import CrequestMiddleware
class UserOrganisationManager(models.Manager):
def get_queryset(self):
request = CrequestMiddleware.get_request()
return super().get_queryset().filter(id=request.user.organisation.id)
class Organisation(models.Model):
name = models.CharField(max_length=255,
unique=True, null=False, verbose_name="Name")
...
objects = UserOrganisationManager()
You should use groups and permissions for that, a group per organization. And only members of some group can read (the permission) objects within their organization group.
This will allow you to have more that one organization for a user or vice-versa. And of course no require any other dependency.

Django AUTHENTICATION AND AUTHORIZATION groups and permissions want to create verified users

Hi Djangonauts,
I am new to Django please forgive any silly mistake in logic or code.
Intro:
I am building a web app in which members can write posts on a topic and offer courses on that topic. Example A member can write a blog about doing a wheelie on a bicycle and offer courses on that.
What I want:
I want members who want to offer courses to be verified. Example: The member has to fill a form with their details like...
name, address, and photo ID. Plus pay a charge of $9.99 to get verified. After admin (I in this case) checks if everything is good I will approve them. and then they will be "Verified Members" and be able to offer courses
What I have so far: Right now members can offer courses as there is no verified clause
class Event(models.Model):
user = models.ForeignKey(User, related_name='seller')
post = models.ForeignKey(Post, related_name='course')
price = models.DecimalField(max_digits=6, decimal_places=2)
stock = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(35)])
date = models.DateField()
time_from = models.TimeField()
time_to = models.TimeField()
event_types = (
('1', 'Webinar'),
('2', 'Actual Meet'),
)
event_choice = models.CharField(max_length=1, choices=event_types)
def get_absolute_url(self):
return reverse('posts:single', kwargs={'username': self.user.username,
'slug': self.post.slug})
def __str__(self):
return 'Meet for ' + self.post.title
How I plan to do it: I was planning to add a group in Django's admin AUTHENTICATION AND AUTHORIZATION
Home › Authentication and Authorization › Groups › Add group
Name: Verified
Permissions: Chosen permissions
event| event| Can add event
event| event| Can change event
event| event| Can delete event
Now what do I do from here?: Have I done things right so far, How do I take it from here. Do I create a model called verified and add forms.py to have members verified. How do permissions come in the picture.
My monkey patch (not a part of the question, for #Ojas Kale )
class Contact(models.Model):
user_from = models.ForeignKey(User, related_name='supporter')
user_to = models.ForeignKey(User, related_name='leader')
def __str__(self):
return '{} follows {}'.format(self.user_from, self.user_to)
User.add_to_class('following',
models.ManyToManyField('self', through=Contact, related_name='followers', symmetrical=False))
One way to go about it is adding a is_verified column in the user. There are various ways for doing this. but extending from abstractUser is probably the most straightforward and suitable in your case, since the class django.contrib.auth.models.AbstractUser provides the full implementation of the default User as an abstract model.
in your app_name.models.py create user class like this.
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
is_verified = models.BooleanField(default=False)
in your settingps.py
AUTH_USER_MODEL = 'app_name.User'
notice how app_name is used.
Now you can add as many attributes as you want as well.
By defaul is_verified is set to False, As soon as admin approves (verifies) the user change it to True.
Hope this helps.

ManyToManyField how I can store many data in one DB field -- Django

class Roles(models.Model):
name = models.CharField(max_length=60, unique=True)
def __str__(self):
return self.name
class Permission(models.Model):
name = models.CharField(max_length=60, unique=True)
create = models.BooleanField(max_length=5)
read = models.BooleanField(max_length=5)
update = models.BooleanField(max_length=5)
delete = models.BooleanField(max_length=5)
def __str__(self):
return self.name
class RolesHasPermission(models.Model):
role_id = models.ForeignKey(Roles, on_delete=models.CASCADE)
permission_id = models.ManyToManyField(Permission)
In RoleHasPermission I need to store many permission_id names in one role_id field in DB
How can I do this? I have tried a lot but I can't find the solution for this problem.
for example :-
i want to make the role_id take many permission names example if i have in my permissions ,, Manager (Read, Update , Delete) IT (Create) and have in my Role ,, Admin i want to make the Admin Role take two permissions (Manager , IT) together by make model make me select from list my role Admin and checkbox for Manager and checkbox for IT when i chose Admin Role and Check the Box of Manager and IT and press Save it will store it in DB (Admin || Manager,IT)
Django comes with simple permissions by default, which I strongly recommend to use.
Apart from that, I'm not sure what's the problem you're trying to solve?

Django admin - giving users access to specific objects/fields?

I need to make an "owners" login for the admin. Say we have this model structure:
class Product(models.Model):
owner = models.ManyToManyField(User)
name = models.CharField(max_length=255)
description = models.CharField(max_length=255)
photos = models.ManyToManyField(Photo, through='ProductPhoto')
class Photo(models.Model):
order = models.IntegerField()
image = models.ImageField(upload_to='photos')
alt = models.CharField(max_length=255)
class ProductPhoto(models.Model):
photo = models.ForeignKey(Photo)
product = models.ForeignKey(Product)
We have a group called Owners that some users are part of. The ProductPhoto is a TabularInline on the Product admin page.
Now, owners need permission to edit
(primary goal) only products where product__in=user.products (so basically, only products owned by them).
(secondary goal) only the description and photos of products
How would I do this with Django's admin/permission system?
This is row (or object) level permission. Django provides basic support for object permissions but it is up to you to implement the code.
Luckily, there are a few apps that provide drop-in object-level permission framework. django-guardian is one that I have used before. This page on djangopackages.com provides some more that you can try out.
You may implement using get_form. For complex rule, you can add this too: https://github.com/dfunckt/django-rules
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
# permission check;
if form.base_fields and not request.user.is_superuser:
# when creating or updating by non-reviewer (except superuser)
# allow only reviewer to allow updating
form.base_fields['usertype'].disabled = True