Django annotate count for another queryset - python

Models:
class Regions(models.Model):
name = models.CharField(max_length=255, unique=True)
class Owners(models.Model):
name = models.CharField(max_length=255, null=False, unique=True)
url = models.URLField(null=True)
class Lands(models.Model):
region = models.ForeignKey(Regions, on_delete=models.CASCADE)
owner = models.ForeignKey(Owners, on_delete=models.PROTECT, null=True)
description = models.TextField(max_length=2000, null=True)
class LandChangeHistory(models.Model):
land = models.ForeignKey(Lands, on_delete=models.CASCADE, null=False, related_name='lands')
price = models.IntegerField()
size = models.IntegerField()
date_added = models.DateField(auto_now_add=True)
Queryset that works but i need it to be annotated in another queryset somehow:
lands_in_region = Lands.objects.values('region__name').annotate(count=Count('region_id'))
returns for example:
{'region__name': 'New York', 'count': 3}, {'region__name':
'Chicago', 'count': 2}
In the 2nd queryset i need count of lands available in region. But instead of real count, i always get count = 1. How to combine them? Im pretty sure i could do it in raw sql by joining two tables on field "region__id", but dont know how to do it in django orm.
f = LandFilter(request.GET, queryset=LandChangeHistory.objects.all()
.select_related('land', 'land__region', 'land__owner')
.annotate(usd_per_size=ExpressionWrapper(F('price') * 1.0 / F('size'), output_field=FloatField(max_length=3)))
.annotate(count=Count('land__region_id'))
)
For example. If it returns:
land1 | 100$ | 100m2 | New York
land2 | 105$ | 105m2 | New York
land3 | 102$ | 102m2 | Chicago
i need 1 more column, that counts for each land how many NewYork's and Chicago's are there
land1 | 100$ | 100m2 | New York | 2
land2 | 105$ | 105m2 | New York | 2
land3 | 102$ | 102m2 | Chicago | 1

This worked for me. Hope helps somebody.
First i tried to simply call Count() method right after filter, but that breaks query, since it tries to get data from DB immediately. But that was the correct way to think, so i added count annotate and selected it and it worked.
f = LandFilter(request.GET, queryset=LandChangeHistory.objects.all()
.select_related('land', 'land__region', 'land__owner')
.annotate(usd_per_size=ExpressionWrapper(F('price') * 1.0 / F('size'), output_field=FloatField(max_length=3)))
.annotate(count=Subquery(
Lands.objects.filter(region_id=OuterRef('land__region_id'))
.values('region_id')
.annotate(count=Count('pk'))
.values('count')))
)

Related

Python, Django: Query on combined models?

