I have a curious problem.
I have 3 objects. All the same
class Articles(models.Model):
owner = models.ForeignKey(Author)
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_length=255)
and so I have 3 Articles. With all the same tags: 'tag1' and 'tag2'
And I have queries
actionsAll = Articles.objects.filter((Q(tags__name__exact="tag1") | Q(tags__name__exact="tag2"))).distinct()
This gives me all my articles. It will return 6 articles w/o distinct() since it will collect each article 2x since they have both tags.
However with this query:
actionsAll = Articles.objects.filter((Q(tags__name__exact="tag1") & Q(tags__name__exact="tag2"))).distinct()
This gives me no articles.
Since the articles contain both the tags, it should return them all shouldnt it?
If you look at the SQL it generates, you'll see that it checks to see if the same tag has both names. What you need is an IN query or an EXISTS query that traverses the relation.
** import Q from django
from *models import SuperUser, NameUser
import operator
# we do not know the name in the superhero
super_users = SuperUser.objects.all()
q_expressions = [Q(username=user.username) for user in super_users]
# we have bind super_hero with user
name_superheroes_qs = models.NameUser.objects.filter(reduce(operator.or_, q_expressions))
Related
I am a newbie to django and was reading about select_related. I understand that whenever a foreign key is accessed django executes an additional query. But when I checked with DEBUG log in my code, it seems django executes two queries no matter if the foreign key is accessed or not. Can someone explain this behaviour ?
class Person(models.Model):
# ...
name = models.CharField(max_length=250)
class Book(models.Model):
# ...
author = models.ForeignKey(Person, on_delete=models.CASCADE)
As per doc
# Without select_related()...
b = Book.objects.get(id=4) # Executes a query.
p = b.author #Executes a query.
But with the get() it executes two queries
b = Book.objects.get(id=4) # Executes two queries (one for books one for author).
First of all, you need to call select select_related:
ids = [1,2,3,4]
query = Book.objects.filter(id__in=ids).select_related('author')
notice that I did that using the filter method and not the get method.
the reason is that select/prefetch related doesn't work with the get method.
if you still want only one object with select related you should do:
book = Book.objects.filter(id=4).select_related('author')[0]
author = book.author
or do:
book = Book.objects.select_related('author').get(id=4)
author = book.author
if you want to do it for multiple objects and get all the authors:
ids = [1,2,3,4]
query = Book.objects.filter(id__in=ids).select_related('author')
authors_in_query = [book.author for book in query]
I have the following models:
## Tags for issues
class issueTags(models.Model):
name = models.CharField(max_length=400)
class issues(models.Model):
tags = models.ManyToManyField(issueTags,blank = True)
In my view I get an array from some client side JavaScript i.e.
(Pdb) array_data = request.POST['arr']
(Pdb) array_data
'["2","3"]'
How should I filter my issues object to find all issues which match all tags in the array? (the 2,3 are the ID values for tag__id.
If there is a better way to arrange the objects that would also work so I can search in this fashion.
At the time of writing this, the existing answers are either incorrect (e.g. filtering matching all Issues that have any of the specified tags and the correct tag count) or inefficient (e.g. attaching filters in a loop).
For the following models:
class IssueTag(models.Model):
name = models.CharField(max_length=400, blank=True)
class Issue(models.Model):
label = models.CharField(max_length=50, blank=True)
tags = models.ManyToManyField(IssueTag, related_name='issues')
I suggest using Django Annotation in conjunction with a filter like so:
from django.db.models import Count, Q
tags_to_match = ['tag1', 'tag2']
issues_containing_all_tags = Issue.objects \
.annotate(num_correct_tags=Count('tags',
filter=Q(tags__name__in=tags_to_match))) \
.filter(num_correct_tags=2)
to get all Issues that have all required tags (but may have additional tags, as is required in the question).
This will produce the following SQL query, that resolves all tag matching in a single IN clause:
SELECT "my_app_issue"."id", "my_app_issue"."label",
COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE "my_app_issuetag"."name" IN ('tag1', 'tag2'))
AS "num_correct_tags"
FROM "my_app_issue"
LEFT OUTER JOIN "my_app_issue_tags" ON ("my_app_issue"."id" = "my_app_issue_tags"."issue_id")
LEFT OUTER JOIN "my_app_issuetag" ON ("my_app_issue_tags"."issuetag_id" = "my_app_issuetag"."id")
GROUP BY "my_app_issue"."id", "my_app_issue"."label"
HAVING COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE ("my_app_issuetag"."name" IN ('tag1', 'tag2'))) = 2;
args=('tag1', 'tag2', 'tag1', 'tag2', 2)
I haven't tested this, but I think you could do the following:
from django.db.models import Q
array_data = array_data.split(',')
issues.objects.filter(
tags__in=array_data,
).exclude(
# Exclude any that aren't in array_data
~Q(tags__in=array_data)
).annotate(
matches=Count(tags, distinct=True)
).filter(
# Make sure the number found is right.
matches=len(array_data)
)
FYI, you should be using Issue, IssueTag for your model names to follow Django's naming pattern.
It isn't most elegant solution or pythonic but I ended up just looping around the resulting filter.
def filter_on_category(issue_object,array_of_tags):
#keep filtering to make an and
i = 0
current_filter = issue_object
while (i < (len(array_of_tags))):
#lets filter again
current_filter=current_filter.filter(tags__id__in=array_of_tags[i])
i=i+1
return current_filter
Django field lookups argument (__) for many-to-many fields needs list argument. I have created a dummy list for each array element of IssueTags and pass it to lookups argument and it works as expected.
Let you have this models:
class IssueTags(models.Model):
name = models.CharField(max_length=400)
class Issues(models.Model):
tags = models.ManyToManyField(IssueTags,blank = True)
You want to get Issues which contains all of these IssueTags = ["1","2","3"]
issue_tags_array = ["1","2","3"]
#First initialize queryset
queryset = Issues.objects.all()
i = 0
while i < len(issue_tags_array):
#dummy issue_tag list
issue_tag = [issue_tags_array[i]]
#lets filter again
queryset = queryset.filter(tags__id__in=issue_tag)
i=i+1
return queryset
Let's say we have the following models:
class Author(Model):
...
class Serie(Model):
...
class Book(Model):
authors = ManyToManyField(Author, related_name="books")
serie = ForeignKey(Serie)
...
How can I get the list of authors, with their series ?
I tried different combinations of annotate and prefetch:
list_authors = Author.objects.prefetch(Prefetch("books__series", queryset=Serie.objects.all(), to_attr="series"))
Trying to use list_authors[0].series throws an exception because Author has no series field
list_authors = Author.objects.annotate(series=FilteredExpression("books__series", condition=Q(...))
Trying to use list_authors[0].series throws an exception because Author has no series field
list_authors = Author.objects.annotate(series=F('books__series'))
returns all possible combinations of (author, serie) that have a book in common
As I'm using PostgreSQL for my database, I tried:
from django.contrib.postgres.aggregates import ArrayAgg
...
list_authors = Author.objects.annotate(series=ArrayAgg('books__serie', distinct=True, filter=Q(...)))
It works fine, but returns only the id of the related objects.
list_authors = Author.objects.annotate(series=ArrayAgg(
Subquery(
Serie.objects.filter(
livres__auteurs=OuterRef('pk'),
...
).prefetch_related(...)
)
))
fails because it needs an output_field, and a Model is not a valid value for output_field
BUT
I can get the number of series for an author, so why not the actual list of them:
list_authors = Author.objects.annotate(nb_series=Count("books__series", filter=Q(...), distinct=True)
list_authors[0].nb_series
>>> 2
Thus I assume that what I try to do is possible, but I am at a loss regarding the "How"...
I don't think you can do this with an annotation on the Author queryset - as you've already found you can do F('books__series') but that will not return distinct results. Annotations generally only make sense if the result is a single value per row.
What you could do instead is have a method on the Author model that fetches all the series for that author with a relatively simple query. This will mean one additional query per author, but I can't see any alternative. Something like this:
class Author:
def get_series(self):
return Serie.objects.filter(book__authors=self).distinct()
Then you just do:
list_authors = Author.objects.all()
list_authors[0].get_series()
I want to build an webapp like Quora or Medium, where a user can follow users or some topics.
eg: userA is following (userB, userC, tag-Health, tag-Finance).
These are the models:
class Relationship(models.Model):
user = AutoOneToOneField('auth.user')
follows_user = models.ManyToManyField('Relationship', related_name='followed_by')
follows_tag = models.ManyToManyField(Tag)
class Activity(models.Model):
actor_type = models.ForeignKey(ContentType, related_name='actor_type_activities')
actor_id = models.PositiveIntegerField()
actor = GenericForeignKey('actor_type', 'actor_id')
verb = models.CharField(max_length=10)
target_type = models.ForeignKey(ContentType, related_name='target_type_activities')
target_id = models.PositiveIntegerField()
target = GenericForeignKey('target_type', 'target_id')
tags = models.ManyToManyField(Tag)
Now, this would give the following list:
following_user = userA.relationship.follows_user.all()
following_user
[<Relationship: userB>, <Relationship: userC>]
following_tag = userA.relationship.follows_tag.all()
following_tag
[<Tag: tag-job>, <Tag: tag-finance>]
To filter I tried this way:
Activity.objects.filter(Q(actor__in=following_user) | Q(tags__in=following_tag))
But since actor is a GenericForeignKey I am getting an error:
FieldError: Field 'actor' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
How can I filter the activities that will be unique, with the list of users and list of tags that the user is following? To be specific, how will I filter GenericForeignKey with the list of the objects to get the activities of the following users.
You should just filter by ids.
First get ids of objects you want to filter on
following_user = userA.relationship.follows_user.all().values_list('id', flat=True)
following_tag = userA.relationship.follows_tag.all()
Also you will need to filter on actor_type. It can be done like this for example.
actor_type = ContentType.objects.get_for_model(userA.__class__)
Or as #Todor suggested in comments. Because get_for_model accepts both model class and model instance
actor_type = ContentType.objects.get_for_model(userA)
And than you can just filter like this.
Activity.objects.filter(Q(actor_id__in=following_user, actor_type=actor_type) | Q(tags__in=following_tag))
What the docs are suggesting is not a bad thing.
The problem is that when you are creating Activities you are using auth.User as an actor, therefore you can't add GenericRelation to auth.User (well maybe you can by monkey-patching it, but that's not a good idea).
So what you can do?
#Sardorbek Imomaliev solution is very good, and you can make it even better if you put all this logic into a custom QuerySet class. (the idea is to achieve DRY-ness and reausability)
class ActivityQuerySet(models.QuerySet):
def for_user(self, user):
return self.filter(
models.Q(
actor_type=ContentType.objects.get_for_model(user),
actor_id__in=user.relationship.follows_user.values_list('id', flat=True)
)|models.Q(
tags__in=user.relationship.follows_tag.all()
)
)
class Activity(models.Model):
#..
objects = ActivityQuerySet.as_manager()
#usage
user_feed = Activity.objects.for_user(request.user)
but is there anything else?
1. Do you really need GenericForeignKey for actor? I don't know your business logic, so probably you do, but using just a regular FK for actor (just like for the tags) will make it possible to do staff like actor__in=users_following.
2. Did you check if there isn't an app for that? One example for a package already solving your problem is django-activity-steam check on it.
3. IF you don't use auth.User as an actor you can do exactly what the docs suggest -> adding a GenericRelation field. In fact, your Relationship class is suitable for this purpose, but I would really rename it to something like UserProfile or at least UserRelation. Consider we have renamed Relation to UserProfile and we create new Activities using userprofile instead. The idea is:
class UserProfile(models.Model):
user = AutoOneToOneField('auth.user')
follows_user = models.ManyToManyField('UserProfile', related_name='followed_by')
follows_tag = models.ManyToManyField(Tag)
activies_as_actor = GenericRelation('Activity',
content_type_field='actor_type',
object_id_field='actor_id',
related_query_name='userprofile'
)
class ActivityQuerySet(models.QuerySet):
def for_userprofile(self, userprofile):
return self.filter(
models.Q(
userprofile__in=userprofile.follows_user.all()
)|models.Q(
tags__in=userprofile.relationship.follows_tag.all()
)
)
class Activity(models.Model):
#..
objects = ActivityQuerySet.as_manager()
#usage
#1st when you create activity use UserProfile
Activity.objects.create(actor=request.user.userprofile, ...)
#2nd when you fetch.
#Check how `for_userprofile` is implemented this time
Activity.objects.for_userprofile(request.user.userprofile)
As stated in the documentation:
Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. Because a GenericForeignKey isn’t a normal field object, these examples will not work:
You could follow what the error message is telling you, I think you'll have to add a GenericRelation relation to do that. I do not have experience doing that, and I'd have to study it but...
Personally I think this solution is too complex to what you're trying to achieve. If only the user model can follow a tag or authors, why not include a ManyToManyField on it. It would be something like this:
class Person(models.Model):
user = models.ForeignKey(User)
follow_tag = models.ManyToManyField('Tag')
follow_author = models.ManyToManyField('Author')
You could query all followed tag activities per Person like this:
Activity.objects.filter(tags__in=person.follow_tag.all())
And you could search 'persons' following a tag like this:
Person.objects.filter(follow_tag__in=[<tag_ids>])
The same would apply to authors and you could use querysets to do OR, AND, etc.. on your queries.
If you want more models to be able to follow a tag or author, say a System, maybe you could create a Following model that does the same thing Person is doing and then you could add a ForeignKey to Follow both in Person and System
Note that I'm using this Person to meet this recomendation.
You can query seperately for both usrs and tags and then combine them both to get what you are looking for. Please do something like below and let me know if this works..
usrs = Activity.objects.filter(actor__in=following_user)
tags = Activity.objects.filter(tags__in=following_tag)
result = usrs | tags
You can use annotate to join the two primary keys as a single string then use that to filter your queryset.
from django.db.models import Value, TextField
from django.db.models.functions import Concat
following_actor = [
# actor_type, actor
(1, 100),
(2, 102),
]
searchable_keys = [str(at) + "__" + str(actor) for at, actor in following_actor]
result = MultiKey.objects.annotate(key=Concat('actor_type', Value('__'), 'actor_id',
output_field=TextField()))\
.filter(Q(key__in=searchable_keys) | Q(tags__in=following_tag))
i can't find a way to do the mysql "IF SELECT" with the django orm:
I have this model:
class Friendship(models.Model):
to_user = models.ForeignKey(User, related_name="friends")
from_user = models.ForeignKey(User, related_name="_unused_")
added = models.DateField(default=datetime.date.today)
objects = FriendshipManager()
class Meta:
unique_together = (('to_user', 'from_user'),)
Now to get all friends of user X i use this:
q = Friendship.objects.raw('SELECT IF( from_user_id = X, `to_user_id` ,
`from_user_id`) AS friend,id FROM `friends_friendship` WHERE `to_user_id` = X
OR `from_user_id` = X'
But the raw sqls do not return any objects, only ids. So it doesn't help me at all.
How could i use the django ORM to return a queryset of users?
Best regards,
I suggest you to have a look at the Many-to-Many relationship supported by Django's ORM.
It seems that your case fits the example from the Django Documentation here.
QuerySet objects have an extra method that lets you add raw SQL to the generated query. Something like the following should work for you.
qs = Friendship.objects.filter(Q(to_user=X) | Q(from_user=X))
qs = qs.extra(select={ "friend_id": "IF(from_user_id = X, `to_user_id`, `from_user_id`" })
When used like this the Friendship objects will have an extra parameter, friend, that contains the id of the friend. You might want to add a property to the Friendship that returns the User object, but there's not enough information in your question to say for certain that this is a good idea.
#property
def friend(self):
return User.object.get(id=self.friend_id)
You can use the select_related method to follow the foreign key fields so when you access them an extra query is not made.
qs = Friendship.objects.filter(Q(to_user=X) | Q(from_user=X)).select_related(depth=1)
qs[0].to_user # doesn't cause another query to be made
If you then add a new method to the Friendship class you can get the correct User object. The values for both to_user and from_user will already be in memory so no queries are made when calling this function.
def friend_of(self, user):
if self.to_user == user:
return self.from_user
else:
return self.to_user
Though an old question, I found Case and When to be useful:
from django.db.models import Case, When, Q
Friendship.objects.values('id')\
.annotate(friend=Case(When(from_user=X, then='to_user'), default='from_user'))\
.filter(Q(to_user=X) | Q(from_user=X))
See Django doc on conditional expressions.