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
Related
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', )
...
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
I have this view
class UserView(GenericAPIView):
def get(self, request, format=None, **kwargs):
pass
def post(self, request, format=None, **kwargs):
pass
This works fine with this url
url(r'^user$', UserView.as_view(),name='user'),
but i want to have custom url
def custom():
pass
I want that
url(r'^user/custom/$', UserView.as_view(custom),name='user'),
How can i do that
You can't do this.
from django.conf.urls import url
from django.views.generic import TemplateView
urlpatterns = [
url(r'^about/', TemplateView.as_view(template_name="about.html")),
]
Any arguments passed to as_view() will override attributes set on the
class. In this example, we set template_name on the TemplateView. A
similar overriding pattern can be used for the url attribute on
RedirectView.
If you want a 'custom' url, Use the Functions Based views
Urls
url(r'^user/custom/$', custom, name='user'),
Views
def custom(request):
# your custom logic
# return something
Edit 1*
If you want pass parameters to the CBV.
class View(DetailView):
template_name = 'template.html'
model = MyModel
# custom parameters
custom = None
def get_object(self, queryset=None):
return queryset.get(custom=self.custom)
Url
url(r'^about/', MyView.as_view(custom='custom_param')),
I'm working on a Django project and trying to figure out how I can test for user ownership and allow editing or redirect based on the result.
I have a model Scene. Scene is linked to User to track which user created a particular Scene:
class Scene(models.Model):
user = models.ForeignKey(User)
[rest of Scene model]
I have a URL pattern to edit a particular Scene object like this:
url(r'^scenes/(?P<pk>[0-9]+)/edit/', SceneUpdateView.as_view(), name='scene-edit'),
I have a logged in user via django-allauth. I want only Scene owners to be able to edit Scenes.
I'm trying to figure out how to use a decorator to test if scene.user.id == self.request.user.id for the particular scene called by the URL.
Do I need to send URL information into permission_required or user_passes_test decorators (is this possible)?
How can I make this happen?
You can use a custom decorator for your specefic need.
Note: I'm using function based view, you will have to modify the code to class based view if you want:
import json
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_protect
from django.contrib.auth.models import User
from yourmodels.models import Scene
#Custom decorator
def must_be_yours(func):
def check_and_call(request, *args, **kwargs):
#user = request.user
#print user.id
pk = kwargs["pk"]
scene = Scene.objects.get(pk=pk)
if not (scene.user.id == request.user.id):
return HttpResponse("It is not yours ! You are not permitted !",
content_type="application/json", status=403)
return func(request, *args, **kwargs)
return check_and_call
#View Function
#must_be_yours
#csrf_protect
def update_scene(request, pk=None):
print pk
if request.method == 'PUT':
#modify merely
pass
Urls:
url(r'^scenes/(?P<pk>[0-9]+)/edit/', 'update_scene'),
In Function Based Views it's common to see decorators. Yet, in Class Based Views (CBV) it's more common to use Mixins or QuerySet.
Adapted from this answer, one can create the following custom Mixin that overrides the dispatch method
class UserOwnerMixin(object):
def dispatch(self, request, *args, **kwargs):
if self.object.user != self.request.user:
return HttpResponseForbidden()
return super(UserOwnerMixin, self).dispatch(request, *args, **kwargs)
This is a generalized way across multiple model class, as long as one is using user = models.ForeignKey(User).
Then use it in the CBV in a similar fashion to
class MyCustomView(UserOwnerMixin, View):
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