How to restrict access to certain groups in django class based view - python

My views.py have a mix of def and ClassViews:
#login_required(login_url='login')
#allowed_users(allowed_roles=['Admin', 'Staff', 'Lite Scan'])
def litescan(request):
filteredOutput = Stock.objects.all()
val = {}...
#method_decorator(login_required(login_url='login'), name='dispatch')
class HomeView(ListView):
model = Post
template_name = 'community.html'
ordering = ['-id']
And here's my decorators.py if that is helpful:
from django.shortcuts import redirect
from django.http import HttpResponseRedirect
def unauthenticated_user(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
return redirect('home')
else:
return view_func(request, *args, **kwargs)
return wrapper_func
def allowed_users(allowed_roles=[]):
def decorator(view_func):
def wrapper_func(request, *args, **kwargs):
group = None
if request.user.groups.exists():
group = request.user.groups.all()[0].name
if group in allowed_roles:
return view_func(request, *args, **kwargs)
else:
url = ('/forbidden')
return HttpResponseRedirect(url)
return wrapper_func
return decorator
I found out that #login_required and #allowed_users give out an error when used with ClassView. So i used #method_decorator which brings me to the login page before redirecting to the page. However, I can not find a way to restrict access to only certain groups like Admin, Staff, Lite Scan with my ClassView.
Little help will be appreciated. Thanks!

You can use AccessMixin for your class views.
Example I found:
from django.contrib.auth.mixins import AccessMixin
from django.http import HttpResponseRedirect
class FinanceOverview(AccessMixin, TemplateMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
# This will redirect to the login view
return self.handle_no_permission()
if not self.request.user.groups.filter(name="FinanceGrp").exists():
# Redirect the user to somewhere else - add your URL here
return HttpResponseRedirect(...)
# Checks pass, let http method handlers process the request
return super().dispatch(request, *args, **kwargs)
More info found here: Use LoginRequiredMixin and UserPassesTestMixin at the same time

Relying on Django Permissions may be a far simpler approach to giving access to such a view. Rather than checking for a specific list of groups, you can assign permissions to those groups and give access to the view based on whether the user's groups have the appropriate permissions.
views.py
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionsRequiredMixin
#permission_required('foo.view_bar')
def my_view(request):
...
class MyView(PermissionRequiredMixin, DetailView):
permission_required = ('foo.view_bar', )
...

Related

How do I create links in Django such that other users can't access them?

I'm pretty confused about how do I prevent users' from accessing the data of other users.
The case at hand :
I'm creating a Notes + To-Do app in which a user logs in, creates their notes and tasks.
How to create links to those notes such that they aren't accessible by other users? As in the correct syntax for UserPassesTestMixin.
In the To-Do app, how do I keep the tasks of one user unique to them? Similarly for the note app, how do I achieve that?
Not sure what you mean by "create links". For what you describe, the links don't change for people that have access or not. The difference if that a user that owns note 5 and goes to /note/5/, they should be able to see their note, but if another user goes to /note/5/ they should either 1) get a 404 error (Note not found) or 403 (Permission Denied) just be redirected to another page (say, the home page), maybe with a message.
Using Class based views, this is easy to do.
Prevent access to views
from django.core.exceptions import PermissionDenied
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
class LoginRequiredAccessMixin(object):
# This will ensure the user is authenticated and should
# likely be used for other views
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(LoginRequiredAccessMixin, self).dispatch(request, *args, **kwargs)
class AccessMixin(LoginRequiredAccessMixin):
def get_object(self, queryset=None):
obj = get_object_or_404(Note, pk=self.kwargs['id'])
# Assumes you have a notes.user, but change to created_by
# or whatever is your user field name
if obj.user == self.request.user:
# User owns object
return obj
raise PermissionDenied("User has no access to this note")
class NoteView(AccessMixin, DetailView):
# This is a regular DetilView, but with the Mixin,
# you are overwriting the get_object() function.
# If you don't want the Mixin, then you can just add
# get get_object() function here. Except that with the
# Mixin, you can reuse it for your UpdateView, DeleteView
# and even across both your notes and task views
model = Note
template_name = 'note/details.html'
def get_context_data(self, **kwargs):
context = super(NoteView, self).get_context_data(**kwargs)
# Add any special context for the template
return context
If instead you want to just direct users to another page, you would do something like:
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.contrib import messages
class NoteView(DetailView):
model = Note
template_name = 'note/details.html'
def get_context_data(self, **kwargs):
context = super(NoteView, self).get_context_data(**kwargs)
# Add any special context for the template
return context
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
note = self.get_objet()
if note and not note.user == self.request.user:
messages.error(
self.request,
'You are not allowed to access this Note'
)
return HttpResponseRedirect('/home')
return super(NoteView, self).dispatch(request, *args, **kwargs)
You didn't supply any code so I cannot be more specific, but hopefully you get an idea of the two techniques. The first is usually a cleaner solution, and the Mixin I show can be shared across both your Note views and ToDo Tasks records, assuming they use the same user/created_by field name.
In case you are using functions (FBV) you could use if request.user == item.user
#login_required
def post_edit(request, post_id):
item = Post.objects.get(pk=post_id)
if request.user == item.user:
CBV - Class Based View - using UserPassesTestMixin
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
# [...]
You can use the decorator in Django called "user_passes_test"
You can import like:
from django.contrib.auth.decorators import user_passes_test
For detail check docs here

Where/how to replace default upload handler in Django CBV?

I am trying to specify a specific method of handling file uploads for a class based view. Per the docs this can be achieved by something like:
from django.core.files.uploadhandler import TemporaryFileUploadHandler
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
If i specify this in post method of a FormView like so:
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return super().post(self, request, *args, **kwargs)
I get:
AttributeError: You cannot set the upload handlers after the upload has been processed.
Variants like yield the same result:
def post(self, request, *args, **kwargs):
self.request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
However when i do this in the get method this is ineffective:
def get(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
return super().get(self, request, *args, **kwargs)
If I upload a small file it still uses the default django.core.files.uploadhandler.MemoryFileUploadHandler.
What am I doing wrong?
EDIT
Also when i try to mirror what is suggested in the note, I get the same AttributeError:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#csrf_exempt
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request, *args, **kwargs)
#csrf_protect
def _post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Ok, finally got it to work (using the suggestions provided by #Alasdair). Setting a method decorator(crsf_exempt) on post is not engough it needs to be on dispatch. For anyone struggling with this in the future, it goes like this:
from django.views.generic import FormView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#method_decorator(csrf_exempt, 'dispatch')
class UploadDataSetView(FormView):
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request)
#method_decorator(csrf_protect)
def _post(self, request):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Also it will fail if you remove the {% csrf_token %} from your template (which is what you want).
Because you cannot change upload handler in view as it is something that gets invoked prior to your view function.
Get shouldn't collect post parameters so it behaves accordingly.
Upload Handlers
When a user uploads a file, Django passes off the file data to an
upload handler – a small class that handles file data as it gets
uploaded. Upload handlers are initially defined in the
FILE_UPLOAD_HANDLERS setting, which defaults to:
["django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler"]
If you want different upload handler you can change FILE_UPLOAD_HANDLERS settings
If this is not enough you can write your custom upload handler
Edit:
Also, request.POST is accessed byCsrfViewMiddleware which is enabled
by default. This means you will need to use csrf_exempt() on your view
to allow you to change the upload handlers.

How to use django-tastypie with django-axes

Settings:
django==1.8
django-tastypie==0.13
django-axes==2.3
I've got login resource through tastypie what looks like below
from django.contrib.auth import login
class LoginResource(Resource):
class Meta:
resource_name = 'login'
allowed_methods = ['post']
def obj_create(self, bundle, **kwargs):
form = AuthForm(data=bundle.data)
if form.is_valid():
request.session.set_expiry(0)
if form.get_user():
login(bundle.request, form.get_user())
raise ImmediateHttpResponse(response=HttpResponse(status=200))
raise ImmediateHttpResponse(response=http.HttpBadRequest(status=400))
And I can't figure out how to log these login attempts in django-axes.
My own solution was next: I write custom login view in views.py
from django.contrib.auth.views import login
def core_login(request, *args, **kwargs):
kwargs["authentication_form"] = AuthForm
return login(request, *args, **kwargs)
And in tastypie resource:
from core.views import core_login
class LoginResource(Resource):
class Meta:
resource_name = 'login'
allowed_methods = ['post']
def obj_create(self, bundle, **kwargs):
bundle.request.POST = bundle.data
if core_login(bundle.request).status_code == 302:
raise ImmediateHttpResponse(response=HttpResponse(status=200))
raise ImmediateHttpResponse(response=http.HttpBadRequest(status=400))
Looking at the code from django-axes we can see it uses a decorator called watch_login to provide its functionality.
To log the login attempts using your resource you will have to apply that decorator to the view that is called when the user tries to login using that given resource.
Based on tastypie code, you could override prepend_urls method of your resource and add your url. Like this (this is just an example, wasn't tested):
def prepend_urls(self):
from axes.decorators import watch_login
urls = [
url(r"^(?P<resource_name>%s)/login$" % (self._meta.resource_name,), watch_login(self.wrap_view('dispatch_list')), name="login-enpoint")
]
return urls

Django: How to provide context into a FormView get() method (also using request param)

I'm trying to provide some additional context into the get() method in my FormView. I need get() because I need to run some logic first, check for a potential redirect. I also need access to the request object (because I need to check session data). Can't figure out how to do it. Simplified code below..
Attempt 1:
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def get(self, request):
# check if to redirect
if self.request.session.get('user'):
return redirect('/dashboard/')
# render page with extra context
else:
context = super(LoginView, self).get(request)
context['message'] = self.request.session['message']
return context
No errors, but context does not come through in the template.
Attempt 2:
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def get_context_data(self, request, **kwargs):
# check if to redirect
if self.request.session.get('user'):
return redirect('/dashboard/')
# render page with extra context
else:
context = super(LoginView, self).get_context_data(**kwargs)
context['message'] = self.request.session['message']
return context
Getting TypeError: get_context_data() takes exactly 2 arguments (1 given)
P.S. This work relates to a workaround Django's buggy messages middleware which seems to be working locally flawlessly but on live (Heroku) is not 100% reliable, renders on some pages only. Ugh, frustration setting in...
Ditch the request argument to the get_context_data method. You should also use the dispatch method to check if the user is logged in.
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def dispatch(self, *args, **kwargs):
"""Use this to check for 'user'."""
if request.session.get('user'):
return redirect('/dashboard/')
return super(LoginView, self).dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
"""Use this to add extra context."""
context = super(LoginView, self).get_context_data(**kwargs)
context['message'] = self.request.session['message']
return context

Django class based views - threading issue?

I think I'm experiencing a threadding issue with the Django class-based views I have written.
After launching the application, the UpdateView functions fine until CreateView is called/visited. Then subsequent UpdateViews populate the 'code' field with the value generated in the get_initial method of CreateView.
The problem only shows itself on the web server, and not when using the development runserver command.
E.g. if an instance of MyObject has a code of '123', then visiting the UpdateView shows the code in the form as '123'. After visiting a page which calls CreateView, a new code is generated by get_initial(), say '456'. From then on, visiting any url which calls UpdateView shows '456' in the form instead of the instances actual code.
Sample myproject.app.views.myobject view classes:
from django.contrib.auth.decorators import permission_required
from django.utils.decorators import method_decorator
from django.views import generic
from myproject.app.forms import MyObjectForm
from myproject.app.models import MyObject
class EditMixin(generic.base.View):
form_class = MyObjectForm
def get_success_url(self):
return self.object.get_absolute_url()
def form_valid(self, form):
self.object = form.save(commit=False)
if not self.object.pk:
self.object.created_by = self.request.user
self.object.updated_by = self.request.user
self.object.save()
messages.success(self.request, 'Object saved.')
return HttpResponseRedirect(self.get_success_url())
class CreateView(EditMixin, generic.edit.CreateView):
model = MyObject
#method_decorator(permission_required('app.add_myobject'))
def dispatch(self, *args, **kwargs):
return super(CreateView, self).dispatch(*args, **kwargs)
def get_initial(self):
initial = super(CreateView, self).get_initial()
#TODO: proper auto-generation of code
myobject = MyObject.objects.order_by('-code')[0]
code = int(myobject.code) + 1
initial.update({'code': str(code)})
return initial
class UpdateView(EditMixin, generic.edit.UpdateView):
#method_decorator(permission_required('app.change_myobject'))
def dispatch(self, *args, **kwargs):
return super(UpdateView, self).dispatch(*args, **kwargs)
def get_queryset(self):
return MyObject.objects.filter(created_by=self.request.user)
Url Patterns:
from myproject.app.views import myobjects
urlpatterns = patterns('',
url(r'^$', myobjects.ListView.as_view(), name='myobject_list'),
url(r'^(?P<pk>[\d]+)/$', myobjects.DetailView.as_view(),
name='myobject_detail'),
url(r'^(?P<pk>[\d]+)/edit$', myobjects.UpdateView.as_view(),
name='myobject_edit'),
url(r'^new$', myobjects.CreateView.as_view(),
name='myobject_new'),
)
Can anyone help explain where I might be causing the threadding issue, and the best practice to avoid this?
Try removing the call to super's get_initial. It's seems to use a class property instead of an instance property, causing you trouble. Try this:
def get_initial(self):
myobject = MyObject.objects.order_by('-code')[0]
code = int(myobject.code) + 1
initial={'code': str(code)}
return initial

Categories

Resources