Django Postgres JSONField query - django

I have a class with a json field in it
class A(models.Model)
brand = JSONField()
If I post an array of JSON like [{'brand_id:1', 'name':'b1'}, {'brand_id:2', 'name':'b2'}] it is stored as an array of JSON. This works fine.
How should I query so as to check if '1' is present in the brand_id of any dictionary in that array?

Well first of all your JSON here is malformed. I presume it is meant to be:
[{'brand_id': 1, 'name': 'b1'}, {'brand_id': 2, 'name': 'b2'}]
If that's the case, to test for 1 in such a blob, something like this will tell you if 1 is to be found anywhere in the JSON as a value:
def check_for_one(json_data):
return any([1 in data.values() for data in json_data])
But you want to know specifically if 1 is a value owned by a key
brand_id anywhere in the JSON so you can also use a loop to add in some extra conditions:
def check_for_one(json_data):
match = []
for data in json_data:
for key, value in data.items():
if key == 'brand_id' and value == 1:
match.append(True)
return any(match)
You can incorporate such logic as methods on your model class like this:
class A(models.Model):
brand = JSONField()
def check_for_one_comprehension(self):
return any([1 in data.values() for data in self.brand])
def check_for_one_loop(self):
match = []
for data in self.brand:
for key, value in data.items():
if key == 'brand_id' and value == 1:
match.append(True)
return any(match)
But, if you actually want to filter instances from the database where the JSON data is an array at the top level and brand_id == 1, that needs a different approach, and this should do it:
A.objects.filter(brand__contains=[{'brand_id': 1}])
Note the additional [{}] braces! If you just call contains=['brand_id': 1] it will throw a syntax error and if you call contains={'brand_id': 1} it will not match.

This worked:
A.objects.filter(brands__contains=[{'brand_id':1}])
I didnt check it first by using the array.
Link pointed put by #Bear Brown gives sufficient info.
Its easy in django, but finding it out took time :P.

Related

Django JsonField filter by two fields

