Django - inclusion tag - python

In my html, there is a div containing all the comments. I need to restrict the amount of comments to be shown. So, I created an inclusion tag:
<div class="past_comments">
{% limit_amount_in_a_page page_nr=page_nr topic_id=topic.id amount=4 %}
</div>
#register.inclusion_tag('post/comment_block.html')
def limit_amount_in_a_page(page_nr, topic_id=1, amount=5):
topic = get_object_or_404(Topic, id=topic_id)
comments = Comment.objects.filter(topic=topic)
selected_comments = []
starting_index = page_nr*amount
for index in range(starting_index, starting_index + amount):
if index >= len(comments):
break;
selected_comments.append(comments[index])
return {
'topic': topic,
'page_nr': int(page_nr),
'selected_comments': selected_comments,
'amount_comments': comments.all().count(),
'limit_amount_comment': amount,
}
so, this tag shows the code from comment_block.html
{% load static %}
{% load post_tags %}
{% load post_filters %}
{% for comment in selected_comments %}
<div class="comment_body">
<div class="user_info_block">
<div class="content">
<div class="photo_profile"></div>
<div class="user_info"></div>
</div>
</div>
<div class="content_block">
<p>{{comment.content}}</p>
</div>
</div>
{% endfor %}
topic id is {{topic.id}}
page nr is {{page_nr}}<br>
comment amount is {{amount_comments}}<br>
limit amount is {{limit_amount_comment}}<br>
{% if page_nr != 0 %}
Previous Page
{% endif %}
{%page_not_over_amount page_nr amount_comments limit_amount_comment%}
{% if comment_not_over_amount %}
Next Page
{% endif %}
As you can see, I created another tag page_not_over_amount after all comments are shown to hide the link directing to the next page if all comment already been shown
#register.inclusion_tag('post/comment_block.html')
def page_not_over_amount(page_nr, comment_amount, comment_limit):
result = page_nr * comment_limit < comment_amount - comment_limit
return {'comment_not_over_amount': result}
However, I found my tag page_not_over_amount didn't ready any variable from the comment_block.html (After I get the exception, I checked the local variables. All their values are "").
I guess the possible cause of this is both tags are redirecting to the same html file. After the second tag redirects, it refreshs all the variables in the page.
Should I call a context variable?

Okay, so it seems like you're going for a basic pagination scheme. First things first, you should look at the built-in solution in Django. You should definitely take an hour and try and make that work.
Django's a heavyweight framework that has a built-in way of doing most things, and libraries for everything else. As a general rule, if you find yourself doing a common task in a convoluted way, there's probably a stock solution out there.
If you decide you want to do it the quick and dirty way, how about calculating has_next_page and has_prev_page in limit_amount_in_a_page?
#register.inclusion_tag('post/comment_block.html')
def limit_amount_in_a_page(page_nr, topic_id=1, amount=5):
topic = get_object_or_404(Topic, id=topic_id)
comments = Comment.objects.filter(topic=topic)
num_comments = comments.count()
selected_comments = comments[page_nr*amount, (page_nr + 1)*amount]
has_prev = (page_nr != 0)
has_next = ((page_nr + 1) * amount) < num_comments
return {
'topic': topic,
'page_nr': int(page_nr),
'selected_comments': selected_comments,
'has_next' : has_next,
'has_prev' : has_prev,
'amount_comments' : num_comments,
}
Also, note the use of slices to select comments.
But again, I highly recommend checking out the Paginator class in Django. Reading through the docs and doing the tutorial is a great thing to do as well, if you have not yet. Skimming through all the docs gives you an idea of what is built-in, which saves a lot of time in the long run.

Related

Improve the function of a template tag - Django

I am programming a star rating system, to calculate the stars graphically of a course, I used the following template tag:
#register.simple_tag
def get_course_rating(course):
decimal, integer = math.modf(course.get_average_rating())
rating_stars = {
'stars': range(int(integer))
}
if decimal > 0:
rating_stars['star_half_empty'] = True
rating_stars['stars_empty'] = range((5 - int(integer)) - 1)
else:
rating_stars['star_half_empty'] = False
rating_stars['stars_empty'] = range(5 - int(integer))
return rating_stars
In this way I put the star rating in the HTML:
{% load course_tags %}
{% get_course_rating course as rating %}
{% for i in rating.stars %}
<i class="icon-star"></i>
{% endfor %}
{% if rating.star_half_empty %}
<i class="icon-star-half-alt"></i>
{% endif %}
{% for i in rating.stars_empty %}
<i class="icon-star-empty"></i>
{% endfor %}
I find it very tedious to do that, so I wonder if there is a way to improve it?
In the sense of the HTML.
I use this html code about 3 times and for me it's repetitive code and I don't want that ...
It occurs to me that the template tag returns html code but I am not sure if it is of good practice ...
Any much more professional solution? It will help my learning a lot ...
You can have this "start rating html" as separate template and reuse it with custom inclusion tag.
Another less dynamic option - reuse with include in templates.
Difference nicely described here.

