Django Custom Permission for Authorization - django

I am working on a Django Project, where one model (lets say Document) has the following field:
#In models.py
class Document (models.Model):
choice = (('Yes','Yes'), ('No','No'))
authorized = models.CharField (max_length=3, choices=choice, default='No')
Now, as a normal user creates a Document object, the authorized field is no. However, the superior needs to authorize the same before it is actually approved. Now, is there a permission system in django where one user can create an object but not authorize, whereas some other user - who has the permission - can authorize? If not, is the only way to do it is to create a custom field in user model and check it every time?

First of all, why you need to store possible values in CharField and not in BooleanField? I think you should consider changing to BooleanField.
You can do that by providing custom ModelAmin class in admin.py:
from django.contrib import admin
from .models import Document
#admin.register(Document)
class DocumentModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if not request.user.is_superuser:
self.exclude = ['authorized']
return super(DocumentModelAdmin, self).get_form(request, obj, **kwargs)
So now on admin page of Document if it is not a superuser, user won't see authorized field. You can change that code for checking if it should be specific user, or has some permissions, or if user belongs to some Group and etc.
UPDATE
If you want it to be in general views, you can just pass different forms to users, depend on their roles|permissions|groups(i don't know how your so called senior is different from rest of the users). So the answer would be: create two forms, then pass on of them in template based on your request.user attributes.

Django has awesome auth system. I couldn't understand you scenario.
But you could try something like this below
By default every Model object comes with three Permission object like (add_document, change_document and delete_document in your case).
If you want some custom permission you can add it in model Meta class like this:
You can add these permission to User object or Group object.
models.py
class Document (models.Model):
######
class Meta:
permissions = (("Can see document dashbaord", "see_document" ),)
and run python manage.py migrate to create new Permission object with codename as "see_document".
You can implement permissions in request handled by view like this:
view.py
from django.contrib.auth.mixins import PermissionRequiredMixin, permission_required
# For function based view
#pemission_required('document.see_document')
def someview(request):
######
pass
# For class based views
class SomeView(PermissionRequiredMixin, BaseView):
permission_required = 'document.see_document'
This could redirect any user with out the permssion to permission denied page. For more go through this https://docs.djangoproject.com/en/1.10/topics/auth/

Related

Enforcing permissions through a related model in Django Rest Framework

I'm working on building out permissions for an API built with Django REST Framework. Let's say I have the following models:
from django.db import models
class Study(models.Model):
pass
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
I have basic serializers and views for both of these models. I'll be using per-object permissions to grant users access to one or more studies. I want users to only be able to view Results for a Study which they have permissions to. There are two ways I can think of to do this, and neither seem ideal:
Keep per-object permissions on Results in sync with Study. This is just a non-starter since we want Study to always be the source of truth.
Write a custom permissions class which checks permissions on the related Study when a user tries to access a Result. This actually isn't too bad, but I couldn't find examples of others doing it this way and it got me thinking that I may be thinking about this fundamentally wrong.
Are there existing solutions for this out there? Or is a custom permissions class the way to go? If so, do you have examples of others who've implemented this way?
As you stated, you can make custom permission as per the second way:
And include the permission in your view:
I am considering your study model with some parameter course, based on that i am writing the solution you can consider for any element in study model
models.py
from django.db import models
class Study(models.Model):
course = models.CharField(max_length=50)
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
In permission.py
from rest_framework import permissions
class ResultOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS and obj.study.course == request.GET.get('course') :
# read only requests
return True
else:
# other requests such as post, patch, put
return obj.study == request.GET.get('course')
And include ,
class ReviewDetail(viewsets.ViewSet):
permission_classes =[ResultOrReadOnly]
And in urls.py,
Modify it to accept the URL parameter course
I would create a new field called enrolled_users in the Study model to indicate which all user has access to the particular Study object.
from django.db import models
from django.conf import settings
class Study(models.Model):
enrolled_users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name="studies"
)
# other fields
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
Then, it will very easy in DRF to filter the queryset in the views
# views.py
from .models import Study, Result
from rest_framework.permissions import IsAuthenticated
class StudyModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Study.objects.filter(enrolled_users=self.request.user)
class ResultModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Result.objects.filter(study__enrolled_users=self.request.user)
Notes
This will handle the object permission (Detail View) request as well
This will not return a status code of HTTP 403, but HTTP 404
Here I used the ModelViewSet class, but, you can use any views, but the filter plays the role.

Django Rest Framework custom Permission Class with ManyToManyField

