Redirect if query has no result - django

I've made a page with an input which connects to this view:
class SearchResultView(ListView):
model = RecipeSet
template_name = 'core/set_result.html'
context_object_name = 'recipe_set'
def get_queryset(self):
query = self.request.GET.get('q')
object_list = RecipeSet.objects.filter(
Q(set_name__exact=query)
)
if object_list.exists():
return object_list
else:
return redirect('core:dashboard')
I've used set_name__exact for this query and want to redirect users if the search returned no objects, how do I go about this? I've tried to use an if/else statement to check the objects but that doesn't seem to work.

The .get_queryset(…) [Django-doc] method should return a QuerySet, not a list, tuple, HttpResponse, etc.
You can however alter the behavior, by setting the allow_empty attribute to allow_empty = False, and override the dispatch method such that in case of a Http404, you redirect:
from django.http import Http404
from django.shortcuts import redirect
class SearchResultView(ListView):
allow_empty = False
model = RecipeSet
template_name = 'core/set_result.html'
context_object_name = 'recipe_set'
def get_queryset(self):
return RecipeSet.objects.filter(
set_name=self.request.GET.get('q')
)
def dispatch(self, *args, **kwargs):
try:
return super().dispatch(*args, **kwargs)
except Http404:
return redirect('core:dashboard')

Personally I would just change the .exists to .count:
class SearchResultView(ListView):
model = RecipeSet
template_name = 'core/set_result.html'
context_object_name = 'recipe_set'
def get_queryset(self):
query = self.request.GET.get('q')
object_list = RecipeSet.objects.filter(
Q(set_name__exact=query)
)
if object_list.count():
return object_list
else:
return redirect('core:dashboard')

Related

Django Class Based View return view or redirect to another page

I'm rewriting my function to class based views, this is the function I currently have.
#login_required
def invoice(request, invoice_no, template_name="invoice.html"):
context = {}
invoice_exists = Invoice.objects.filter(invoice_no=invoice_no)
if invoice_exists:
context['invoice'] = invoice_exists.first()
else:
return HttpResponseRedirect(reverse('invoices'))
return render(request, template_name, context)
you have to be logged in, it filters using a filter named invoice_no
path('invoice/<int:invoice_no>', views.InvoiceView.as_view(), name="invoice"),
and if a match is found returns it, if not redirects you back to the invoices page.
this is what I have as a class
class InvoiceView(DetailView):
queryset = Invoice.objects.all()
context_object_name = 'invoice'
pk_url_kwarg = 'invoice_no'
template_name = "invoice.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_object(self):
obj = super().get_object()
return obj
also the get object or 404 will do also since all it needs is a 404 page and it'll work.
Try this:
class ArticleDetailView(LoginRequiredMixin, DetailView):
template_name = "invoice.html"
context_object_name = 'invoice'
model = Invoice
def dispatch(self, request, *args, **kwargs):
try:
return super().dispatch(request, *args, **kwargs)
except Invoice.DoesNotExist:
return HttpResponseRedirect(reverse('invoices'))
def get_object(self):
return Invoice.objects.get(invoice_no=self.kwargs['invoice_no'])
Adjust according to your code.
from django.contrib.auth.mixins import LoginRequiredMixin
class InvoiceView(LoginRequiredMixin, DetailView):
template_name = "invoice.html"
context_object_name = 'invoice'
def get_queryset(self, *args, **kwargs):
invoice = get_object_or_404(Invoice, invoice_no=kwargs['invoice_no'])
return invoice
This will however return a 404 page if no data is found, if you like it to redirect it to invoices page, use a filter. Then use an IF statement to compare length > 0, if 0 then just redirect to page. Might as well put a message error then too.

Django: get_object_or_404 for ListView

I have the following ListView. I know about get_object_or_404. But is there a way to show a 404 page if the object doesn't exist?
class OrderListView(ListView):
template_name = 'orders/order_list.html'
def get_queryset(self):
return OrderItem.objects.filter(
order__order_reference=self.kwargs['order_reference'],
)
You can raise a 404 error for a ListView, by changing the allow_empty [django-doc] attribute to False:
class OrderListView(ListView):
template_name = 'orders/order_list.html'
allow_empty = False
def get_queryset(self):
return OrderItem.objects.filter(
order__order_reference=self.kwargs['order_reference'],
)
If we inspect the soure code of the BaseListView (a class that is one of the ancestors of the ListView class), then we see:
class BaseListView(MultipleObjectMixin, View):
"""A base view for displaying a list of objects."""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
'class_name': self.__class__.__name__,
})
context = self.get_context_data()
return self.render_to_response(context)
So it also takes pagination, etc. into account, and shifts the responsibility at the get(..) function level.
You can use get_list_or_404:
from django.shortcuts import get_list_or_404
def get_queryset(self):
my_objects = get_list_or_404(OrderItem, order__order_reference=self.kwargs['order_reference'])

How to use django-el-pagination with filtering?

