Pre-process model childeren from a template in Django - django

I've got a DetailView to display a Menu (restaurant), with the following structure:
Menu > Courses > Course_Categories > Dishes
i.e. (Theather Menu) > (Starters, Entrees, Desserts) > (Fish Soup, Ribs, etc.)
I want to prefetch the course_categories and dishes, in addition to that I want to set a property on each dish to be used in the template. This property (the price) is dependent on time of day, therefore it's not simple stored as a value on the Dish.
I tried the following:
class MenuView(generic.DetailView):
template_name = "cms/detail/menu.html"
model = Menu
def get_object(self, queryset=None):
pkValue = pk = self.kwargs.get(self.pk_url_kwarg, None)
menu = Menu.objects.get(id=pkValue)
courses = menu.courses.all().prefetch_related('course_categories').prefetch_related('dishes')
for course in courses.all().iterator():
for course_category in course.course_categories.all().iterator():
for dish in course_category.dishes.all().iterator():
dish.price = "0.00"
return menu
When I iterate the data in my template, it shows everything, but no value for the dish.price property. What I think happens is that the related sets are re-retrieved, and therefore my custom set property doesn't show.
Template:
{% for course in menu.courses.iterator %}
<tr>
<td><strong>{{ course.name }}</strong></td>
</tr>
{% for course_category in course.course_categories.iterator %}
<tr>
<td><em> {{ course_category.name }}</em></td>
</tr>
{% for dish in course_category.dishes.iterator %}
<tr>
<td> {{ dish.name }} {{ dish.price}}</td>
</tr>
{% endfor %}
{% endfor %}
{% endfor %}
Any suggestions?

You are using iterator() which explicitly does not cache the results. When you do {% for course in menu.courses.iterator %}, the results are fetched from the database again.
Use simply .all() in your view instead of .all().iterator(), and stop using .iterator in your template, and you shouldn't have the problem.

Related

Summarizing values for each row in a table dynamically generated from django context variables

