I have implemented a search bar and function into a django web app in the following view:
def search_products(request):
search_query = request.GET.get('search', '')
products = Product.objects.filter(Q(name__icontains=search_query) | Q(brand__icontains=search_query))
paginator = Paginator(products, 40)
page_number = request.GET.get('page', 1)
page = paginator.get_page(page_number)
if page.has_next():
next_url = f'?page={page.next_page_number()}'
else:
next_url = ''
if page.has_previous():
prev_url = f'?page={page.previous_page_number()}'
else:
prev_url = ''
return render(request, 'store/search_products.html',
context={'products': page.object_list, 'page': page, 'next_page_url': next_url,
'prev_page_url': prev_url})
The URLs are setup as follows:
urlpatterns = [
path('', views.store_view, name='store'),
path('wishlist/', views.wishlist_view, name='wishlist'),
path('update_item/', views.updateItem, name='update_item'),
path('search_products/', views.search_products, name='search_products'),
]
And the HTML for the search results is as follows:
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item {% if not prev_page_url %} disabled {% endif %}">
<a class="page-link" href="{{ prev_page_url }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% for n in page.paginator.page_range %}
{% if page.number == n %}
<li class="page-item active" aria-current="page">
<a class="page-link" href="?page={{ n }}">{{ n }}
<span class="sr-only"></span>
</a></li>
{% elif n > page.number|add:-4 and n < page.number|add:4 %}
<li class="page-item">
<a class="page-link" href="?page={{ n }}">{{ n }}</a>
</li>
{% endif %}
{% endfor %}
<li class="page-item {% if not next_page_url %} disabled {% endif %}">
<a class="page-link" href="{{ next_page_url }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
I think the issue may be due to the fact that the search query enters the URL before the page number (?page={{ n }}), this may be wrong however. I am not sure how to fix this or implement this change. Any help would be appreciated.
The reason that your pagination is not working is that when you click on another page link the page query parameter replaces the search query parameter and your search results will be gone.
The solution is to to append page query parameter to search query parameter it will be some thing like this for example: url/?search=example&page=2
This is how you can do that:
Inside your app directory create a folder called templatetags
Inside the templatetags folder create a file called pagination_tags.py(name is up to you)
# Your template tag in app_name/templatetags/pagination_tags.py
from django import template
from urllib.parse import urlencode
register = template.Library()
#register.simple_tag
def url_replace (request, field, value):
dict_ = request.GET.copy()
dict_[field] = value
return dict_.urlencode()
After creating your template tag load it in your html page and use it
<!-- load your templatetag in your html file -->
<!-- you should load your .py file -->
{% load pagination_tags %}
<!-- now you can use it -->
<!-- Here is an Example to know how to use it -->
<a class="page-link text-dark" href="?{% url_replace request 'page' 1 %}">1</a>
<!-- first param is request , second param is is your lookup field and third param is your page number -->
Related
I’m trying to create a next and previous button on a single(detail) page. I don’t understand how to make this happen. These are my codes
views.py
def playsong(request, id,slug, tag_slug=None):
beat = Number_beat.objects.get(created_beats=id)
album = Album.objects.get(album_id= id)
albu = Album_beats.objects.filter(album_name= id)
bea = Beats.objects.all()
nextbeat = Number_beat.objects.filter(id__gt = beat.id).order_by(‘id’).first() #.values(‘id’)[:1]
lastbeat = Number_beat.objects.filter(id__lt= beat.id).order_by(‘id’).last()
tag = None
if tag_slug:
tag = get_object_or_404(Tag,slug=tag_slug)
beat=beat.filter(tags__in=[tag])
context={
'beat':beat,
'nextbeat':nextbeat,
'lastbeat':lastbeat,
'album':album,
'albu':albu, }
return render(request, 'music/playsong.html', context)
html
{% if lastbeat %}
<li class="page-item">
<a class="page-link" href="{% url 'playsong' lastbeat %}?{{ request.GET.urlencode }}" id="pred">{{ lastbeat }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="{% url 'index' %}" id="pred">home</a>
</li>
{% endif %}
{% if nextbeat %}
<li class="page-item">
<a class="page-link" href="{% url 'playsong' %}?next={{ nextbeat|urlencode }}" id="predj">{{nextbeat}}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="{% url 'index' %}" id="pred">home</a>
</li>
{% endif %}
please can anyone help on can I do
my goal is to have a 'user_profile' page that displays relevant information of the user of interest.
Furthermore, the 'user_profile' page should include all the posts that were created by the respective user as new blog entries.
These posts, however, should be filterable with the application 'django-filter' and be paginated. At the moment I have difficulties to paginate the filtered posts. So my question is how to achieve the latter?
So far, I used following approach:
filters.py
import django_filters
class AccountPostFilter(django_filters.FilterSet):
title = django_filters.CharFilter(lookup_expr='icontains')
category = django_filters.ChoiceFilter(choices=cat_list)
class Meta:
model = Post
fields = ['title', 'category']
views.py
class UserProfile(DetailView, MultipleObjectMixin):
model = Account
template_name = 'account/user_profile.html'
paginate_by = 5
def get_context_data(self, **kwargs):
posts = Post.objects.all().filter(author=self.kwargs['pk'])
context = super().get_context_data(object_list=posts, **kwargs)
context['filterset'] = AccountPostFilter(self.request.GET, queryset=posts)
return context
Thank you very much for your time. Best wishes,
Daniel
There is another way of doing this, and do it in a clean and professional way, which will save you the trouble of using Django Filters:
Create a helper function called clean_filters (This will help you clean filters that come in from the brownser:
def clean_filters(filters):
filters = {k: v for k, v in filters.items() if v}
return filters
Create another help function called search (this will help you get the parameters from the GET request and put them in a **filters inside the django filter directive. And return them back with the paginator so you can keep the same filters when moving from page to page):
from 'your_utils_file' import clean_filters
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def search(request):
filters = {
"account__first_name__icontains": request.GET.get("fname_kw"), # put your filters here
"account__last_name__icontains": request.GET.get("lname_kw"), # put your filters here
}
html_queries = {
"fname_kw": request.GET.get("fname_kw"),
"lname_kw": request.GET.get("lname_kw"),
}
filters = clean_filters(filters)
html_queries = clean_filters(html_queries)
posts = Post.objects.filter(**filters) # put your model here
page = request.GET.get('page', 1)
paginator = Paginator(posts, 8)
try:
posts= paginator.page(page)
except PageNotAnInteger:
posts= paginator.page(1)
except EmptyPage:
posts= paginator.page(paginator.num_pages)
return posts
Here is your view (this simply calls the search function to reduce the code of your view and make it easy for your code to maintain):
def search_page(request):
posts = search(request)
if posts is not None:
context = {
'posts': posts,
}
return render(request, "core/index.html", context)
return redirect("index")
Here is your HTML (just a classic pagination code for Django and Bootstrap. This also has the filter and the value of the filter in a loop inside the GET request):
<div class="mb-5">
{% if posts.has_other_pages %}
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
{% if posts.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ posts.previous_page_number }}{% for fil, fil_value in filters.items %}&{{fil}}={{fil_value}}{% endfor %}" tabindex="-1">
<i class="fa fa-angle-left"></i>
<span class="sr-only">Prev</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="javascript:void(0)" tabindex="-1">
<i class="fa fa-angle-left"></i>
<span class="sr-only">Prev</span>
</a>
</li>
{% endif %}
{% for i in posts.paginator.page_range %}
{% if posts.number == i %}
<li class="page-item active"><a class="page-link" href="javascript:void(0)">{{ i }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ i }}{% for fil, fil_value in filters.items %}&{{fil}}={{fil_value}}{% endfor %}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ posts.next_page_number }}{% for fil, fil_value in filters.items %}&{{fil}}={{fil_value}}{% endfor %}">
<i class="fa fa-angle-right"></i>
<span class="sr-only">Next</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="javascript:void(0)">
<i class="fa fa-angle-right"></i>
<span class="sr-only">Next</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
implemented sorting and pagination on the product page, but sorting gets confused when moving to the next page. How do I apply a filter to all pagination pages?and i got smth weird with dropdown selection: after moving to next pages it starts act like crazy,after choosing a filter and refreshing the page, it returns to the default value and seems to be changing places (exmp: $$-$ -> $-$$ and vice versa , and i must guess who is who :) )
template
<div class="product-sorting d-flex">
<p>Sort by:</p>
<form name="selectForm" action="{% url 'shop' %}" method="get">
<label for="orderby"></label>
<select name="orderby" id="orderby" onchange="selectForm.submit();">
<option value="price">price: $$ - $</option>
<option value="-price">price: $ - $$</option>
</select>
<input type="submit" class="d-none" value="submit">
</form>
</div>
views
class Shop(ListView):
template_name = 'essense/shop.html'
context_object_name = 'items'
paginate_by = 9
allow_empty = False
model = Item
def get_context_data(self, *, object_list=None, **kwargs):
***context***
def get_ordering(self):
return self.request.GET.get('orderby', )
pagination
{% if page_obj.has_other_pages %}
</div>
<nav aria-label="navigation">
<ul class="pagination mt-50 mb-70">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link"
href="?page={{ page_obj.previous_page_number }}"><i
class="fa fa-angle-left"></i></a>
</li>
{% endif %}
{% for p in page_obj.paginator.page_range %}
{% if page_obj.number == p %}
<li class="page-item"><a class="page-link" href="#">{{ p }}</a></li>
{% elif p > page_obj.number|add:-3 and p < page_obj.number|add:+3 %}
<li class="page-item"><a class="page-link" href="?page={{ p }}">{{ p }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link"
href="?page={{ page_obj.next_page_number }}"><i
class="fa fa-angle-right"></i></a></li>
{% endif %}
</ul>
</nav>
</div>
</div>
{% endif %}
You are only passing the new page number with the pagination link, so any other parameters that may have been involved in filtering will get lost on the new page.
You need a way to maintain the extra GET params. One way to do this might be a template tag that you can pass the new page number to;
project/templatetags/pagination_tags.py:
from django import template
register = template.Library()
#register.simple_tag
def url_replace(request, field, value):
d = request.GET.copy()
d[field] = value
return d.urlencode()
Then in your template the next (and previous) links would use that tag:
{% load pagination_tags %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% url_replace request 'page' page_obj.next_page_number %}">
<i class="fa fa-angle-right"></i>
</a>
</li>
{% endif %}
What can I do when my pagination breaks after first page when I specify the keyword in a form for search results? When I'am moving over pages without any filters, everything works perfectly fine. I had the same problem in my Django project, but I fixed it by creating a custom template tag, it was hard tho and now I can't manage to find any solution for my Flask App. Any ideas? Maybe modifying url?
1st page when "jquery" keyword is specified:
2nd page - completly breaks pagination and search result:
VIEWS.PY
#app.route('/cve_list', methods=['GET', 'POST'])
def cve_list():
search_form = SearchForm()
page = request.args.get('page', 1, type=int)
cve = CVE.query.order_by(CVE.date.desc()).paginate(page=page, per_page=2)
if search_form.validate_on_submit(): # = when search_form was submitted
form_data = search_form.search.data
query = CVE.query.filter(CVE.content.contains(f'{form_data}')).paginate(page=page, per_page=2)
cve_amount = CVE.query.filter(CVE.content.contains(f'{form_data}')).count()
if cve_amount > 0:
flash(f'Found {cve_amount} vulnerabilities based on your search - {form_data}', 'success')
else:
flash(f'No vulnerabilities found based on your search - {form_data}', 'warning')
return render_template('cve_list.html',
title=f'Cve{form_data}',
form=search_form,
cve=query,
cve_amount=cve_amount)
return render_template('cve_list.html',
title='Cve List',
form=search_form,
cve=cve)
HTML
{% for cve in cve.items %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2">{{ cve.title }}</a>
<small class="text-muted">{{ cve.date }}</small>
</div>
<p class="article-content">{{ cve.content }}</p>
</div>
</article>
{% endfor %}
<!-- PAGINATION -->
{% for page_num in cve.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}
{% if page_num %}
{% if cve.page == page_num %}
<a class="btn btn-info mb-4" href="{{ url_for('cve_list', page=page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-info mb-4" href="{{ url_for('cve_list', page=page_num) }}">{{ page_num }}</a>
{% endif %}
{% else %}
{% endif %}
{% endfor %}
<!-- PAGINATION -->
I am trying to change the language of the website when users click a button in Django.
I have a base project and the urls are:
urlpatterns += i18n_patterns(
# Ecommerce is the app where I want to change the language
url(r'^', include("ecommerce.urls")),
)
The url inside Ecommerce.urls is:
urlpatterns = [
url(r'^testing/$', views.test, name='url_testing'),
... other urls
]
When I visit the url above, I first go to: http://localhost/en/testing/.
I want to set a link Change Language so that when users click it, it will change language to http://localhost/zh-hans/testing/. How do I do this in my template?
EDIT
I can now change the language using the following code but the problem is that it only works once:
<form id="languageForm" action="/i18n/setlang/" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{% url 'url_testing' %}" />
<input id="newLanguageInput" type="hidden" name="language"/>
</form>
And my links are:
<li><a onclick="changeLanguage('zh-hans')">简体</a></li>
<li><a onclick="changeLanguage('zh-hant')">繁體</a></li>
The function changeLanguage is defined like:
function changeLanguage(newLanguage) {
$('input[name="newLanguageInput"]').val(newLanguage);
$('#languageForm').submit();
}
The code works when I first click any of the 2 links, and I will be redirected to the url http://localhost/zh-hans/testing/ or http://localhost/zh-hant/testing/. The problem is after I change the language once, it no longer changes. Is there something wrong with my submit?
Actually it's not going to be a simple <a> link but a <form>.
Have a read on how to set_language redirect view. This form will be responsible for changing languages. It's easy as a pie.
Make sure you have set some LANGUAGES first.
You can change the language of the website when users click a link (no url translation, no post) like this:
navigation.html (with bootstrap4 and font awesome)
<li class="nav-item dropdown">
{% get_current_language as LANGUAGE_CODE %}
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown">{{ LANGUAGE_CODE }}</a>
<div class="dropdown-menu dropdown-menu-right">
{% get_available_languages as languages %}
{% for lang_code, lang_name in languages %}
<a href="{% url 'main:activate_language' lang_code %}" class="dropdown-item">
{% if lang_code == LANGUAGE_CODE %}
<i class="fas fa-check-circle"></i>
{% else %}
<i class="far fa-circle"></i>
{% endif %}
{{ lang_name }} ({{ lang_code }})
</a>
{% endfor %}
</div>
</li>
views.py
from django.shortcuts import redirect
from django.utils import translation
from django.views.generic.base import View
class ActivateLanguageView(View):
language_code = ''
redirect_to = ''
def get(self, request, *args, **kwargs):
self.redirect_to = request.META.get('HTTP_REFERER')
self.language_code = kwargs.get('language_code')
translation.activate(self.language_code)
request.session[translation.LANGUAGE_SESSION_KEY] = self.language_code
return redirect(self.redirect_to)
urls.py
from django.urls import path
from .views import ActivateLanguageView
app_name = 'main'
urlpatterns = [
path('language/activate/<language_code>/', ActivateLanguageView.as_view(), name='activate_language'),
]
It's work for me.
New snippets compatible with new API (Boostrap 5 and Django >= 4.0) updated from the excellent #Boris Đurkan answer.
Django has dropped the translation.LANGUAGE_SESSION_KEY support.
So basically we need to make this setup using the session cookie.
class ActivateLanguageView(View):
def get(self, request, lang, **kwargs):
url = request.META.get('HTTP_REFERER', '/')
translation.activate(lang)
response = HttpResponseRedirect(url)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
return response
Boostrap has changed its jQuery binding for dropdown:
<li class="nav-item dropdown">
{% get_current_language as LANGUAGE_CODE %}
<a class="nav-link dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false">
<strong>{{ LANGUAGE_CODE }}</strong>
</a>
<div class="dropdown-menu dropdown-menu-right">
{% get_available_languages as languages %}
{% for lang_code, lang_name in languages %}
<a href="{% url 'activate_language' lang_code %}" class="dropdown-item">
{% if lang_code == LANGUAGE_CODE %}
<i class="bi bi-check-circle"></i>
{% else %}
<i class="bi bi-circle"></i>
{% endif %}
{{ lang_name }} ({{ lang_code }})
</a>
{% endfor %}
</div>
</li>
Other parts of code remains basically the same.