I have a django Listview and i am using django-el-pagination's ajax endless pagination to paginate my results and it works well. The problem is I need to apply some filters to it and have no clue on how to do it.
Can i send parameters through GET to the view? Have searched a lot and seems like no one had this problem before.
Data is always returned in the get_queryset() function.
If you want to simply filter from the database, you can just return objects.
class IndexView(AjaxListView):
template_name = '****'
context_object_name = '****'
page_template = '****'
def get_queryset(self):
return your_model.objects.filter(title='***').order_by('**')
Else if you want to get data from non-database, you need to implement a proxy accordding to this answer.
If not,the pagination ajax will request all data, then slice it.The proxy make your data sliced while querying.
This is my filter that getting data from ElasticSearch.
class IndexView(AjaxListView):
template_name = '****'
context_object_name = '****'
page_template = '****'
def get_queryset(self):
params = {}
# get query params
for item in self.request.GET.items():
if item[0] == 'page' or item[0] == 'querystring_key':
continue
params[item[0]] = item[1]
# no filter
if len(params) == 0:
return ****.objects.filter().order_by('-date')
else:
return ESResult(params)
class ESResult(object):
def __init__(self, params):
self.params = params
def __len__(self):
s = self.search_es()
if s:
s = s[:1]
r = s.execute()
return r['hits']['total']
else:
return 0
def __getitem__(self, item):
assert isinstance(item, slice)
result = []
s = self.search_es()
if s:
s = s[item.start:item.stop] # slice before querying
r = s.execute()
for a in r.to_dict()['hits']['hits']:
one = a['_source']
one['id'] = int(a['_id'])
result.append(one)
return result
def search_es():
...
# filter request here
...
The list object used by AjaxListView is defined by get_queryset() method. To filter the queryset based on the users input, you may refer to POST method:
from app.forms import BlogFilterForm
class Blog(LoginRequiredMixin, AjaxListView):
context_object_name = "posts"
template_name = 'blog/blog.html'
page_template = 'blog/post_list.html'
success_url = '/blog'
def get_queryset(self): # define queryset
queryset = Post.objects.all() # default queryset
if self.request.method == 'POST': # check if the request method is POST
form = BlogFilterForm(self.request.POST) # define form
if form.is_valid():
name = form.cleaned_data['name'] # retrieve data from the form
if name:
queryset = queryset.filter(name=name) # filter queryset
else:
queryset = queryset
return queryset
def get_context_data(self, **kwargs):
context = super(Blog, self).get_context_data(**kwargs)
context['form'] = BlogFilterForm() # define context to render the form on GET method
return context
def post(self, request, *args, **kwargs): # define post method
return super(Blog, self).get(request, args, kwargs)
The endless pagination should work fine.
using filters with django-endless-pagination

Django DetailView + show related record of another Model

I would like to return to the User related record.
Somebody can help me?
part of my view
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
Something like a old way>
def my_view(request, slug):
var = get_object_or_404(Model, slug=slug)
xxx = AnotherModel.objects.filter(var=var)
...
how can i perfome this in the first view UserProfileDetailView,
show related data?
What I do in this case is add the related object into the context data. It would be something like this:
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_context_data(self, **kwargs):
# xxx will be available in the template as the related objects
context = super(UserProfileDetailView, self).get_context_data(**kwargs)
context['xxx'] = AnotherModel.objects.filter(var=self.get_object())
return context
Another approach is to extend DetailView with MultipleObjectMixin, as in this example:
from django.views.generic import DetailView
from django.views.generic.list import MultipleObjectMixin
from django.contrib.auth import get_user_model
class DetailListView(MultipleObjectMixin, DetailView):
related_model_name = None
def get_queryset(self):
# a bit of safety checks
if not hasattr(self, "related_model_name"):
raise AttributeError(
"%s.related_model_name is missing." % (
self.__class__.__name,))
if not self.related_object_name:
raise NotImplementedError(
"%s.related_model_name must not be None." % (
self.__class__.__name,))
# get the object
obj = self.get_object()
# get the related model attached to the object
related_model = getattr(obj, "%s_set" % self.related_model_name, None)
# safety check if related model doesn't exist
if not related_model:
raise AttributeError(
"%s instance has no attribute \"%s_set\"" % (
obj.__class__.__name__, self.related_model_name)
# return the related model queryset
return related_model.all()
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
return super(DetailListView, self).get(request, *args, **kwargs)
class UserProfileDetailView(DetailListView):
template_name = "perfil.html"
model = get_user_model()
slug_field = "username"
related_model_name = "anothermodel"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
In my opinion this approach is a little less cleaner and understandable, but it has a huge advantage in reusability. It definitely has one downside: if you are using the class variable context_object_name, it will refer to the related objects list and not to the object itself (this has to do with how the inheritance chain is set up when constructing the class).

Pass url argument to ListView queryset

models.py
class Lab(Model):
acronym = CharField(max_length=10)
class Message(Model):
lab = ForeignKey(Lab)
urls.py
urlpatterns = patterns('',
url(r'^(?P<lab>\w+)/$', ListView.as_view(
queryset=Message.objects.filter(lab__acronym='')
)),
)
I want to pass the lab keyword argument to the ListView queryset. That means if lab equals to TEST, the resulting queryset will be Message.objects.filter(lab__acronym='TEST').
How can I do that?
You need to write your own view for that and then just override the get_queryset method:
class CustomListView(ListView):
def get_queryset(self):
return Message.objects.filter(lab__acronym=self.kwargs['lab'])
and use CustomListView in urls also.
class CustomListView(ListView):
model = Message
def get(self, request, *args, **kwargs):
# either
self.object_list = self.get_queryset()
self.object_list = self.object_list.filter(lab__acronym=kwargs['lab'])
# or
queryset = Lab.objects.filter(acronym=kwargs['lab'])
if queryset.exists():
self.object_list = self.object_list.filter(lab__acronym=kwargs['lab'])
else:
raise Http404("No lab found for this acronym")
# in both cases
context = self.get_context_data()
return self.render_to_response(context)