How to join 2 tables in django - django

I have 2 models which i wanna join
class CollectionBook(models.Model):
collection = models.ForeignKey('Collection')
book = models.ForeignKey('Book')
class Meta:
unique_together = (('collection', 'book'))
class Book(models.Model):
main_author = models.ForeignKey('Author', verbose_name=u'Main author')
publisher = models.ForeignKey('Publisher', verbose_name=u'Publisher')
title = models.CharField(unique=False, max_length=255, verbose_name=u'Title')
text = models.TextField(verbose_name=u'Book text', max_length=523000)
I tried to do it this way
book = Book.objects.extra(
select = {'id':'mainapp_collectionbook.book_id'})
book.query.join((None,'mainapp_collectionbook',None,None))
connection = (
Book._meta.db_table,
CollectionBook.book.field,
)
book.query.join(connection, promote=True)
But it didn't work out and gave me error
Could you offer me another pythonic solutions of this problem or improve my way of doing it, I don't wanna write sql query, I hope that there are better django orm functions for this

Taking the clarification from the comment:
I have another table "Collection". I want select books where collection to which book belongs is in allowed collection list. I generate collection list before this query
First, replace your explicit CollectionBook table with a ManyToManyField on either Book or Collection. For this example I'll assume it's on Book, since that keeps the syntax clearer and the Collection model isn't shown.
class Book(models.Model):
main_author = models.ForeignKey('Author', verbose_name=u'Main author')
publisher = models.ForeignKey('Publisher', verbose_name=u'Publisher')
title = models.CharField(unique=False, max_length=255, verbose_name=u'Title')
text = models.TextField(verbose_name=u'Book text', max_length=523000)
collection = models.ManyToManyField('Collection')
Then you can use __ syntax to follow relationships:
Books.objects.filter(collection__in=SOME_LIST_OF_COLLECTIONS).distinct()
If you need additional information on the book/collection relation, EG a date collected or something, specify a through argument to the ManyToManyField.

if I understand correctly, you can try it, but don't forget to change the YOU_CONDITION
allow_collections = CollectionBook.objects.filter(YOU_CONDITION)
books_pks = allow_collections.values_list('book__pk', flat=true)
Book.objects.filter(pk__in=books_pks)

Related

Selecting first item in a subquery of many to many relationship in django

Consider having a Book model with many Comments (from different Users) which may belong to many Categories.
class Category(Model):
title = CharField(max_length=200)
class Book(Model):
title = CharField(max_length=200)
categories = ManyToManyField(Category)
class User(Model):
name = CharField(max_length=200)
class Comment(Book):
content = TextField()
book = ForeignKey(Book, null=False, on_delete=CASCADE)
user = ForeignKey(User, null=False, on_delete=CASCADE)
I want to select comments for an specific Book, and for each Comment, annotate it with the number of comments that the writer of that Comment wrote for the first Category of that book. And I want to this in a single query. Something like this:
def get_commnets(book):
main_category = book.categories.first()
n_same_comments_subquery = Subquery(
Comments.objects
.filter(user__id=OuterRef('user_id'), book__category__first=main_category)
.annotate(count=Count('pk'))
.values('count')
)[:1]
comments = (
book.comment_set
.annotate(n_same_comments=n_same_comments_subquery)
)
return comments
The previous piece of code does not work, obviously. This is because there is not any lookup like first. I've tried many other solutions, but non of them worked.
How can I achieve this goal?
Thank you in advance.
The trick was to use through:
def get_commnets(book):
main_category_id = book.categories.first().id
n_same_comments_subquery = Subquery(
Comments.objects
.alias(first_book_category_id=Book.categories.through.objects.filter(book_id=OuterRef('book_id')).values('id')[:1])
.filter(user__id=OuterRef('user_id'), first_book_category_id=main_category_id)
.annotate(count=Count('pk'))
.values('count')[:1]
)
comments = (
book.comment_set
.annotate(n_same_comments=n_same_comments_subquery)
)
return comments

Django query joining two tables

