I'm creating a Django web application on which users can create an account for free. I have also set-up a demo user which is already configured and has data attached to its account. Purpose of this demo account is to give a new user a quick overview of what the application can do.
Now I would like to have this demo user access all my views but not save to the database when the user saves a form.
Off course there are multiple ways off doing this that I know off. But they all require me to edit multiple pages or views:
When saving a form check if it is the demo user, if yes: don't save
Remove the save button from my templates when the demo user is logged in
Is there a easier/cleaner solution to do this? How can I set-up my application in a way that a specific user can never save to the database?
The solution I used
marcusshep's idea provided a solution for me. I created the following Views for pages where the form should be loaded but not saved when hitting the save button. Until now I wasn't able to do that. At this moment the pages below will render a 303 immediately
class FormViewOPRadio(FormView):
def dispatch(self, request, *args, **kwargs):
# Return 403 for demo user
temp = 'temp'
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(FormViewOPRadio, self).dispatch(request, *args, **kwargs)
class UpdateViewOPRadio(UpdateView):
def dispatch(self, request, *args, **kwargs):
# Return 403 for demo user
temp = 'temp'
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(UpdateViewOPRadio, self).dispatch(request, *args, **kwargs)
class DeleteViewOPRadio(DeleteView):
def dispatch(self, request, *args, **kwargs):
# Return 403 for demo user
temp = 'temp'
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(DeleteViewOPRadio, self).dispatch(request, *args, **kwargs)
Furthermore there are also some pages which should be inaccessible for which I used
from braces.views import UserPassesTestMixin
class UserNotDemoUser(UserPassesTestMixin):
raise_exception = True
def test_func(self, user):
return user.email != 'demo#opradio.nl'
What I tried
I created the following Views for pages where the form should be loaded but not saved when hitting the save button
class FormViewOPRadio(FormView):
def form_valid(self, form):
# Return 403 for demo user
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(FormViewOPRadio, self).form_valid(form)
class AddStream(LoginRequiredMixin, UserPassesTestMixin, SuccessMessageMixin, FormViewOPRadio):
"""Is the page used to add a Stream"""
template_name = 'opradioapp/addoreditstream.html'
form_class = AddStreamForm
success_url = reverse_lazy('opradioapp_home')
success_message = "De stream is opgeslagen"
# Validate if the user is the maintainer of the station
def test_func(self):
user = self.request.user
mainuserstation = MainUserStation.objects.get(slugname=self.kwargs['mainuserstationslug'])
if mainuserstation.maintainer == user:
return True
else:
return False
def form_valid(self, form):
user = self.request.user
mainuserstation = MainUserStation.objects.get(slugname=self.kwargs['mainuserstationslug'])
userstream = UserStream()
userstream.mainuserstation = mainuserstation
userstream.name = form.cleaned_data['name']
userstream.slugname = 'temp'
userstream.description = form.cleaned_data['description']
userstream.save()
member = Member.objects.get(user=user, mainuserstation=mainuserstation)
member.streamavailable.add(userstream)
member.save()
return super(AddStream, self).form_valid(form)
When doing it this way
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
is called after the save() calls. How can I change this? I tried calling super earlier but than I ran into problems.
Off course there are multiple ways of doing this that I know of. But they all require me to edit multiple pages or views:
Well, you won't have to repeat the logic for every template or view if you utilize certain DRY features of Python and Django.
Class Based View Inheritance
class CheckForDemoUser(View):
def dispatch(self, request, *args, **kwargs):
# check for demo user
# handle which ever way you see fit.
super(CheckForDemoUser, self).dispatch(request, *a, **kw)
class ChildClass(CheckForDemoUser): # notice inheritance here
def get(request, *args, **kwargs):
# continue with normal request handling
# this view will always check for demo user
# without the need to repeat yourself.
Function Decorators
def check_for_demo_user(func):
def func_wrapper(request, *args, **kwargs):
# implement logic to determine what the view should
# do if the request.user is demo user.
return func_wrapper
#check_for_demo_user
def my_view(request, *args, **kwargs):
# automatic checking happening before view gets to this point.
With Inclusion Tags you can isolate the logic of hiding/showing form submit buttons in one place and refer to your custom tag in multiple pages that the demo user would be on.
These are just some of the ways you can implement this logic without having to repeat yourself over and over.
Related
I don't know how can I prevent using Global variable to pass data to another function. It's too much hassle to create request.session on every page just to get the value set by another function. I know there's a way e.g using decorators etc. but I haven't found yet the best way on how to implement it. Can anyone help me? Below I been using Global variable in usertype request.session['type_user'] = usertype. Thanks for the help.
def example_login(request):
usertype = "Admin"
request.session['type_user'] = usertype
return redirect ('dashboard')
def dashboard(request):
value_usertype = {'usertype_var':request.session['type_user']}
return render(request,'dashboard.html', value_usertype)
def anotherview(request): # too hassle to create another request.session in every page
value_usertype = {'usertype_var':request.session['type_user']}
return render(request,'dashboard.html', value_usertype)
Function-based view
You can construct a decorator that will pass the usertype_var to the view function:
from functools import wraps
def with_usertype(f):
#wraps(f)
def wrapped(request, *args, **kwargs):
return f(request, *args, **kwargs, usertype=request.session.get('type_user'))
return wrapped
You can then use the decorator as:
#with_usertype
def dashboard(request, usertype):
return render(request,'dashboard.html', {'usertype_var': usertype})
#with_usertype
def anotherview(request, usertype):
return render(request,'dashboard.html', , {'usertype_var': usertype})
You can also restrict it with:
from functools import wraps
from django.core.exceptions import PermissionDenied
def restrict_usertype(*restrictions):
def mydecorator(f):
#wraps(f)
def wrapped(request, *args, **kwargs):
usertype = request.session.get('type_user')
if usertyp in restrictions:
return f(request, *args, **kwargs, usertype=usertype)
else:
raise PermissionDenied
return wrapped
return mydecorator
then you can restrict this with:
#restrict_usertype('Admin')
def dashboard(request, usertype):
return render(request,'dashboard.html', {'usertype_var': usertype})
#restrict_usertype('Admin', 'Other')
def anotherview(request, usertype):
return render(request,'dashboard.html', , {'usertype_var': usertype})
Class-based views
For class-based views, since django-2.2 you can make use of the setup(…) method [Django-doc]. This method normally sets the request, args and kwargs to the view object:
# since Django-2.2
class UsertypeMixin:
def setup(self, request, *args, **kwargs):
self.usertype = request.session.get('type_user')
return super().setup(*args, **kwargs)
def get_context_data(self, *args, **kwargs):
return super().get_context_data(
*args,
**kwargs,
usertype=self.usertype
)
Then the mixin can be used, for example in a ListView with:
class MyListView(UserTypeMixin, ListView):
def get_queryset(self, *args, **kwargs):
# use self.usertype
# …
return super().get_queryset(*args, **kwargs)
we can restrict this as well with:
# since Django-2.2
from django.core.exceptions import PermissionDenied
class UsertypeRestrictMixin(UsertypeMixin):
required_usertype = ()
def setup(self, request, *args, **kwargs):
result = super().setup(*args, **kwargs)
if self.usertype not in self.required_usertype:
raise PermissionDenied
return result
and then use this as:
class MyListView(UsertypeRestrictMixin, ListView):
required_usertype = ('Admin',)
Django's groups and permissions
However often a better modeling is using groups, since a user can often have multiple roles. It can be a commentator, administrator, author, etc. and often after a while users can have multiple roles. Django has a system of groups and permissions [Django-doc] for this in place.
The advantage of using permissions is that this is more fine-grained, so one can make permissions for each (type of) view, and furthermore it allows a person to be a member of multiple groups, and also thus obtain the permissions of all these groups. This makes it more flexible since sometimes you want to give a small set of users an extra permission.
I suggest you to rely on Django's auth system for both authentication (username + password = user1) and authorization (can user1 user this view?)
Using Django's auth system:
Login:
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...
Logout:
from django.contrib.auth import logout
def logout_view(request):
logout(request)
# Redirect to a success page.
Limiting access for authenticated users
redirecting to login
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
redirecting to custom template
from django.contrib.auth.decorators import login_required
#login_required(redirect_field_name='my_redirect_field')
def my_view(request):
...
Limiting access for authorized users
based on roles (Django's recommended approach)
from django.contrib.auth.decorators import permission_required
#permission_required('your_app.admin_rigths')
def my_view(request):
...
based on groups (custom way)
from django.contrib.auth.decorators import user_passes_test
def is_admin(user):
if your_custom_verification_for_admin_rights:
return True
return Fa
#user_passes_test(email_check)
def my_view(request):
...
See more in https://docs.djangoproject.com/en/3.1/topics/auth/default/#auth-web-requests
Custom auth approach
You can also not make use of Django's approach, as I needed once. I did the following:
Create an authentication middleware
class CustomAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# do your custom verification
user_type = get_user_type_function(request)
# add its result to the request object
request.__setattr__('type_user', user_type)
# go to normal request flow
return self.get_response(request)
Add it to settings
MIDDLEWARE = [
...django's middleware
'your_auth_app.path.to.middleware.CustomAuthMiddleware',
Create a decorator for authorization enforcement
from django.core.exceptions import PermissionDenied
def needs_user_type(user_type):
def auth_decorator(function):
def wrapper(request, *args, **kwargs):
authorized = False
if hatattr(request, 'type_user'):
request_user_type = request.__getattribute__('type_user')
authorized = request_user_type == user_type
if authorized:
return function(*args, **kwargs)
else:
return PermissionDenied()
return wrapper
return auth_decorator
Use the decorator in your views
#needs_user_type('Admin')
def dashboard(request):
# do stuff
#needs_user_type('Admin')
def anotherview(request):
# do stuff
If you want to access user_type in your views, see Django's Request Context
It's a simple question. I've organised my models so that most objects served to the page are of one type - Item. The model contains various attributes which help me serve it in different ways.
I have articles, and videos, which are determined by a 'type' field on the model. Type = 'article' etc.
I have a listview, which shows all the objects in the Item model, sorted by date.
class ItemListView(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.all().order_by('-create_date')
I want a view which shows all the articles, sorted by date, and all the videos, sorted by date. I have a feeling I'll be writing many more such views.
Is there a better way to do it than to write a new view for every single query? As, this is what I'm currently doing:
Views.py
class ItemListViewArticle(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.filter(type__contains='article').order_by('-create_date')
class ItemListViewVideo(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.filter(type__contains='video').order_by('-create_date')
urls.py
path('new/', views.ItemListView.as_view(), name='new_list'),
path('new/articles', views.ItemListViewArticle.as_view(), name='article_list'),
path('new/videos', views.ItemListViewVideo.as_view(), name='video_list'),
You can use URL querystring(ie request.GET) to get type of the item from url and filter by it. Like this:
path('new/', views.ItemListView.as_view(), name='new_list'),
class ItemListViewArticle(generic.ListView):
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
content_type = self.request.GET.get('type')
return Item.objects.filter(type__contains=content_type).order_by('-create_date')
# usage
localhost:8000/new/?type=article
localhost:8000/new/?type=video
Or you can use URL parameter to get the type of data:
path('new/<str:content_type>/', views.ItemListView.as_view(), name='new_list'),
class ItemListViewArticle(generic.ListView):
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
content_type = self.kwargs.get('content_type')
return Item.objects.filter(type__contains=content_type).order_by('-create_date')
# usage
localhost:8000/new/article/
localhost:8000/new/video/
I want to create DRY Django Mixins for things I do frequently. I have multiple Access Mixins that all need current Organization, so I wanted the code responsible for getting the Organization to be in separate Mixin, that also inherits from Djangos LoginRequiredMixin, because we will always check that if we're in organization page.
But instead of preforming dispatch method of inherited classes, saving the response and doing its thing, it skips it.
I started reading about Python's MRO and all, but I still can't find a way to make it all work.
Is my basic idea wrong? What is the best way to achieve this?
Thanks in advance,
Paweł
Code:
class OrganizationMixin(LoginRequiredMixin):
"""
CBV mixin for getting current organization.
Inherits from `LoginRequiredMixin` because we need logged in user to check
their membership and/or organization role.
"""
org_kwarg_name = 'organization_slug'
organization = None
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
self.organization = self.get_organization()
return response
def get_organization(self):
# Get the slug from URL
slug = self.kwargs.get(self.org_kwarg_name, None)
return get_object_or_404(Organization, slug=slug)
class MembershipRequiredMixin(OrganizationMixin, AccessMixin):
"""
CBV mixin for checking if user belongs to current organization.
Already checks if user is logged in.
"""
permission_denied_message = _("You need to belong to the organization "
"to visit this page.")
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
# Check if user is a member or a superuser
if (not self.organization.is_member(request.user) and
not request.user.is_superuser):
return self.handle_no_permission()
return response
On a Django form, I'm getting my users to upload an avatar. I have a clean_avatar method associated to the relevant ModelForm in forms.py, where I'm doing some processing on the submitted avatar before uploading it. How do I access self.request.user in this clean_avatar method? I tried over-riding __init__ of the ModelForm but goofed up.
For those interested, I want access self.request.user in the clean_avatar method in order to check whether the avatar being uploaded now is the same as the one the user submitted just before this. This will help me save bandwidth in cases where the form's being re-submitted (I've checked; in my app, re-submission causes the avatar to be re-submitted too, and re-processed).
The clean_avatar method currently looks like this:
def clean_avatar(self):
image=self.cleaned_data.get("avatar")
if image:
try:
if image.size > 1000000:
return None
except:
pass
image = Image.open(image)
image = MakeThumbnail(image)
return image
else:
return None
I think it's better to pass it to your form as an extra parameter so that you could use it throughout form:
class FooForm(models.ModelForm):
def __init__(self, *args, **kwargs):
# you take the user out of kwargs and store it as a class attribute
self.user = kwargs.pop('user', None)
super(FooForm, self).__init__(*args, **kwargs)
def clean_avatar(self):
current_user = self.user
# use current_user
Your views.py method:
def view(request):
foo_form = FooForm(request.POST or None, user=request.user)
As part of a form validation in Django, I want to check when a user last made a post.
I've had a look at this, and this question on stackoverflow, which appear to address this issue, and initially used the accepted answer here. Essentially the process at the moment has been to add the request.user object during the
def form_valid(self, form):
method of the CreateView subclass. However, this doesn't allow the user variable to be used during form validation.
Since I want to access the user variable during validation, as the check on time since last post is logically validation, I need the user variable injected to the form earlier.
I've tried altering the get_initial() method:
def get_initial(self):
initial = super(ArticleCreateView, self).get_initial()
initial['user'] = self.request.user
return initial
This successfully sets the ['user'] key in the initial dictionary, but this never makes it through the cleaning process, which deals only with the stuff in the ['data'] dictionary, and removes anything in initial. I've deliberately excluded user from the form display, but this appears to also remove it from the cleaning process and validation.
Is the correct way to do this to override the init() method of the Form, and the get_initial() method of the view, putting 'user' into initial, and then the form pulling the extra information from initial into the form at init()?
I'm looking to pass the request.user to the form, and then I can access it within the form-wide clean() method to do the additional checks, which amount to validation.
def clean(self):
super(ArticleForm, self).clean()
**check whether user has posted recently, if so, raise ValidationError**
return self.cleaned_data
J
You can override get_form_kwargs method of the CreateView (ArticleCreateView in your case) to pass user to the form.
def get_form_kwargs(self):
kwargs = super(ArticleCreateView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Override your form's init method
...
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
Now you can use self.user in the clean method.
The way I've solved this at the moment is to keep validation within the form, and the rest of the business logic in the view. Consequently, I've done the following:
In the view:
def get_initial(self):
initial = super(MyCreateView, self).get_initial()
initial['user'] = self.request.user
return initial
In the form:
def __init__(self, *args, **kwargs):
self.user = kwargs['initial']['user']
super(MyObjectForm, self).__init__(self, *args, **kwargs)
def clean(self):
# Clean all the submitted data
super(MyObjectForm, self).clean()
# Validate that the user submitted > n time ago
profile = self.user.get_profile()
if datetime.now() - profile.lastpost < timedelta(hours=1)
raise forms.ValidationError("Need to wait longer than n=1 hours")
else:
self.cleaned_data['user'] = self.user
return self.cleaned_data
Which seems to work - are there any flaws I haven't seen with this solution?
i would probably override the form init method to take a user argument, and set this somewhere on the form.
class MyForm()
def __init__(self, user, *args, **kwargs):
super(MyForm, self)__init__(*args, **kwargs)
self.user = user
def clean_field(...)
if self.user ...
etc