Slicing pagination in ListView? - django

I have successfully added pagination to a list_contacts view (obviously for a Contact model). And am now trying to setup the template context so that I can limit the pagination display to look like this :
< 1 ... 5 6 7 8 9 ... 42 >
I went from this answer and tried to use a "paginator" template tag, unsuccessfully. The main problem is that I'm using ListView instead of the old object_list, and these two don't set the context the same way.
Here's my view :
from django.views.generic import ListView
from app.models import Contact
list_contacts = ListView.as_view(
model=Contact,
http_method_names = ['get'],
template_name='contacts.html',
context_object_name='contacts',
paginate_by=6
)
And my template "contacts.html" :
<ul class="pagination">
{# Previous page link #}
{% if page_obj.has_previous %}
<li>
«
</li>
{% else %}
<li class="disabled">
«
</li>
{% endif %}
{# First page #}
{% if show_first %}
<li>
1
</li>
<li>...</li>
{% endif %}
{# List of pages (with current "active") #}
{% for page in page_numbers %}
{% ifequal page page_obj.number %}
<li class="active">
{{ page }}
</li>
{% else %}
<li>
{{ page }}
</li>
{% endifequal %}
{% endfor %}
{# Last page #}
{% if show_last %}
<li>...</li>
<li>{{ page_obj.pages }}</li>
{% endif %}
{# Next page link #}
{% if page_obj.has_next %}
<li>
»
</li>
{% else %}
<li class="disabled">
»
</li>
{% endif %}
</ul>
EDIT: here's my view code that ended up working
class ListContactsView(ListView):
model = Contact
http_method_names = ['get']
template_name = 'contacts.html'
context_object_name = 'contacts'
paginate_by = 6
def get_context_data(self, **kwargs):
_super = super(ListContactsView, self)
context = _super.get_context_data(**kwargs)
adjacent_pages = 2
page_number = context['page_obj'].number
num_pages = context['paginator'].num_pages
startPage = max(page_number - adjacent_pages, 1)
if startPage <= 3:
startPage = 1
endPage = page_number + adjacent_pages + 1
if endPage >= num_pages - 1:
endPage = num_pages + 1
page_numbers = [n for n in xrange(startPage, endPage) \
if n > 0 and n <= num_pages]
context.update({
'page_numbers': page_numbers,
'show_first': 1 not in page_numbers,
'show_last': num_pages not in page_numbers,
})
return context
list_contacts = ListContactsView.as_view()

There are different ways to achieve this (or even a combination of some):
Override or extend get_context_data by calling super
Write a templatetag
Write a template filter
Override or extend the PaginationMixin
An example of the first
class MyListView(ListView):
def get_context_data(self, **kwargs):
context = super(MyListView, self).get_context_data(**kwargs)
context['foo'] = 'bar'
return context

Related

How to customise ModelAdmin UI

How does one go about editing the template(s) shown when using wagtail.contrib.modeladmin?
Assuming
class EventsAdmin(ModelAdmin):
menu_label = 'Events'
list_display = ('title',)
Say I want to make the titles clickable, how would I go about doing so?
I figured I could check the templates in wagtail/contrib/modeladmin/templates and add something like <a href="{{ url }}" /> alas the furthest I got to finding that title section was in wagtail/contrib/modeladmin/templates/modeladmin/includes/result_row_value.html where it outputs the title section through the {{ item }} tag
{% load i18n modeladmin_tags %}
{{ item }}{{ url }}{% if add_action_buttons %}
{% if action_buttons %}
<ul class="actions">
{% for button in action_buttons %}
<li>{% include 'modeladmin/includes/button.html' %}</li>
{% endfor %}
</ul>
{% endif %}
{{ closing_tag }}
{% endif %}
#modeladmin_tags.py
#register.inclusion_tag(
"modeladmin/includes/result_row_value.html", takes_context=True)
def result_row_value_display(context, index):
add_action_buttons = False
item = context['item']
closing_tag = mark_safe(item[-5:])
request = context['request']
model_admin = context['view'].model_admin
field_name = model_admin.get_list_display(request)[index]
if field_name == model_admin.get_list_display_add_buttons(request):
add_action_buttons = True
item = mark_safe(item[0:-5])
context.update({
'item': item,
'add_action_buttons': add_action_buttons,
'closing_tag': closing_tag,
})
return context
Going further I don't really understand how the <td> with the title are put in there nor what I can do to make it clickable or customise that template.
A screenshot to illustrate the UI section

Django pagination - How to limit the pages?

I'm using this code to display the pagination on my page
<ul class="pagination pagination-sm">
{% if restaurants.has_previous %}
<li>Prev</li>
{% endif %}
{% for page in restaurants.paginator.page_range %}
<li class="{% if restaurants.number == page %}active{% endif %}">{{ page }}</li>
{% endfor %}
{% if restaurants.has_next %}
<li>Next</li>
{% endif %}
</ul>
The issue is that the code above shows all page like this:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | and so on..
Instead I would like to show something like this:
1 | 2 | 3 | 4 | 5
How can I do that?
This is my view:
def listing(request):
list_restaurants = ShopAccount.objects.filter(is_active=1)
# only active products
paginator = Paginator(list_restaurants, 20) # Show 20 products per page
page = request.GET.get('page')
try:
restaurants = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
restaurants = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
restaurants = paginator.page(paginator.num_pages)
return render_to_response('restaurants/list.html', {'restaurants': restaurants}, context_instance=RequestContext(request))
I use something like this :
<li {% ifequal page current %}class="active"{% endifequal %}>
<a href={% ifequal page -4 %}
"/blog/1/"><<
{% else %}{% ifequal page -3 %}
"/blog/{{ current | add:"-1" }}/"><
{% else %}{% ifequal page -2 %}
"/blog/{{ current | add:"1" }}/">>
{% else %}{% ifequal page -1 %}
"/blog/{{ maximum }}/">>>
{% else %}{% ifequal page 0 %}
"#">...
{% else %}{% ifequal page current %}
"#" class="page_link">{{ page }}
{% else %}
"/blog/{{ page }}/">{{ page }}
{% endifequal %}
{% endifequal %}
{% endifequal %}
{% endifequal %}
{% endifequal %}
{% endifequal %}
</a>
</li>
and these views :
def makepaginator(current, maximum):
"""makepaginator() returns a set of number which reprensents buttons.
-4 means first page.
-3 means previous page.
-2 means next page.
-1 means last page.
0 means a dot.
others means the number of the target page."""
NB_PAGES_LEFT_LEFT = 3
NB_PAGES_RIGHT_RIGHT = NB_PAGES_LEFT_LEFT
NB_PAGES_CENTER_RIGHT = 3
NB_PAGES_CENTER_LEFT = NB_PAGES_CENTER_RIGHT
i = 2
current = int(current)
if current > 1:
p = [-4, -3]
else:
p = []
p.append(1)
while i <= maximum:
if (i > NB_PAGES_LEFT_LEFT and i < maximum - NB_PAGES_RIGHT_RIGHT
and (i - current > NB_PAGES_CENTER_RIGHT
or current - i > NB_PAGES_CENTER_RIGHT + 1)):
p.append(0)
while (i > NB_PAGES_LEFT_LEFT
and i < maximum - NB_PAGES_RIGHT_RIGHT
and (i - current > NB_PAGES_CENTER_RIGHT
or current - i > NB_PAGES_CENTER_LEFT + 1)):
i += 1
else:
p.append(i)
i += 1
if current < maximum:
p.append(-2)
p.append(-1)
return (p)
def news(request, page_num=1):
paginator = Paginator(Article.objects.all().order_by('-date'), settings.ARTICLES_PER_PAGE)
try :
page = paginator.page(page_num)
print(type(page_num))
return (render(request, "main/blog.html",
{"paginator" : paginator, "page_num" : int(page_num),
"page" : page,
"buttons" : makepaginator(page_num,
paginator.num_pages)}))
except (PageNotAnInteger, EmptyPage) as e:
return (notfound(request))
With bootstrap I get this :
It may look dirty but it works with my website. You can try it.
If you have some improvements to suggests, I would be glad though. :)
ShopAccount.objects.filter(is_active=1)[:5]
Try this code above, or use the [:5] on the paginator.
I solved this issue with JS.
Here the views:
class AllOrders(LoginRequiredMixin, ListView):
model = Order
template_name = 'all_orders.html'
paginate_by = 20
def get_queryset(self):
filter_val = self.request.GET.get("filter", "")
order_by = self.request.GET.get("orderby", "id")
if filter_val != "":
orders = Order.objects.filter(Q(transaction_id__contains=filter_val) | Q(customer_id__auth_user_id__first_name__contains=filter_val) |
Q(customer_id__auth_user_id__last_name__contains=filter_val)).order_by(order_by)
else:
orders = Order.objects.all().order_by(order_by)
return orders
def get_context_data(self, **kwargs):
context = super(AllOrders, self).get_context_data(**kwargs)
context["filter"] = self.request.GET.get("filter", "")
context["orderby"] = self.request.GET.get("orderby", "id")
context["all_table_fields"] = Order._meta.get_fields()
return context
Here the template:
<nav class="d-inline-block">
<ul class="pagination mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="{% url 'custom_user:all_orders' %}?filter={{ filter }}&orderby={{ orderby }}&page={{ page_obj.previous_page_number }}" tabindex="-1"><i class="fas fa-chevron-left"></i></a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1"><i class="fas fa-chevron-left"></i></a>
</li>
{% endif %}
{% for i in paginator.page_range %}
<li class="page-item {% if i == page_obj.number %}active{% endif %}"><a class="page-link" href="{% url 'custom_user:all_orders' %}?filter={{ filter }}&orderby={{ orderby }}&page={{ i }}">{{ i }} <span class="sr-only">(current)</span></a></li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="{% url 'custom_user:all_orders' %}?filter={{ filter }}&orderby={{ orderby }}&page={{ page_obj.next_page_number }}"><i class="fas fa-chevron-right"></i></a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#"><i class="fas fa-chevron-right"></i></a>
</li>
{% endif %}
</ul>
</nav>
Here the JS:
<script>
let pages = document.getElementsByClassName('page-item');
let pageActive;
if (pages.length > 5) {
for (let i = 0; i < pages.length; i++) {
if (pages[i].classList.contains('active')){
pageActive = i;
break;
}
}
for (let j = 0; j < pages.length; j++) {
if ( j != 0 && j != 1 && j < (pageActive - 1)) {
pages[j].setAttribute('hidden', true);
} else if (j != (pages.length - 1) && j != (pages.length - 2) && j > (pageActive + 1)) {
pages[j].setAttribute('hidden', true);
}
};
};
</script>
This is the result:
paginator

How to iterate dictionary objects in template

Code
engine.py ==>
class YearGroupCount():
def __init__(self, year, count, months):
self.year = year
self.count = count
self.months = months
class MonthGroupCount():
def __init__(self, month, count):
self.month = month
self.month_name = calendar.month_name[month]
self.count = count
class BlogCountsEngine():
def __init__(self):
self.year_group_counts = {}
def _add_date(self, year, month):
if str(year) in self.year_group_counts:
year_obj = self.year_group_counts[str(year)]
else:
year_obj = YearGroupCount(year, 0, {})
year_obj.count += 1
if str(month) in year_obj.months:
month_obj = year_obj.months[str(month)]
else:
month_obj = MonthGroupCount(month, 0)
month_obj.count += 1
year_obj.months[str(month)] = month_obj
self.year_group_counts[str(year)] = year_obj
def get_calculated_blog_count_list(self):
if not Blog.objects.count():
retval = {}
else:
for blog in Blog.objects.all().order_by('-posted'):
self._add_date(blog.posted.year, blog.posted.month)
retval = self.year_group_counts
return retval
views.py ==>
def outer_cover(request):
archives = BlogCountsEngine().get_calculated_blog_count_list()
retdict = {
'categories': Category.objects.all(),
'posts': posts,
'archives': archives,
}
return render_to_response('blog/blog_list.html', retdict, context_instance=RequestContext(request))
template html ==>
<div class="well">
<h4>Arşivler</h4>
<ul>
{% if archives %}
{% for y_key, yr in archives %}
<li>{{ yr.year }} ({{ yr.count }})</li>
{% for m_key,mth in yr.months %}
<li> - {{ mth.month_name }} ({{ mth.count }})</li>
{% endfor %}
{% endfor %}
{% endif %}
</ul>
</div>
Question:
I am building my own blog with django. I want to iterate in archives to show them in main page but i cannot reach instances' attributes in dictionary
When i run the code over in here the result html is ==>
<div class="well">
<h4>Arşivler</h4>
<ul>
<li> ()</li>
</ul>
</div>
What am i missing or doing wrong?
You can use dict.items method. In python 2.x better use dict.iteritems instead.
{% for y_key, yr in archives.items %}
<li>{{ yr.year }} ({{ yr.count }})</li>
{% for m_key, mth in yr.months.items %}
<li> - {{ mth.month_name }} ({{ mth.count }})</li>
{% endfor %}
{% endfor %}

Get current URL kwargs in the template?

I'm accomplishing this at the moment by doing:
context['card_type'] = self.kwargs['card_type']
context['role_line'] = self.kwargs['role_line']
context['sort_by'] = self.kwargs['sort_by']
Which seems crazy counter intuitive to me.
Say if i was already at the urls of players/one/two/three
Is there an already prebuilt way to get current kwargs of one, two & three for use in templates?
Edit
urls.py
urlpatterns = patterns('',
url(
r'^$',
NationListView.as_view(),
name='index'
),
url(
r'^(?P<slug>[a-z-]*)/$',
NationDetailView.as_view(),
name='nation'
),
url(
r'^(?P<slug>[a-z-]*)/(?P<card_type>[a-z]*)/(?P<role_line>[a-z]*)/(?P<sort_by>[a-z0-9]*)/$',
NationDetailFilteredView.as_view(),
name='nation_filter'
),
)
The mixin that builds the context
class CoreDetailFilteredMixin(object):
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(CoreDetailFilteredMixin, self).get_context_data(**kwargs)
base_objects(context)
# Pull all the players that belong to the object_type
context['players'] = Player.objects.filter(
**{filters: context['object'].asset_id}
)
# Define the available card types
card_types = {
'if': {'card_type__gte': 2},
'gold': {'overall_rating__gte': 75},
'silver': {'overall_rating__range': (65, 74)},
'bronze': {'overall_rating__lte': 64}
}
# Check if the given card type is in the dictionary because 'all' throws an KeyError
if self.kwargs['card_type'] in card_types:
context['players'] = context['players'].filter(**card_types[self.kwargs['card_type']])
# Don't show inform cards for specific colour card types
if self.kwargs['card_type'] not in ['if', 'all']:
context['players'] = context['players'].exclude(card_type__gte=2)
# Define available role lines
role_lines = {
'att': 3,
'mid': 2,
'def': 1,
'gk': 0
}
# Check if the given role line is in the dictionary because 'all' throws an KeyError
if self.kwargs['role_line'] in role_lines:
context['players'] = context['players'].filter(role_line=role_lines[self.kwargs['role_line']])
# Define the available sort by keys
sorts = {
'ovr': 'overall_rating',
'att1': 'card_att1',
'att2': 'card_att2',
'att3': 'card_att3',
'att4': 'card_att4',
'att5': 'card_att5',
'att6': 'card_att6r'
}
# Add a descending order to the existing queryset
context['players'] = context['players'].order_by('-' + sorts[self.kwargs['sort_by']])
# Create pagination
cbv_pagination(self, context, context['players'], 28, 'players')
context['card_type'] = self.kwargs['card_type']
context['role_line'] = self.kwargs['role_line']
context['sort_by'] = self.kwargs['sort_by']
return context
How i use it currently in the template
{% with object.get_class_name|add:'s'|add:':'|add:object.get_class_name|add:'_filter'|lower as url_string %}
<dl class="sub-nav">
<dt>Card Type:</dt>
{% with 'all if gold silver bronze' as card_types %}
{% for ct in card_types.split %}
{% cycle 'All' 'Inform' 'Gold' 'Silver' 'Bronze' as card_type_name silent %}
{% if card_type == ct %}
<dd class="active">
{% else %}
<dd>
{% endif %}
<a href="{% url url_string object.slug ct role_line|default:'all' sort_by|default:'ovr' %}">
{{ card_type_name }}
</a>
</dd>
{% endfor %}
{% endwith %}
</dl>
<dl class="sub-nav">
<dt>Role Line:</dt>
{% with 'all att mid def gk' as role_lines %}
{% for rl in role_lines.split %}
{% cycle 'All' 'Attackers' 'Midfielders' 'Defenders' 'Goalkeepers' as role_lines_name silent %}
{% if role_line == rl %}
<dd class="active">
{% else %}
<dd>
{% endif %}
<a href="{% url url_string object.slug card_type|default:'all' rl sort_by|default:'ovr' %}">
{{ role_lines_name }}
</a>
</dd>
{% endfor %}
{% endwith %}
</dl>
<dl class="sub-nav">
<dt>Sort By:</dt>
{% with 'ovr att1 att2 att3 att4 att5 att6' as sorts %}
{% for sort in sorts.split %}
{% ifequal role_line 'gk' %}
{% cycle 'Overall' 'Diving' 'Handling' 'Kicking' 'Reflexes' 'Speed' 'Positioning' as sorts_name silent %}
{% else %}
{% cycle 'Overall' 'Pace' 'Shooting' 'Passing' 'Dribbling' 'Defending' 'Heading' as sorts_name silent %}
{% endifequal %}
{% if sort_by == sort %}
<dd class="active">
{% else %}
<dd>
{% endif %}
<a href="{% url url_string object.slug card_type|default:'all' role_line|default:'all' sort %}">
{{ sorts_name }}
</a>
</dd>
{% endfor %}
{% endwith %}
</dl>
{% endwith %}
As I learnt here, the view is already passed to the context (for class-based views). So you can do the following in the template without needing to explicitly pass the kwargs from the view:
{{ view.kwargs.card_type }}
{{ view.kwargs.role_line }}
{{ view.kwargs.sort_by }}
You can use dict.update():
update([other])
Update the dictionary with the key/value pairs from other, overwriting existing keys.
context.update(self.kwargs)

Django Conflict between 2 pagination

I'm trying to display all the data assoicated to every person and every house on an single template each with pagination.
The problem is everytime I view the entries of a particular data using pagination example person. The other pagination called house get reseted.
For example if I am on page 3 for house and I try to view other entries for page person . The house pagination will get reset back to 1. How do I fix this pagination conflict?
models
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100, blank=True)
def __unicode__(self):
return self.name
class House(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
views
def Display(request):
user= User.objects.get(username=request.user)
comment = Person.objects.get(user=user)
posts = House.objects.filter(user=user)
paginator = Paginator(comment, 5)
try: n = int(request.GET.get("n", '1'))
except ValueError: page = 1
try:
comment = paginator.page(n)
except (InvalidPage, EmptyPage):
comment = paginator.page(paginator.num_pages)
paginator = Paginator(posts, 5)
try: page = int(request.GET.get("page", '1'))
except ValueError: page = 1
try:
posts = paginator.page(page)
except (InvalidPage, EmptyPage):
posts = paginator.page(paginator.num_pages)
return render(request,'display.html',{'posts':posts,'comment':comment})
HTML
{% for p in posts.object_list %}
{{p.name}}
{% endfor %}
{% if posts.object_list and posts.paginator.num_pages > 1 %}
<div class="pagination" style="margin-top: 20px; margin-left: -20px; ">
Page {{ posts.number }} of {{ posts.paginator.num_pages }}<br>
{% if posts.has_previous %}
<a class="Link" href= "{% if formula %}?text={{formula}}&{% else %}?{% endif %}page={{ posts.previous_page_number }}">newer entries << </a>
{% endif %}
{% if posts.has_next %}
<a class="Link" href="{% if formula %}?text={{formula}}&{% else %}?{% endif %}page={{ posts.next_page_number }}"> >> older entries</a>
{% endif %}
</span>
</div>
{% endif %}
{% for c in comment.object_list %}
{{c.name}}
{% endfor %}
{% if comment.object_list and comment.paginator.num_pages > 1 %}
Page {{ comment.number }} of {{ comment.paginator.num_pages }}
{% if comment.has_previous %}
<a class="Link" href= "?n={{ comment.previous_page_number }}">newer entries << </a>
{% endif %}<br>
{% if comment.has_next %}
<a class="Link" href="?n={{ comment.next_page_number }}"> >> older entries</a>
{% endif %}
</span>
</div>
{% endif %
First off, your variable names versus how you describe everything is quite confusing.
The issue is you are using two different variables for the page: n for person (or comment) and page for house (or posts). In each of your <a href=""> you need to set both n and page not just one. So:
<a class="Link" href= "{% if formula %}?text={{formula}}&{% else %}?{% endif %}page={{ posts.previous_page_number }}&n={{ comment.number }}">newer entries << </a>
<a class="Link" href="{% if formula %}?text={{formula}}&{% else %}?{% endif %}page={{ posts.next_page_number }}&n={{ comment.number }}"> >> older entries</a>
<a class="Link" href= "?n={{ comment.previous_page_number }}&page={{ posts.number }}">newer entries << </a>
<a class="Link" href="?n={{ comment.next_page_number }}&page={{ posts.number }}"> >> older entries</a>
In the same view you change the value of paginator two times
paginator = Paginator(comment, 5)
# here you have code that does stuff and some lines below
paginator = Paginator(posts, 5)
if you want two paginators in the same view you should also name them differently (and add them to your context variables)
Something I can suggest is to have an ajax pagination view that you're going to call to switch between pages in your templates. In this way, you can change page to each resultset separately without needing to reload the entire view (and probably loosing your pagination state)