Querying for Multiple ManyToMany Fields - python

I have a model, Entry, with a ManyToMany field:
tags = models.ManyToManyField(Tag)
The Tag model is simple:
class Tag(models.Model):
name = models.CharField(max_length=25)
def __unicode__(self):
return self.name
I want to create a function, getEntryByTags that takes a list of tag names and returns any Entry that has those tags. If I knew the number of tags and the names of each, this would be trivial:
Entry.objects.filter(tags__name="tech").filter(tags__name="music").filter(tags__name="other")
But since this is going to be based on user input and the tags number will be variable, I'm not sure how to proceed. How would I iterate over a multiple item list to get an object that contains each of the ManyToMany objects with the names represented in the list?

You can try,
from django.db.models.query_utils import Q
tag_name_list = xxx # dynamic tag name list based on user input
query_list = [Q(tags__name=tag_name) for tag_name in tag_name_list]
query_set = Entry.objects
for query in query_list:
query_set = query_set.filter(query)
return query_set

Related

How to fetch related model in django_tables2 to avoid a lot of queries?

I might be missing something simple here. And I simply lack the knowledge or some how-to.
I got two models, one is site, the other one is siteField and the most important one - siteFieldValue.
My idea is to create a django table (for site) that uses the values from siteFieldValue as a number in a row, for a specific site, under certain header. The problem is - each site can have 50s of them. That * number of columns specified by def render_ functions * number of sites equals to a lot of queries and I want to avoid that.
My question is - is it possible to, for example, prefetch all the values for each site (SiteFieldValue.objects.filter(site=record).first() somewhere in the SiteListTable class), put them into an array and then use them in the def render_ functions by simply checking the value assigned to a key (id of the field).
Models:
class Site(models.Model):
name = models.CharField(max_length=100)
class SiteField(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
def __str__(self):
return self.name
class SiteFieldValue(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
field = models.ForeignKey(SiteField, on_delete=models.CASCADE)
value = models.CharField(max_length=500)
Table view
class SiteListTable(tables.Table):
name = tables.Column()
importance = tables.Column(verbose_name='Importance',empty_values=())
vertical = tables.Column(verbose_name='Vertical',empty_values=())
#... and many more to come... all values based on siteFieldValue
def render_importance(self, value, record):
q = SiteFieldValue.objects.filter(site=record, field=1).first()
# ^^ I don't want this!! I would want the SiteFieldValue to be prefetched somewhere else for that model and just check the array for field id in here.
if (q):
return q.value
else:
return None
def render_vertical(self, value, record):
q = SiteFieldValue.objects.filter(site=record, field=2).first()
# ^^ I don't want this!! I would want the SiteFieldValue to be prefetched somewhere else for that model and just check the array for field id in here.
if (q):
return q.value
else:
return None
class Meta:
model = Site
attrs = {
"class": "table table-striped","thead" : {'class': 'thead-light',}}
template_name = "django_tables2/bootstrap.html"
fields = ("name", "importance", "vertical",)
This might get you started. I've broken it up into steps but they can be chained quite easily.
#Get all the objects you'll need. You can filter as appropriate, say by site__name).
qs = SiteFieldValue.objects.select_related('site', 'field')
#lets keep things simple and only get the values we want
qs_values = qs.values('site__name','field__name','value')
#qs_values is a queryset. For ease of manipulation, let's make it a list
qs_list = list(qs_values)
#set up a final dict
final_dict = {}
# create the keys (sites) and values so they are all grouped
for site in qs_list:
#create the sub_dic for the fields if not already created
if site['site__name'] not in final_dict:
final_dict[site['site__name']] = {}
final_dict[site['site__name']][site['name']] = site['site__name']
final_dict[site['site__name']][site['field__name']] = site['value']
#now lets convert our dict of dicts into a list of dicts
# for use as per table2 docs
data = []
for site in final_dict:
data.append(final_dict[site])
Now you have a list of dicts eg,
[{'name':site__name, 'col1name':value...] and can add it as shown in the table2 docs

Custom Form Field Naming Within Loop

I'm trying to specify field names within a loop on a form, so that the fields do not overwrite themselves. Right now I'm using this in my form:
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
exclude = ['products']
class ProductForm(forms.Form):
product_id = forms.IntegerField()
count = forms.IntegerField()
class TicketProductForm(forms.ModelForm):
class Meta:
model = Ticket
exclude = []
for Product in Product.objects.all():
product_id = forms.IntegerField(initial=Product.product_id)
count = forms.IntegerField(initial=0)
The for loop within TicketProductForm currently only displays one set of fields, as product_id and count overwrite the fields over and over, only displaying for the last entry in the set of Product.
Is there a way to customize field names so that I can name them, preferably with a way I can set a specifier to allow easier lookup later?

How to order queryset based on best match in django-rest-framework?

I am trying to order results of a query with parameters by number of matches.
For example, let's say we have a Model:
class Template(models.Model):
headline = CharField(max_length=300)
text = TextField()
image_text = TextField(max_length=500, blank=True, null=True)
tags = TaggableManager(through=TaggedItem)
...
With a Serializer:
class TemplateSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Template
fields = (...)
And a ViewSet:
class TemplateViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Templates to be viewed or edited.
"""
queryset = Template.objects.all()
serializer_class = TemplateSerializer
def get_queryset(self):
queryset = Template.objects.all()
tags = self.request.query_params.getlist('tags', None)
search_text = self.request.query_params.getlist('search_text', None)
if tags is not None:
queries = [Q(groost_tags__name__iexact=tag) for tag in tags]
query = queries.pop()
for item in queries:
query |= item
queryset = queryset.filter(query).distinct()
if search_tags is not None:
queries = [Q(image_text__icontains=string) |
Q(text__icontains=string) |
Q(headline__icontains=string) for string in search_tags]
query = queries.pop()
for item in queries:
query |= item
queryset = queryset.filter(query).distinct()
What I need to do is count every match the filter finds and then order the queryset by that number of matches for each template. For example:
I want to find all the templates that have "hello" and "world" strings in their text, image_text or headline. So I set the query parameter "search_text" to hello,world. Template with headline="World" and text="Hello, everyone." would have 2 matches. Another one with headline="Hello" would have 1 match. The template with 2 matches would be the first in the queryset. The same behaviour should work for tags and tags with search_text combined.
I tried to calculate these numbers right in the ViewSet and then return a sorted(queryset, key=attrgetter('matches')) but encountered several issues with the DRF, like Template has no attribute 'matches'. Or 404 when directly accessing a Template instance through API.
Any ideas?
Give a try to annotation where each matching pair returns 1 or 0 that are summarized into rank:
from django.db.models import Avg, Case, F, FloatField, Value, When
Template.objects.annotate(
k1=Case(
When(image_text__icontains=string, then=Value(1.0)),
default=Value(0.0),
output_field=FloatField(),
),
k2=Case(
When(text__icontains=string, then=Value(1.0)),
default=Value(0.0),
output_field=FloatField(),
),
k3=Case(
When(headline__icontains=string, then=Value(1.0)),
default=Value(0.0),
output_field=FloatField(),
),
rank=F("k1") + F("k2") + F("k3"),
).order_by("-rank")

Specialized django query

As you can guess from the title, I'm not exactly sure how to describe what I want. Please take a look at the following classes:
from django.db import models
from django.contrib.auth.models import User as Auth_User
class User(Auth_User):
Portfolio = models.ManyToManyField('PortfolioItem', through='SkillTag')
Age = models.IntegerField(blank=False)
#property
def full_name(self):
return self.first_name + ' ' + self.last_name
def __unicode__(self):
return self.full_name
class PortfolioItem(models.Model):
Title = models.CharField(max_length=200, blank=False)
class SkillTag(models.Model):
User = models.ForeignKey('User')
PortfolioItem = models.ForeignKey('PortfolioItem')
Tag_Name = models.CharField(max_length=200, blank=False)
What I need to do, is for every user, get all the Tag_Name values of it's SkillTags, how do I do this?
You can do something like this
class User(Auth_User):
#other attributes
def tag_names(self):
return self.skilltag_set.values_list('Tag_Name', flat=True)
So, here, we are doing a couple of things:
Querying in reverse ForeignKey relationship.
Since you are not using a related_name in the ForeignKey attribute, by default django would assign the model name (lowercase) followed by _set attribute, which makes it .skilltag_set.all()
values_list
Returns a ValuesQuerySet — a QuerySet subclass that returns tuples when used as an iterable, rather than model-instance objects.
Example: [('a'), ('b'), ('c')]
Basically, you are retriving an iterable of ValuesQuerySet (think of it as a list or any other iterables) consisting of tuples.
flat=True
This basically flattens the on-tuples into single values.
Example: ['a', 'b', 'c']
most obvious: using the reverse relationship of ForeignKey fields:
def skill_names_1(user):
return [t.name for t in user.skilltag_set.all()]
The same thing, but explicitly selecting for the user. also, it fetches only the required field from the database.
def skill_names_2(user):
return SkillTag.objects.filter(User=user).values_list('Tag_Name',flat=True)
Either of these can also work as a method of User. Of course, typically the argument would be called self instead of user.
All the skills for a group of users:
def skill_names_3(users):
return SkillTag.objects.filter(User__in=users).values_list('Tag_Name',flat=True)

Django Combine a Variable Number of QuerySets

Is there a way to concatenate a unknown number of querysets into a list?
Here are my models:
class Item(models.Model):
name = models.CharField(max_length=200)
brand = models.ForeignKey(User, related_name='brand')
tags = models.ManyToManyField(Tag, blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ['-id']
class Tag(models.Model):
name = models.CharField(max_length=64, unique=True)
def __unicode__(self):
return self.name
I have two types of queries that I'm working with:
items = Item.objects.filter(brands__in=brands)
items = Item.objects.filter(tags__name='80s').filter(tags__name='comedy')
With regards to the second type of query, users can save searches (for example "80s comedy"), and can save multiple searches at the same time, so I will need to create a query for each search that they have saved.
I originally wanted to try and construct a single query that will handle both cases (see Django Combining AND and OR Queries with ManyToMany Field ), but I now think the best way to do this would be to combine all queries into a list.
I like what #akaihola suggests here:
How to combine 2 or more querysets in a Django view? but I can't figure out how to use itertools.chain with a variable number of queries.
Does anyone know the best way to accomplish that?
EDIT: Forgot to mention, what I'm looking for are items that have a certain brand OR have all of the required tags.
Slightly unorthodox, but you could use recursion. So in your example:
def recursive_search(tags, results_queryset):
if len(tags) > 0:
result_qs = result_queryset.filter(tags_name=tags[0])
if result_queryset.exists():
return filter_recursion(tags[1:],result_queryset)
else:
return None
return result_queryset
tags = ["comedy", "80s", "action", "thriller"] # This can be variable
result_queryset = Item.objects.filter(brands__in=brands) # Could be Item.objects.all()
print recursive_search(tags, result_queryset)
So you start off with a list of the tags you are searching for, and a queryset of ALL of your items that could possibly fit your criteria (in this case we start with the list of items of a particular brand)
You then recursively go through the list of tags one by one and cut the queryset down. For each level, you re-filter the entire queryset to only those items which have all the mentioned tags.
so:
the first call/level would be for all the items that have the tag favourite,
the second call/level would be for all the items that have the tags favourite and loudest,
etc.
If the queryset returned by the filter is None, it means there are no items that have all the required tags, and the method will quit and return None (i.e. it quits at the first possible instance of failure). Furthermore, there should only be a single hit to the database (I think!)
I've tested this out and it should work, so give it a shot
EDIT
To concatonate the queryset returned from the brands (q1) and the queryset created above using itertools (q2):
list = []
for item in itertools.chain(q1, q2):
list.append(item)
EDIT 2
does this not accomplish what you need in one query?
# list of tags = ['comedy','80s']
qs = Item.objects.all( Q(brand__iexact="brand name") | Q(tags__name__in=[tag for tag in list_of_tags]) )

Categories