Template variables based on calculations using fields from Django Model - django

I am using Django 1.10.7 and Python 3.6.1. First my code, then my questions.
My model looks like this:
class Thing(models.Model):
user = models.ForeignKey('auth.User')
title = models.CharField(max_length=200)
start_date = models.DateTimeField(
default=timezone.now)
cost_per_day = models.DecimalField(max_digits=6, decimal_places=2)
Here are my views:
def something_list(request):
things =Thing.objects.filter(start_date__lte=timezone.now()).order_by('start_date')
return render(request, 'myApp/something_list.html', {'things': things})
def thing_detail(request, pk):
thing = get_object_or_404(Thing, pk=pk)
return render(request, 'myApp/thing_detail.html', {'thing': thing})
I have two template blocks for each of these views, where I am using template tags to display variables. For example:
<h2 class="title">{{ thing.title }}</h2>
<p>User: {{ thing.user }}</p>
<div class="date">
<p>Start Date: {{ thing.start_date }}</p>
</div>
<p>Cost per day: ${{ thing.cost_per_day }}</p>
So what is happening here?
Web users can enter what I am calling any number of "Things" with 4 fields: User, Title, Start Date, and Cost per Day. I have TWO calculations that I would like to make, and render them in the template.
Problem 1) The user needs to see how many days have elapsed since they originally entered a Start Date. The calculation will be a subtraction of the current time/date (Now) and the Start Date. I was able to accomplish this by using the Django timesince feature shown here:
<button type="button" class="btn btn-info">{{ thing.quit_date|timesince }}</button>
This renders how many days and hours have elapsed - lets call it 'time elapsed' - perfectly. The problem lies in my next calculation.
Problem 2) I need to display a calculation of 'time elapsed' (shown above) multiplied by the Cost per Day variable in the current Model instance. Just for clarity, let's say the start date was 30.5 days ago, and the cost per day is $5.00. I need to multiply 30.5 by $5.00. I would love to simply multiply template tags but I understand that's not how it works. For example:
{{ thing.quit_date|timesince }} * {{ thing.cost_per_day }}
Is there a way to capture the result of this timesince calculation... {{ thing.quit_date|timesince }}... as a variable? I may not be able to use the timesince feature for this calculation.
Once again, what I ultimately need to do here is to multiply the "time elapsed" by the cost per day, like this: (Time Now - Start Date) * (Cost Per Day).
I don't know how to do this in models.py or views.py. I'm looking for the best practice way to do this using Django and Python 3.6. I'm a newbie and I have searched all day for an answer to my particular problem.
Thanks in advance and please let me know if I need to provide more information!
UPDATE
Here is my updated model based on suggestions, albeit not working (the timesince property needs work):
from django.db import models
from django.utils import timezone
from datetime import datetime
class Thing(models.Model):
quitter = models.ForeignKey('auth.User')
title = models.CharField(max_length=200)
quit_date = models.DateTimeField(default=timezone.now)
cost_per_day = models.DecimalField(max_digits=6, decimal_places=2)
notes = models.TextField()
#property
def timesince(self):
"Time since quit date"
now = datetime.now()
then = self.quit_date
return (now - then)
#property
def savings(self):
"Savings since quitting"
return self.timesince * self.cost_per_day
Here is my updated template:
<h4>Here are some calculations for this information...</h4>
<p>Days since you quit:</p>
<button type="button" class="btn btn-info">{{ thing.timesince }}</button>
<hr>
<p>How much money you have saved:</p>
<button type="button" class="btn btn-info">{{ thing.savings }}</button>
I'm certain the problem lies in the subtraction of the dates. Any insight into that would be very helpful. The template variables {{ }} are working, but there is a missing piece in the model.
By the way, when I concatenated "days" to any datetime variable, it game me errors saying .days it gave me errors. Perhaps there is an issue using both a DateTimeField and a datetime.now() instance?

I would recommend creating a property on the model itself to calculate this. Subtracting the quit date from the current date will give you a timedelta object. From there we can get the days and take it times the cost.
from django.utils import timezone
class Thing(models.Model):
...
#property
def cost(self):
days = (timezone.now().date() - self.quit_date).days
return days * self.cost_per_day
Then your template becomes very simple.
{{ thing.cost }}

