Django queryset dates: aggregate and format months as name strings - django

I've got a simple blog application which I'm porting to Django. One stumbling block I've come across is the aggregation of article entries by month, and displaying that as a list. Each article in the db has a simple datetime field. The HTML output should be something like:
2010
January (3 entries)
February (2 entries)
March (3 entries)
etc.
I'm currently using a {% foreach %} block in the template to loop through all the months returned from the query.
Going with dates('datestamp','month') seems to be the right direction, but how do I get article counts in the results, and how would I format the month integers to be name strings in the template?

You can use django-cube for this. With the following code in your views.py (and then connect entry_count in your urls), you should get what you need :
# cube declaration
from cube.models import Cube, Dimension
class EntryCube(Cube):
#replace <your_date_field> by the field name
month = Dimension('<your_date_field>__month')
year = Dimension('<your_date_field>__year')
#staticmethod
def aggregation(queryset):
return queryset.count()
# views declaration
from .models import <YourModel> #replace by your model
from cube.views import table_from_cube
def entry_count(request):
cube = EntryCube(<YourModel>.objects.all())
return table_from_cube(request, cube, ['year', 'month'], template='table.html')
#template 'table.html' is in 'example/template' folder, copy it in your template folder, and modify it to your wish.
EDIT :
If you want to customize the way dimensions are displayed, you can just subclass Dimension (see this snippet):
class MonthDimension(Dimension):
#property
def pretty_constraint(self):
#just return the month name according to the month number that you can get in "self.constraint", e.g. self.constraint = 1 -> return 'january'
return month_name
(I plan to provide subclasses of dimensions to allow different formats like what you need. So if you do use django-cube, and write a good subclass of Dimension to format dates, I would be happy to take a look at it !)
Ok ... it is overkill for what you want. However, if you decide after that you want more statistics on your entries (for example number of entries/author/month/year/tag, etc ...), you simply add a dimension to the cube. If you decide to use this, and need more help, please ask !

Related

Django - HTML Template get month name from number

I have a number represented month and I want to replace it with month name. The date filter not working, as it works with datetime, and I have the field as integer. For sure the value will be between 1 and 12.
{{ monthnumber|date:"M" }}
Please help.
From what I read I assume you are just passing an integer. You have to pass a value that is a datetime object, ie in your views context, monthname must be a datetime object.
If you would still like to work with an integer from 1 to 12, you could write your own filter with something along the lines:
#register.filter
def monthtextual(value):
return datetime.date(2020, value, 1).strftime('%B')
See https://docs.djangoproject.com/en/3.0/howto/custom-template-tags/ for more details.
Thanks all for the valuable solutions.
Instead of using custom filter or any 3rd party code.. I tend to add an additional field for Month Name along with Month Number in the queryset itself.. and in HTML template i used that additional field for Month Name for display purpose. This seems to be fast.

Django template counting days from now to specific date

I have a model with DateField:
end_date = models.DateField(default=datetime.date.today)
In the template I need to calculate how many days there is to the end_date from now.
I tried:
{% now "d m Y"|timeuntil:placement.end_date|date:"d m Y" %}
but it doesn't work. How can I get number of days until that date?
There is a limitation to using Django functionality in Templates. You could solve this by combining the timesince and timuntil methods to calculate the difference between two dates. However you would benefit more from doing this in a python view and then pass the result to the template.
This way you can truly use the power of Datetime. So in your views.py and the specific function that renders the template, include this:
d0 = datetime.now().date()
d1 = placement.end_date
delta = d0 - d1
print delta.days
You can read more about the Datetime documentation here. Now you can pass that variable along in a context, or by itself to be rendered by the template
Option 1: Create a custom filter
Create a custom filter function which grabs the current date and calculates the days.
In your template you simply use:
{{ placement.end_date | days_until }}
To define and register the filter, include this code in a python file:
from datetime import datetime
from django import template
register = template.Library()
#register.filter
def days_until(date):
delta = datetime.date(date) - datetime.now().date()
return delta.days
More about Django custom template tags and filters here.
Option 2: Calculate the days in your view and store it in the context
This is straightforward for one date. You calculate the days and store the information in the context:
from datetime import datetime
delta = placement.end_date - datetime.now().date()
context['days_left'] = delta.days
In your template you access it directly:
{{ days_left }}
But, if you want to pass a list of "placements" to the template, you will have to "attach" this new information to every placement. This is a different topic and the implementation depends on the project... you can create a wrapper over placement... or store the days_left in a different dictionary...
Because I like Django built-in filters
timesince
Formats a date as the time since that date (e.g., “4 days, 6 hours”).
Takes an optional argument that is a variable containing the date to
use as the comparison point (without the argument, the comparison
point is now). For example, if blog_date is a date instance
representing midnight on 1 June 2006, and comment_date is a date
instance for 08:00 on 1 June 2006, then the following would return “8
hours”:
{{ blog_date|timesince:comment_date }}
Comparing offset-naive and offset-aware datetimes will return an empty
string.
Minutes is the smallest unit used, and “0 minutes” will be returned
for any date that is in the future relative to the comparison point.

django paginate with non-model object

