Django: Add a list to a QuerySet - python

I am new to django so apologies if this is not possible or easy.
I have a view that takes a subset of a model
data = Terms.objects.filter(language = language_id)
The subset is one language. The set has a number of concepts for a language. Some languages might use the same word for multiple concepts, and I want to colour these the same in an SVG image. So I do this next:
for d in data:
if d.term is None:
d.colour = "#D3D3D3"
else:
d.colour = termColours[d.term]
Where termColours is a dictionary with keys as the unique terms and values as the hexadecimal colour I want.
I thought this would add a new colour attribute to my queryset. However, when I convert the queryset to json (in order to pass it to JS) the colour object is not there.
terms_json = serializers.serialize('json', data)
How can I add a new colour element to my queryset?

Convert your Queryset to Dict and then modify values.
Ex:
data = Terms.objects.filter(language = language_id).values()
for d in data:
if d.term is None:
d.colour = "#D3D3D3"
else:
d.colour = termColours[d.term]

If I understand correctly - you need Django ORM annotation. And it might look like that:
from django.db.models import Case, When, Value
data = Terms.objects.filter(language = language_id)
.annotate(color = Case(
When(term__isnull = True, then = "#D3D3D3"),
When(term__isnull = False, then = termColours[Value(term)]),))
Only problem here - I don't exactly know this moment - termColours[Value(term)], you need to test different combinations of that expressions to get the value of field term.

Related

Django: how to use a variable key filter with __range