I am trying to write a permission class for django rest api view.
this is my models
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
name = models.CharField(max_length=50)
auth_user = models.OneToOneField(User, on_delete=models.CASCADE)
class Group(models.Model):
admin = models.ManyToManyField(Profile, related_name='g_admin')
members = models.ManyToManyField(Profile, related_name='g_mess')
I want only group admin can perform an action in particular pages, for this i am writing a permission class:
class IsGroupAdmin(BasePermission):
"""
Allows access only to Group Admin.
"""
def has_permission(self, request, view):
# return bool() do something here
I am going through trouble to write the permission class to check if group admin or not. i am facing issue coz, this is many to many field.
can anyone help me to write this?
You can get the User from request.user. I don't know how you can get the Group from request (it may be in the request payload, it depends on your design) but lets assume that you pass the Group to the permission class, then it would be something like that:
class IsGroupAdmin(BasePermission):
"""
Allows access only to Group Admin.
"""
def __init__(self, group):
super().__init__()
self.group = group
def has_permission(self, request, view):
return self.group.profile_set.filter(auth_user=request.user).any()
Note: If you can get the group from request so you don't need to pass group to the IsGroupAdmin class and this would be much easier. But if you cant do that and you'r going to use just the above permission class, you'r going to need the partial function. so you need to import it:
from functools import partial
And then in the views, initiate permission_classes like that:
permission_classes = [partial(IsGroupAdmin, group)]
However, I suggest you to use the easier way, cause I think the group must be in request. Lets say that group is in the request payload, then you must implement IsGroupAdmin as:
class IsGroupAdmin(BasePermission):
"""
Allows access only to Group Admin.
"""
def has_permission(self, request, view):
group = request.data.get('group', '')
return group.profile_set.filter(auth_user=request.user).any()
And use it just like a normal permission. (no need to use partial function)
your data structure is wrong
admin and members should be m2m with user not with Profile.
for permission, you have to make a query like
Group.objects.filter(admin__auth_user__id=request.id) the true else return flase.
use through table it will reduce your work and make some sort of flag which gives you an idea that request user is the admin or not(in User model use signal) so every time you don't have to fire a query in the database.

Django Admin: Restrict staff to update

I had a Blog Model. All new blog post has DRAFT status.
In the admin page, admin would be all permissions (CRUD) on the Blog Model. I can solve this by using Django's register method.
What I want is is_staff user can view all the Blog Post but can't update Status (Eg. from DRAFT to APPROVE).
Is there any way to check the request's group, then allow the user to update or not? Thanks.
The simplest solution is to mark the status field as read only in the admin class, by setting readonly_fields:
from django.contrib import admin
from .models import BlogPost
#admin.register(BlogPost):
class BlogPostAdmin(admin.ModelAdmin):
readonly_fields = ('status',)
Note that this makes status read-only for ALL admin users. If you need more fine-grained control based on the current user, there is a get_readonly_fields method you can override.
Also, since ALL users of the Django Admin have is_staff, we'll explicitly check for the built-in permission to change a BlogPost.
class BlogPostAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
# obj is None if we're creating a new BlogPost, or a BlogPost instance
# if we are editing an existing BlogPost
if not request.user.has_perm("your_app.change_blogpost"):
return ("status",)
return []

Creating custom users in Django and customizing the admin

I need to create custom users in my app.
In the example given in the doc
class CustomUser(models.Model):
user = models.OneToOneField(User)
#custom fields
a user must exists before creating a CustomUser.
What I want to do is to create automatically a User when I create a CustomUser.
In the CustomUser admin (only visible by the superuser), I'd like to have only the custom fields and a few fields from the User model, as well as some form to allow the superuser to change the password for existing instance.
Anybody could help?
The first part of your question is easy, you can use a signal:
def create_custom_user(sender, instance, created, **kwargs):
if created:
custom_user, created = CustomUser.objects.get_or_create(user=instance)
post_save.connect(create_custom_user, sender=User)
As for the second part, theres already a change password form in the admin. To filter out the displayed fields you can create a CustomUserAdmin and register it together with the model. It's pretty self explaining in the django docs.
django docs: list_display

Is it possible to limit of object creation of a model in admin panel?

I just want to know that is it possible to limit the number of objects of a model in admin panel?
It is that, for example, I have a model named 'Homepage' and in the admin panel I don't want a user can create more than one instance of Homepage.
Is there a way I can do this?
If it's just the admin that you want to affect (and don't want to affect the database model), you can create a custom ModelAdmin subclass:
class HomePageAdmin(admin.ModelAdmin):
def add_view(self, request):
if request.method == "POST":
# Assuming you want a single, global HomePage object
if HomePage.objects.count() > 1:
# redirect to a page saying
# you can't create more than one
return HttpResponseRedirect("foo")
return super(HomePageAdmin, self).add_view(request)
# ...
admin.site.register(HomePage, HomePageAdmin)
An alternative strategy to do the same thing is to create a custom ModelForm for HomePage, with a clean method that enforces the single HomePage requirement. This will make your requirement appear as a validation error, rather than as a redirect (or as a database error):
from django import forms
from django.forms.util import ErrorList
class HomePageModelForm(forms.ModelForm):
def clean(self):
if HomePage.objects.count() > 1:
self._errors.setdefault('__all__', ErrorList()).append("You can only create one HomePage object.")
return self.cleaned_data
# ...
class HomePageAdmin(admin.ModelAdmin):
form = HomePageModelForm
# ...
admin.site.register(HomePage, HomePageAdmin)
If it's "one HomePage per user", you will need HomePage to have a ForeignKey to User and adapt the above. You may also need to store the current User object in threadlocals in order to access it from HomePageModelForm.clean
If you want to limit Homepage to one for every user, then you could use one-to-one relation, with OneToOneField. As for limiting to N - a pre_save signal might be useful.
Try
class HomePage(models.Model):
user = models.ForeignKey(User, unique=True)
homepage = models.CharField(max_length=100, unique=True)
class Meta:
unique_together = (("user", "homepage"),)