I am new in django. I want to create a query in django I tried with select_related but I don't know how to insert the second part of the condition: AND model1.qty >= model2.items
I've tried:
Model1.objects.select_related('model2).filter(model1.qty__gte=?)
But it's not working properly.
Below is the SQL query which I want to implement with django queryset:
SELECT model1.name,model2.name WHERE model1.id=model2.model1.id AND model1.qty >= model2.items
My models:
class Article(models.Model):
date_crea = models.DateTimeField('Créer le', auto_now_add=True)
designation = models.TextField('designation', max_length=500)
seuil = models.IntegerField('Seuil d\'alerte')
class Stock(models.Model):
date_crea = models.DateTimeField('Créer le', auto_now_add=True)
article = models.ForeignKey(Article, on_delete=models.CASCADE)
qte_reel = models.IntegerField('stock reel',default=0)
You use an F expression to refer to the value of a field in the database. There have to be relations to follow from the current object to the field on the object that you want to compare against.
I'm not clear how the question relates to the posted models, but an F expression can follow a foreign key so
Stock.objects.filter( qte_reel__gte = F( 'article__seuil' ))
would work, if those fields are the ones you want to compare.

Django query of table with implicit join on itself

I've read the documentation and looked at other questions posted here, but I can't find or figure out whether this is possible in Django.
I have a model relating actors and movies:
class Role(models.Model):
title_id = models.CharField('Title ID', max_length=20, db_index=True)
name_id = models.CharField('Name ID', max_length=20, db_index=True)
role = models.CharField('Role', max_length=300, default='?')
This is a single table that has pairs of actors and movies, so given a movie (title_id), there's a row for each actor in that movie. Similarly, given an actor (name_id), there's a row for every movie that actor was in.
I need to execute a query to return the list of all title_id's that are related to a given title_id by a common actor. The SQL for this query looks like this:
SELECT DISTINCT r2.title_id
FROM role as r1, role as r2
WHERE r1.name_id = r2.name_id
AND r1.title_id != r2.title_id
AND r1.title_id = <given title_id>
Can something like this be expressed in a single Django ORM query, or am I forced to use two queries with some intervening code? (Or raw SQL?)
Normally I would break this into Actor and Movie table to make it easier to query, but your requirement is there so I will give it a go
def get_related_titles(title_id)
all_actors = Role.objects.filter(title_id=title_id).values_list('pk', flat=True)
return Role.objects.filter(pk__in=all_actors).exclude(title_id=title_id) # maybe u need .distinct() here
this should give you one query, validate it this way:
print(get_related_titles(some_title_id).query)

Query intermediate through fields in django

I have a simple Relation model, where a user can follow a tag just like stackoverflow.
class Relation(models.Model):
user = AutoOneToOneField(User)
follows_tag = models.ManyToManyField(Tag, blank=True, null=True, through='TagRelation')
class TagRelation(models.Model):
user = models.ForeignKey(Relation, on_delete=models.CASCADE)
following_tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
pub_date = models.DateTimeField(default=timezone.now)
class Meta:
unique_together = ['user', 'following_tag']
Now, to get the results of all the tags a user is following:
kakar = CustomUser.objects.get(email="kakar#gmail.com")
tags_following = kakar.relation.follows_tag.all()
This is fine.
But, to access intermediate fields I have to go through a big list of other queries. Suppose I want to display when the user started following a tag, I will have to do something like this:
kakar = CustomUser.objects.get(email="kakar#gmail.com")
kakar_relation = Relation.objects.get(user=kakar)
t1 = kakar.relation.follows_tag.all()[0]
kakar_t1_relation = TagRelation.objects.get(user=kakar_relation, following_tag=t1)
kakar_t1_relation.pub_date
As you can see, just to get the date I have to go through so much query. Is this the only way to get intermediate values, or this can be optimized? Also, I am not sure if this model design is the way to go, so if you have any recomendation or advice I would be very grateful. Thank you.
You need to use Double underscore i.e. ( __ ) for ForeignKey lookup,
Like this :
user_tags = TagRelation.objects.filter(user__user__email="kakar#gmail.com").values("following_tag__name", "pub_date")
If you need the name of the tag, you can use following_tag__name in the query and if you need id you can use following_tag__id.
And for that you need to iterate through the result of above query set, like this:
for items in user_tags:
print items['following_tag__name']
print items['pub_date']
One more thing,The key word values will return a list of dictionaries and you can iterate it through above method and if you are using values_list in the place of values, it will return a list of tuples. Read further from here .

Django using the values method with m2m relationships / filtering m2m tables using django

class Book(models.Model):
name = models.CharField(max_length=127, blank=False)
class Author(models.Model):
name = models.CharField(max_length=127, blank=False)
books = models.ManyToMany(Books)
I am trying to filter the authors so I can return a result set of authors like:
[{id: 1, name: 'Grisham', books : [{name: 'The Client'},{name: 'The Street Lawyer}], ..]
Before I had the m2m relationship on author I was able to query for any number of author records and get all of the values I needed using the values method with only one db query.
But it looks like
Author.objects.all().values('name', 'books')
would return something like:
[{id: 1, name: 'Grisham', books :{name: 'The Client'}},{id: 1, name: 'Grisham', books :{name: 'The Street Lawyer'}}]
Looking at the docs it doesn't look like that is possible with the values method.
https://docs.djangoproject.com/en/dev/ref/models/querysets/
Warning Because ManyToManyField attributes and reverse relations can
have multiple related rows, including these can have a multiplier
effect on the size of your result set. This will be especially
pronounced if you include multiple such fields in your values() query,
in which case all possible combinations will be returned.
I want to try to get a result set of n size with with the least amount of database hits authorObject.books.all() would result in at least n db hits.
Is there a way to do this in django?
I think one way of doing this with the least amount of database hits would be to :
authors = Authors.objects.all().values('id')
q = Q()
for id in authors:
q = q | Q(author__id = id)
#m2m author book table.. from my understanding it is
#not accessible in the django QuerySet
author_author_books.filter(q) #grab all of the book ids and author ids with one query
Is there a built in way to query the m2m author_author_books table or am I going to have the write the sql? Is there a way to take advantage of the Q() for doing OR logic in raw sql?
Thanks in advance.
I think you want prefetch_related. Something like this:
authors = Author.objects.prefetch_related('books').all()
More on this here.
If you want to query your author_author_books table, I think you need to specify a "through" table:
class BookAuthor(models.Model):
book = models.ForeignKey(Book)
author = models.ForeignKey(Author)
class Author(models.Model):
name = models.CharField(max_length=127, blank=False)
books = models.ManyToMany(Books, through=BookAuthor)
and then you can query BookAuthor like any other model.