Pagination when search and results are on the same page

I have a template where the users pass a query and select a couple of checkboxes (these can range from 1-100). Then, my view does the following:
def search(request):
results_list = search(request.GET.get("q", ""), request.GET.getlist("c"))
# Pagination
paginator = Paginator(results_list, 10)
page = request.GET.get("page")
results = paginator.get_page(page)
return render(
request,
"web/search/show.html",
{
"query": query,
"results": results,
},
)
The issue arrises because both the search and the presentation of the results happen on the same page. Therefore, when I want to include pagination, in my template, I have to do the following:
<div class="pagination">
<div class="step-links">
{% if results.has_previous %}
Previous
{% endif %}
{% if results %}
<span class="current">
Page {{ results.number }} of {{ results.paginator.num_pages }}
</span>
{% endif %}
{% if results.has_next %}
Next
{% endif %}
</div>
</div>
Please consider the usage of request.GET.urlencode because when the user wants to either go to the previous or next page the same query and checkboxes need to be passed. However, this creates a bug when the user goes past the second page, because the ?page=3&page=2 keep on piling up. Can someone point me in the right direction for solving this issue?
An easy solution would be to repeat every GET parameter you need to handle in the link rather than use the full querystring, something like:
Next
A more elegant solution would be to create a Django template tag to build the URL based on the current state of the querystring, something like what is described in this article: Dealing With QueryString Parameters.

How to get the current URL linkable within a Django template? [duplicate]

