Django - why is this hitting the database twice - python

I have a property in my model:
def _get_image(self):
return Media.objects.get_for_object(self)
image = property(_get_image)
It calls the following function on my Media model:
def get_for_object(self, obj):
ctype = ContentType.objects.get_for_model(obj)
return self.filter(items__content_type__pk=ctype.pk, items__object_id=obj.pk)
Then in my template I am iterating through the results like so:
{% if entry.image %}
<h2>Current image:</h2>
{% for m in entry.image %}
{{ m }}
{% endfor %}
{% endif %}
For some reason, my SQL readout shows these two queries, right next to each other:
0.40 SELECT
EXPLAIN
Toggle Stacktrace
SELECT `media_media`.`id`, `media_media`.`file`, `media_media`.`content_type`, `media_media`.`created` FROM `media_media` INNER JOIN `media_mediaattachment` ON (`media_media`.`id` = `media_mediaattachment`.`media_id`) WHERE (`media_mediaattachment`.`content_type_id` = 12 AND `media_mediaattachment`.`object_id` = 20 )
0.38 SELECT
EXPLAIN
Toggle Stacktrace
SELECT `media_media`.`id`, `media_media`.`file`, `media_media`.`content_type`, `media_media`.`created` FROM `media_media` INNER JOIN `media_mediaattachment` ON (`media_media`.`id` = `media_mediaattachment`.`media_id`) WHERE (`media_mediaattachment`.`content_type_id` = 12 AND `media_mediaattachment`.`object_id` = 20 )
So whenever I access entry.image, the database is getting hit. Surely it should store the results or something?

"or something"?
Why should it store the results? You've explicitly written the _get_image function so that it queries the database each time. If you want it to store the results, you need to tell it to do it.
Probably the simplest way would be to just get it once in the template:
{% with entry.image as images %}
{% if images %}
<h2>Current image:</h2>
{% for m in images %}
{{ m }}
{% endfor %}
{% endif %}
{% endwith %}

Here's how you write a caching property without explicitly setting the cache to None in the __init__ method:
def _get_image(self):
if not hasattr(self, '_image'):
self._image = Media.objects.get_for_object(self)
return self._image
image = property(_get_image)
or in more modern syntax
#property
def image(self):
if not hasattr(self, '_image'):
self._image = Media.objects.get_for_object(self)
return self._image

Related

Group blocks from StreamField Wagtail

What am I trying to achieve will be easier to explain on lists.
e.g
list_of_blocks=[1,2,3,4,5,6,7,8,9,10,11,12]
block_first_row = list_of_blocks[:3]
block_rest_rows = [list_of_blocks[i:i+4] for i in range(3, len(list_of_blocks), 4)]
block_rows = block_rest_rows.insert(0, list_of_blocks)
I want to group blocks from StreamField and display them in template grouped by those rows.
Is there a way to do it in my model? Or should i do it somehow in template..
I've tried to do:
operate on StreamField as on list
deconstuct StreamField.. then operate as on list
The value of a StreamField is a list-like object of type StreamValue. Since it isn't a real list, it may not support slicing - if not, you can get around that by casting it to a real list with list(self.body) (where body is your StreamField). A good place to do this is the page's get_context method:
def get_context(self, request):
context = super().get_context(request)
list_of_blocks = list(self.body)
block_first_row = list_of_blocks[:3]
block_rest_rows = [list_of_blocks[i:i+4] for i in range(3, len(list_of_blocks), 4)]
block_rows = block_rest_rows.insert(0, block_first_row)
context['block_rows'] = block_rows
return context
You can then access block_rows in your template:
{% for block_row in block_rows %}
<div class="row">
{% for block in block_row %}
render block as normal, with {% include_block block %} or otherwise
{% endfor %}
</div>
{% endfor %}

Trouble using python dictionary in a Django template

