How to get the Primary key of annotate Count - django

Hi stackoverflow community, my question is about django annotate.
Basically what I am trying to do is to find duplicated value with same values from two different fields in two different tables.
This is my models.py
class Order(models.Model):
id_order = models.AutoField(primary_key=True)
class OrderDelivery(models.Model):
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True, blank=True)
delivery_address = models.TextField()
class OrderPickup(models.Model):
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True, blank=True)
pickup_date = models.DateField(blank=True, null=True)
This is my current code:
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)
Based on what I have, I am getting result like this (delivery_address is omitted for privacy purpose):
{'orderdelivery__delivery_address': '118A', 'orderpickup__pickup_date': datetime.date(2022, 3, 9), 'duplicated': 2}
{'orderdelivery__delivery_address': '11', 'orderpickup__pickup_date': datetime.date(2022, 3, 2), 'duplicated': 6}
{'orderdelivery__delivery_address': '11 A ', 'orderpickup__pickup_date': datetime.date(2022, 3, 3), 'duplicated': 5}
{'orderdelivery__delivery_address': '21', 'orderpickup__pickup_date': datetime.date(2022, 3, 10), 'duplicated': 3}
{'orderdelivery__delivery_address': '642', 'orderpickup__pickup_date': datetime.date(2022, 3, 7), 'duplicated': 2}
{'orderdelivery__delivery_address': '642', 'orderpickup__pickup_date': datetime.date(2022, 3, 8), 'duplicated': 2}
{'orderdelivery__delivery_address': 'N/A,5', 'orderpickup__pickup_date': datetime.date(2022, 3, 8), 'duplicated': 19}
Is there a way to get the id_order of those 'duplicated'?
I have tried include id_order in .values() but the output will not be accurate as the annotation is grouping by the id_order instead of delivery_address.
Thank you in advance

You can get the smallest (or largest) item with a Min [Django-doc] (or Max) aggregate:
from django.db.models import Min
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
min_id_order=Min('id_order')
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)
or for postgresql, you can make use of the ArrayAgg [Django-doc] to generate a list:
# PostgreSQL only
from django.contrib.postgres.aggregates import ArrayAgg
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
min_id_order=ArrayAgg('id_order')
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)

Related

Django: Sum of annotated field after grouping

