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
Related
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).
Here is the db looks like:
id | Post | tag
1 | Post(1) | 'a'
2 | Post(1) | 'b'
3 | Post(2) | 'a'
4 | Post(3) | 'b'
And here is the code of the module
class PostMention(models.Model):
tag = models.CharField(max_length=200)
post = models.ForeignKey(Post,on_delete=models.CASCADE)
Here is the code of search,
def findPostTag(tag):
keywords=tag.split(' ')
keyQs = [Q(tag=x) for x in keywords]
keyQ = keyQs.pop()
for i in keyQs:
keyQ &= i
a = PostMention.objects.filter(keyQ).order_by('-id')
if not a:
a=[]
return a
(this code does not work correctly)
I withdraw the tags and save each as one row in the database. Now I want to make a search function that the user can input more than one keywords at the same time, like 'a b', and it will return 'Post(1)'. I searched for some similar situations, but seems all about searching for multi keywords in one row at the same time, like using Q(tag='a') & Q(tag='b'), it will search for the tag that equals to both 'a' and 'b'(in my view), which is not what I want (and get no result, obviously). So is there any solution to solve this? Thanks.
Is this cases, django provides, ManyToManyField, to work correctly you must to use:
class Tags(models.Model):
tag = models.CharField(unique=True, verbose_name='Tags')
class Post(models.Model): #your model
title = models.CharField(verbosone_name = 'Title')
post_tags = models.ManyToManyField(Tags, verbose_name='Choice your tags')
So you'll choice many tags to your post
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.
I have a ORM like this
from django.db import models,
class MyObject(models.Model):
class Meta:
db_table = 'myobject'
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=48)
status = models.CharField(max_length=48)
Imagine I have the following entries
1 | foo | completed
2 | foo | completed
3 | bar | completed
4 | foo | failed
What is the django ORM query that I have to make in order to get a queryset somewhat like the following
[{'name': 'foo', 'status_count': 'completed: 2, failed: 1'},
{'name': 'bar', 'status_count': 'completed: 1'}]
I started with the following but I don't know how to "merge" the two columns:
from django.db.models import Count
models.MyObject.objects.values(
'name',
'status'
).annotate(my_count=Count('id'))
The goal of all this to get a table where I can show something like the following:
Name | completed | failed
foo | 2 | 1
bar | 1 | 0
This should work as expected:
test = MyObject.objects.values('name').annotate(
total_completed=Count(
Case(
When(
status='completed', then=1), output_field=DecimalField()
)
),
total_failed=Count(
Case(
When(status='failed', then=1), output_field=DecimalField()
)
)
)
You need to include an "order_by" on to the end of your query to group the like items together.
Something like this should work:
from django.db.models import Count
models.MyObject.objects.values(
'name',
'status'
).annotate(my_count=Count('id')).order_by()
See https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#interaction-with-default-ordering-or-order-by for details.
EDIT: Sorry, I realize this doesn't answer the question about merging the columns... I don't think you can actually do it in a single query, although you can then loop through the results pretty easily and make your output table.
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)