How to set permission as per user using Django and Python? - python

I need to set permission as per registered user using Django and Python. I have done something but confused whether it is fulfilled my requirement or not. I am providing my code below.
class Control(models.Model):
"""docstring for Control"""
user_id = models.ForeignKey(User)
control_reactor = models.IntegerField(default=0)
find_reactor = models.IntegerField(default=0)
view_reactor = models.IntegerField(default=0)
class Meta:
"""docstring for Meta"""
permissions = (
("view_reactor", "can view reactor"),
("find_reactor", "can find reactor"),
("controll_reactor", "can controll reactor"),
)
I am access those permission using Django decorator function like #permission_required. Here I need as per user I will set the 3 permissions but confused that this model class is doing as per requirement.

The meta class defined is just a representation that your model will have only these permissions so it becomes manageable.
You can create permissions programmatically using Permission class from django. From django docs
User objects have two many-to-many fields: groups and user_permissions. User objects can access their related objects in the same way as any other Django model
Permission models stores all the permission which can be set to a specific user. All you have to do is add the create a permission and add it to user.
from django.contrib.auth.models import Permission, User
content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.get(
codename='change_blogpost',
content_type=content_type,
)
user.user_permissions.add(permission)
The related name is user_permissions so you can directly add the permission same as M2M relationships.
Now suppose you want to check permissions
user.has_perm('app_name.code_name') # generic example
user.has_perm('myapp.change_blogpost')
Now for decorator you can do same
#permission_required('app_name.code_name')
EDIT: Generally you grant permission when you create a user so the example mentioned above can be put in your views where you signup the user. If you don't want to grant permissions right away then you can make a separate view which grants permission to the user
def grant_permissions(request):
user = request.user
# then you put the code mentioned above
for your case you can either create permissions in you code using permissions model or use meta class.
Just migrate the database to create permissions defined in the meta class. After migrate don't forget to give permission to user using the code mentioned above using Permissions model Then you can use the permissions in the decorator.
#permission_required('view_reactor')
custom_permissions
permissions

Related

Django admin - handling permissions of many to many fields

Let's say I have 2 models as follows. As there is a many to many relation between the models, django created the required table (clientreport) and the table's permissions can be set using django admin's group permission's tab. (see screenshot)
class Report(models.Model):
is_visible = models.BooleanField(default=False)
clients = models.ManyToManyField(Client)
class Client(models.Model):
name = models.CharField(max_length=32)
On django admin I granted change permissions to a specific user group (e.g. group_a) on Report model. I did NOT grant any permissions on clientreport model.
My desired output is, a group_a user can change is_visible field of any Report instance but could not change/delete client X reports (or clientreport).
However even if there is no actual table reference between Report and Client models, a group_a user can still edit client-reports from django admin panel. (see screenshot)
Is this really intended? If so, how can I get my desired goal?
If you need to restrict access to certain fields by certain rights, you can use the ModelAdmin.get_readonly_fields method.
Usage example:
class ReportAdmin(admin.ModelAdmin):
...
def get_readonly_fields(self, request, obj = None):
if request.user.groups.filter(name=groupname).exists():
return ('field1', 'field2')
else:
return super().get_readonly_fields(request, obj)

Implement roles in django rest framework