This really isn't what template tags are for, at all. They're meant to display variables and call it a day, with some minor functional utility bolted on.
For your equations, best practice would be to implement them in the model.
from datetime import datetime
class Thing(models.Model):
quit_date = models.DateTimeField()
cost_per_day = models.FloatField() ???
#property
def timesince(self):
# Time since last whatever
elapsed = datetime.now() - self.quit_date
return elapsed.days
#property
def calculate_cost(self):
return self.timesince * self.cost_per_day
Then you can just display each of the values using {{ thing.timesince }} and {{ thing.calculate_cost }} in the template.

Here is an answer that works. I think the problem was that I was using "datetime.now()" instead of "timezone.now()". This might not be the best way of approaching this in Django/Python. I'd appreciate comments on how to make it "best practice". I really appreciate the assistance on this one!
#property
def timesince(self):
"Time since quit date"
now = timezone.now()
then = self.quit_date
return (now - then).days
#property
def savings(self):
"Savings since quitting"
return self.timesince * self.cost_per_day

Related

Django - how to obtain the latest related model in an efficent way I can iterate on the template?

Let's say I have two related models, one of the two with a datetime field.
(Author & Book with a pub_date).
I want to display a list of authors and the latest book each of them has written.
I made a method on the Author model:
def get_latest_book(self):
return self.books.all().latest('pub_date')
That is working, but it's very inefficent when it comes to be rendered on a template:
views.py:
class AuthorListView(ListView):
model = Author
template_name = 'author_list.html'
def get_queryset(self):
return self.model.objects.filter(owner=self.request.user).order_by('name').prefetch_related('books')
author_list.html:
...
{% for author in author_list %}
Name: {{author.name}} - Latest publication: {{author.get_latest_book}}
{% endfor %}
...
This is generating a large number of queries like:
SELECT ••• FROM `app_book` WHERE `app_book`.`author_id` = 374 ORDER BY `app_book`.`pub_date` DESC LIMIT 1
36 similar queries.
for each Author I have in the database!
This results in huge loading times for the book list.
How can I print on the template a list of Authors with their latest book in an efficient way?
An easy way to boost efficiency is with a Prefetch object [Django-doc]:
from django.db.models import Prefetch
class AuthorListView(ListView):
model = Author
template_name = 'author_list.html'
def get_queryset(self):
return self.model.objects.filter(
owner=self.request.user
).prefetch_related(
Prefetch('books', Book.objects.order_by('-pub_date'), to_attr='books_ordered')
).order_by('name')
and then render this with:
{% for author in author_list %}
Name: {{author.name}} - Latest publication: {{ author.books_ordered.0 }}
{% endfor %}
This however will load all related books in memory, but in a single query.

Filtering Dates : TypeError at / expected string or bytes-like object