I have the following two models:
class ProductionSet(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
line = models.ForeignKey(Line, on_delete=models.CASCADE)
class ProductionSetEntry(models.Model):
productionset = models.ForeignKey(
ProductionSet, on_delete=models.CASCADE, related_name="set"
)
audited_at = models.DateTimeField(
verbose_name=_("Audited at"), null=True, blank=True
)
What I want to achieve is a result set grouped for each line with two counts: an entries total count and a count for fully audited sets. Sample result:
<QuerySet [{'line': 1, 'sum_fully_audited': 0, 'total': 43}, {'line': 2, 'sum_fully_audited': 0, 'total': 51}, {'line': 3, 'sum_fully_audited': 2, 'total': 4}]>
I'm able to add the fully_audited to each row before grouping by:
ProductionSet.objects.annotate(
audited=Count("set", filter=Q(set__audited_at__isnull=False)),
).annotate(
fully_audited=Case(
When(audited=Count("set"), then=1),
default=0,
output_field=IntegerField(),
)
)
Following query:
ProductionSet.objects.annotate(
audited=Count("set", filter=Q(set__audited_at__isnull=False)),
).annotate(
fully_audited=Case(
When(audited=Count("set"), then=1),
default=0,
output_field=IntegerField(),
)
).values(
"line", "fully_audited"
).annotate(
total=Count("id", distinct=True),
sum_fully_audited=Sum("fully_audited"),
).order_by(
"line"
)
raises django.core.exceptions.FieldError: Cannot compute Sum('fully_audited'): 'fully_audited' is an aggregate while
ProductionSet.objects.annotate(
audited=Count("set", filter=Q(set__audited_at__isnull=False)),
).annotate(
fully_audited=Case(
When(audited=Count("set"), then=1),
default=0,
output_field=IntegerField(),
)
).values(
"line", "fully_audited"
).annotate(
total=Count("id", distinct=True),
).order_by(
"line"
)
results in <QuerySet [{'line': 1, 'fully_audited': 0, 'total': 43}, {'line': 3, 'fully_audi... which looks good so far but obviously has no sum_fully_audited yet.

Django Query month wise with last 6 month

I am trying to query and the group is the Order of the last 6 months.
and this is my models:
class Order(models.Model):
created_on = models.DateTimeField(_("Created On"), auto_now_add=True)
and this is my method to parse month:
from django.db.models import Func
class Month(Func):
"""
Method to extract month
"""
function = 'EXTRACT'
template = '%(function)s(MONTH from %(expressions)s)'
output_field = models.IntegerField()
And this is my query:
current_date = date.today()
months_ago = 6
six_month_previous_date = current_date - timedelta(days=(months_ago * 365 / 12))
order = Order.objects.filter(
created_on__gte=six_month_previous_date,
).annotate(
month=Month('created_on')
).values(
'month'
).annotate(
count=Count('id')
).values(
'month',
'count'
).order_by(
'month'
)
In my database order table, there is only on entry:
So it is returning
[{'month': 10, 'count': 1}]
But i dont want like this, i want like these of last 6 month, if in one month, there is no sales, it should return the count: 0
Like thise bellow:
[
{'month': 10, 'count': 1},
{'month': 9, 'count': 0}
{'month': 8, 'count': 0}
{'month': 7, 'count': 0}
{'month': 6, 'count': 0}
{'month': 5, 'count': 0}
]
A database works under the closed world assumption, so it will not insert rows with 0. You can however post-process the list.
from django.utils.timezone import now
order = Order.objects.filter(
created_on__gte=six_month_previous_date,
).values(
month=Month('created_on')
).annotate(
count=Count('id')
).order_by('month')
order = {r['month']: r['count'] for r in order}
month = now().month
result = [
{'month': (m % 12)+1, 'count': order.get((m % 12) + 1, 0)}
for m in range(month-1, month-8, -1)
]
Note that Django already has an ExtractMonth function [Django-doc].

How to set ordering and number of items for each item in filter list?

How to set ordering and number of items for each item in filter list?
class Category(models.Model):
category = models.CharField(max_length = 20)
Class Question(TimeStampedModel):
category = models.ForeignKey(Category, related_name='question_set', on_delete=models.CASCADE)
question = models.TextField(_('Question Text'))
Category.objects.filter(category__in=['Sport', 'History']).values_list('question_set',flat=True).order_by('?')[:3]
<QuerySet [6, 7, 3]>
I need result like:
<QuerySet [1, 3, 2, 7, 11, 9]>

Django How to access another object by having a user name?

I have this in models:
class CustomUser(AbstractUser):
selectat = models.BooleanField(default=False)
def __str__(self):
return self.username
class Score(models.Model):
VALUE = (
(1, "Nota 1"),
(2, "Nota 2"),
(3, "Nota 3"),
(4, "Nota 4"),
(5, "Nota 5"),
(6, "Nota 6"),
(7, "Nota 7"),
(8, "Nota 8"),
(9, "Nota 9"),
(10, "Nota 10"),
)
user_from = models.ForeignKey(settings.AUTH_USER_MODEL, default=0)
user_to = models.ForeignKey(settings.AUTH_USER_MODEL, default=0, related_name='user_to')
nota = models.PositiveSmallIntegerField(default=0, choices=VALUE)
def __str__(self):
return str(self.user_to)
How can i access the score objects by having the user?
When i give the user to score object i can get the notes.
x = Score.objects.filter(user_to__username='Fane')
x
<QuerySet [<Punctaj: Fane>, <Punctaj: Fane>]>
for a in x:
print(a.nota)
1
5
I want to use something like this:
y = CustomUser.objects.get(id=7)
x = x.score.all()
for a in x:
print(a.nota)
1
5
But this won't work, it's giving me:
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'CustomUser' object has no attribute 'score'
You have two foreign keys from CustomUser to Score. The first one, user_from, does not set a related_name, so it uses the default, which is score_set:
x = y.score_set.all()
The second does set a related_name, so you use that:
x = y.user_to.all()
Note that this does not make much sense as a related name, since it points to scores, not users; it should probably be something like scores_to_user.

How to join one table with grouped values from different query?

I have a models like this:
class Subscription(models.Model):
visable_name = models.CharField(max_length=50, unique=True)
recipe_name = models.CharField(max_length=50)
website_url = models.URLField()
class User(models.Model):
username = models.CharField(max_length=50)
class UserSubs(models.Model):
subscription = models.ForeignKey(Subscription, to_field='visable_name')
user = models.ForeignKey(User, to_field='username')
And I want to prepare simple ranking, so I came up with something like this:
rank = UserSubs.objects.values('subscription').
annotate(total=Count('user')).
order_by('-total')`
what gives:
>> rank
[
{'total': 3, 'subscription': u'onet'},
{'total': 2, 'subscription': u'niebezpiecznik'},
{'total': 1, 'subscription': u'gazeta'}
]
what I need, is similar list of full objects:
[
{'total': 3, 'subscription': <Subscription: onet>},
{'total': 2, 'subscription': <Subscription: niebezpiecznik>},
{'total': 1, 'subscription': <Subscription: gazeta>}
]
I am not sure, whether 'select_related' will be helpful here, but I can't figured out how to use it :(
Better to build your query from Subscription because you need it:
Subscription.objects.annotate(total=models.Count('usersubs')).order_by('-total')
Maybe you can use dict and list comprehension, and filter this as simple python objects:
d = {sub.visable_name: sub for sub in Subscriptions.objects.all()}
new_rank = [
{
'total': row['total'],
'subscriptions': d[row['subscriptions']]
}
for row in rank
]
what will give:
>> new_rank
[
{'total': 3, 'subscriptions': <Subscriptions: onet>},
{'total': 2, 'subscriptions': <Subscriptions: niebezpiecznik>},
{'total': 1, 'subscriptions': <Subscriptions: gazeta.pl>}
]