I am building an API that should have the following kind of users
super_user - create/manage admins
admin - manage events(model) and event participants
participants - participate in events, invited to events by admins
Additional i want to have each type of user to have phone number field
I tried
class SuperUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
class Admin(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
class Participant(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
But gut is telling me its a wrong way to handle this. Can someone please help.
One possible solution is:
Have only one User Model with role field, which defines what user role is.
Create a User Group and add each group needed permissions.
Add User to User Group
Limit access using a Django REST Framework (later DRF) Permission Class.
Explanation:
Using only one user model is a more simple and flexible solution. You can query all users, or filtered by feature (like user role). Standart Django auth system expects one UserModel.
Read more about Django user groups. See "Django Permissions Docs #1" and "Django Groups Docs #2". Also useful is "User groups and permissions".
You need to create a group for each user role, and add needed permissions for each group. (Django has a default model permission, created automatically, look at the docs on the given links) or create the needed permission manually in the model definition.
Manually or using a script, add User to the needed group by defining his role when a user is created or manually by Django Admin interface.
Now everything should be ready for limited access by the user's role. You can easily limit access to the DRF View using a permission class. See more information in the "DRF Permission Docs".
Let's define our own:
from rest_framework.permissions import DjangoModelPermissions
# Using DjangoModelPermissions we can limit access by checking user permissions.
# Rights need only for CreateUpdateDelete actions.
class CUDModelPermissions(DjangoModelPermissions):
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': ['%(app_label)s.read_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
# Or you can inherit from BasePermission class and define your own rule for access
from rest_framework.permissions import BasePermission
class AdminsPermissions(BasePermission):
allowed_user_roles = (User.SUPERVISOR, User.ADMINISTRATOR)
def has_permission(self, request, view):
is_allowed_user = request.user.role in self.allowed_user_roles
return is_allowed_user
# ----
# on views.py
from rest_framework import generics
from .mypermissions import CUDModelPermissions, AdminsPermissions
class MyViewWithPermissions(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [CUDModelPermissions, ]
queryset = SomeModel.objects.all()
serializer_class = MyModelSerializer
You can add additional permission class to combine access limitation.
So in Django any user has a flag is_superuser that corresponds to your 'superuser'. So just use that - e.g. User.objects.create(is_superuser=True).
For the rest you can simply use a field for a normal User model to differentiate between subroles of a normal user.
class User(AbstractBaseUser):
can_participate_event = models.Boolean(default=False)
can_create_event = models.Boolean(default=False)
Or
class User(AbstractBaseUser):
permissions = models.CharField(default='') # and populate with e.g. 'create_event,participate_event'
Still you will need to check all those fields in your view probably. The more you add to your application, the hairier this becomes so I would suggest using a 3rd party library like rest-framework-roles (I'm the author) or guardian.

How to have a Custom User model for my app while keeping the admins working as default in Django?

Here is what I am trying to accomplish:
- Have admins login to the admin page using the default way (username and password).
- Have users register/login to my web app using a custom User Model which uses email instead of password. They can also have other data associated that I don't need for my admins.
- Separate the admin accounts and user accounts into different tables.
I checked how to create a Custom User class by extending AbstracBaseUser, but the result I got is that my admins also became the new user type. So can I have the Custom User model be used for my app users while keeping the default admin system untouched? Or what is a good alternative to my design?
The recommended Django practice is to create a OneToOne field pointing to the User, rather than extending the User object - this way you build on top of Django's User by decorating only the needed new model properties (for example):
class Profile(models.Model):
user = models.OneToOneField(User,parent_link=True,blank=True,null=True)
profile_image_path = models.CharField(max_length=250,blank=True, null=True)
phone = models.CharField(max_length=250,blank=True, null=True)
address = models.ForeignKey(Address,blank=True,null=True)
is_admin = models.NullBooleanField(default=False,blank=True,null=True)
class Meta:
verbose_name = 'Profile'
verbose_name_plural = 'Profiles'
After couple more hours of digging, I think it is best to keep a single User model and use permissions and roles for regulations.
There are ways that can make multiple different user model authentications work, such as describe in here: How to have 2 different admin sites in a Django project? But I decided it wasn't worth it for my purposes.

Django return "Profile matching query does not exist"

I have some issue.
I import django User model, create new user then trying to get it profile, all what I have is "Profile matching query does not exist". Why? I just create the user.
Here is my code:
from django.contrib.auth.models import User
user = User.objects.create(username="stackoverflow", password="tester1234")
user.get_profile()
You might have forgotten to set
AUTH_PROFILE_MODULE
in your settings.py.
The online documentation suggests that get_profile()...
Returns a site-specific profile for this user. Raises django.contrib.auth.models.SiteProfileNotAvailable if the current site doesn't allow profiles, or django.core.exceptions.ObjectDoesNotExist if the user does not have a profile. For information on how to define a site-specific user profile, see the section on storing additional user information below.
Are you sure you've enabled profiles?
From your code snippet it looks like you've perhaps not created a profile which is a separate class (see here.
also write save method in signals.py:
#receiver(post_save,sender=User)
def save_profile(sender,instance,**kwargs):
instance.profile.save()
and also add this in app.py
class UserProfileConfig(AppConfig):
name = 'UserProfile'
def ready(self):
import UserProfile.signals
Django docs define this clearly, I miss that, sorry
Storing additional information about users
If you'd like to store additional information related to your users,
Django provides a method to specify a site-specific related model --
termed a "user profile" -- for this purpose.
To make use of this feature, define a model with fields for the
additional information you'd like to store, or additional methods
you'd like to have available, and also add a OneToOneField named user
from your model to the User model. This will ensure only one instance
of your model can be created for each User. For example:
from django.contrib.auth.models import User
class UserProfile(models.Model):
# This field is required.
user = models.OneToOneField(User)
# Other fields here
accepted_eula = models.BooleanField()
favorite_animal = models.CharField(max_length=20, default="Dragons.")
To indicate that this model is the user profile model for a given
site, fill in the setting AUTH_PROFILE_MODULE with a string consisting
of the following items, separated by a dot:
The name of the application (case sensitive) in which the user profile model is defined (in other words, the name which was passed to
manage.py startapp to create the application).
The name of the model (not case sensitive) class.
For example, if the profile model was a class named UserProfile and
was defined inside an application named accounts, the appropriate
setting would be:
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
When a user profile model has been defined and specified in this
manner, each User object will have a method -- get_profile() -- which
returns the instance of the user profile model associated with that
User.
The method get_profile() does not create a profile if one does not
exist. You need to register a handler for the User model's
django.db.models.signals.post_save signal and, in the handler, if
created is True, create the associated user profile:
in models.py
from django.contrib.auth.models import User from
django.db.models.signals import post_save
# definition of UserProfile from above
# ...
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)

How to add custom permission to the User model in django?

in django by default when syncdb is run with django.contrib.auth installed, it creates default permissions on each model... like foo.can_change , foo.can_delete and foo.can_add. To add custom permissions to models one can add class Meta: under the model and define permissions there, as explained here https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#custom-permissions
My question is that what should I do if I want to add a custom permission to the User model? like foo.can_view. I could do this with the following snippet,
ct = ContentType.objects.get(app_label='auth', model='user')
perm = Permission.objects.create(codename='can_view', name='Can View Users',
content_type=ct)
perm.save()
But I want something that plays nicely with syncdb, for example the class Meta under my custom models. Should I just have these in class Meta: under UserProfile since that is the way to extend the user model. but is that the RIGHT way to do it? Wouldn't that tie it to UserProfile model?
You could do something like this:
in the __init__.py of your Django app add:
from django.db.models.signals import post_syncdb
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import models as auth_models
from django.contrib.auth.models import Permission
# custom user related permissions
def add_user_permissions(sender, **kwargs):
ct = ContentType.objects.get(app_label='auth', model='user')
perm, created = Permission.objects.get_or_create(codename='can_view', name='Can View Users', content_type=ct)
post_syncdb.connect(add_user_permissions, sender=auth_models)
I don't think there is a "right" answer here, but i used the exact same code as you except i changed Permission.objects.create to Permission.objects.get_or_create and that worked find to sync with syncdb
An updated answer for Django 1.8. The signal pre_migrate is used instead of pre_syncdb, since syncdb is deprecated and the docs recommend using pre_migrate instead of post_migrate if the signal will alter the database. Also, #receiver is used to connect add_user_permissions to the signal.
from django.db.models.signals import pre_migrate
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import models as auth_models
from django.contrib.auth.models import Permission
from django.conf import settings
from django.dispatch import receiver
# custom user related permissions
#receiver(pre_migrate, sender=auth_models)
def add_user_permissions(sender, **kwargs):
content_type = ContentType.objects.get_for_model(settings.AUTH_USER_MODEL)
Permission.objects.get_or_create(codename='view_user', name='View user', content_type=content_type)
This is a bit hacky but mentioning it here anyway for reference.
My site has a generic model called Setting, which stores various settings concerning the site I want certain users to be able to edit, without needing to go through me the developer (like registration limit, or an address, or the cost of items, etc).
All the permissions that don't nicely map onto other models (eg "Send Password Reminder Email to Student", "Generate Payment Reconciliation Report", "Generate PDF Receipt"), which really just relate to pages that get viewed in the admin area, get dumped onto this Setting model.
For example, here's the model:
class Setting(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(editable=False)
description = models.TextField()
value = models.TextField()
class Meta:
#for permissions that don't really relate to a particular model, and I don't want to programmatically create them.
permissions = (
("password_reminder", "Send Password Reminder"),
("generate_payment_reconciliation_report", "Generate Payment Reconciliation Report"),
("generate_pdf_receipt", "Generate PDF Receipt"),
)
Do each of those settings strictly relate to the Setting model? No, which is why I said this is a bit hacky. But it is nice that I can now just dump all those permissions here, and Django's migration commands will take care of the rest.

Categories

Resources