I'm trying to make a filter based on dates for scheduled events. I want items that happened between 60 days ago and that are scheduled 60 days from now. So the start of the range is today - 60 days and the end of the range is today + 60 days. I haven't used Django in a long time and I don't know if this is a new issue with the current version and the auto_now and auto_add_now or if the issue is using DateField. I used DateField on the models because I don't care about the time and don't want these time fields added to the database. I want clean dates.
Index.html
<!DOCTYPE html>
{% load static %}
<title>Document</title>
<body>
<div id=form>
<select name="Title" id="title_box">
{% for item in items %}
<option value="{{item.name}}">{{item.name}}</option>
{% endfor %}
</select>
</div>
</body>
</html>
Models.py
from __future__ import unicode_literals
from django.db import migrations, models
import datetime
class Cycle(models.Model):
name= models.CharField(max_length=255)
start_date = models.DateField(auto_now=False, auto_now_add=False,
blank=True, null=True)
end_date = models.DateField(auto_now=False, auto_now_add=False,
blank=True, null=True)
I changed the field from DateField to DateTimeField and it made no difference, so I changed it back to DateField.
Views.py
from django.shortcuts import render, HttpResponse, redirect
from .models import *
import datetime
def main(request):
beginDate = datetime.date.today() - datetime.timedelta(days=60)
stopDate = datetime.date.today() + datetime.timedelta(days=60)
context = {
"items": Cycle.objects.filter(start_date=[beginDate,stopDate])
}
return render (request, 'app/index.html', context)
I want to display the events that happened between 60 days ago and that will happen the next 60 days. The code I have now only reflects the start_date in the model. I'm just trying to take baby steps and figure out one and then use OR to get the other later. I imagine it would be something like:
"items": Cycle.objects.filter(start_date=[beginDate,stopDate] | end_date=[beginDate, stopDate])
Anyways, the error I'm getting is:
TypeError at /expected string or bytes-like object
I think the error is with :
context = {
"items": Cycle.objects.filter(start_date=[beginDate,stopDate])
}
I printed the "beginDate" and it gave me 2018-11-09, which is correct for the range I want. I used type to see if it would help me figure out the mismatch and got <class 'datetime.date'> . I'm thinking now that maybe it doesn't match with the models.py, but I'm not sure if it is the case and if it is how to make them match. After reading a few posts I also tried using timezone.now() and date.today() for the beginDate and the stopDate, but got errors that said that date and timezone were not defined. I also made sure to use 'makemigrations' and 'migrate'.
Any assistance would be greatly appreciated.
To filter on a date range like you're doing here you need to change your filter arguments to use __range. By doing start_date= like you have there, you're looking for a value for start_date that matches exactly e.g. start_date= beginDate
You've also got a | operator in there. If you want to do a start_date in a range, and end_date in a range, you need to change that for a ,.
Cycle.objects.filter(
start_date__range=[beginDate, stopDate],
end_date__range=[beginDate, stopDate]
)
If you're looking to say start_date between dates OR end_date between dates, then you need to use django Q objects.
from django.db.models import Q
Cycle.objects.filter(
Q(start_date__range=[beginDate, stopDate]) | Q(end_date__range=[beginDate, stopDate])
)
The documentations for Q objects is worth a read.

Class Based View MySQL DateTimeField received a naive datetime

I am very new to Django and at the end of my rope and really need some help.
I do not know how to use a "class based view" and change the incoming datetimefield from my MySQL database into a Time Zone Supported entry that it seems to need. The database stores it in UTC and my system is on PST.
I am getting this error:
DateTimeField received a naive datetime (2012-09-01 00:00:00) while time zone support is active
On my MonthArchiveView, DayArchiveView, DateDetailView 's only. For some reason my ArchiveIndexView, YearArchiveView class based views work ok.
Here is my model:
class blogpost(models.Model):
blog_title = models.CharField(max_length=200)
blog_slug = models.SlugField(unique_for_date='blog_pub_date', max_length=200)
blog_content = models.TextField()
blog_pub_date = models.DateTimeField(default=datetime.now())
blog_category = models.ForeignKey('blogcategory')
Here is one of my Views:
class ThoughtsDetailView(DateDetailView):
template_name='thoughts/thoughts_detail.html'
queryset = blogpost.objects.all()
date_field = 'blog_pub_date'
slug_field = 'blog_slug'
context_object_name = 'thoughts_detail'
month_format = '%m'
allow_future = 'true'
Here is an example template:
{% block content-inner-left %}
<h1>{{ thoughts_detail.blog_title }}</h1>
<div id="blogpost">
<p class="blogsmalldate">[ Posted on {{ thoughts_detail.blog_pub_date|date:"l, F dS, Y" }}, {{ thoughts_detail.blog_pub_time|date:"g:i a" }} ]</p>
<br />
<p>{{ thoughts_detail.blog_content|safe|linebreaks }}</p>
</div>
{% endblock content-inner-left %}
Can someone help me understand how to fix my Day Detail View so that it stays as a Class Based View and then I can probably figure out the others. I even tried to use PYTZ but don't understand enough how to change the class based view to use it. Thank you....
The problem is not in the view, but in the fact that the dates are stored in the database without a timezone information, while Django is set up to support timezones. If you don't need timezone support, simply set USE_TZ = False in the settings.py; if you do, make sure that the database stores the dates with the timezone information. More details on that can be found at https://docs.djangoproject.com/en/1.4/topics/i18n/timezones/#naive-and-aware-datetime-objects

