Django admin - handling permissions of many to many fields - python

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)

Related

How to set permission as per user using Django and 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

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.

How to create custom groups in django from group

I'm trying to figure out how to create custom groups in Django, in way that groups are related to application and not to a Project.
For example when a user want to create a company, it should be the owner of the company:
model
class Company(models.Model):
owner = models.ForeignKey(User)
name = models.CharField(max_length=64, unique=True)
description = models.TextField(max_length=512)
created_on = models.DateTimeField(auto_now_add=timezone.now)
class Meta:
permissions = (
("erp_view_company", "Can see the company information"),
("erp_edit_company", "Can edit the company information"),
("erp_delete_company", "Can delete the company"),
)
view
# Create your views here.
def register(request, template_name='erp/register.html'):
if request.method == 'POST':
company_form = CompanyRegistrationForm(request.POST)
if company_form.is_valid():
# Create a new company object but avoid saving it yet
new_company = company_form.save(commit=False)
# Set the owner
if request.user.is_authenticated():
new_company.owner = request.user
# Add the user to group admin
group = Group.objects.get_or_create(name="erp_admin")
request.user.groups.add(group)
# Save the User object
new_company.save()
template_name = 'erp/register_done.html'
return render(request, template_name, {'new_company': new_company})
else:
company_form = CompanyRegistrationForm()
return render(request, template_name, {'company_form': company_form})
Here the questions:
1) Instead of having Group.objects.get_or_create(name="erp_admin") I'd like to have CompanyGroup.objects.get_or_create(name="admin") in way that the Groups of an application are restricted to the application.
2) How I can map the permissions defined in the Meta class of each model to a group?
3) The custom groups are related to a Company, this mean each company has the group Admin, that starts with the owner. The owner can creates user like "Manager". The manager can creates groups/permissions, add user, set permission to user but can't in anyway CRUD the admin groups. So there's a kind of hierarchy in groups (I suppose that the hierarchy depend on the permissions that are added to a group).
To make it more clear here you can see a picture of the concept:
So I think the main problems are:
1) How to inherit Groups to create custom groups.
2) How to map the permissions of a model to a group.
3) How groups can be restricted to a CompanyID.
I hope the problem is well defined. Let me know if I have to clarify something.
Thanks in advance for the support.
Edit 1)
For the point 3 I found this answer on SO: Extend django Groups and Permissions but the problem then is how to query for those data and how to check the permissions. And how could you override the save() method to add meta information to the property 'name'? The group name could be something like: "company_id#admin" or "company_id#manager".
You may be looking for Per object permissions. Django does not support this out of the box but it is possible with the app Django-guardian

tweak the django admin default permission

I don't know if this possible, but here is what I want to achieve.
There's three default permissions in django admin. add/change/delete.
What I want is something like a "view" permission, kinda like the "change" permission but without the ability to edit.
I have a Country model, when a client is created, it needs to select a country as it's foreign key.
But if I set this field to readonly, it will not be able to select the country as I want. So I have to give the user "change" permission so that the country will be available when user create the client info.
Give the "change" permission is fine, but it would be better if it has a "view" permission.
I've done a lot search and didn't find a "perfect" solution.
I think there must be a lot of people would want this permission as well.
So how to make this custom "view" permission and integrate with django admin smoothly.
I hope this will help you:
class Client(models.Model):
name = models.CharField(...)
readonlyfield = models.CharField(...)
country = models.ForeignKey(...)
class Meta:
permissions = (
("change_country", "Can change country"),
)
class ClientAdmin(admin.ModelAdmin):
fields = ['name', 'readonlyfield', 'country']
def get_readonly_fields(self, request, obj=None):
if obj and not request.user.has_perm('yourapp.change_country'):
return['readonlyfield', 'country']
else:
return['readonlyfield']
So, if the user doesn't have the change_country permission, he’ll set the country field once (at creation)

Categories

Resources