inside my app I have multiple models, like:
models.py:
class Company(models.Model):
name = models.CharField(max_length=100)
class Coworker(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
company = models.ForeignKey(Company, null=False, blank=False, on_delete=models.CASCADE)
As you can see, a Company can contain one, multiple or no Coworker! Is it possible to query Company but also receive the data from connected Coworker? For example something like this:
id | Company | Coworker
1 | Company_A | Coworker_A
2 | Company_B |
3 | Company_C | Coworker_B
4 | Company_C | Coworker_C
5 | Company_C | Coworker_D
6 | Company_D | Coworker_E
7 | Company_D | Coworker_F
8 | Company_E |
9 | Company_F | Coworker_G
10 | Company_F | Coworker_H
...
My problem is, that I can't query on the Coworker, because I don't want to miss those Companies that have no related data!
Thanks for your help and have a great day!
Quite simply, query by company and prefetch results for workers :
Company.objects.prefetch_related("coworker_set").all()
What this will do is give you a list of companies containing their list of workers in the attribute coworker_set. It will also populate those attributes in a single query (that's the whole point of prefetch_related).
More specifically, here is how you could use such a query :
for company in Company.objects.prefetch_related("coworker_set").all():
print(company.name)
for coworker in company.coworker_set.all():
print(coworker.first_name)
print(coworker.last_name)
This guarantees that you will iterate through all companies as well as through all coworkers, and you will only iterate through each one once (indeed, if you queried by coworkers you would see some companies multiple times and others none at all).

Django queryset with bidirectional relations

I'm struggling with making an efficient queryset with the following model:
class Connection(models.Model):
from = models.ForeignKey(User, related_name='from_connections')
to = models.ForeignKey(User, related_name='to_connections')
status = models.SmallIntegerField()
What I'm trying to do is, for a specified user, fetch a list of all his connections (so where he is either from or to) and annotate the status where he is from but also annotate the reverse status where he is to.
So for the example:
from | to | status
------------------------
A | B | 1
B | A | 0
C | A | 2
A | D | 2
the results would be something like this:
user | status | reverse_status
-----------------------------------
B | 1 | 0
C | None | 2
D | 2 | None
The closest solution I've got to so far is something like this:
qs = Connection.objects.filter(from_id=a_id)
reverse_qs = Connection.objects.filter(from_id=OuterRef("to_id"), to_id=a_id)
qs = qs.annotate(reverse_status=Subquery(reverse_status.values("status")[:1]))
This almost gives me what I need but since the queryset is including only results where user A is from, the results obviously don't contain anything for user C (from the example table above).
I also tried exploring the route with using related_names like
User.objects.filter(Q(from_connections__to=a_id)|Q(to_connections__from=a_id).annotate...
but this approach didn't get me far.
Does anyone have any ideas how to solve this using Django ORM? Much appreciated.
When you create a model having 2 ForeignKeys, you have to specify a related_name, Example :
class Connection(models.Model):
from = models.ForeignKey(User, related_name = 'from_connection')
to = models.ForeignKey(User, related_name = 'to_connection')
status = models.SmallIntegerField()
That will help in backwards relationships, more details in the docs

Django ORM: Joining a table to itself

I have a table with cryptocurrency prices:
id | price | pair_id | exchange_id | date
---+--------+---------+-------------+---------------------------
1 | 8232.7 | 1 | 1 | 2018-02-09 09:31:00.160837
2 | 8523.8 | 1 | 2 | 2018-02-09 09:31:01.353998
3 | 240.45 | 2 | 1 | 2018-02-09 09:31:02.524333
I want to get the latest prices of a single pair from different exchanges. In raw SQL, I do it like this:
SELECT b.price, b.date, k.price, k.date, AVG((b.price + k.price) / 2)
FROM converter_price b JOIN converter_price k
WHERE b.exchange_id=1 AND k.exchange_id=2 AND b.pair_id=1 AND k.pair_id=1
ORDER BY b.date DESC, k.date DESC LIMIT 1;
8320.1|2018-02-09 11:23:00.369810|8318.2|2018-02-09 11:23:06.467424|8245.05199328066
How to do such query in the Django ORM?
EDIT: Adding models.py. I understand that it's quite likely that I'll need to update it.
from django.db import models
# Create your models here.
class Pair(models.Model):
identifier = models.CharField('Pair identifier', max_length=6)
def __str__(self):
return self.identifier
class Exchange(models.Model):
name = models.CharField('Exchange name', max_length=20)
def __str__(self):
return self.name
class Price(models.Model):
pair = models.ForeignKey(Pair, on_delete=models.CASCADE)
exchange = models.ForeignKey(Exchange, on_delete=models.CASCADE)
price = models.FloatField(default=0)
date = models.DateTimeField(auto_now=True, db_index=True)
def __str__(self):
return '{} - {}: {} ({})'.format(
self.date, self.pair, self.price, self.exchange)
EDIT2: To clarify what I'm really after.
I want the latest price with pair_id=1 and exchange_id=1, and the latest price with pair_id=1 and exchange_id=2. With a single query, and without any subsequent processing in Python - of course I can get Price.objects.all() and then search for it myself, but that's not the right way to use the ORM.
The way to do this with raw SQL is joining the table to itself. Showing how to join a table to itself using the Django ORM would (probably) answer the question.

Django many to many avoiding duplication

I'm getting duplication in my path (many to many) table, and would like it only to contain unique items.
models.py
class Image(models.Model):
path = models.CharField(max_length=128)
class User(models.Model):
username = models.CharField(max_length=32)
created = models.DateTimeField()
images = models.ManyToManyField(Image, through='ImageUser')
class ImageUser(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ForeignKey(Image, on_delete=models.CASCADE)
But, I seem to be able to create more than one image with the same path. I would like one unique image path to point to multiple users without having duplicate images in the Image table.
u = User.objects.create(username='AndyApple')
i = Image(path='img/andyapple.jpg')
i.save()
ui = ImageUser(user=u, image=i)
ui.save()
u2 = User.objects.create(username='BettyBanana')
ui = ImageUser(user=u2, image=i)
This seems to create two rows in the images table for the same image. The docs suggest this should not happen ManyToManyField.through
Thanks!
Are you sure your code adds duplicates to Image and not to ImageUser (which is how many-to-many table works)?
-------------------- --------------------------- ----------------------------
| Users | | ImageUser | | Image |
-------------------- --------------------------- ----------------------------
| id | username | | id | user_id | image_id | | id | path |
-------------------- --< --------------------------- >-- ----------------------------
| 1 | AndyApple | | 1 | 1 | 1 | | 1 | 'img/andyapple.jpg' |
-------------------- --------------------------- ----------------------------
| 2 | BettyBanana | | 2 | 2 | 1 |
-------------------- ---------------------------
But anyway, the problem is not right here, if you want:
"one unique image path to point to multiple users without having
duplicate images in the Image table."
then you have to define the field as unique, see code sample below. In this case if you will try to save the image with the pathy that is already in DB the exception will be raised. But note that in this case if two users upload different images, but with the same name, then the last uploaded image will be used for both users.
class Image(models.Model):
path = models.CharField(max_length=128, unique=True)

If jinja markup is in string format, how do I use it in django and jinja2?

Part 1 of this question asked and answered separately.
I have a Report and a ReportTemplate.
+----+----------+---------------+-------------+
| id | title | data | template_id |
+----+----------+---------------+-------------+
| 1 | report 1 | {data: [...]} | 1 |
+----+----------+---------------+-------------+
reports table
+----+-----------+---------------+------------+
| id | title | markup | css |
+----+-----------+---------------+------------+
| 1 | template1 | <doctype!>... | body {.... |
+----+-----------+---------------+------------+
templates table
A Report belongs to a ReportTemplate. A ReportTemplate has many Report.
I have a custom admin action for Report in admin.py called print_as_pdf
import logging
logger = logging.getLogger('reports.admin')
from django.contrib import admin
# Register your models here.
from reports.models import Report, ReportTemplate
class ReportAdmin(admin.ModelAdmin):
fields = ['commodity',
'date',
'trade_period',
'quantity_cutoff',
'data',
'template',
'title']
actions = ['print_as_pdf']
def print_as_pdf(self, request, queryset):
logger.debug('anything')
for report in queryset:
markup = report.template.markup
logger.debug(markup)
return
print_as_pdf.short_description = 'Generate as pdf'
These are models:
class ReportTemplate(models.Model):
title = models.CharField(max_length=50)
markup = models.TextField(default = 'markup here...')
styles = models.TextField(default = 'styles here...')
# __unicode__ on Python 2
# __str__ on Python 3
def __unicode__(self):
return self.title
class Report(models.Model):
title = models.CharField(max_length=50)
commodity = models.CharField(max_length=10)
date = models.DateTimeField('date traded')
trade_period = models.CharField(max_length=10, default='open')
quantity_cutoff = models.IntegerField(default=0)
printed = models.BooleanField(default=0)
datetime_email_sent = models.DateTimeField('date email sent', blank=True, null=True)
data = models.TextField(default = 'data here...')
template = models.ForeignKey(ReportTemplate)
What I want to do is:
retrieve the associated ReportTemplate and its markup field value
put the data field value of the Report through the markup value in 1 which is written with jinja2 markup
use weasyprint and print out the data-filled markup from 2 as pdf
I am stuck at step 2.
Since the markup I have retrieved is in a string format, how do I run it through with the data I have?
Adjusting from Jinja 2 documentation, it could be as simple as
>>> template = Template(report.markup)
>>> template.render(report=report)
<html>...
If you want to store the output into another variable
>>> final_markup = template.render(report=report)
provided that your templates expect to get the whole report as the report template parameter.

Categories

Resources