I noticed that my django code calls my database very often with the exact same queries.
I understand that a db hit is made when I actually need the data to display on a page or to evaluate. However, my template code looks like this:
template:
{% if item.listing %}
{{ item.name }} text <strong>{{ item.listing|lowestprice }}</strong> more text
{% else %}
{{ item.name }} even more text
{% endif %}
....
{% for listed_item in item.listing %}
....
{% endfor %}
custom filter:
def lowestprice(value):
try:
val = unicode(value[0].price) + unicode(value[0].symbol)
return val
except:
return "not available"
This code hits my db three times. First on template {% if .. %} second on my custom filter, third on the {% for %} loop.
listing is a method of my models class which is returning a raw SQL queryset with some very expensive joins.
def listing(self):
return Universe.objects.raw("ONE HELL OF A QUERY")
How can I reduce my code to hit the db only once?
Edit: Using with works, but is it possible to avoid db hits on custom filters?
You should use with to do the expensive query once and store it the context.
{% with item.listing as item_listing %}
{% if item_listing %} ... {% endif %} ... etc ...
{% endwith %}
Related
I have a for loop in Django template. After that, I check for coincidences. But in some cases, there are might be 3 coincidences. I need to show only the first coincidence. Now, my code returns the name for 3 times, because, there are 3 coincidences
{% for ip in ips %}
{% if d.name == ip.name %}
<strong>{{ d.name}} </strong>
{% endif %}
{% endfor %}
SOLUTION
It is impossible to break forloop in django template, so I decided to change in views.py through queryset distinction of similar names
ips = Point.objects.defer('point').order_by('name').distinct('name')
I don't recommend doing this in Django Template , but in views itself. But if you can't then you can use {{ forloop|break }}.
Something like this :
{% for ip in ips %}
{% if d.name == ip.name %}
{{ forloop|break }}
<strong>{{ d.name}} </strong>
{% endif %}
{% endfor %}
Check the small snippet example here...
I am trying to get access to the instances of a ModelChoiceField to render them the way I want in a template by displaying several fields of the instances.
class MyForm(forms.Form):
mychoices = forms.ModelChoiceField(
queryset=ObjectA.objects.all(), widget=forms.RadioSelect(), empty_label=None
)
This does not work:
{% for choice in form.mychoices %}
{{ choice.pk}}
{% endfor %}
I also tried to use the queryset but it does not render anything
{% for choice in form.mychoices.queryset %}
{{ choice.pk}}
{% endfor %}
Any idea?
Thanks
{% for choice in form.mychoices.field.queryset %}
{{ choice.pk }}
{% endfor %}
Notice the extra .field. It's a bit strange the first time you come across it, but it gives you what you want. You can also access the choices attribute of that object, instead of accessing queryset directly, but you'll need to access the first element of the choice to get the PK of the instance, like this:
{% for choice in form.mychoices.field.choices %}
{{ choice.0 }}
{% endfor %}
How can I check if a certain object/id is in a list?
I want something to be displayed if the ID of the connected object is not "6".
Tried with something like this:
{% if user.benefits.all != "6" %}
You do not have a benefit with ID 6.
{% endif %}
It is better to not put much logic into templates. View (or model) - is a better place for that.
For example in view you can check, that user.benefits has element with id=6 by this code:
has_benefit = user.benefits.filter(id=6).count() > 0
context['has_benefit'] = has_benefit
Now in template just use this new context variable:
{% if not has_benefit %}
You do not have a benefit with ID 6.
{% endif %}
UPDATED:
If you still want to do it in template, it is better to create a custom template filter:
from django import template
register = template.Library()
#register.filter(name='has_benefit')
def has_benefit(user, benefit_id):
b_id = int(benefit_id)
return user.benefits.filter(id=b_id).count() > 0
Now in template load your templatetags module using {% load module_name %} and use:
{% if not user|has_benefit:"6" %}
You do not have a benefit with ID 6.
{% endif %}
{% for benefit in user.benefits.all %}
{% if benefit.id != 6 %}
You do not have a benefit with id 6
{% endif %}
{% endfor %}
But this will loop through all the benefits and print it every time the condition passes.
So, you should write a template tag which returns you a list of ids for all benefits for a particular user and once you have that list you can do:
{% if 6 not in list_of_benefit_ids %}
You do not have a benefit with id 6
{% endif %}
I want to create such loop:
{% for object in objects %}
{% if object.before != object %}
{{ object }} this is different
{% else %}
{{ object }} this is the same
{% endfor %}
Based on https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs#for I can't. Is there really no simple way to do this? Or I just need to use counter and check for objects[counter-1]?
P.S. .before is theoretical and objects is simple query list. I want to take and do something with the loop member that encountered before current loop member.
Check ifchanged template tag
There is a "simple way" to do this: write a custom template tag. They're really not hard. This would probably do the trick (untested):
#register.simple_tag
def compare_objects(object_list):
comparisons = []
for i in range(1, len(object_list)):
if object_list[i] > object_list[i-1]:
comparisons.append('bigger')
else:
comparisons.append('smaller')
return comparisons
The built-in template tags and filters don't make it easy (as of Django 1.4), but it is possible by using the with tag to cache variables and the add, slugify, and slice filters to generate a new list with only one member.
The following example creates a new list whose sole member is the previous member of the forloop:
{% for item in list %}
{% if not forloop.first %}
{% with forloop.counter0|add:"-1" as previous %}
{% with previous|slugify|add:":"|add:previous as subset %}
{% with list|slice:subset as sublist %}
<p>Current item: {{ item }}</p>
<p>Previous item: {{ sublist.0 }}</p>
{% endwith %}
{% endwith %}
{% endwith %}
{% endif %}
{% endfor %}
This isn't an elegant solution, but the django template system has two faults that make this hack unavoidable for those who don't what to write custom tags:
Django template syntax does not allow nested curly parenthesis. Otherwise, we could do this:
{{ list.{{ forloop.counter|add:-1 }} }}
The lookup operator does not accept values stored using with (and perhaps for good reason)
{% with forloop.counter|add:-1 as index %}
{{ list.index }}
{% endwith %}
This code should work just fine as a django template, as long as object has a property or no-argument method called before, and objects is iterable (and '<' is defined).
{% for object in objects %}
{% if object.before < object %}
this is bigger
{% else %}
this is smaller
{% endfor %}
Given is a model called "comment" with a foreign key relationship to a model called "task".
{% for task in tasks %}
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
...
What is the best way to limit this to 5 comments like:
Entry.objects.all()[:5]
{% for task in tasks %}
{% for comment in task.comment_set.all|slice:"5" %}
{{ comment }}
{% endfor %}
{% endfor %}
You don't. You should not do "real work" in a template, this breaks the MVC pattern.
Do the real work in the view, and pass the data to the template (using the context dictionary).
def handle_comments(request):
tasks = Task.objects.all()
comments = {}
for task in tasks:
comments[task] = task.comment_set.all()[:5]
return render_to_response('commenting.html', {'comments': comments})
You can then iterate over the comments in your template:
{% for task, task_comments in comments.items %}{{ task }}{% endfor %}