I'm working on a side project using python and Django. It's a website that tracks the price of some product from some website, then show all the historical price of products.
So, I have this class in Django:
class Product(models.Model):
price = models.FloatField()
date = models.DateTimeField(auto_now = True)
name = models.CharField()
Then, in my views.py, because I want to display products in a table, like so:
+----------+--------+--------+--------+--------+....
| Name | Date 1 | Date 2 | Date 3 |... |....
+----------+--------+--------+--------+--------+....
| Product1 | 100.0 | 120.0 | 70.0 | ... |....
+----------+--------+--------+--------+--------+....
...
I'm using the following class for rendering:
class ProductView(objects):
name = ""
price_history = {}
So that in my template, I can easily convert each product_view object into one table row. I'm also passing through context a sorted list of all available dates, for the purpose of constructing the head of the table, and getting the price of each product on that date.
Then I have logic in views that converts one or more products into this ProductView object. The logic looks something like this:
def conversion():
result_dict = {}
all_products = Product.objects.all()
for product in all_products:
if product.name in result_dict:
result_dict[product.name].append(product)
else:
result_dict[product.name] = [product]
# So result_dict will be like
# {"Product1":[product, product], "Product2":[product],...}
product_views = []
for products in result_dict.values():
# Logic that converts list of Product into ProductView, which is simple.
# Then I'm returning the product_views, sorted based on the price on the
# latest date, None if not available.
return sorted(product_views,
key = lambda x: get_latest_price(latest_date, x),
reverse = True)
As per Daniel Roseman and zymud, adding get_latest_price:
def get_latest_price(date, product_view):
if date in product_view.price_history:
return product_view.price_history[date]
else:
return None
I omitted the logic to get the latest date in conversion. I have a separate table that only records each date I run my price-collecting script that adds new data to the table. So the logic of getting latest date is essentially get the date in OpenDate table with highest ID.
So, the question is, when product grows to a huge amount, how do I paginate that product_views list? e.g. if I want to see 10 products in my web application, how to tell Django to only get those rows out of DB?
I can't (or don't know how to) use django.core.paginator.Paginator, because to create that 10 rows I want, Django needs to select all rows related to that 10 product names. But to figure out which 10 names to select, it first need to get all objects, then figure out which ones have the highest price on the latest date.
It seems to me the only solution would be to add something between Django and DB, like a cache, to store that ProductView objects. but other than that, is there a way to directly paginate produvt_views list?
I'm wondering if this makes sense:
The basic idea is, since I'll need to sort all product_views by the price on the "latest" date, I'll do that bit in DB first, and only get the list of product names to make it "paginatable". Then, I'll do a second DB query, to get all the products that have those product names, then construct that many product_views. Does it make sense?
To clear it a little bit, here comes the code:
So instead of
#def conversion():
all_products = Product.objects.all()
I'm doing this:
#def conversion():
# This would get me the latest available date
latest_date = OpenDate.objects.order_by('-id')[:1]
top_ten_priced_product_names = Product.objects
.filter(date__in = latest_date)
.order_by('-price')
.values_list('name', flat = True)[:10]
all_products_that_i_need = Product.objects
.filter(name__in = top_ten_priced_product_names)
# then I can construct that list of product_views using
# all_products_that_i_need
Then for pages after the first, I can modify that [:10] to say [10:10] or [20:10].
This makes the code pagination easier, and by pulling appropriate code into a separate function, it's also possible to do Ajax and all those fancy stuff.
But, here comes a problem: this solution needs three DB calls for every single query. Right now I'm running everything on the same box, but still I want to reduce this overhead to two(One or Opendate, the other for Product).
Is there a better solution that solves both the pagination problem and with two DB calls?

Add an IntegerField to a Datefield in a Django template

I'm trying to display the expiry date of a bonus from within a Django template. At the moment the opening_date is stored as a datefield and we store the bonus term as an integerfield. Unfortunately just trying to add the bonus term to the opening date fails and the furthest I have got so far is:
{{product_form.instance.opening_date|add:product_form.instance.bonus_term}}
I have tried just adding it to the month but unfortunately I need the whole date returned to display.
For a better idea of what I want is say the opening date was 01/01/2012 and the bonus term was 12, I want to display the expiry date of 01/01/2013. I realise this is probably better off being in the database but due to the way it has been previously set up there is a large amount of existing data that wouldn't have it.
Thanks.
I think that, for your scenario, the most elegant solution is to create a model method in your model that calcule expire date, then call the method in template:
In model:
class product(models.Model):
opening_date = ...
bonus_term = ...
def expire_date( self ):
return self.opening_date + timedelta( days = self.bonus_term )
In template:
{{product_form.instance.expire_date}}
I'm sure that you will call this method in more other lines of your code.

Grouping Django model entries by day using its datetime field

I'm working with an Article like model that has a DateTimeField(auto_now_add=True) to capture the publication date (pub_date). This looks something like the following:
class Article(models.Model):
text = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
I want to do a query that counts how many article posts or entries have been added per day. In other words, I want to query the entries and group them by day (and eventually month, hour, second, etc.). This would look something like the following in the SQLite shell:
select pub_date, count(id) from "myapp_article"
where id = 1
group by strftime("%d", pub_date)
;
Which returns something like:
2012-03-07 18:08:57.456761|5
2012-03-08 18:08:57.456761|9
2012-03-09 18:08:57.456761|1
I can't seem to figure out how to get that result from a Django QuerySet. I am aware of how to get a similar result using itertools.groupby, but that isn't possible in this situation (explanation to follow).
The end result of this query will be used in a graph showing the number of posts per day. I'm attempting to use the Django Chartit package to achieve this goal. Chartit puts a constraint on the data source (DataPool). The source must be a Model, Manager, or QuerySet, so using itertools.groupby is not an option as far as I can tell.
So the question is... How do I group or aggregate the entries by day and end up with a QuerySet object?
Create an extra field that only store date data(not time) and annotate with Count:
Article.objects.extra({'published':"date(pub_date)"}).values('published').annotate(count=Count('id'))
Result will be:
published,count
2012-03-07,5
2012-03-08,9
2012-03-09,1