Best way to write views for multiple queries in Django? - python

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/

Related

How to check object status before showing in django CBVs

I want to check the objects' status in views. If it is True nothing changes but if the status not True I want to redirect users to another page.
Here is my views:
class ProductDetailView(LoginRequiredMixin, MultiSlugMixin, DetailView):
model = Product
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
product_name = self.object.title
category_commission = self.object.category.commission
data = Stocks.objects.filter(product__title__icontains=product_name).order_by('price')
context['category_commission'] = category_commission
context['stocks'] = data
return context
And I have a status field at Product model like this:
status = models.BooleanField(default=False)
I want to achieve something like this:
if self.object.status:
do sth
else:
redirect('productlistpage')
It's very easy to display the 404 page when the status is not True, just override get_queryset.
def get_queryset(self):
return = super(ProductDetailView, self).get_queryset().filter(status=True)
However, that's not quite the behaviour you asked for. If you want to redirect, then you'll have to override get or dispatch, for example:
class ProductDetailView(LoginRequiredMixin, MultiSlugMixin, DetailView):
...
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.status:
return redirect('productlistpage')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
This isn't ideal because you are duplicating the code from the BaseDetailView.get() method, but it makes the code flow clear. You could call super() in your get() method, but then you'd end up calling get_object() twice or unnecessarily rendering the template before redirecting.

How to send HTTP response in Json format in Generic Class Views [Django]

I have sub-classed Generic DetialView class in views.py and trying to figure out a way to return data in JSON format based on an argument received in the url. Here's what I have tried doing...
# views.py
from django.views.generic import DetailView
from django.http import JsonResponse
class ExtendedView(DetailView):
context_object_name = 'post'
model = StorageModel
template_name='posts.html'
def get_context_data(self, **kwargs):
data = super(HacksViewPost, self).get_context_data(**kwargs)
if bool(self.request.GET):
data__ = JsonForm(request.GET)
if data__.is_valid():
json = data__.cleaned_data['json']
if json == 'true':
return JsonResponse({'data': 'data'})
return data
But this gave me TypeError as it should be:
TypeError at /category/extended-slug/
context must be a dict rather than JsonResponse.
The url that activates the ExtendedView class is:
/category/extended-slug?json=true
So, the question is how could i send data in JSON Format from a Generic View Class and are there any better ways of acheiving this?
I think you patch it at the wrong level. The get_context_data is used by the get function to render it. As a result, the get_context_data object has no control about what is done with the result, in order to construct a server response,
You can however patch the get(..) function like:
class ExtendedView(DetailView):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
data = self.get_context_data(object=self.object)
if self.request.GET:
data__ = JsonForm(request.GET)
if data__.is_valid():
json = data__.cleaned_data['json']
if json == 'true':
return JsonResponse({'data': data})
return self.render_to_response(data)
The same holds for post, put, and other requests.
If we take a look at the DetailView source code we see:
class BaseDetailView(SingleObjectMixin, View):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
Hence the get(..) calls the get_context_data(..) function. But it does not immediately returns the result, it wraps it into a rendered response.
You can't return a JsonResponse inside the get_context_data method. The get_context_data method allows you to send extra information (context) to the template and is expected to return a dict, not a JsonResponse.
If you want to return a JsonResponse, do that in the get or post method of your class.

Creating a Django demo-user who can't save to the database

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.

DRF Custom Permission not blocking the view

So for my project I was trying to implement custom permissions for a view. I created the permission in permissions.py, which looks like this:
class TeamViewPermission(permissions.BasePermission):
"""
Global permission for viewing team pages
"""
def has_permission(self, request, view):
team_id = self.kwargs.get('team_id')
teamqs = MAIN_TEAMS.all()
pk_list = []
for item in MAIN_TEAMS:
pk_list.append(str(item.pk))
if team_id in pk_list:
return True
return False
Pretty simple, checks if the configuration team is matching the team page you're requesting and blocking the user out if that is not the case.
The views.py:
class PlayerList(ListView):
model = player_model
template_name = 'player_list.html'
permission_classes = (TeamViewPermission, )
def get_team(self):
if not hasattr(self, '_team'):
team_id = self.kwargs.get('team_id')
self._team = team_model.objects.get(pk=self.kwargs.get('team_id'))
return self._team
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['team'] = self.get_team()
return context
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
return queryset.filter(team_id=self.kwargs.get('team_id'))
I know for a fact that the page returns True or False when it should, because I debugged it, although it's not blocking the page out? It returns False for the page, but I can still access the page like it returned True .. Am I missing something here?
It looks like you are mixing djangos class based views and the DRF views.
ListView is a class based view from django and NOT from DRF. It therefore does not allow to set permission_classes.
Check the docs to see how to use DRF api views.

Django: ListView with post() method?

I am trying to process two forms in a Django class based view. The site contains a form called form (based on GET) for narrowing the list results of the ListView and the second form status_form (based on POST).
Both forms are required since the ListView returns a list of items. Form lets the user restrict the choices and status_forms lets the user flag incorrect items via a modal form (therefore it needs to be in the same template).
My trouble is that ListView does not come with the method post, however FormView does. My class List inherits from both classes, but when I execute the class I get the error message:
Attribute Error: 'List' object has no attribute 'status_form'
How should I change my implementation to allow the second form been processed via the post method?
class List(PaginationMixin, ListView, FormMixin):
model = ListModel
context_object_name = 'list_objects'
template_name = 'pages/list.html'
paginate_by = 10 #how may items per page
def get(self, request, *args, **kwargs):
self.form = ListSearchForm(self.request.GET or None,)
return super(List, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.status_form = StatusForm(self.request.POST or None)
if self.status_form.is_valid():
...
else:
return super(List, self).post(request, *args, **kwargs)
def get_queryset(self):
# define the queryset
...
# when done, pass to object_list
return object_list
def get_context_data(self, **kwargs):
context = super(List, self).get_context_data(**kwargs)
context.update(**kwargs)
context['form'] = self.form
context['status_form'] = self.status_form # Django is complaining that status_form is not existing, result since the post method is not executed
return context
# Django is complaining that status_form does not exist,
# result since the post method is not executed
context['status_form'] = self.status_form
Because you didn't define self.status_from in the first place.
You have defined it in get_context_data, and it's accessible from there.
You can access you object from get_context_data in your post method;
context = self.get_context_data(**kwargs)
status_form = context['status_form']
Also consider that you can define your status_form directly in post method itself without getting it from self or get_context_data.
Redesign you views to separate each Form processing in separate Views then tight them with each-other.
Views redesign:
In nutshell, let each view to do one job. You can create a View just for processing your status_form and name it like StatusFormProcessView then on your List view return it on its post method
class List(ListView);
def post(self, request, *args, **kwargs):
return StatusFormView.as_view()(request) # What ever you need be pass to you form processing view
This is just an example of it, need more work to be real.
For another example; On my website index page I have a search form. when user POST or GET the search form, The processing of searching doesn't exist in my IndexView, instead I handle the whole form stuff in separate view, If form should process on GET method, I'll override get() method, If form should process on POST, I'll override post() method to send search_form data to the view that is responsible for handling of processing the search_form.
Comments response
status_form = context['status_form']
shouldn't it be
context['status_form'] = status_form
after I created it ?
You want to get status_form from context, So you need to
status_form = context['status_form']
Anyway, your form data are available on self.request.POST

Categories