class Blog:
values = JSONField(blank=True, default=list)
[
{
"id": 1,
"value": "31"
},
{
"id": 2,
"value": "Hello"
},
...
]
I need to get all objects where the id is 1 and value of that field is greater than 31.
I have tried q = queryset.filter(values__0__id=1, values__0__value_gte=31) but it works only for objects if an object that I need located only in first element.
Apparently, nowadays Django has not built-in support for array elements comparison for JSONField. Fortunately, Django allows to make a lot of custom staff. For example, Django's raw SQL feature.
And if you use PostgreSQL as your main DB, you can use JSON Processing Functions. The jsonb_array_elements() from JSON processing functions is pretty nice choice.
Combining features above, we can make a little workaround for your case:
# Create method that uses raw SQL within your `objects` Model Manager.
# Create your own filters as you want. This example should be improved
# and handle exception cases of course.
def filter_blogs_json(json_field, sql_operator, value):
return Blog.objects.raw(f"SELECT id, data FROM yourappname_blog CROSS JOIN jsonb_array_elements(values) AS data WHERE (data->'{json_field}')::numeric {sql_operator} {value};")
# You can get raw objects queryset
raw_blogs_qs = filter_blogs_json('value', '>=', 31)
# Then you can process it anyway you want
filtered_blog_ids = [b.id for b in raw_blogs_qs]
queryset = Blog.objects.filter(...).filter(id__in=filtered_blog_ids)
Pretty easy, isn't it? :)
Moreover, I believe it is possible to make your own queryset Lookup's for JSONField, extend queries as you want and etc.
Hello and welcome to Stack Overflow!
Try looking at Django's Q objects. Some documentation is available here.
Case 1
As mentioned in this answer, try using the __contains filter:
Blog.objects.filter(values__contains=[{'id': 1}])
and then manually filter the results for the second field.
Case 2
Perhaps a better option would be to have a second table for the individual values, like the following models:
class Blog(models.Model):
name = models.CharField(max_length=100) # or something else
class Value(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
json = models.JSONField(blank=True, related_name="values")
and then perform the search like so:
Blog.objects.filter(Q(values__json__id=1) & Q(values__json__value__gte=31))

Overwrite field in queryset annotate

I have a django model with the fields name (string) and value (integer). Say I need to return a queryset of {name:..., value:...} objects, with each value doubled. What I am trying to do now is:
queryset.annotate(value=F('value') * 2)
However, django tells me that the field value already exists.
I also tried using extra:
queryset.annotate(value_double=F('value') * 2).extra(select={'value': 'value_double'})
but that also does not work since value_double is not a valid field.
Of course, in this case I could use something like queryset.extra(select={'value': 'value * 2'}), but I have some other, more complicated cases involving a lot of functions where I really don't want to write sql, but I'd rather find a solution in django. Again, in all of my cases, annotate works perfectly fine as long as I give the result a new name.
Say your model is XYZ with fields name and val. If you want val to contain val*2 use below queryset
x = XYZ.objects.values('name').annotate(val=F('val')*2)
print(x)
Result
<QuerySet [{'name': abc, 'val': 104},...............]
If you want queryset that return name,val and doubleval. You can use below query for same.
x = XYZ.objects.values('name','val',doubleval=F('val')*2)
print(x)
Result
<QuerySet [{'name': abc, 'val':52,'doubleval': 104},...............]
Hope this help.

Getting a value from a different class in django

As new to both Python and Django I have encounterd what I think is a simple problem, but which does not want to go away. I have a "table" with factors obtained from
class TjgFaktor(models.Model):
typ = models.CharField(max_length=2)
factor = models.FloatField()
Next I have another class which is foreign-keyed to this:
class Moment(models.Model):
typ = models.ForeignKey(TjgFaktor,on_delete=models.SET_NULL,null=True)
Now, what I want to do is to get the factor from the first class as an attribut to Moment. I have tried
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
in the hope of getting the correct factor. However, when I do something like
person_moment = Moment.objects.all()
for e in person_moment:
print(e.factor())
what I get is "TjgFaktor matching query does not exist".
So how should I do this? I guess it is the function: it works if I replace type=self with pk=1.
You do not need to obtain the TjgFaktor through an explicit query. If you query for some_moment.typ, Django itself will perform an implcit query to fetch the TjgFaktor that corresponds to the Moment (through the foreign key), or None, if the foreign key is set to None.
We can thus query like:
def factor(self):
tjgfaktor = self.typ
if tjgfaktor:
return tjgfaktor.factor
In case there is no related TjgFaktor, then this function will return None as well.
In case you define a large amount of values, then this fetch might be inefficient: Django will fetch all columns from the database, and since we are only interested in a single one, this will thus result in some overhead.
We can avoid that by using the following query:
def factor(self):
if self.typ_id:
return (TjgFaktor.objects.values_list('factor', flat=True)
.get(pk=self.typ_id))
Assuming factor is function within Moment class, you can access factor if Moment object has related TjgFaktor object:
def factor(self):
return self.typ.factor if self.typ else None
So, the in the factor method, you need to enter the value for typ as a string value like this: A self does not satisfy the conditions of a string parameter that is required. You could do something like this -
def factor(self):
return TjgFaktor.objects.get(typ="YOUR_TYPE_IN_STRING").factor
def factor(self):
return TjgFaktor.objects.get(typ=self).factor
You can't actually compare the object of Moment with objects in TjgFaktor. You can directly access the values of parent model or foreignkey directly by doing like this.
e.typ.factor #this will directly give you factor values of foreign key.
Or you can compare with
def factor(self):
return TjgFaktor.objects.get(typ=self.typ.id).factor

Get django field "choice" from arbitrary value

I have a django class like this:
class my_thing(models.Model):
AVAILABLE = 1
NOT_AVAILABLE = 2
STATUSES = (
(AVAILABLE, "Available"),
(NOT_AVAILABLE, "Not available")
)
status = models.IntegerField(...., choices = STATUSES)
In another bit of code I have the number corresponding to a status, but due to some legacy code I need to compare it via strings (and I don't want to hard code it anywhere other than the model definition - DRY)
So in the code I have the number "1" and I want to get the text "Available".
My current (awful) workaround is to do the following:
status_getter = my_thing()
my_thing.status = my_thing.AVAILABLE
comparison_str = status_getter.get_status_display()
Is there a better/builtin way to directly access the string value for the field's choices given that I don't have an object of that type already instantiated? I could write a function
def get_status_on_value(self, value):
for tup in STATUSES:
if tup[0] == value:
return tup[1]
But I have a sneaking suspicion django has a built-in way to do this
Not really. Your best bet is to convert the CHOICES tuple to a dict and do a lookup:
status_dict = dict(my_thing.STATUSES)
return status_dict[value]

how to deal with multiple arguments in a dictionary construct when query in sqlalchemy?

In sqlalchemy, I define a function whose argument is a dictionary may contain multiple key-values.I want to query according to the key-values. Here is my program:
def get_contact_conditions(kwds):
for fieldName, fieldValue in kwds.items():
condition = fieldName + "=:fieldName"
contact = session.query(Contact).filter(condition).params(fieldName = fieldValue).all()
session.commit()
return contact
this situation above is when the dictionary has only one key-values, but maybe there are two or three in it, then how to code condition and the values in params(). In another word how to code query clause.
Thank you!
If the only comparison you need is "=" then this should work:
def get_contact_conditions(kwds):
contact = session.query(Contact).filter_by(**kwds).all()
return contact
You can read about it here:
http://docs.sqlalchemy.org/en/rel_0_7/orm/query.html#sqlalchemy.orm.query.Query.filter_by
If you need arbitrary operators(<,>,in_,like,etc.) you can pass in a list of sqlalchemy clauses as arguments to the and_ function and pass the result to filter(). These expressions are generated when you compare a model class attribute to something, like:
Contact.id < 5
So you could do something like this:
from sqlalchemy.sql import and_
def get_contacts(*exps):
return session.query(Contact).filter(and_(*exps)).all()
# All contacts with an id less than 5 and a name that starts with user.
contacts = get_contacts(Contact.id < 5, Contact.name.like('user%'))
But you should consider that at this point the function is getting closer to just calling session.query directly.
You should read the sqlalchemy docs more to learn about the operators/comparisons that are available on model columns/relations. There are some really useful ones.