Some solutions provided on doing a Google search for "Django breadcrumbs" include using templates and block.super, basically just extending the base blocks and adding the current page to it. http://www.martin-geber.com/thought/2007/10/25/breadcrumbs-django-templates/
http://www.djangosnippets.org/snippets/1289/ - provides a template tag but I'm not sure this would work if you don't have your urls.py properly declared.
I'm wondering what's the best way? And if you have implemented breadcrumbs before how did you do it?
--- Edit --
My question was meant to be: is there a general accepted method of doing breadcrumbs in Django, but from the answers I see there is not, and there are many different solutions, I'm not sure who to award the correct answer to, as I used a variation of using the block.super method, while all the below answers would work.
I guess then this is too much of a subjective question.
Note: I provide the full snippet below, since djangosnippets has been finicky lately.
Cool, someone actually found my snippet :-) The use of my template tag is rather simple.
To answer your question there is no "built-in" django mechanism for dealing with breadcrumbs, but it does provide us with the next best thing: custom template tags.
Imagine you want to have breadcrumbs like so:
Services -> Programming
Services -> Consulting
Then you will probably have a few named urls: "services", and "programming", "consulting":
(r'^services/$',
'core.views.services',
{},
'services'),
(r'^services/programming$',
'core.views.programming',
{},
'programming'),
(r'^services/consulting$',
'core.views.consulting',
{},
'consulting'),
Now inside your html template (lets just look at consulting page) all you have to put is:
//consulting.html
{% load breadcrumbs %}
{% block breadcrumbs %}
{% breadcrumb_url 'Services' services %}
{% breadcrumb_url 'Consulting' consulting %}
{% endblock %}
If you want to use some kind of custom text within the breadcrumb, and don't want to link it, you can use breadcrumb tag instead.
//consulting.html
{% load breadcrumbs %}
{% block breadcrumbs %}
{% breadcrumb_url 'Services' services %}
{% breadcrumb_url 'Consulting' consulting %}
{% breadcrumb 'We are great!' %}
{% endblock %}
There are more involved situations where you might want to include an id of a particular object, which is also easy to do. This is an example that is more realistic:
{% load breadcrumbs %}
{% block breadcrumbs %}
{% breadcrumb_url 'Employees' employee_list %}
{% if employee.id %}
{% breadcrumb_url employee.company.name company_detail employee.company.id %}
{% breadcrumb_url employee.full_name employee_detail employee.id %}
{% breadcrumb 'Edit Employee ' %}
{% else %}
{% breadcrumb 'New Employee' %}
{% endif %}
{% endblock %}
DaGood breadcrumbs snippet
Provides two template tags to use in your HTML templates: breadcrumb and breadcrumb_url. The first allows creating of simple url, with the text portion and url portion. Or only unlinked text (as the last item in breadcrumb trail for example). The second, can actually take the named url with arguments! Additionally it takes a title as the first argument.
This is a templatetag file that should go into your /templatetags directory.
Just change the path of the image in the method create_crumb and you are good to go!
Don't forget to {% load breadcrumbs %} at the top of your html template!
from django import template
from django.template import loader, Node, Variable
from django.utils.encoding import smart_str, smart_unicode
from django.template.defaulttags import url
from django.template import VariableDoesNotExist
register = template.Library()
#register.tag
def breadcrumb(parser, token):
"""
Renders the breadcrumb.
Examples:
{% breadcrumb "Title of breadcrumb" url_var %}
{% breadcrumb context_var url_var %}
{% breadcrumb "Just the title" %}
{% breadcrumb just_context_var %}
Parameters:
-First parameter is the title of the crumb,
-Second (optional) parameter is the url variable to link to, produced by url tag, i.e.:
{% url person_detail object.id as person_url %}
then:
{% breadcrumb person.name person_url %}
#author Andriy Drozdyuk
"""
return BreadcrumbNode(token.split_contents()[1:])
#register.tag
def breadcrumb_url(parser, token):
"""
Same as breadcrumb
but instead of url context variable takes in all the
arguments URL tag takes.
{% breadcrumb "Title of breadcrumb" person_detail person.id %}
{% breadcrumb person.name person_detail person.id %}
"""
bits = token.split_contents()
if len(bits)==2:
return breadcrumb(parser, token)
# Extract our extra title parameter
title = bits.pop(1)
token.contents = ' '.join(bits)
url_node = url(parser, token)
return UrlBreadcrumbNode(title, url_node)
class BreadcrumbNode(Node):
def __init__(self, vars):
"""
First var is title, second var is url context variable
"""
self.vars = map(Variable,vars)
def render(self, context):
title = self.vars[0].var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.vars[0]
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_unicode(title)
url = None
if len(self.vars)>1:
val = self.vars[1]
try:
url = val.resolve(context)
except VariableDoesNotExist:
print 'URL does not exist', val
url = None
return create_crumb(title, url)
class UrlBreadcrumbNode(Node):
def __init__(self, title, url_node):
self.title = Variable(title)
self.url_node = url_node
def render(self, context):
title = self.title.var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.title
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_unicode(title)
url = self.url_node.render(context)
return create_crumb(title, url)
def create_crumb(title, url=None):
"""
Helper function
"""
crumb = """<span class="breadcrumbs-arrow">""" \
"""<img src="/media/images/arrow.gif" alt="Arrow">""" \
"""</span>"""
if url:
crumb = "%s<a href='%s'>%s</a>" % (crumb, url, title)
else:
crumb = "%s %s" % (crumb, title)
return crumb
The Django admin view modules have automatic breadcumbs, which are implemented like this:
{% block breadcrumbs %}
<div class="breadcrumbs">
{% trans 'Home' %}
{% block crumbs %}
{% if title %} › {{ title }}{% endif %}
{% endblock %}
</div>
{% endblock %}
So there is some kind of built-in support for this..
My view functions emit the breadcrumbs as a simple list.
Some information is kept in the user's session. Indirectly, however, it comes from the URL's.
Breadcrumbs are not a simple linear list of where they've been -- that's what browser history is for. A simple list of where they've been doesn't make a good breadcrumb trail because it doesn't reflect any meaning.
For most of our view functions, the navigation is pretty fixed, and based on template/view/URL design. In our cases, there's a lot of drilling into details, and the breadcrumbs reflect that narrowing -- we have a "realm", a "list", a "parent" and a "child". They form a simple hierarchy from general to specific.
In most cases, a well-defined URL can be trivially broken into a nice trail of breadcrumbs. Indeed, that's one test for good URL design -- the URL can be interpreted as breadcrumbs and displayed meaningfully to the users.
For a few view functions, where we present information that's part of a "many-to-many" join, for example, there are two candidate parents. The URL may say one thing, but the session's context stack says another.
For that reason, our view functions have to leave context clues in the session so we can emit breadcrumbs.
Try django-breadcrumbs — a pluggable middleware that add a breadcrumbs callable/iterable in your request object.
It supports simple views, generic views and Django FlatPages app.
I had the same issue and finally I've made simple django tempalate tag for it: https://github.com/prymitive/bootstrap-breadcrumbs
http://www.djangosnippets.org/snippets/1289/ - provides a template tag but i'm not sure this would work if you don't have your urls.py properly declared.
Nothing will work if you don't have your urls.py properly declared. Having said that, it doesn't look as though it imports from urls.py. In fact, it looks like to properly use that tag, you still have to pass the template some variables. Okay, that's not quite true: indirectly through the default url tag, which the breadcrumb tag calls. But as far as I can figure, it doesn't even actually call that tag; all occurrences of url are locally created variables.
But I'm no expert at parsing template tag definitions. So say somewhere else in the code it magically replicates the functionality of the url tag. The usage seems to be that you pass in arguments to a reverse lookup. Again, no matter what your project is, you urls.py should be configured so that any view can be reached with a reverse lookup. This is especially true with breadcrumbs. Think about it:
home > accounts > my account
Should accounts, ever hold an arbitrary, hardcoded url? Could "my account" ever hold an arbitrary, hardcoded url? Some way, somehow you're going to write breadcrumbs in such a way that your urls.py gets reversed. That's really only going to happen in one of two places: in your view, with a call to reverse, or in the template, with a call to a template tag that mimics the functionality of reverse. There may be reasons to prefer the former over the latter (into which the linked snippet locks you), but avoiding a logical configuration of your urls.py file is not one of them.
Try django-mptt.
Utilities for implementing Modified Preorder Tree Traversal (MPTT) with your Django Model classes and working with trees of Model instances.
This answer is just the same as #Andriy Drozdyuk (link). I just want to edit something so it works in Django 3.2 (in my case) and good in bootstrap too.
for create_crumb function (Remove the ">" bug in the current code)
def create_crumb(title, url=None):
"""
Helper function
"""
if url:
crumb = '<li class="breadcrumb-item">{}</li>'.format(url, title)
else:
crumb = '<li class="breadcrumb-item active" aria-current="page">{}</li>'.format(title)
return crumb
And for __init__ in BreadcrumbNode, add list() to make it subscriptable. And change smart_unicode to smart_text in render method
from django.utils.encoding import smart_text
class BreadcrumbNode(Node):
def __init__(self, vars):
"""
First var is title, second var is url context variable
"""
self.vars = list(map(Variable, vars))
def render(self, context):
title = self.vars[0].var
if title.find("'")==-1 and title.find('"')==-1:
try:
val = self.vars[0]
title = val.resolve(context)
except:
title = ''
else:
title=title.strip("'").strip('"')
title=smart_text(title)
And add this in base.html for the view for Bootstrap. Check the docs
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb">
{% block breadcrumbs %}
{% endblock breadcrumbs %}
</ol>
</nav>
Obviously, no one best answer, but for practical reason I find that it is worth considering the naïve way. Just overwrite and rewrite the whole breadcrumb... (at least until the official django.contrib.breadcrumb released )
Without being too fancy, it is better to keep things simple. It helps the newcomer to understand. It is extremely customizable (e.g. permission checking, breadcrumb icon, separator characters, active breadcrumb, etc...)
Base Template
<!-- File: base.html -->
<html>
<body>
{% block breadcrumb %}
<ul class="breadcrumb">
<li>Dashboard</li>
</ul>
{% endblock breadcrumb %}
{% block content %}{% endblock content %}
</body>
</html>
Implementation Template
Later on each pages we rewrite and overwrite the whole breadcrumb block.
<!-- File: page.html -->
{% extends 'base.html' %}
{% block breadcrumb %}
<ul class="breadcrumb">
<li>Dashboard</li>
<li>Level 1</li>
<li class="active">Level 2</li>
</ul>
{% endblock breadcrumb %}
Practicallity
Realworld use cases:
Django Oscar: base template, simple bread
Django Admin: base template, simple bread, permission check breadcrumb
You could also reduce the boiler plate required to manage breadcrumbs using django-view-breadcrumbs, by adding a crumbs property to the view.
urls.py
urlpatterns = [
...
path('posts/<slug:slug>', views.PostDetail.as_view(), name='post_detail'),
...
]
views.py
from django.views.generic import DetailView
from view_breadcrumbs import DetailBreadcrumbMixin
class PostDetail(DetailBreadcrumbMixin, DetailView):
model = Post
template_name = 'app/post/detail.html'
base.html
{% load django_bootstrap_breadcrumbs %}
{% block breadcrumbs %}
{% render_breadcrumbs %}
{% endblock %}
Something like this may work for your situation:
Capture the entire URL in your view and make links from it. This will require modifying your urls.py, each view that needs to have breadcrumbs, and your templates.
First you would capture the entire URL in your urls.py file
original urls.py
...
(r'^myapp/$', 'myView'),
(r'^myapp/(?P<pk>.+)/$', 'myOtherView'),
...
new urls.py
...
(r'^(?P<whole_url>myapp/)$', 'myView'),
(r'^(?P<whole_url>myapp/(?P<pk>.+)/)$', 'myOtherView'),
...
Then in your view something like:
views.py
...
def myView(request, whole_url):
# dissect the url
slugs = whole_url.split('/')
# for each 'directory' in the url create a piece of bread
breadcrumbs = []
url = '/'
for slug in slugs:
if slug != '':
url = '%s%s/' % (url, slug)
breadcrumb = { 'slug':slug, 'url':url }
breadcrumbs.append(breadcrumb)
objects = {
'breadcrumbs': breadcrumbs,
}
return render_to_response('myTemplate.html', objects)
...
Which should be pulled out into a function that gets imported into the views that need it
Then in your template print out the breadcrumbs
myTemplate.html
...
<div class="breadcrumb-nav">
<ul>
{% for breadcrumb in breadcrumbs %}
<li>{{ breadcrumb.slug }}</li>
{% endfor %}
</ul>
</div>
...
One shortcoming of doing it this way is that as it stands you can only show the 'directory' part of the url as the link text. One fix for this off the top of my head (probably not a good one) would be to keep a dictionary in the file that defines the breadcrumb function.
Anyways that's one way you could accomplish breadcrumbs, cheers :)
You might want to try django-headcrumbs (don’t worry, they are not going to eat your brains).
It’s very lightweight and absolutely straightforward to use, all you have to do is annotate your views (because defining crumbs structure in templates sounds crazy to me) with a decorator that explains how to get back from the given view.
Here is an example from the documentation:
from headcrumbs.decorators import crumb
from headcrumbs.util import name_from_pk
#crumb('Staff') # This is the root crumb -- it doesn’t have a parent
def index(request):
# In our example you’ll fetch the list of divisions (from a database)
# and output it.
#crumb(name_from_pk(Division), parent=index)
def division(request, slug):
# Here you find all employees from the given division
# and list them.
There are also some utility functions (e.g. name_from_pk you can see in the example) that automagically generate nice names for your crumbs without you having to wright lots of code.
I've created template filter for this.
Apply your custom filter (I've named it 'makebreadcrumbs') to the request.path like this:
{% with request.resolver_match.namespace as name_space %}
{{ request.path|makebreadcrumbs:name_space|safe }}
{% endwith %}
We need to pass url namespace as an arg to our filter.
Also use safe filter, because our filter will be returning string that needs to be resolved as html content.
Custom filter should look like this:
#register.filter
def makebreadcrumbs(value, arg):
my_crumbs = []
crumbs = value.split('/')[1:-1] # slice domain and last empty value
for index, c in enumerate(crumbs):
if c == arg and len(crumbs) != 1:
# check it is a index of the app. example: /users/user/change_password - /users/ is the index.
link = '{}'.format(reverse(c+':index'), c)
else:
if index == len(crumbs)-1:
link = '<span>{}</span>'.format(c)
# the current bread crumb should not be a link.
else:
link = '{}'.format(reverse(arg+':' + c), c)
my_crumbs.append(link)
return ' > '.join(my_crumbs)
# return whole list of crumbs joined by the right arrow special character.
Important:
splited parts of the 'value' in our filter should be equal to the namespace in the urls.py, so the reverse method can be called.
Hope it helped.
A generic way, to collect all callable paths of the current url could be resolved by the following code snippet:
from django.urls import resolve, Resolver404
path_items = request.path.split("/")
path_items.pop(0)
path_tmp = ""
breadcrumb_config = OrderedDict()
for path_item in path_items:
path_tmp += "/" + path_item
try:
resolve(path_tmp)
breadcrumb_config[path_item] = {'is_representative': True, 'current_path': path_tmp}
except Resolver404:
breadcrumb_config[path_item] = {'is_representative': False, 'current_path': path_tmp}
If the resolve function can't get a real path from any urlpattern, the Resolver404 exception will be thrown. For those items we set the is_representative flag to false. The OrderedDict breadcrumb_config holds after that the breadcrumb items with there configuration.
For bootstrap 4 breadcrumb for example, you can do something like the following in your template:
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
{% for crumb, values in BREADCRUMB_CONFIG.items %}
<li class="breadcrumb-item {% if forloop.last or not values.is_representative %}active{% endif %}" {% if forloop.last %}aria-current="page"{% endif %}>
{% if values.is_representative %}
<a href="{{values.current_path}}">
{{crumb}}
</a>
{% else %}
{{crumb}}
{% endif %}
</li>
{% endfor %}
</ol>
</nav>
Only the links which won't raises a 404 are clickable.
I believe there is nothing simpler than that (django 3.2):
def list(request):
return render(request, 'list.html', {
'crumbs' : [
("Today", "https://www.python.org/"),
("Is", "https://www.python.org/"),
("Sunday", "https://www.djangoproject.com/"),
]
})
Breadcrumbs.html
<div class="page-title-right">
<ol class="breadcrumb m-0">
{% if crumbs %}
{% for c in crumbs %}
<li class="breadcrumb-item {{c.2}}">{{c.0}}</li>
{% endfor %}
{% endif %}
</ol>
</div>
css:
.m-0 {
margin: 0!important;
}
.breadcrumb {
display: flex;
flex-wrap: wrap;
padding: 0 0;
margin-bottom: 1rem;
list-style: none;
border-radius: .25rem;
}
dl, ol, ul {
margin-top: 0;
margin-bottom: 1rem;
}
ol, ul {
padding-left: 2rem;
}

Django pagination of filtered results using AJAX

I've been trying to figure this out for days and was unable to find any useful information online.
What I am trying to do is paginate objects from my model after filtering them using a drop down menu and supplying the data to python via AJAX. I know where the problem is but I am not sure how to solve it. I have two templates, first one is:
entry_index.html:
{% extends 'main/base.html' %}
<form action="" method="get" accept-charset="utf-8">
<select class="selectpicker" name="times" onchange="FilterCategories()" id="times">
<option value="1">last 24 hours</option>
<option value="30">past month</option>
<option value="365">past year</option>
<option value="10000">all time</option>
</select>
</form>
<ul id="all-games" class="list-unstyled">
{% include page_template %}
</ul>
The template that is being included in the above template is entry_index_page.html:
{% if objects %}
{% for object in objects %}
do something
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if objects.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ objects.number }} of {{ objects.paginator.num_pages }}.
</span>
{% if objects.has_next %}
next
{% endif %}
</span>
</div>
urls.py:
url(r'^$', views.entry_index, name='index')
views.py:
def entry_index(
request,
template='entry_index.html',
page_template='entry_index_page.html'):
date_from = timezone.now() - timezone.timedelta(days=1)
obj_list=Object.objects.filter(submitted__gte = date_from).order_by('-votes')
message=[]
context = {
'objects': obj_list,
'page_template': page_template}
if request.is_ajax():
template = page_template
message = []
if request.method == "GET":
time_range = request.GET.get('time_range')
if time_range is not None and time_range != u"":
time_range = request.GET['time_range']
date_from = timezone.now() - timezone.timedelta(days=int(time_range))
obj_list= Object.objects.filter.filter(submitted__gte=date_from)
paginator = Paginator(obj_list, 2)
page = request.GET.get('page')
try:
objects= paginator.page(page)
except PageNotAnInteger:
objects= paginator.page(1)
except EmptyPage:
objects= paginator.page(paginator.num_pages)
context.update({"message":message,"objects":objects})
return render_to_response(
template, context, context_instance=RequestContext(request))
ajax.js:
function FilterCategories() {
var timePosted = document.getElementById('times');
$.ajax({
type: "GET",
url: "",
data: {
'time_range': timePosted.value,
'csrfmiddlewaretoken': $("input[csrfmiddlewaretoken]").val()
},
success: filterResults,
dataType: 'html'
});
}
Now to explain what I think is going on and hopefully someone can help me find a solution.
When the home page is loaded (entry_index.html) the model gets filtered based on the first option in the drop down menu (i.e. value="1", which is filtering for the data entries submitted within the last day). The obj_list variable gets populated and is passed to the paginator and everything works as expected. I get a certain number of pages and can navigate through pages. Now lets assume we are on the home page again and I select "all time" from the drop down menu. This will trigger the onchange callback and it will call the FilterCategories() function. Note the url in AJAX is "" (an empty string, so pointing to my index page). According to urls.py, it will call my entry_index() view. Because request is ajax, the template used will change (page_template becomes the new template, page_template = entry_index_page.html). Because the new time range specified with the drop down menu and passed on with ajax, I get a new obj_list which is then paginated and produces "objects" which are then passed as context onto the template. Up until this point everything works as expected. I get right amount of pages etc. However, the problem starts when I try to go to the next page with the newly selected filter. When I click the next page button, the request that is being made is not an ajax request so everything that is in the request.is_ajax() conditional is not executed. So in another words a click to the next page is calling my entry_index view again and the template being used this time around is entry_index.html and my filter is reset back to the default, which is the "last 24 hours" filter. Therefore, when I click the next page what I end up getting is actually the default home page again instead of getting the next page of objects with my newly selected drop down filter.
My question is, is there an easy way to fix this so that I can scroll through the pages of my filtered model? Or should I completely abandon this approach and there is an easier way to do this? I apologize for a long post and I hope someone out there will be able to help me out. Thank you for taking your time to read this.
Here's an example of an approach I've taken to having dynamic content displayed on a page using Django and Ajax:
I was tooling a little browser Sci-fi game just to practice this specific technique. Everything took place in a single view:
class GameViewport(TemplateView):
template_name = "game_viewport.html"
#cached_property
def slug(self):
return self.kwargs['slug']
#cached_property
def game(self):
return Game.objects.get(url=self.kwargs['slug'])
#cached_property
def player(self):
return Player.objects.get(game=self.game)
#cached_property
def current_planet(self):
return self.player.current_planet
#cached_property
def left_column(self):
player = self.player
if player.current_location:
node = player.current_node
if len(Location.objects.filter(node=node)) == 0:
spawn_locations(node)
locations = Location.objects.filter(node=node)
else:
locations = Location.objects.filter(planet=node)
html = "Other Sites in ".format(str(node))
for location in locations:
html += '<li>{} ({})</li>'.format(location.name, location.type.name)
return html
elif player.current_node:
planet = player.current_planet
if len(Node.objects.filter(planet=planet)) == 0:
spawn_nodes(planet, get_name_choices())
nodes = Node.objects.filter(planet=planet)
else:
nodes = Node.objects.filter(planet=planet)
html = '<h4><b>Other Starports on {}:</b></h4>'.format(planet.name)
for node in nodes:
html += '<li> {} ({})</li>'.format(node.name, node.type.name)
return html
elif player.current_planet:
system = player.current_system
html = '<h4><b>Known Planets in {}:</b></h4>'.format(system.name)
for known_planet in player.known_planets.filter(solar_system=system):
html += '<li> {} ({})</li>'.format(
known_planet.name,
known_planet.classification.name
)
return html
else:
html = '<h4><bShip Status</b></h4>'
html += '<p><b>Fuel:</b> 100%</p>'
return html
So as you can see, the left column would generate different html data based on what the player's current settings are. This would be plugged into the template like so:
<div class="col-md-3">
<div class="leftColumn">
{% autoescape off %}
{{ view.left_column }}
{% endautoescape %}
</div>
</div>
If the user clicked on a new location, I would send her decision through AJAX:
$(".planetChoice").click(function(){
event.preventDefault();
var submission_data = {planet: $(this).text()};
console.log(submission_data);
$.ajax({
url: $('#visitPlanet').attr('href'),
type: 'GET',
dataType: "json",
data: submission_data,
success: function(html_data) {
window.location.reload();
},
failure: function(data) {
alert('Something went wrong. Please refresh the page.');
}
});
});
All this would do is update the player's state and reload the page according to her new settings:
def visit_planet(request, slug):
player = Game.objects.get(url=slug).player_1
planet = Planet.objects.get(name=request.GET.get('planet', "").strip())
if planet:
player.current_location = None
player.current_node = None
player.current_planet = planet
player.save()
response = {'status': 1, 'message': "Ok"}
return JsonResponse(response)
Thus displaying the new data, as determined by the left_column property.
I went about this by changing the state in the database, but it could just as easily be accomplished with session variables. I found it to be a relatively clean and DRY way of cycling dynamic content. It also has the advantage of giving Django an opportunity to generate or modify data in between clicks.
Not sure if this applies to your situation, but hopefully it sparks an idea!
EDIT: You don't even necessarily need to output HTML. Here's an approach I am using in a different application:
<!--Product Tile #1-->
{% if view.tile_data.0 %}
<div class="col-md-4">
<div class="card hoverable">
<!--Card content-->
<div class="card-block" id="tile_{{ view.tile_data.0.invoice }}_id">
<!--Title-->
<h4 class="card-title">Shipment {{ view.tile_data.0.invoice }}</h4>
<!--Text-->
<p class="card-text">{{ view.tile_data.0.supplier.name }}
<br>
<b>{{ view.tile_data.0.lbs|floatformat }} Lbs # {{ view.tile_data.0.price }} USD</b>
<br>
{{ view.tile_data.0.variety.commodity }} {{ view.tile_data.0.variety }} {{ view.tile_data.0.inshell|shell_display }}</p>
</div>
<!--/.Card content-->
</div>
</div>
{% endif %}
<!--./Product Tile #1-->
This data is directly fed through the view from a model manager:
def tile_data(self, status, first, last):
return self.model.objects.filter(status=status)[first:last]

