I want to reduce the number of template filter calls in my_filter_function(). Because It's used inside two for-loops inside a template. Please see below for my code setup.
class ModelA(models.model):
models.ForeignKey(OtherModel1)
class ModelB(models.model):
models.ForeignKey(OtherModel2)
class ModelC(models.Model):
a = models.ForeignKey(ModelA)
b = models.ForeignKey(ModelB)
def my_views(request):
return render(request, 'my_template.html', {
'a_list': ModelA.objects.all(),
'b_list': ModelB.objects.all(),
})
and in my template, I have
{% for a in a_list %}
{% for b in b_list %}
{% with b|my_filter_function:a as my_val %}
Val: {{my_val}}
{% endwith %}
{% endfor %}
{% endfor %}
the above template will will call the my_filter_function filter function, I need to find another way to reduce the number of my_filter_function function calls, because the filter function is accessing the DBs several thousand times per template now.
#register.filter
def my_filter_function:(b, a):
z = ModelC.objects.filter(a=a, b=b)
if z.count() > 0:
return "OK"
else:
return "Not OK"
Here's a faster alternative.
Fetch all the ids of A and B in C at once:
z = ModelC.objects.values_list('a_id', 'b_id')
a_related, b_related = zip(*z) # split into a and b ids
Pass these to your context:
def my_views(request):
return render(request, 'my_template.html', {
'a_list': ModelA.objects.all(),
'b_list': ModelB.objects.all(),
'a_related': a_related,
'b_related': b_related,
})
And then use if...in in your template. The custom template filter can now be discarded:
{% for a in a_list %}
{% for b in b_list %}
{% if a.id in a_related and b.id in b_related %}
"OK"
{% else %}
"Not ok"
{% endif %}
{% endfor %}
{% endfor %}
That replaces all the multiple queries in your filter with just one.
Related
I have two lists like:
DevInDB = ['Dev1','Dev2','Dev3']
and
DevInMesh = [['Dev1',0,0],['Dev2',1,1]]
I want to know if, for any DevInDB string, there is the same string in DevInMesh.
I wrote this code that works (in python)
for dev in DevInDB:
if any(dev in DevMesh for DevMesh in DevInMesh ):
print(dev)
else:
print('no')
I try to ust this code in a HTML file using jinja, but the 'any' function doesn't work. How can I do it?
You need to compute this before sending it to jinja.
Ex:
DevInDB = ['Dev1','Dev2','Dev3']
DevInMesh = [['Dev1',0,0],['Dev2',1,1]]
DevInMesh = {k for k, *_ in DevInMesh}
DevInDB = [(i, i in DevInMesh) for i in DevInDB]
print(DevInDB)
I've done it this way:
{% for DevDB in DevInDB %}
{% set ns = namespace(foo=false) %}
{% for DevMesh in DevInMesh %}
{% if DevMesh [0] == DevDB %}
{% set ns.foo = True %}
{% endif %}
{% endfor %}
and later on:
{% if ns.foo %}
{% else %}
{% endif %}
You can replace the any expression with the simple list comprehension where non-empty list will be True:
[1 for DevMesh in DevInMesh if dev in DevMesh]
Or better is to remove the computational part from template because a template is the view part that works on presentation level of the context, such that all pre-computed expressions should be stored in the context and only printed in the template.
I want to declare a flag variable in django template and the change it if some thing happened.
But when I change value of variable by custom tag it is declared a new variable and doesn't change.
for example my template tag and django template is:
template tag:
#register.simple_tag
def update_variable(value):
return value
html:
{% with True as flag %}
<h1>1: {{ flag }}</h1>
{% for e in events %}
{% if e.title == '***' %}
{% update_variable False as flag %}
<h1>2: {{ flag }}</h1>
{% endif %}
{% endfor %}
<h1>3: {{ flag }}</h1>
{% endwith %}
and result is:
1: True
2: False
3: True
But the end result should be False! How to do this?
I find a solution for it. for check all element of list we can use custom filter an do some thing there:
html:
//load custom filter
{% load my_filters %}
{% if "anything we want"|is_in:events.all %}
//do some thing...
{% else %}
//do some thing...
{% end if%}
custom filter in my_filter file:
register = template.Library()
def is_in(case, list):
for element in list:
if element.time_span == case:
return True
return False
register.filter(is_in)
I try to rebuild this example:
https://blog.roseman.org.uk/2010/01/11/django-patterns-part-2-efficient-reverse-lookups/
I have a model "Product" and a model "Order". Order has a foreignkey to "product". So for 1 Product I have N Orders
In my template I have to display a lot of information so I would like to avoid to do "for order in Product.order_set.all()" in my template
In my template, if I write :
{{ object_list.1.related_items }}
everything is fine and I get what I want
but if I write:
{% for i in object_list %}
{{ object_list.i.related_items }}
{% endfor %}
I don't get a result.
Can somebody tell me how I solve this problem?
My object_list is nearly the same as in the above example:
products = Product.objects.all()
i = 0
qs = Product.objects.all()
obj_dict = dict([(obj.id, obj) for obj in qs])
objects = Order.objects.filter(producttyp__in=qs)
relation_dict = {}
for obj in objects:
relation_dict.setdefault(obj.producttyp_id, []).append(obj)
for id, related_items in relation_dict.items():
obj_dict[id].related_items = related_items
def get(self,request,*args,**kwargs):
context = {'object_list':self.obj_dict}
return render(request,self.template_name,context)
the only change i did is from
obj_dict[id]._related_items to obj_dict[id].related_items because of the not allowed underscore?!
How do I print the list in my template like:
- Product A
- Order 1
- Order 2
- Order 5
- Product B
- Order 3
- Order 6
best regards
That is logical, since here Django interprets i not as the variable, but as the an identifier, so it aims to access object_list.i, or object_list['i'], not object_list.1 for example.
You however do not need i here, you can just access the related_items of the object, like:
{% for object in object_list %}
{{ object.related_items }}
{% endfor %}
If related_items is, as the name suggests, a collection as well, we can iterate over these items as well:
{% for object in object_list %}
{% for subitem in object.related_items %}
{{ subitem }}
{% endfor %}
{% endfor %}
for a dictionary, we can access the .values, like:
{% for object in object_dict.values %}
{{ object.related_items }}
{% endfor %}
EDIT: as for the specific case of the listview. You can use .prefetch_related to fetch all the relations with one extra query:
class MyListView(ListView):
queryset = Product.objects.prefetch_related('order_set')
template = 'my_template.html'
In the template you can then render this like:
<ul>
{% for product in object_list %}
<li>{{ product }}</li>
<ul>
{% for order in product.order_set %}
<li>{{ order }}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
I've got a Django template I'd sometimes like to pass a list and sometimes like to pass a single value. How can the template tell which it was given?
I'm thinking the value would be set like one of these:
context = {
'foo' : 'bar
}
or:
context = {
'foo' : ['bar', 'bat', 'baz']
}
Then, the template would have code that looks something like this:
{% if foo isa list %}
{% for item in foo %}
{{ item }}<br>
{% endfor %}
{% else %}
{{ item}}<br>
{% endif %}
I can set it up to have foo or foolist, for example, and check for one or the other. However, it'd be a bit nicer (imo) to just have foo that was either a list or not.
If you intend to do it this way then just add a check that it doesn't have format(in case of string) method and has 0 index, if so then its a list else considered single value
{% if foo.0 and not foo.format %}
{% for item in foo %}
{{ item }}<br>
{% endfor %}
{% else %}
{{ item}}<br>
{% endif %}
I think your approach is needlessly complicated.
I would just go with a list:
views.py
foo_list = ['bar']
context = {
'foo': foo_list,
'foo_len': len(foo_list),
}
template
{% if foo_len == 1 %}
{{ foo.0 }}
{% else %}
{% for item in foo %}
{{ item }}
{% endfor %}
{% endif %}
this is my views.py :
a=['aaa','bbb','oooo','qqqq','gggg']
def main(request, template_name='index.html'):
context ={
'n':range(len(a)),
'a':a,
}
return render_to_response(template_name, context)
this is my html :
{% for i in n %}
{{a.i}} ww {{a.i+1}}
{% endfor %}
it show ww ww ww ww ww ,
but i want to show 'aaawwbbb bbbwwoooo oooowwqqqq qqqqwwgggg ggggww'
so what can i do ,
thanks
>>> c=Context({'a':['aaa', 'bbb', 'oooo', 'qqqq', 'gggg']})
>>> Template("{% for x in a %}{% if not forloop.first %}{{ x }} {% endif %}{{ x }}ww{% endfor %}").render(c)
u'aaawwbbb bbbwwoooo oooowwqqqq qqqqwwgggg ggggww'
You could create a custom filter, http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/ and have something like this:
# myfilters.py
def return_element(list, index):
return list[index+1]
And then you can use it from the template,
{% include myfilters %}
...
{% for i in a %}
{{ i }}ww{{ a|return_element:forloop.counter0 }}
{% endfor %}
The forloop template variable is automatically set within a for tag.. forloop.counter0 returns the number of times loop is entered, and uses zero-indexing.
Don't do that. Iterate on list directly.
context = {
'a': a
}
return render_to_response(template_name, context)
And in template:
{% for x in a %}
{{ x }}
{% endfor %}