date_one = form.cleaned_data.get('date_one')
date_two = form.cleaned_data.get('date_two')
date_type = form.cleaned_data.get('date_type')
search = MyClass.objects.filter(date_type__range(date_one, date_two))
My model has two different date columns. (created and expires). The user can make a query filtering between two dates, but he can choose if he wants to filter by creation or expiration.
I could make two query lines using if, but I really want to know how to do it in the way I'm asking.
How can I do this? Since the key before __range is a variable. I tried with (**{ filter: search_string }) but it seems not to be compatible with __range.
try this
filter_dict = {"{}__range".format(date_type): [date_one, date_two]}
search = MyClass.objects.filter(**filter_dict)
The thing you attempted is almost correct!
Lookups are not functions (so it's not foo__range(start, end)), but they are keyword arguments: foo__range=(start, end)
So you would have:
date_one = form.cleaned_data.get('date_one')
date_two = form.cleaned_data.get('date_two')
date_type = form.cleaned_data.get('date_type')
query_kwargs = {
"{}__range".format(date_type): (date_one, date_two)
}
search = MyClass.objects.filter(**query_kwargs)

Querying objects using attribute of member of many-to-many

I have the following models:
class Member(models.Model):
ref = models.CharField(max_length=200)
# some other stuff
def __str__(self):
return self.ref
class Feature(models.Model):
feature_id = models.BigIntegerField(default=0)
members = models.ManyToManyField(Member)
# some other stuff
A Member is basically just a pointer to a Feature. So let's say I have Features:
feature_id = 2, members = 1, 2
feature_id = 4
feature_id = 3
Then the members would be:
id = 1, ref = 4
id = 2, ref = 3
I want to find all of the Features which contain one or more Members from a list of "ok members." Currently my query looks like this:
# ndtmp is a query set of member-less Features which Members can point to
sids = [str(i) for i in list(ndtmp.values('feature_id'))]
# now make a query set that contains all rels and ways with at least one member with an id in sids
okmems = Member.objects.filter(ref__in=sids)
relsways = Feature.geoobjects.filter(members__in=okmems)
# now combine with nodes
op = relsways | ndtmp
This is enormously slow, and I'm not even sure if it's working. I've tried using print statements to debug, just to make sure anything is actually being parsed, and I get the following:
print(ndtmp.count())
>>> 12747
print(len(sids))
>>> 12747
print(okmems.count())
... and then the code just hangs for minutes, and eventually I quit it. I think that I just overcomplicated the query, but I'm not sure how best to simplify it. Should I:
Migrate Feature to use a CharField instead of a BigIntegerField? There is no real reason for me to use a BigIntegerField, I just did so because I was following a tutorial when I began this project. I tried a simple migration by just changing it in models.py and I got a "numeric" value in the column in PostgreSQL with format 'Decimal:( the id )', but there's probably some way around that that would force it to just shove the id into a string.
Use some feature of Many-To-Many Fields which I don't know abut to more efficiently check for matches
Calculate the bounding box of each Feature and store it in another column so that I don't have to do this calculation every time I query the database (so just the single fixed cost of calculation upon Migration + the cost of calculating whenever I add a new Feature or modify an existing one)?
Or something else? In case it helps, this is for a server-side script for an ongoing OpenStreetMap related project of mine, and you can see the work in progress here.
EDIT - I think a much faster way to get ndids is like this:
ndids = ndtmp.values_list('feature_id', flat=True)
This works, producing a non-empty set of ids.
Unfortunately, I am still at a loss as to how to get okmems. I tried:
okmems = Member.objects.filter(ref__in=str(ndids))
But it returns an empty query set. And I can confirm that the ref points are correct, via the following test:
Member.objects.values('ref')[:1]
>>> [{'ref': '2286047272'}]
Feature.objects.filter(feature_id='2286047272').values('feature_id')[:1]
>>> [{'feature_id': '2286047272'}]
You should take a look at annotate:
okmems = Member.objects.annotate(
feat_count=models.Count('feature')).filter(feat_count__gte=1)
relsways = Feature.geoobjects.filter(members__in=okmems)
Ultimately, I was wrong to set up the database using a numeric id in one table and a text-type id in the other. I am not very familiar with migrations yet, but as some point I'll have to take a deep dive into that world and figure out how to migrate my database to use numerics on both. For now, this works:
# ndtmp is a query set of member-less Features which Members can point to
# get the unique ids from ndtmp as strings
strids = ndtmp.extra({'feature_id_str':"CAST( \
feature_id AS VARCHAR)"}).order_by( \
'-feature_id_str').values_list('feature_id_str',flat=True).distinct()
# find all members whose ref values can be found in stride
okmems = Member.objects.filter(ref__in=strids)
# find all features containing one or more members in the accepted members list
relsways = Feature.geoobjects.filter(members__in=okmems)
# combine that with my existing list of allowed member-less features
op = relsways | ndtmp
# prove that this set is not empty
op.count()
# takes about 10 seconds
>>> 8997148 # looks like it worked!
Basically, I am making a query set of feature_ids (numerics) and casting it to be a query set of text-type (varchar) field values. I am then using values_list to make it only contain these string id values, and then I am finding all of the members whose ref ids are in that list of allowed Features. Now I know which members are allowed, so I can filter out all the Features which contain one or more members in that allowed list. Finally, I combine this query set of allowed Features which contain members with ndtmp, my original query set of allowed Features which do not contain members.

How to set a Django filter to select all

I have a view in Django that will read a get request and its parameters and make a query based on the parameters. Currently my view looks like this:
def getInventory(request):
c = request.GET.get('category', '')
g = request.GET.get('gender', '')
s = request.GET.get('size', '')
available = Item.objects.filter(gender=g,category=c,size=s)
data = serializers.serialize('json',available)
return HttpResponse(data,'json')
Sometimes, though, one of the parameters is not specified. I would like this to result in a value representing 'all' rather than an individual value. Is this possible in the way I've done it? I've tried gender=None but that just results in an empty list.
You need to apply a filter only if you want to filter something. You could do something like this:
# Start off with a base queryset
available = Item.objects.all()
allowed_filters = ['category', 'gender', 'size']
for f in allowed_filters:
if request.GET.get(f):
available = available.filter(**{f: request.GET[f]})
data = serializers.serialize('json',available)
return HttpResponse(data,'json')
That said, you might want to consider using the Forms API to validate the inputs before passing them to your queryset.

How to filter a Django query set by passing in a list of options?

I've got an abstract ProductAttribute model that can be associated with a Product model. The attributes can be accessed using product.attribute_values.
Say I've got a form which contains two select boxes that allow the user to specify the values of two attributes - Size and Weight.
How do I, in a single query, pass in those values into a filter, so that - as you can with a list of integers with Object.objects.filter(pk__in=(1,2,3)) - I can select the Product that matches against all those attribute values?
I'd like to be able to do something like:
options = ['XL','50lbs']
p = Product.objects.filter(attribute_values__matches=options)
Is this possible in a one-liner in Django?
TIA
You can use __contains or __exact
and for case insensitive matches __icontains and __iexact
You can use Q model lookups to do an and filtering:
options = ['X1', 'X2', 'X3']
qs = [Q(attribute_name=option) for option in options] #or attribute_name__icontains - or whatever
query = qs.pop() #get the first element
for q in qs:
query |= q
qs = MyModel.objects.filter(query)
I ended up cracking this one with a little help from #karthikr above, and the the Q.add() method:
options = {'Size':'XL'}
qs = Q()
for key, val in options.iteritems():
qs.add(Q(attributes__name=key) & Q(attribute_values__value_option__option=val), qs.connector)
Hope this helps someone else in some small way in future.

Differentiate lists according to translations

I have a multilingual field which i used hvad package.
I have a script like below, which i use for the tastypie dehydration.
array = []
for t in bundle.obj.facilities.filter(foo_type = i.foo_type):
for field in get_translatable_fields(t.foo_type.__class__):
for translation in t.foo_type.translations.all():
value = getattr(translation, field)
array.append(value)
print array
But i get all the language translations in the same list. Do you have any idea to have different lists belong to different languages.
I just want to have different arrays that differentiate during for translation in .... iteration
You can store them in a dictionary, indexed by the translation, using a collections.defaultdict:
import collections
dict_all = collections.defaultdict(list)
for t in bundle.obj.facilities.filter(foo_type = i.foo_type):
for field in get_translatable_fields(t.foo_type.__class__):
for translation in t.foo_type.translations.all():
value = getattr(translation, field)
dict_all[translation.language_code].append(value)
If you'd like to turn it back into a regular dictionary afterwards (instead of a defaultdict):
dict_all = dict(dict_all.items())

Categories

Resources