"TemplateSyntaxError: Invalid filter:"; custom django template filter based on django docs broken, but template tags working

I have a template filter based on the django docs at https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/. For the life of me I can't see any difference in my usage and theirs, and am slowly going insane. I have a working tag I got on a forum as such:
myproject/index/templatetags/add_get_parameter.py:
from django.template import Library, Node, resolve_variable
register = Library()
class AddGetParameter(Node):
def __init__(self, values):
self.values = values
def render(self, context):
req = resolve_variable('request', context)
params = req.GET.copy()
for key, value in self.values.items():
params[key] = value.resolve(context)
return '?%s' % params.urlencode()
#register.tag
def add_get(parser, token):
pairs = token.split_contents()[1:]
values = {}
for pair in pairs:
s = pair.split('=', 1)
values[s[0]] = parser.compile_filter(s[1])
return AddGetParameter(values)
This one, add_get on lines 8-9, works, whereas shorten_title on line 4 doesn't work:
myproject/templates/index/silo.html:
{% load bootstrap add_get_parameter extras %}
...other stuff...
{% for article in articles %}
<div class="col-md-4 article-link">
<div class="panel panel-default hover">
<div class="panel-heading"><h4 class="url-link">{{ article.title|shorten_title }}</h4></div>
<div class="panel-body">
<p> <span class="url-text">{{ article.url }}</span></p>
<div class="article_button">Edit</div>
<div class="article_button"><p>Archive</p></div>
<div class="article_button">Delete</div>
<div style="margin-top:8px;">
{% for tag in article.tags.all %}
<p class="tag">{{ tag.name }}</p>
{% endfor %}
</div>
</div>
</div>
</div>
{% endfor %}
Here's the villian:
from django.template import Library
register = Library()
#register.filter
def shorten_title(title):
length = len(title)
new = title
if length > 65:
new = title[0:65] + "..."
return new
register.filter('shorten_title', shorten_title)
He's been so rude I double registered him, just to see what happens (he doesn't work registered once as a decorator or afterward, and doesn't work registered twice).
{{ article.title }} works, but {{ article.title|shorten_title }} breaks the page with:
django.template.base.TemplateSyntaxError
TemplateSyntaxError: Invalid filter: 'shorten_title'
'Index' is definitely registered and working, and the page works when I delete the filter from that article.title tag.
Usually when I get a stubborn error I missed something small, but following the docs word for word has me baffled (I've written several working filters before). Is this filter bad, or is there maybe something else in my page that causes the issue? Thanks
You need to make sure you import the file with register.filter('shorten_title', shorten_title) before you render the template. Since that call happens outside of any functions it is run when you import the module. This has the side effect of registering it so that it will be available in your templates afterwards.

Categories

Resources