So I have a python dictionary storing the size and the amount each size has, titled final_list. Then I'm passing that to the template using "sizes": final_list. In the html template, I'm trying to create a drop-down selection with all the sizes, and only display sizes that are available, or have an amount not equal to 0. The problem is, it seems as if it isn't accepting the dict or something, since the dropdown shows up empty. Below is the code I have, any help would be much appreciated.
Views.py
I'm getting the sizing info from a model called 'Size' then checking if there are 0 objects of that size. Then I'm creating the dictionary to only have sizes that are actually available.
from django.template.defaulttags import register
#register.filter
def get_item(dictionary, key):
return dictionary.get(key)
def product(request, code):
sizes = Size.objects.get(code=code)
all_size = ['small', 'medium', 'large', 'XL']
final_list = {}
for size in all_size:
if getattr(sizes, size) == 0:
pass
else:
final_list[size] = getattr(sizes, size)
return render(request, "website/listing.html", {
"sizes": final_list
})
HTML (website/listing.html)
<form method="POST">
<select name="sizes" style="width: 90px; height: 20px;">
{% csrf_token %}
{% for size in sizes %}
{% if final_list|get_item:size != 0 %}
<option>{{size}}</option>
{% endif %}
{% endfor %}
</select>
</form>
You don't pass a context variable named final_list to the template. You should use {% if sizes|get_item ... instead.
That being said, this code could be simplified as:
{% for size, value in sizes.items %}
{% if value %}
<option>{{ size }}</option>
{% endif %}
{% endfor %}
This way you don't have to use the custom filter get_item either.

How to iterate through multidimensional list/dict in a template

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>

Django model query tune up

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.

WTforms IntegerField in fieldlist never validates using manual iteration

I have an InterField, that validates if a number is between the values 0 and 99. For some reason it never validates.
I have a feeling it is related to the FieldList and ther way I iterate over it in the template, but can't seem to get it working.
The form:
class dpiaImpactAnalysisForm(Form):
severity_score = IntegerField("Severity Score"),
validators=[NumberRange(min=0, max=99, message="Please provide a valid number")]))
identifiability_score = IntegerField("Identifiability Score"),
validators=[NumberRange(min=0, max=99, message="Please provide a valid number")]))
class dpiaThreatAnalysisForm(Form):
impact = FieldList(FormField(dpiaImpactAnalysisForm), min_entries=1)
In views I append the entries dynamically as required:
#app.route('/dpia/analysis/<project_id>', methods=["GET", "POST"])
def analysis(project_id):
form = dpiaThreatAnalysisForm()
prim = Assets.query.filter_by(selected=True, primary=True).all()
primary_assets = list(map(vars, prim))
ev = Events.query.all()
events = list(map(vars, ev))
# add fields to the form...
for z in range(len(prim) * len(ev)):
form.impact.append_entry()
supp = Assets.query.filter_by(selected=True, primary=False).all()
supporting_assets = list(map(vars, supp))
ths = Threats.query.all()
threats = list(map(vars, ths))
# add fields to the form
for z in range(len(ths) * len(supp)):
form.likelihood.append_entry()
if form.is_submitted():
print "submitted"
if form.validate():
print "valid"
print form.errors
if form.validate_on_submit():
# This is never printed:
app.logger.info("success!!")
pprint(form.likelihood)
return redirect(url_for(next_step, project_id=project_id))
return render_template('analysis.html', form=form, threats=threats, supporting_assets=supporting_assets, primary_assets=primary_assets, events=events)
In the template I iterate over a list primary_assets in a list events, and add the fields per iteration:
{% for val in events %}
{% if not counter or loop.index0 == 0 %}
{% set counter = [] %} <!-- loop hack !-->
{% endif %}
<strong>Event: {{ val.name }}</strong><br />
Jeopardizes: {{ val.jeopardizes }}<br />
{% for pa in primary_assets %}
<strong>{{ pa['name'] }}</strong><br />
{{ form.impact[counter|length].identifiability_score(placeholder='') }} <br />
{{ form.impact[counter|length].severity_score(placeholder='') }}
{{ form.impact[counter|length].hidden_tag() }}
{% if counter.append('1') %}{% endif %}
{% endfor %}
{% endfor %}
The hidden_tag() doesn't work either. Normally I iterate of the forms with with something like
{% for impact in form.impact %}
{{ impact.form.hidden_tag() }}
# do cbg
{% endfor %}
and that works, that's why I believe it's my manual looping that spoils it...
EDIT 2 march, 17:26
After some testing, I found that using
severity_score = IntegerField("Severity Score", validators=[Optional(), NumberRange(
min=0, max=9999999999, message="Please provide a valid number")])
works (if I set min=50 I get an error when inserting a number below 50), however, the CSRF is still not getting passed.
{{ form.impact[counter|length].hidden_tag() }} or
{{ form.impact[counter|length].form.hidden_tag() }} both don't work :(
I'm getting: {'impact': [{'csrf_token': ['CSRF token missing']}, {'csrf_token': ['CSRF token missing']}]}
EDIT 18:22
It seems that this: Form validation fails due missing CSRF is the solution. Investigating...
This: Form validation fails due missing CSRF is the solution.
In previous versions this wasn't needed, and after an installation of a new extension pip updated flask-wtf as well...

Categories