So I have the following table in my template:
<tbody>
{% for user in users %}
<tr>
<td>{{ user.title }} </td>
{% for fruit in fruits %}
{{ sum|add:user|get_attr:fruit }}
<td>{{ user|get_attr:fruit }} </td>
{% endfor %}
{% for vegetable in vegetables %}
{{ sum|add:user|get_attr:vegetable }}
<td>{{ user|get_attr:vegetable }} </td>
{% endfor %}
<td>{{ sum }}</td>
{% endfor %}
</tbody>
"fruits" and "vegetables" are lists passed as context from the view. The following custom filter allows me to iterate through those lists extract integer values from the model "user". The table columns are generated the same way, so this filter has to be in the table:
#register.filter
def get_attr(obj, attr):
return getattr(obj, attr)
The variable "sum" is passed as context from the view with the value 0. I'm trying to make it summarize all the relevant row variables in the template, but it remains 0. As far as I can see there are three ways I could go about this:
Solve this in the template (as I'm trying now)
Generate the value in the view (although I have no idea how I would go about this)
Add some JS to solve it (would prefer to avoid this).
How should I go about solving this?
Solved this on the back end by generating a dictionary:
views.py
sum = {}
for user in users:
identity = user.pk
score = 0
all_food = fruits + vegetables
for food in all_food:
score += getattr(user,food)
sum[identity] = score
The summarized value can now be accessed in the template by using another custom template tag:
#register.filter
def get_value(dictionary, key):
return dictionary.get(key)
Adding it to the template:
{{ sum|get_value:user.pk }}

Display Django model data to table in HTML

I have two Django models that record time. Model one records time during the morning and Model two records time during the evening. I want to present both of these times along with the difference between the times within an HTML table but am confused about how to do it. I am new to Django and would really appreciate some advice.
This is what I have so far:
models.py:
class Alltime(models.Model):
id= models.ForeignKey(User, on_delete = models.CASCADE)
mtime = models.DateTimeField()
etime = models.DateTimeField()
views.py:
def panel(request):
time_data = User.objects.filter(pk__gt=1) #I need all data except for the default Super User account
get_time = Alltime.objects.all()
return render(request, 'users/interface.html', {'data': time_data, "get_time": get_time})
panel.html:
<form>
{% csrf_token %}
<table>
<tr>
<th>Name</th>
<th>Morning timeE</th>
<th>Evening time</th>
<th>Difference in hours</th>
</tr>
{% for data in data %}
<tr>
<td>{{data.username}}</td>
{% endfor %}
{% if get_time %}
{% for m in get_time %}
<td>{{m.mtime}}</td>
<td>{{m.etime}}</td>
{% endfor %}
{% else %}
<td> Not available </td>
{% endif %}
</tr>
</table>
</form>
How can I get the difference between the times and place them within the HTML table?
If I understand correctly what you want to do, then you can/need to structure your data differently. An easy way is to prepare the data in your view:
def panel(request):
time_data = User.objects.filter(pk__gt=1)
time_table=[]
for user in time_data:
morning_time = Morning.objects.filter(user=user)
evening_time = Evening.objects.filter(user=user)
diff = morning_time - evening_time
time_table.append((user.name, morning_time, evening_time, diff))
return render(request, 'users/interface.html', {'data': time_table})
And in the template:
<table>
<tr>
<th>Name</th>
<th>Morning timeE</th>
<th>Evening time</th>
<th>Difference in hours</th>
</tr>
{% for line in data %}
<tr>
<td>{{line.0}}</td>
<td>{{line.1}}</td>
<td>{{line.2}}</td>
<td>{{line.3}}</td>
</tr>
{% endfor %}
</table>
You need to add the handling of not existing data in the view code.
Some remarks:
The whole thing does not really make sense to me. I guess you will need to filter for dates too. But you should get the idea from this. And why is it in a form?
You can add a property to the Alltime model that returns the difference between the morning and evening time
#property
def diff(self):
return self.etime - self.mtime
Then in your template you can use this property
{% for m in get_time %}
<td>{{m.mtime}}</td>
<td>{{m.etime}}</td>
<td>{{m.diff}}</td>
{% endfor %}

How to create two columns from a QuerySet in a Django template [duplicate]

I am trying to split a list from my model across two columns, using this html code in the template:
< div class ="col-md-6" >
{%for value in object_list %}
<ul>< ahref="/sites/{{value.url}}/">{{value.Site}}</a></ul>
{% endfor %}
I was planning to achieve this with the slice tag to filter the list, e.g.:
{%for value in object_list|slice:"10:20" %}
It does not work however, and I think it might be because I have context data i.e. {{value.Site}}, instead of just {{Site}} for example. This is the corresponding view:
class homeview(ListView):
template_name = 'annual_means/home.html'
def get_queryset(self):
return AnnualMean.objects.values("Site", "url").distinct()
What do I need to do to get the slice to work?
I think, what you need is this:
<table>
<tr>
<th>URL</th>
<th>SITE</th>
</tr>
{% for value in object_list %}
<tr>
<td>{{value.url}}</td>
<td>{{value.Site}}</td>
</tr>
{% endfor %}
</table>
URLs and Sites will be displayed as a table.

django filter a regroup within a forloop

I have a model called Subtopic. One of my templates runs a forloop on an object, returning a different field for each cell of a table row.
Two of the table cells look up a field which is a ManytoMany foreign key, both to the same foreign model, Resource. I want each to display different results, based on the value of a boolean field within the Resource model.
What you see below is currently working fine, but doesn't attempt to filter by the boolean field.
models.py:
class ICTResourceManager(models.Manager):
def get_query_set(self):
return super(ICTResourceManager, self).get_query_set().filter('is_ict': True)
class NonICTResourceManager(models.Manager):
def get_query_set(self):
return super(NonICTResourceManager, self).get_query_set().filter('is_ict': False)
class Resource(models.Model):
subtopics = models.ManyToManyField(Subtopic)
external_site = models.ForeignKey(ExternalSite)
link_address = models.URLField(max_length=200, unique=True, verify_exists=False)
requires_login = models.BooleanField()
is_ict = models.BooleanField()
flags = models.ManyToManyField(Flag, blank=True)
comment = models.TextField()
def __unicode__(self):
return u'%s %s' % (self.external_site, self.link_address)
objects = models.Manager()
ict_objects = ICTResourceManager()
nonict_objects = NonICTResourceManager()
class Meta:
ordering = ['external_site', 'link_address']
views.py:
def view_ks5topic(request, modulecode, topicshortname):
listofsubtopics = Subtopic.objects.filter(topic__module__code__iexact = modulecode, topic__shortname__iexact = topicshortname)
themodule = Module.objects.get(code__iexact = modulecode)
thetopic = Topic.objects.get(module__code__iexact = modulecode, shortname__iexact = topicshortname)
return render_to_response('topic_page.html', locals())
My template:
{% for whatever in listofsubtopics %}
<tr>
<td>
{{ whatever.objective_html|safe }}
<p>
{% if request.user.is_authenticated %}
{% with 'objective' as column %}
{% include "edit_text.html" %}
{% endwith %}
{% else %}
{% endif %}
</td>
<td>
{% regroup whatever.resource_set.all by external_site.name as resource_list %}
{% for external_site in resource_list %}
<h4>{{ external_site.grouper }}</h4>
<ul>
{% for item in external_site.list %}
<li>{{ item.comment }}</li>
{% endfor %}
</ul>
{% endfor %}
</td>
</tr>
{% endfor %}
As you can see, I've added extra managers to the model to do the filtering for me, but when I replace the appropriate lines in the template, I just get blanks. I have tried: for external_site.ict_objects in resource_list and for item.ict_objects in resource_list and <a href="{{ item.ict_objects.link_address }}">. If this were in the view I could probably do the filter just by .filter('is_ict': True), but with this being inside a forloop I don't know where to do the filtering.
I also tried writing regroup whatever.resource_set.filter('is_ict': True) in the template, but the syntax for regrouping seems to use resource_set.all rather than resource_set.all() (and I don't know why) so the filter text doesn't work here.
Turns out it was possible to do it using a custom template filter. The original efforts to filter within the template weren't working, given that as is well documented the template language is not a fully-fledged python environment. My original question remains open for anyone who knows an alternative method that more directly addresses the question I was originally asking, but here's how I did it:
myapp_extras.py:
from django import template
register = template.Library()
def ict(value, arg):
"filters on whether the is_ict Boolean is true"
return value.filter(is_ict=arg)
register.filter('ict', ict)
My template, note the use of the custom filter in line 2:
<td>
{% regroup whatever.resource_set.all|ict:1 by external_site.name as resource_list %}
{% for external_site in resource_list %}
<h4>{{ external_site.grouper }}</h4>
<ul>
{% for item in external_site.list %}
<li>{{ item.comment }}</li>
{% endfor %}
</ul>
{% endfor %}
</td>
After this I was able to remove the additional custom managers from the model.
One further question, when filtering for the boolean is_ict field, I found that I had to use filter(is_ict=1) and filter(is_ict=0). Is that the only way to refer to a True or False value?

How can insert Hyperlinks for image fields in Django template

I am displaying the database field names and values in html table in django templae with this code
<table id="listTable" >
<tr>
{% for fieldname in object_fields %}<th>{{ fieldname }}</th>{% endfor %}
</tr>
{% for object in object_list %}
<tr class="{% cycle 'row1' 'row2' as rowcolors %}">
{% for fieldvalue in object %}<td>{{ fieldvalue }}</td>{% endfor %}
</tr>
{% endfor %}
</table>
Now the problem i have one field called image_name and i want that for that there should be hyperlink inserted with the text but how can do that so that other columns don't get affected.
I use this code for all the tables
What I would do in this case is add a property to the class that wraps the value of the field in an anchor tag and add that property to your object_list instead of the default value of the field. Here's a quick and dirty example to get you going.
#models.py
class MyClass(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
#property
def email_link(self):
return u'%(email)s' % {'email' : self.email}