I'm querying a ManyToMany field (tags). The values come from a list:
tag_q = Q()
tag_list = ["work", "friends"]
for tag in tag_list:
tag_q &= Q(tags__tag=tag)
Post.objects.filter(tag_q)
When I have only one value, it works flawlessly, but when more objects are stacked it always return an empty queryset.
I didn't used tags__tag__in=tag_list because it returned any post that contained any of the tags in the tag list (an OR filter), and I need an AND filter here.
This is my models:
class Tag(models.Model):
tag = models.CharField(max_length=19, choices=TagChoices.choices())
class Post(models.Model):
tags = models.ManyToManyField(Tag, related_name='posts', blank=True)
This is the Q object that is being passed in the filter query:
(AND: ('tags__tag', 'work'), ('tags__tag', 'friends')
You can not work with a Q object like that: a filter is an existential quantifier, not a universal. It means you are looking for a single Tag that has as tag name work and friends at the same time.
What you can do is work with a list of tags, and then count if the number of Tags is the same as the number of items to search for, like:
tag_list = ['work', 'friends']
Post.objects.filter(tags__tag__in=tag_list).annotate(
ntags=Count('tags')
).filter(ntags=len(set(tag_list))
since django-3.2, you can work with .alias(…) [Django-doc] instead of .annotate(…) [Django-doc]:
tag_list = ['work', 'friends']
Post.objects.filter(tags__tag__in=tag_list).alias(
ntags=Count('tags')
).filter(ntags=len(set(tag_list))
Related
I'm having a Django model that contains a JSON field to store a list, like so:
class Movie(models.Model):
tags = JSONField()
As such, a movie mymovie contains a list of tags, such as:
mymovie.tags = ["horror", "love", "adventure"].
And now, I want a query to fetch all movies having at least one tag of a list of tags, like so:
GET /movies?tags=horror,cowboy
In my previous example, the query will get mymovie, because it has horror in its tag, even if it doesn't have cowboy.
Using *django_filters, I managed to do it with the following implementation:
class CharInFilter(BaseInFilter, CharFilter):
pass
class MovieFilter(FilterSet):
class Meta:
model = Movie
fields = ["tags"]
tags = CharInFilter(method="tags_filter")
def tags_filter(self, queryset, name, value):
# value will contain a list of tags, and we want any movie that at least one of
# its tag is in the list of provided tags
query = Q()
for tag in value:
query |= Q(
tags__icontains=tag
)
if query:
queryset = queryset.filter(query)
return queryset
It works, but it feels very hacky, and I won't be surprised if an ad hoc implementation of this exists.
Does someone have a better idea ?
Thank youuuu ^^
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
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 .
I have following models.
class Contents (models.Model):
...
tags = models.ManyToManyField('Tag')
class Tag (models.Model):
...
name = models.CharField(max_length=20)
Just to consider, I am trying to get contents which are tagged with both tag1 and tag2.
Is there a way in Django to do something like Contents.objects.filter(tags__name = ['tag1','tag2'])
Here tag1,tag2,... are dynamically generated.
Update:
I have been using for loop. I am looking for an efficient solution.
If you're looking for a list, then you should be able to use the look up __in:
Contents.objects.filter(tags__name__in = ['tag1','tag2'])
See the docs.
For a queryset with both and only both tags, you may need to chain your filter calls:
tags = ['tag1','tag2']
contents = Contents.objects.all()
for tag in tags:
contents = contents.filter(tags__name = tag)
That should filter the queryset so that you only have a queryset where both tags match. The method copied from this quesiton: Django queryset to match all related objects
Is there a way of having only a filtered part of a model as a SeachQuerySet?
Something like:
query = SearchQuerySet().models(Entry.filter(categories__name='something'))
instead of
query = SearchQuerySet().models(Entry)
The field I want to filter by is a manytomany field and non indexed.
The search index doesn't store any relations, it is therefore 'flat'. You can only add your categories' IDs to the index for Entry (note that you have to use a prepare_-method for this):
class EntryIndex(indexes.SearchIndex, indexes.Indexable):
# your other fields
categories = MultiValueField()
def prepare_categories(self, obj):
return [category.pk for category in obj.categories.all()]
The you can do something like:
category = Category.objects.get(name='something')
sqs = SearchQuerySet().models(Entry).filter(categories=category.pk)