using aggregate with values : group by : django - mysql

i want to return a product names in distinct , and also i've used aggregate to some calculation as well
this is my models.py
class CustomerInvoice(models.Model):
customer = models.CharField(max_length=50)
items = models.ManyToManyField(Product,through='ProductSelecte')
date = models.DateTimeField(auto_now_add=True)
class ProductSelecte(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
products= models.ForeignKey(CustomerInvoice,on_delete=models.CASCADE,related_name='product')
qnt= models.IntegerField(default=1)
price = models.IntegerField()
cash = models.IntegerField()
i want to make table to track customers activity
and this is my query
context['clients'] = ProductSelecte.objects.filter(
products__customer=self.object.name).values('product').annotate(quantity=Sum(F('qnt'))).order_by('product')
till here works fine but i also need to aggregate price using this
.aggregate(
total_price=Sum(F('price')))
it raise this error
'dict' object has no attribute 'order_by'
thanks for your helping
UPDATE
for example at in first invoice of john 4th june john bought two pen price:20$ with 3 book price : 30$ total price for this invoice :50$ , and for second invoice 5th july he buy 1 pen price:10$ with 2 copybook price:20$ total price:30 , i need a result like this
client : john , activities : 3 pen(total pens he bought till now),price pens : 30 (total pens price he bought till now) , 3 book(total books he bought till now) ; price:30(total books price he bought till now) ,2 copybook ;price:20
and then total price of both invoice : 30+50 = 80

You should make the annotate per client per product and then perform some "grouping" at the Django/Python level:
from django.db.models import F, Sum
from itertools import groupby
from operator import itemgetter
qs = ProductSelecte.objects.values(
customer=F('products__customer')
product=F('product')
).annotate(quantity=Sum('qnt')).order_by('customer', 'product')
data = {
k: list(vs)
for k, vs in groupby(qs, itemgetter('customer'))
}
Here data is a dictionary that maps the name of the customer to a list of dictionaries that contain the id of the product, the customer name and the quantity.
If you pass it to the template, you can for example render this with:
<ul>
{% for c, vs in data.items %}
<li>{{ c }}</li>
<ul>
{% for v in vs %}
<li>{{ v.product }}: {{ v.quantity }}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
EDIT: based on the updated request, you should make an annotate and aggregate request:
class ProfileClientDetailView(LoginRequiredMixin,DetailView):
# …
def get_object(self):
return get_object_or_404(Client,pk=self.kwargs['pk'])
def get_context_data(self,*args,**kwargs):
data = super().get_context_data(*args,**kwargs)
data['products'] = ProductSelecte.objects.filter(
products__customer=self.object.name
).values('product').annotate(
quantity=Sum('qnt'),
total_price=Sum('price')
).order_by('product')
data['total'] = ProductSelecte.objects.filter(
products__customer=self.object.name
).aggregate(
quantity=Sum('qnt'),
total_price=Sum('price')
)
return data
Then it can be rendered as:
<ul>
{% for pr in products %}
<li>{{ pr.product }}: {{ pr.total_price }} ({{ pr.quantity }} items)</li>
</ul>
TOTAL: {{ total.total_price }} ({{ total.quantity }} items)

Related

Get cumulative sum using field names set earlier in the query

I have a for loop embedded in my query that takes some categorical variables, renames it with a "total" in front of it (also replaces spaces with underscores), and then sums a different field, AMOUNT, tied to the variable.
{% set columns = ("transfer out", "transfer in", "fee" %}
{% for column in columns %}
, round(
sum(case when table.variable = '{{ column }}' then table.amount else 0 end)
, 2) as total_{{ column|replace(" ", "_")|replace("-", "_") }}s
{% endfor %}
Say this creates:
column
amount
total_transfer_outs
-90
total_transfer_ins
100
total_fees
-10
In the same query, I want to be able to get the sum of the 3 new columns that were created.
sum(total_transfer_outs + total_transfer_ins + total_fees ) = (-90 +100 -10) = 0. Note: this list could expand to >3 variables in the future, so I assume I need to use some sort of loop and cumulative sum to make this scalable.
Turns out you can just loop over it using a plus sign and starting from zero. I was trying to solve this using cumulative sum and/or append functions. This works though...
,0
{% for column in columns %}
+ (total_{{ column|replace(" ", "_")|replace("-", "_") }}s)
{% endfor %}
as sum_of_everything

i want different between current year and given year in jekyll

i am want get different of number in years:
For example : 2021-2010 = 11
So i am doing similar as my code:
{{assign currentYear = 'now' | date: "%Y"}}{{ assign AllYears = currentYear | minus: 2010 }}
But its show only -2010.
So please help me.
Maybe something like this?
(not tested)
{% assign currentYear = 'now' | date: "%Y" | plus:0 %}
{% assign AllYears = currentYear | minus: 2010 %}
{{ AllYears }}

Liquid filter by date (Jekyll)

I'm trying to filter posts by specific dates in Jekyll. For example, I want to filer all posts posts today, in the past 30 days and everything before that.
However I'm running into a couple of issues:
How can I get the current date, starting at 00:01AM
How do I get the current date, and subtract 30 days
How can I use where_exp to filter by this date
Currently I tried to do something like this, but this converts the date into a string, and can't be used in the where_exp:
{% capture thirty_days_ago %}{{'now' | date: '%s' | minus: 2592000 }}{% endcapture %}
{% assign last_30_days_posts = site.posts | where_exp:"post", "post.posted_on > thirty_days_ago" %}
Liquid error (line 22): comparison of Time with String failed in index.html
I could do a simple check when looping over the posts, but would prefer using a filter before this.
{% capture thirty_days_ago %}{{'now' | date: '%s' | minus: 2592000 }}{% endcapture %}
{% for post in site.posts %}
{% capture post_date %}{{ post.posted_on | date: '%s' | plus: 0 }}{% endcapture %}
{% if job_date > thirty_days_ago %}
{% include components/job.html job=job %}
{% endif %}
{% endfor %}
For anyone looking at a solution; I ended up writing a custom filter:
def last_month_filter(posts)
now = DateTime.now
today = DateTime.new(now.year, now.month, now.day, 0, 0, 0, now.zone)
target = today - 30
posts.select do |post|
postedOn = post.data['posted_on'].to_datetime
if postedOn < today && postedOn > target
post
end
end
end
Usage:
{% assign last_30_days = site.posts | last_month_filter | sort:"posted_on" | reverse %}

In Jekyll, how to show "posts from last week"

I'm not sure to get the liquid syntax to help me pull posts that were published during the same week as the build date. Is that possible?
There are simpler ways to get a list of recent posts, but this approach would be useful for my project. I've tried several things I found by searching but no joy yet.
Before even giving this answer a second thought, consider the fact that, at the very least the first part of this answer doesn't really work, because jekyll is a static site generator and therefore the shown posts are relative to the last build date, which may not be the same as current date.
The second part of the answer goes a bit deeper into the idea of actually generating a list of "recent posts", rather than "posts from last week".
To basically explain the code in words: First we get current year and current week, then we loop through every post and compare current year and current week to the week and year of the post. If they match, the post is shown.
Show — Build Week:
{% assign currentYear = site.time | date: "%Y" %}
{% assign currentWeek = site.time | date: "%W" %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postWeek = post.date | date: "%W" %}
{%- if currentYear == postYear and currentWeek == postWeek -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
Show — Build Day and 6 Days Prior:
{% assign currentYear = site.time | date: "%Y" %}
{% assign currentDay = site.time | date: "%j" | plus: 0 %}
{% assign currentDay_minus_week = site.time | date: "%j" | minus: 7 %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postDay = post.date | date: "%j" | plus: 0 %}
{%- if currentYear == postYear and postDay > currentDay_minus_week and postDay <= currentDay -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
In an effort to sort of salvage this answer, though it already kind of veered off course.... I wrote this code with similar logic, but this time it gets the year and week of the latest post and shows all posts posted that year and that week.
This would be bulletproof in terms of showing something even if you keep building the site without making new posts. But it also just shows only one post, if your last post is the only post posted that week, which may be a bit dumb...
On the other hand, the simplest method for showing "recent posts" is probably just using the limit and limit recent posts to like last 5 posts or something like that: {%- for post in site.posts limit: 5 -%}
Show — Latest Post Week:
{% assign latestPost_year = site.posts.first.date | date: "%Y" %}
{% assign latestPost_week = site.posts.first.date | date: "%W" %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postWeek = post.date | date: "%W" %}
{%- if latestPost_year == postYear and latestPost_week == postWeek -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
Show — Latest Post Day and 6 Days Prior
{% assign latestPost_year = site.posts.first.date | date: "%Y" %}
{% assign latestPost_day = site.posts.first.date | date: "%j" | plus: 0 %}
{% assign latestPost_day_minus_week = site.posts.first.date | date: "%j" | minus: 7 %}
{%- for post in site.posts -%}
{% assign postYear = post.date | date: "%Y" %}
{% assign postDay = post.date | date: "%j" | plus: 0 %}
{%- if latestPost_year == postYear and postDay > latestPost_day_minus_week and postDay <= latestPost_day -%}
{{ post.title }}
{%- endif -%}
{%- endfor -%}
The above solution works, but doesn't span years.
So I utilized the concepts and came up with a simpler and more flexible solution for filtering for a timeframe (which can be any variable time span of seconds, hours, days, weeks, or months). My solution has fewer variables, and less logic.
Liquid date/time uses unix timestamp (%s = seconds since 1970). So I kept the timeframe in seconds and do the conversion for the length of time. 86400 = 1 day, 604800 = 1 wk, 2678400 = 31 days, ... and so on.
The code also assumes your posts use last_modified_at in your post frontmatter. You could substitute for post.date if you're not.
Simple Solution
List posts within last week
{% assign timeframe = 604800 %}
{% for post in site.posts %}
{% assign post_in_seconds = post.last_modified_at | date: "%s" | plus: 0 %}
{% assign recent_posts = "now" | date: "%s" | minus: timeframe %}
{% if post_in_seconds > recent_posts %}
{{ post.title }}
{% endif %}
{% endfor %}
Alternative
List posts within timeframe and flag new or modified
This code will list all posts to within a limit and flag an posts as new or modified in timeframe. Length of list is limited to maxposts. Note: css class is designed to utilize Bootstrap, remove/edit to your liking.
timeframe 2419200 = seconds in 4 weeks
maxposts = 10
label_FOOBAR just clean way to handle html with liquid
Example Results with new/modified flags & dates
Code
{% assign timeframe = 2419200 %}
{% assign maxposts = 10 %}
{% assign date_format = site.minima.date_format | default: "%m/%d" %}
<ul class="post-list text-muted list-unstyled">
{% for post in site.posts limit: maxposts %}
{% assign post_in_seconds = post.last_modified_at | date: "%s" | plus: 0 %}
{% assign recent_posts = "now" | date: "%s" | minus: timeframe %}
{% assign post_updated = post.last_modified_at | date: date_format %}
{% capture post_date %}<small>{{ post.date | date: date_format }}</small>{% endcapture %}
{% if post_in_seconds > recent_posts %}
{% capture label_new %}<span class="label label-primary">new</span>{% endcapture %}
{% if post.last_modified_at > post.date %}
{% assign label_new = '' %}{% comment %}Clear NEW if modified{% endcomment %}
{% capture label_updated %}<span class="label label-info">Updated <span class="badge">{{ post_updated }}</span></span>{% endcapture %}
{% endif %}
{% endif %}
<li>
<h4>{{ post_date }}
<a class="post-link" href="{{ post.url | relative_url }}">
{{ post.title | escape }}</a> {{ label_new }}{{ label_updated }}
</h4>
</li>
{% assign post_date = '' %}
{% assign label_updated = '' %}
{% endfor %}
</ul>
You never know when Jekyll builds, so Jekyll should output a big(ger) list of posts. You can use the code of Joonas for this. Then you should use javascript to hide the non-relevant posts (those older than one week).
This can easily be done by adding a custom attribute to your list, like this:
<li date="{{ post.date }}">{{ post.title }}</li>
Use jQuery (or vanilla js) to hide old posts:
// loop through all list items with a date
$('li[date]').each(function(){
// create a postDate in a date object
var postDate = new Date($(this).attr('date'));
// create an object with the date of one week ago
var oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
// compare dates and hide old posts
if(postDate<oneWeekAgo) $(this).hide();
});

Get date difference using adx studio liquid template

Im trying to get the day difference between two dates using adx studio liquid templates, my current code is
{%assign expirydate = bh_expirydate | date:'MM/dd/yyyy' %}
{%assign datenow = now | date: 'MM/dd/yyyy' %}
{%assign diffdays = expirydate | minus: datenow %}
I know that this line of code will not work, but the logic is that. I just can seem to find appropriate example. Can someone shed some light on this one?
Not sure how you would do this without javascript, but here is my solution assuming bh_expirydate exists:
Days till expiry: <span id="expiryDays"></span>
<script>
Date.daysBetween = function( date1, date2 ) {
//Get 1 day in milliseconds
var one_day=1000*60*60*24;
// Convert both dates to milliseconds
var date1_ms = date1.getTime();
var date2_ms = date2.getTime();
// Calculate the difference in milliseconds
var difference_ms = date2_ms - date1_ms;
// Convert back to days and return
return Math.round(difference_ms/one_day);
}
var dt1 = new Date();
var dt2 = new Date('{{ bh_expirydate| date: "yyyy/MM/dd" }}');
$('#expiryDays').text(Date.daysBetween(dt1, dt2));
</script>
Calculate years since first date until now
With the first assign we are taking the current time and subtracting a date of birth.
This will result in a timespan. Convert it to a string and split to get the number of days in the first array element.
The second assign will take that first string element with the number of days and convert it into an integer.
The display will divide by 365 to give the years since dob.
{% if item.dob %}
{% assign words = now | minus: item.dob | string | split: '.' %}
{% assign days = words.first | integer %}
{{ days | divided_by: 365 }}
{% endif %}