Group blocks from StreamField Wagtail - python

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 %}

Related

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.

Get element of list by variable in Django template

I have line of code in Django template:
<h4>{{ totals.date.weekday }}</h4>
Totals is the Python list, how do i get item of this list by index stored in date.weekday?
This would look in Python like this:
totals[date.weekday]
Creating another variable, which stores date.weekday doesn't work
UPD:
I found a solution:
Just added element of totals list to template context in render
For example:
# ...
return render(request, 'template.html', context={'date_total'=totals[date.weekday()]})
You can access the array directly using
{{ totals.0.date.weekday}} where the 0 is the position that you want.
Also if you want to print all the elements in total you will need a for loop such as:
{% for d in totals %}
{{ d }}
{% endfor %}
about sorting, you can use the pipe order_by but I recommend you to pass the list already ordered from the views
You have to run the "for loop" for this.
{% with counts = 0 %}
{% while counts < totals.count %}
{% if counts == date.weekday %}
<h4>total.counts</h4>
{% endif %}
{% counts += 1 %}
{% endfor %}
I did not get your question completely but i think it might help.

Reducing size of columns in Flask-Admin

Is there a way to limit the size (length/width) of a ModelView column? I am using a WYSIWYG editor and this creates really long text, therefor making the column for the ModelView very long.
Here is picture of what it looks like. Look on the right hand side the last column. It is even longer than the screenshot could handle.
Don't show the column (by exclusion):
class MyView(ModelView):
column_exclude_list = ('description')
Don't show the column (by inclusion):
class MyView(ModelView):
column_list = ('rating', 'category_id', 'year', 'stock', 'image')
Reformat the column:
class MyView(ModelView):
def _description_formatter(view, context, model, name):
# Format your string here e.g show first 20 characters
# can return any valid HTML e.g. a link to another view to show the detail or a popup window
return model.description[:20]
column_formatters = {
'description': _description_formatter,
}
A way to do this could be to override the css style of the relevant column. In the Flask-admin list.html template you find the following code for creating the columns:
{% for c, name in list_columns %}
<td class="col-{{c}}">
{% if admin_view.is_editable(c) %}
{% set form = list_forms[get_pk_value(row)] %}
{% if form.csrf_token %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=form.csrf_token._value()) }}
{% else %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c)) }}
{% endif %}
{% else %}
{{ get_value(row, c) }}
{% endif %}
</td>
{% endfor %}
So e.g. for column 2 you could add a max-width property to the css class col-2 to limit its width.

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.

Django - why is this hitting the database twice

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

Categories