How to check object status before showing in django CBVs - python

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.

Related

Setting pagination_class in Django ListAPIView causes Runtime Error

I have a class-based view for text messages inheriting from the django generic view ListAPIView. As you can probably tell from the inheriting class name, the view is being used as an API with pagination (from the Django Rest framework).
I would like to turn off pagination for some specific queries, however even when I try turning off pagination for all queries via this stack overflow question (Turn off automatic pagination of Django Rest Framework ModelViewSet), I get the following error:
RuntimeError: Do not evaluate the `.queryset` attribute directly, as the result will be cached and reused between requests. Use `.all()` or call `.get_queryset()` instead.
I am overwriting the get_queryset() method in the view without issue, however by just setting the paginator_class variable to None I am receiving this error. Any help would be appreciated. Here's some code:
view.py:
class TextMessageView(generics.ListAPIView):
queryset = TextMessage.objects.all()
serializer_class = TextMessageSerializer
pagination_class = None
def get_queryset(self):
"""
If a phone number is included in the url query, return only the text messages sent by that number in the
queryset, otherwise return all text messages.
:return: A Django queryset with a variable number of text messages.
"""
from_phone_num = self.request.query_params.get('from_phone_number', None)
distinct_nums = self.request.query_params.get('distinct_numbers', None)
all_msgs = self.request.query_params.get('all_messages', None)
if from_phone_num:
return TextMessage.objects.filter(from_phone_number=from_phone_num)
elif distinct_nums == 'True':
return TextMessage.objects.order_by('from_phone_number', '-date_of_message').distinct('from_phone_number')
elif all_msgs == 'True':
return self.queryset
else:
raise Http404
Django Rest Frameworks: ListAPIView.py
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Django Rest Frameworks: mixins.py
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
you'll have to look up GenericAPIView.py because it's too big
Looking at the error message, in your get_queryset method, can you try changing
elif all_msgs == 'True':
return self.queryset
to
elif all_msgs == 'True':
return self.queryset.all()
?

Best way to write views for multiple queries in Django?

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/

Overriding get_context_data() is not working in child view

I am trying to override get_context_data() in a child class-based view to send more contextual data to the template, but it doesn't work. As a sample I am sending a test variable, but it is not rendered in the template.
class ProductList(LoginRequiredMixin, View):
template_name = 'product/product_scroll.html'
def get(self, request, *args, **kwargs):
#...
return render(request, self.template_name, data)
class Feed(ProductList):
template_name = "product/feed.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['test'] = 'sometestinfo'
return context
But in the template:
<p> Prueba: {{test}} </p>
is empty.
It's not working because you override get. Therefore the whole built in functionality of the view - including calling get_context_data - is bypassed. You almost never need to define the get or post methods.

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