I am trying to do a search over many data models in Django to my site.
Now I have code like this in views.
class Search(ListView):
template_name="search.html"
context_object_name="result_serch_all"
paginate_by = 10
def get_queryset(self):
news = Articles.objects.filter(title__icontains=self.request.GET.get("q"))
docs = Doc.objects.filter(name_doc__icontains=self.request.GET.get("q"))
query_sets = list(chain(news, docs))
return query_sets
def get_context_data(self, *args, **kwargs):
paginator = Paginator(self.get_queryset(), 5)
context = super().get_context_data(*args, **kwargs)
context["q"] =f'q={self.request.GET.get("q")}&'
print(context['result_serch_all'])
return context
In the template, I output like this
{%for el in result_serch_all%}
<div> {{el}} </div>
{% endfor %}
How can the output in the template be divided into blocks?So that in front of all found articles there is an inscription Found in the news section. Before the documents Found in the documents section`?
I found this option. In my model im add
def get_my_model_name(self):
return self._meta.model_name
and in the template I check
{% if el.get_my_model_name == 'articles' %}
Maybe there is some standard way in Django?
Related
I have a basic application where Users create Groups. These Groups can have multiple tags (by way of django-taggit) and, once posted, are rendered on a home page, sorted newest to oldest.
In my views.py I have two different tag lists that the home page uses: a list of all tags and a list of common tags:
class Home(ListView):
model = Group
template_name = 'home.html'
ordering = ['-posted']
def get_common_tags(self):
# common tags
common_tags = Group.tags.most_common()[:2]
return common_tags
def get_context_data(self, **kwargs):
kwargs['common_tags'] = self.get_common_tags()
return super().get_context_data(**kwargs)
class TagFilter(ListView):
model = Group
template_name = 'home.html'
context_object_name = 'group'
def get_queryset(self):
# all tags
tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
return tag_list
def get_context_data(self, *args, **kwargs):
tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
tag_list_is_empty = True
if tag_list.exists():
tag_list_is_empty = False
context = super(TagFilter, self).get_context_data()
context["tag_list_is_empty"] = tag_list_is_empty
return context
The idea is that when you click on a tag, it filters the Group results based on that tag:
{% get_taglist as tags %}
<div>
Filter by tag:
{% for tag in tags %}
#{{tag.name}}
{% endfor %}
</div>
<div>
Common tags:
{% for tag in common_tags %}
#{{tag.name}}
{% endfor %}
</div>
{% if tag_list_is_empty == True %}
<p>Sorry! No group found with this tag</p>
{% endif %}
{% for group in object_list %}
<a href="{% url 'group_detail' group.pk %}">
<h1>{{group.name}}</h1>
<p>{{ group.description|slice:":50"}}...</p>
</a>
{% endfor %}
The issue I'm running into is that my list of common tags is only visible at the base url localhost:8000/. But when you click on a tag that brings you to a url of localhost:8000/tags/slug, the list of common tags disappears.
My first thought was that it was something to do with the fact that common_tags is returned in the Home view explicitly and once the url changes it stops being displayed.
This led me to moving both functions into TagFilter, but then I couldn't access tag_list_is_empty in my template. I'm assuming because you can't have two get_context_data function sin the same view.
I'm just wondering what the best way to go about retaining all these variables and passing them to the template so that both tag lists are displayed no matter the url, and the conditional to see if tag_list_is_empty so that I can display the message
<p>Sorry! No group found with this tag</p>
In get_common_tags you don't need the request headers or anything. So you can extract this function from Home class and you might consume/call from multiple views.
def get_common_tags(self):
#consider using try/catch
try:
return Group.tags.most_common()[:2]
except:
return {'no tag'}
class Home(ListView):
model = Group
template_name = 'home.html'
ordering = ['-posted']
def get_context_data(self, **kwargs):
kwargs['common_tags'] = get_common_tags()
return super().get_context_data(**kwargs)
class TagFilter(ListView):
model = Group
template_name = 'home.html'
context_object_name = 'group'
def get_queryset(self):
# all tags
tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
return tag_list
def get_context_data(self, *args, **kwargs):
tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
tag_list_is_empty = True
if tag_list.exists():
tag_list_is_empty = False
context = super(TagFilter, self).get_context_data()
context["tag_list_is_empty"] = tag_list_is_empty
# add common tags to context
context["common_tags"] = get_common_tags()
return context
First of all, as mentioned by Egemen SOYLU in the answers, take the common tags function out of the Home view so you can use it in any other. The "context" is specific to each view and are not inherited when following links to another view. If you switch pages and there is a new view for it, a new request is sent from the client side and the server renders the corresponding page, context included.
With the above said, you have a few options.
Make a function to retrieve common tags:
def get_common_tags(): # this function needs no input, just trigger
try:
tags = Group.tags.most_common()[:2]
return tags
except:
return [] # Return an empty list if not found.
To get the common tags context information from a view, insert the function and assign it a named list (a.k.a. "key-value pair") in the context dictionary:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) #retrieve default view context info, i.e.: model query.
"""add your other context info here..."""
context['common_tags'] = get_common_tags() #retrieve the common tags from the function
"""context['key_name'] = values..."""
return context
The above should work for any view where you want to include the common tags and you should refer to it as 'common_tags' in the template.
For the filtered view, you can simplify the logic a bit by grouping functions:
class TagFilter(ListView):
model = Group
template_name = 'home.html'
context_object_name = 'group' """<--- you can get rid of this line
as you can build your context information at will. Keep reading..."""
""" def get_queryset(self): <--- you don't need this function, send
everything together in the context dictionary.
Just add the queryset to the get_context_data() function below...
tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
return tag_list"""
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs) """Whatever you need from the
request kwargs..."""
tag_list = [] """First define an empty list in case getting
the Group model information returns an empty list."""
try:
tag_list = Group.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
else:
pass """do not modify the empty tag_list"""
group = Group.objects.all() """ignore this line if you're using the
context_object_name variable or add any filter by using the kwargs.get
method. In this last scenario, don't forget to add context['group']=group"""
context["tag_list"] = tag_list """empty if the Group filter returns empty"""
context["common_tags"] = get_common_tags() """ this will return an empty
queryset if there are no common tags found, as defined by the function
above"""
return context
So, how do you use empty values from context dictionaries? Go to your HTML templates and follow this logic:
For an empty queryset, use "if" or "if not" Django template tags.
If the key name returns content, do your algo in the template.
So, in the template:
{% if tag_list %} <!-- if there is content in "tag_list" key name, do... -->
{% for tag in tag_list %}
...
{% endfor %}
{% else %} <!-- if the list is empty, do something (i.e.: display a message) -->
{% endif %}
Same applies for any list in the context dict. If you have doubts with the template tags, {% if not key_from_context_dict %} works the same as {% if key_from_context_dict == Null %}}
Hope this works. :)
I'm trying to display all model objects after a get request in a table using django-tables2. It's currently displaying all and I can't figure out how to filter the queryset based on a model pk in my view:
views.py
class ViewJob(LoginRequiredMixin, SingleTableView):
model = JobResults
table_class = JobResultsTable
template_name = 'app/viewjobresults.html'
paginator_class = LazyPaginator
table_pagination = {"per_page": 30}
def get_context_data(self, **kwargs):
""" ViewJob get_context_data request """
context = super(ViewJob, self).get_context_data(**kwargs)
print("ViewJob >get_context_data()")
context['job_obj'] = Job.objects.get(pk=self.kwargs.get('pk'))
# context['object_list'] = context['object_list'].filter(job_id=context['job_obj'].id)
return context
template - app/viewjobresults.html
{% extends "base.html" %}
{% load render_table from django_tables2 %}
{% render_table table %}
{% endblock %}
tables.py
class JobResultsTable(tables.Table):
job = tables.Column(
accessor='job_id.job_name',
verbose_name='Job')
results = tables.Column(
accessor='results',
verbose_name='Result')
class Meta:
attrs = {"class": "table is-bordered"}
Currently the table rendered is ALL Job objects in a queryset. I have the specific job_obj in my view get_context_data() to filter this, but when i filter context['object_list'] (line hashed out) it still displays the entire list of JobResults. How can I change the queryset given to the table?
You can use the get_table_data() method to modify your queryset.
class ViewJob(LoginRequiredMixin, SingleTableView):
def get_table_data(self):
job_pk = self.request.GET.get('pk')
if job_pk:
return Job.objects.get(pk=job_pk)
else:
return Job.objects.all()
https://django-tables2.readthedocs.io/en/latest/pages/generic-mixins.html
I want to get sum of items i get by using generic date view.
Example views.py
class OrderDayArchiveView(LoginRequiredMixin, generic.dates.DayArchiveView):
queryset = Order.objects.all()
date_field = 'date_ordered'
template_name = 'ordersys/manager/archive_page.html'
Example template:
{% for order in object_list %}
<li class="bg-light">
{{ order.id }}: {{order.print_ordered_items_products_amounts}} (${{order.create_cost}})
</li>
{% endfor %}
Example path in urls.py:
path('<int:year>/<int:month>/<int:day>/', views.OrderDayArchiveView.as_view(month_format='%m'), name="archive_day"),
I want to get sum of all 'order.create_cost' sent to template, is it possible to get filtered queryset in this view? If not, how can i sum it in the template.
You can simply override get_context_data and add your aggregation there:
from django.db.models import Sum
class OrderDayArchiveView(LoginRequiredMixin, generic.dates.DayArchiveView):
queryset = Order.objects.all()
date_field = 'date_ordered'
template_name = 'ordersys/manager/archive_page.html'
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
queryset = context['object_list']
sum_create_cost = queryset.aggregate(Sum('create_cost'))['create_cost__sum']
context['sum_create_cost'] = sum_create_cost
return context
I have a problem when I want to paginate the filter that I create with django_filter, in my template it shows me the query set and filter but paginate does not work, I would like to know why this happens and if you could help me.
I'll insert snippets of my code so you can see.
This is my views.py
PD: i have all the necesary imports.
#method_decorator(staff_member_required, name='dispatch')
class EmployeeListView(ListView):
model = Employee
paginate_by = 4
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('employee.view_employee'):
return redirect(reverse_lazy('home'))
return super(EmployeeListView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = EmployeeFilter(self.request.GET, queryset = self.get_queryset())
return context
filters.py
import django_filters
from .models import Employee, Accident
class EmployeeFilter(django_filters.FilterSet):
class Meta:
model = Employee
fields = {
'rutEmployee' : ['startswith']
}
You should override get_queryset.This means you have to put your filter in get_queryset like this:
#method_decorator(staff_member_required, name='dispatch')
class EmployeeListView(ListView):
model = Employee
paginate_by = 4
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('employee.view_employee'):
return redirect(reverse_lazy('home'))
return super(EmployeeListView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = EmployeeFilter(self.request.GET, queryset = self.get_queryset())
return context
def get_queryset(self):
queryset = super().get_queryset()
return EmployeeFilter(self.request.GET, queryset=queryset).qs
and use object_list instead of filter in employee_list.html like this:
{% for employee in object_list|dictsort:"id" reversed %}
You could also try this:
(A snippet from my source code)
class ModelListView(ListView):
model = YourModel
paginate_by = 4 # Change this if you don't intend to paginate by 4
ordering = model_field_to_order_by
# variable used to know if a match was found for the search made using django_filters
no_search_result = False
def get_queryset(self, **kwargs):
search_results = YourDjangoFiltersForm(self.request.GET, self.queryset)
self.no_search_result = True if not search_results.qs else False
# Returns the default queryset if an empty queryset is returned by the django_filters
# You could as well return just the search result's queryset if you want to
return search_results.qs.distinct() or self.model.objects.all()
def get_query_string(self):
query_string = self.request.META.get("QUERY_STRING", "")
# Get all queries excluding pages from the request's meta
validated_query_string = "&".join([x for x in re.findall(
r"(\w*=\w{1,})", query_string) if not "page=" in x])
# Avoid passing the query path to template if no search result is found using the previous query
return "&" + validated_query_string.lower() if (validated_query_string and not self.no_search_result) else ""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Pass to template if you want to do something whenever an empty queryset is return by django_filters
context["no_search_result"] = self.no_search_result
# This is the query string which should be appended to the current page in your template for pagination, very critical
context["query_string"] = self.get_query_string()
context['filter'] = YourDjangoFiltersForm()
return context
In your html template you need to append the querystring passed to your template from the view, a sample is shown below
{% for i in page_obj.paginator.page_range %}
{% if page_obj.number == i %}
<li class="page-item active" aria-current="page">
<span class="page-link">{{ i }}<span class="sr-only">(current)</span></span>
</li>
{% elif i > page_obj.number|add:'-5' and i < page_obj.number|add:'5' %} <li class="page-item"><a
class="page-link" href="?page={{ i }}{{ query_string }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
I want to create a listview with various optional filters. My url should be look like this:
shop/category_6/?manufacturer_id=3
And it works well when I put this url just in to the browser form. But when i try to add this option to a template it gives me NoReverseMatch. My template:
{% for manufacturer in manufacturer_list %}
<p>{{manufacturer.1}}</p>
{% endfor %}
It is my view:
class ItemListView(ListView):
model = Item
context_object_name = 'item_list'
template_name = 'shop/item_list.html'
def get_queryset(self, **kwargs):
full_item_list=Item.objects.all()
queryset=full_item_list.filter(category__id=int(self.kwargs['category_id']))
manufacturer_id = self.request.GET.get('manufacturer_id')
if manufacturer_id:
queryset=queryset.filter(manufacturer__id=int(manufacturer_id))
return queryset
def get_context_data(self, **kwargs):
context = super(ItemListView, self).get_context_data(**kwargs)
context['category']=get_object_or_404(Category, pk=self.kwargs['category_id'])
context['manufacturer_list'] = self.get_queryset(**kwargs).values_list('manufacturer__id', 'manufacturer__name').distinct().order_by('manufacturer__name')
return context
What is my mistake?
You didn't configure GET params... May be shop/category_6/?manufacturer_id=3
{{manufacturer.1 }}