Django custom decorator for custom permission - django

There is a Project model with a ManyToMany relation to User model, using a join table.
If a user is not member of a project (not in the join table), I want to prevent the user from accessing a view that shows the specific project.
I know it could be solved with a simple if statement inside the view method or a custom filter in the template, but I would like to be able to do this with a custom decorator.
Is this possible? If so how could it be done?
Something like:
def may_user_view_this(function):
def wrapper(request, *args, **kwargs):
user = request.user
project = Project.objects.get(id=???????) #id
if not project.has_user(user):
return HttpResponse('You cannot view this.')
else:
return function(request, *args, **kwargs)
return wrapper
#may_user_view_this() # if not go the specific url
def my_view(request, page_id=None):
# do stuff and render
How can I access the parameter page_id sent to my_view from the decorator function?

Related

How to modify the Login view in inherited class in django?

I want to handle "confirm form resubmission" pop ups, when the user refresh the login page with incorrect data.
I'm using a class named: UserLogin inherited from LoginView('django built-in'), I know that I need to write a method in my own class to handle this issue, and I did it with HttpRespone for my Registration view. who can I solve it for my Login View?
my urls.py:
path('login/', UserLogin.as_view(template_name='userLogin/login.html'), name='login')
in my views:
class UserLogin(LoginView):
def refresh_page(self):
pass
return HttpResponse('login/')
I don't know How to fill my refresh_page method. besides, I tried :
class UserLogin(LoginView):
def refresh_page(self):
print('hello')
and I noticed that the method doesn't called at all! because it didn't print "hello" on my console
I think you can do it on different ways, one of them, just send post data to your view where you can handle user action and react to it. For example, like this:
class UserLogin(LoginView):
def post(self, request, *args, **kwargs):
if request.data.get('refresh'):
self.refresh_page()
return super().post(self, request, *args, **kwargs)
def refresh_page(self):
return HttpResponse('login/')

Django rest framwork: DELETE without pk

I user Django Rest Framwork. I want to make a api for delete an object like this
DELETE .../items/
to delete request.user's item. (Each user can create at most one item only, and only owner can delete his item.)
I use mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet for list view and create. I have tried
#action(methods=['delete'], detail=False, url_path='')
def leave(self, request, *args, **kwargs):
...
but url pattern will go:
.../items/leave/$
How can I config the router or path for this? Thanks
In Django rest framework decorators, if url_path be empty strig that replace by function name. So you cannot use url_path='' as a URL path.
You can use just a simple APIView with GET method, and do what you want in that. like this:
class MyDeleteAPIView(APIView):
def get(self, request, *args, **kwargs):
# for example
try:
user = request.user
instance = SomeModel.objects.get(user=user)
instance.delete()
return Response({"message":"deleted successfuly"}, status=status.HTTP_200_OK)
except:
return Response({"message":"delete fail"}, status=status.HTTP_400_BAD_REQUEST)
now you can define your desired url:
path('delete/', MyDeleteAPIView.as_view(), name='delete'),

Django rest Framework sending json to api endpoint within another class-based view

I want to post to an endpoint from within a CBV and retrieve the response (in order to access the id of the created resource). I've had a look at How to programmatically call a Django Rest Framework view within another view? but cannot find how to send field values through with the request object. request.data is immutable, and passing kwargs through doesn't seem to be doing anything:
from app.views import OtherViewSet
class NewViews(APIView):
def post(self, request, *args, **kwargs):
# kwargs value here is {'field1':1, 'field2':2...}
view_func = OtherViewSet({'post':'create'})
response = view_func(self.request, *args, **kwargs).data
Although the kwargs contains the field values (set through several url named groups), the response always looks like:
{'field1': ['This field is required'], 'field2':['This field is required...
What am I doing wrong? Thanks all :)
Edit:
Once I've got the ID of the created resource, I want to retrieve the resource as rendered by a particular (custom) format (I don't want to return the response object above).
views.py
from app.views import OtherViewSet
class NewViews(APIView):
def post(self, request, *args, **kwargs):
view = OtherViewSet.as_view({'post':'create'})
response = view(request, *args, **kwargs)
return response

Possible to use decorators in django to do permissions based on a view parameter?

I'm not sure it's possible, but is there a way to write something like
#group_required('group_id')
def myview(request, group_id):
.....
Where I look at the value of the parameter group_id and limit access to this view to only people who are in this group?
I know it is possible to create a decorator to check membership in some specific group
#group_required('admin')
def myview(request):
....
But say I want a view that is only accessible to people within a certain group, which is determined by the url.
So for example
/group/1
/group/2
Should each have permissions so that only members of group #X can see that page. I can easily write this functionality within the view
group = get group
if user not in group
raise 404
but that logic is repeated all over. Decorators seems like a promising way to do it, but it seems the scope is backwards. Is there another preferred way to handle this sort of permissions?
Thanks!
The decorator has access to all the view arguments, so it's definitely possible. You could do this:
def group_required(arg_name):
def decorator(view):
def wrapper(request, *args, **kwargs):
group_id = kwargs.get(arg_name)
user = request.user
if group_id in user.groups.values_list('id', flat=True):
return view(request, *args, **kwargs)
else:
return HttpResponseForbidden # 403 Forbidden is better than 404
return wrapper
return decorator
(If you don't need the flexibility of dynamically defining the argument in the decorator, eg if you always call it group_id, you can miss out the outer function here and just use kwargs.get('group_id').)
Daniel's answer appears to not work on Django 1.11. Here's a modified version:
def group_required(group_name):
def decorator(view):
#functools.wraps(view)
def wrapper(request, *args, **kwargs):
if (request.user.groups.filter(name=group_name).exists() or
request.user.is_superuser):
return view(request, *args, **kwargs)
else:
return HttpResponseForbidden("You don't have permission to view this page.")
return wrapper
return decorator

Should I remove user id from URL if I want to keep data available just to exactly one user?

Lets just suppose that we have following url:
example.com/users/1
if user with ID=1 opens it, user receive info about his account, but if user switch 1 with 2, then he can view other user details as well, and we of course do not want that, so I have following solutions:
1) just pass currently logged in user id threw request.user.id inside a template
2) add permission, but I did not find type of permissions that would allow me to do that. Of course I could create dozens of permissions each for each user, but of course that is very nasty way.
Any other ideas how to cope with that in Django?
You can either fill context with the request which makes sure the user will never see another user's data, e.g. (using CBVs):
class AccountView(TemplateView):
"""
Generic account view
"""
template_name = "users/account.html"
def get_context_data(self, **kwargs):
context = super(AccountView, self).get_context_data(**kwargs)
context['user'] = User.objects.get(id=self.request.user.id)
return context
#method_decorator(login_required(login_url=reverse('login')))
def dispatch(self, *args, **kwargs):
return super(AccountView, self).dispatch(*args, **kwargs)
Another approach, to make sure 'fake' urls render 404's is to write an owner_required decorator, e.g.:
def owner_required(function):
#wraps(function)
def decorator(*args, **kwargs):
request = args[1]
user = get_object_or_404(User, username=request.user.username)
if user.is_authenticated() and user.username == kwargs.get('slug'):
return function(*args, **kwargs)
raise Http404
return decorator
You don't need permission to do this.
In your views.py
from django.http import Http404
def myview(request, user_id):
user = request.user
if user_id != request.user.id:
raise Http404
#all your logic here
But if you want user profile to be private, you don't need to use user_id in your url pattern. just use the user object stored in the request variable.