Django: using aggregates to show vote counts for basic polls app

I'm making a very basic poll app. It's similar to the one in the Django tutorial but I chose to break out the vote counting aspect into its own model (the tutorial just adds a vote count field alongside each answer). Here's my models:
class PollQuestion(models.Model):
question = models.CharField(max_length=75)
class PollAnswer(models.Model):
poll = models.ForeignKey('PollQuestion')
answer = models.CharField(max_length=75)
class PollVote(models.Model):
poll = models.ForeignKey('PollQuestion')
answer = models.ForeignKey('PollAnswer')
date_voted = models.DateTimeField(auto_now_add=True)
user_ip = models.CharField(max_length=75)
I'm trying to show all of the vote counts for a given poll. Here's my view code:
from django.db.models import Count
poll_votes = PollVote.objects.select_related('PollAnswer').filter(poll=poll_id).annotate(num_votes=Count('answer__id'))
When I output the results of this query I just get a single row per vote (eg I see about 40 'answers' for my poll, each one representing a vote for one of the 5 actual PollAnswers). If I look at the queries Django makes, it runs something like this for every vote in the poll:
SELECT `poll_answers`.`id`, `poll_answers`.`poll_id`, `poll_answers`.`answer`
FROM `poll_answers`
WHERE `poll_answers`.`id` = 101
Can anyone poke me in the right direction here? I get the feeling this should be easy.
EDIT: here's my template code, for completeness.
<ul>
{% for vote in votes %}
{{ vote.answer }} ({{ votes.num_votes }})<br />
{% endfor %}
</ul>
Try:
poll_votes = PollVote.objects.filter(poll=poll_id).annotate(num_votes=Count('answer__id'))
or:
poll_votes = PollVote.objects.values('poll', 'answer__answer').filter(poll=poll_id).annotate(num_votes=Count('answer__id'))
Relevant docs:
Django offical docs
Never mind, fixed it myself after finding a tutorial which uses the same sort of models as me.
Essentially the fix was in the view:
p = get_object_or_404(PollQuestion, pk=poll_id)
choices = p.pollanswer_set.all()
And in the template:
{% for choice in choices %}
<p class="resultsList">{{choice.answer}} - {{choice.pollvote_set.count}}</p>
{% endfor %}

Query with Django ORM (joining models?)

I have the following models:
class Thread(models.Model):
name = models.CharField(max_length=50)
category = models.ForeignKey(Category)
class Post(models.Model):
thread = models.ForeignKey(Thread)
datetime = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User)
This is for my forum system.
I need to fetch all the Threads of category X and I need to have a few custom attributes like datetime and pk of the last post to the thread, for example:
threads = Thread.objects.filter(category=x)
last_post = Post.objects.filter(thread=threads[0]).order_by('-pk')[0]
Then I want to transfer everything to the template so that I get:
{{ t.pk }} as thread_pk
{{ t.last_post_pk }} as last_post_pk
And so on.
To get last post for every thread you should use aggregation:
Post.objects.filter(thread__in=threads).values('thread').annotate(last_id=Max('id')).order_by()
This will get you last_id from every thread in threads.
Since Django doesn't do joining like you would expect with SQL joins (see my comment for further explanation), I would solve it in the view (or perhaps a method added to the Thread class). Consider the following example view:
def index(request):
res = []
for t in Thread.objects.filter(category__name='x'):
res.append((t, Post.objects.filter(thread=t).order_by('-datetime')[0]))
# Optionally sort by datetime here:
# res.sort(key=lambda x: x[1].datetime, reverse=True)
return render_to_response('index.html', {'result': res})
You could then use it in the template as follows:
{% for r in res %}
Thread {{ r.0.name }}, last post by {{ r.1.author.name }} on {{ r.1.datetime }}.
{% endfor %}
# This would give something like this:
# Thread mythread, last post by john on Oct. 21, 2011, 1:52 a.m..
By creating a tuple of Threads and Posts, you don't really join them, but do make them easily available in the template.