Django queryset grouped by count of values in Postgres JSONField - django

My model:
from django.contrib.postgres.fields import JSONField
class Image(models.Model):
tags = JSONField(null=False, blank=True, default={})
tags field value can be empty, or something like:
[
{"tag": "xxx"},
{"tag": "yyy"},
{"tag": "zzz"}
]
The number or dicts may vary (from 0 to N).
I need to make a query that counts Images grouped by number of tags. Something like:
{
"0": "345",
"1": "1223",
"2": "220",
...
"N": "23"
}
where the key is the number of tags, and the value is the count of Image objects that contains this number of tags.
How can i do that? Thank you for your help!
UPDATE
I modified my code: now I don't use JsonField, but a dedicated model:
class ImageTag(models.Model):
image = models.ForeignKey(Image)
tag = models.CharField()
The question is the same :)

Related

Django Admin - Show form json input value as text insted string

Considering I have a model like:
MyStore = (
id = 1,
name = 'Foobar',
information_as_json = {
'open_at': datetime.now(),
'close_at': datetime.now() + timedelta('+1 day'),
'workers' : {
'Person1' : 'Owner',
'Person2' : 'Boss',
'Person3' : 'Boss',
}
})
Inside Django admin forms, for every field is generated an input, but for the field "information_as_json", I don't want to show it as a string or as JSON. That is because the users who are accessing this store admin page, need to read the field 'information_as_json' easier since no one can edit these values because it is generated in another part of the application.
Is it possible to convert these values to a "div" or a plain text? The contents would be:
This store opens at: {information_as_json.open_at}
This store close at: {information_as_json.close_at}
And for the workers, iterate through keys and values:
for key, value in information_as_json.workers:
Worker {key} has the role: {value}
I'm a beginner at Django, so I'm struggling a little with this part.
Every help would be appreciated :D
I would suggest approaching the model a little differently. Rather than storing the opening and closing hours as JSON they can just be fields directly on the store model. The the workers can be a JSONfield [docs] containing name/role pairs. If you're using PostgreSQL for your database you could even use HStoreField [docs], which might be more appropriate.
Here's how I would write a similar model.
class Store(models.Model):
name = models.CharField(max_length=512, unique=True)
workers = models.JSONField(blank=True, default=dict, editable=False)
closing = models.TimeField(blank=True, null=True, editable=False)
opening = models.TimeField(blank=True, null=True, editable=False)
To display the details in the Django admin we just need to define a property which returns the correct string.
#mark_safe
def details(self):
roles = [
f'{x} has the role: {y}'
for x, y in self.workers.items()
]
return '<br>'.join([
f'This store opens at: {self.opening:%-H:%M}',
f'This store closes at: {self.closing:%-H:%M}',
] + roles)
This method can then be referenced in the ModelAdmin and used like a read-only field.
#admin.register(Store)
class StoreAdmin(admin.ModelAdmin):
list_display = ['name', 'opening', 'closing']
fields = ['name', 'details']
readonly_fields = ['details']

How to match field of different table in Django

I'm using Django as backend, PostgresSQL as DB, and HTML, CSS, and Javascript as frontend. I got stuck to match the field and retrieve specific data in Django.
class Motor(models.Model):
.
.
code = models.CharField(max_length=100)
.
.
class Drum(models.Model):
.
.
code = models.CharField(max_length=100)
.
.
class Cart(models.Model):
.
.
motor = models.ForeignKey(Motor, on_delete=models.CASCADE,null=True, blank=True)
drum = models.ForeignKey(Drum, on_delete=models.CASCADE,null=True, blank=True)
.
.
Now in the Above model, there is the cart Model which saves the data of the Motor Model or else saves the data of the Drum Model or both.
So for example If the user saves the data of the Motor Model in the Cart Model. The data which is saved in the Cart model should match the code field with model Drum and should filter the data accordingly.
So, I have done something like this.
views.py
def Drum(request):
drum_filter = Drum.objects.filter(code__in=Cart.objects.all().values_list('code', flat = True))
return render(request, 'list/drum.html',
{'drum_filter':drum_filter}
)
But now the problem is: The code field in Cart goes as child table and my parent table is Motor. something like this:
[
{
"id": 4,
"quantity": 1,
"user": {
"id": 4,
},
"motor": {
"id": 9,
"name": "...",
"title": "....",
.
.
.
"code": "DD1"
},
"drum": null
}
]
]
Now I unable to match the table field in Django.
So lastly, the code field in the Cart model should match with the code field in the Drum model and if the user does not save any records in the Cart model, then whole Drum model records should filter. Just like this:
Drum = Drum.objects.all().order_by('price')
Last but not least. Is it the right approach to this to filter specific data?
For the first part you could filter as follows
def Drum(request):
drum_filter = Drum.objects.filter(code__in=Cart.objects.values_list('motor__code', flat = True))
return render(request, 'list/drum.html',
{'drum_filter':drum_filter}
)
Second, you can check if cart list is empty
def Drum(request):
carts = Cart.objects.values_list('motor__code', flat = True)
drum_filter = Drum.objects.filter(code__in=carts) if carts else Drum.objects.all().order_by('price')
return render(request, 'list/drum.html',
{'drum_filter':drum_filter}
)
Hope this solves your issue

Django jsonfield, is it possible to filter with json array value length?

Suppose I have a jsonfield with data
from django.contrib.postgres.fields import JSONField
class Foo(models.Model):
json_field = JSONField(default=dict)
json_field = {
'bar': [1,2,3,4]
}
I'd like to filter data where bar has array length greather than 3
Something like the following, Foo.objects.filter(json_field__bar__length__gt=3)
You can try this:
Create one more field in the model i.e json_field_bar_length.
class Foo(models.Model):
....
json_field_bar_length = models.IntegerField(default=0)
And enter the length of json_field['bar'] into it whenever you save the json_field in it and check if the length is greater or not.

How to apply a function on the values selected in Django queryset?

Say I'm having the below model in Django
class Book(models.Model):
id = models.AutoField(primary_key=True)
volumes = JSONField()
I want to get the length of title of all the Books as values -
[
{
"id": 1,
"volumes": [
{
"order": 1
},
{
"order": 2
}
],
"length_of_volumes": 2
},
]
I tried the following, but it's not the proper way to do it as it's not a CharField -
from django.db.models.functions import Length
Books.objects.all().values('id', 'title', length_of_valumes=Length('volumes'))
len('title') will just determine the length of the string 'title' which thus has five characters, so as .values(…), you use .values(length_of_title=5).
You can make use of the Length expression [Django-doc]:
from django.db.models.functions import Length
Books.objects.values('id', 'title', length_of_title=Length('title'))
Note: normally a Django model is given a singular name, so Book instead of Books.
Similar to Willem's answer, but uses annotation. Taken from https://stackoverflow.com/a/34640020/14757226.
from django.db.models.functions import Length
qs = Books.objects.annotate(length_of_title=Length('title')).values('id', 'title', 'length_of_title')
An advantage would be you can then add filter or exclude clauses to query on the length of the title. So if you only wanted results where the title was less than 10 characters or something.
You can use dict structure:
values = []
for b in Books.objects.all().values('id', 'title'):
values.append({
'id': b.id,
'title': b.title,
'length_of_title': len(b.title)
})

Serializers in django rest framework with dynamic fields

I am trying to build a small api with django rest framework but I don't want to map directly the tables with calls (as in the examples).
I have the following database schema:
In models.py:
class ProductType(models.Model):
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
class Product(models.Model):
#staticmethod
def get_accepted_fields(self):
return {'color': 'pink', 'size': 34, 'speed': 0, 'another_prop': ''}
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
class ProductConfig(models.Model):
product_type = models.ForeignKey(ProductType)
product = models.ForeignKey(Product)
# a json field with all kind of fields: eg: {"price": 123, "color": "red"}
value = models.TextField(blank=True)
As you can see, every product can have multiple configurations and the value field is a json with different parameters. The json will be one level only. The configuration will have a flag if is active or not (so, the 1 product will have only 1 active configuration)
So, the data will look for example like this:
store_producttype
=================
1 type1
2 type2
store_product
=============
id name
1 car
store_productconfig
===================
id product_type_id product_id value active
1 2 1 { "color": "red", "size": 34, "speed": 342} 0
2 1 1 { "color": "blue", "size": 36, "speed": 123, "another_prop": "xxx"} 1
What I want to know is how can I get /product/1/ like this:
{
"id": 1,
"name": "car",
"type": "type1",
"color": "blue",
"size": 36,
"speed": 123,
"another_prop": "xxx",
}
and to create a new product posting a json similar with the one above.
The json fields are defined but some of them can miss (eg: "another_prop" in the productconfig.id=1
On update, anyway, it will create a new row in productconfig and it will put inactive=0 on the previous one.
So, every product can have different configuration and I want to go back to a specific configuration back in time in some specific cases). I am not really bound to this data model, so if you have suggentions for improvement I am open to them, but I don't want to have that properties as columns in the table.
The question is, what will be the best way to write the serializers for this model? There is any good example somewhere for a such use case?
Thank you.
Let's take this step by step:
In order to get a JSON like the one you posted, you must first transform your string (productConfig value field) to a dictionary. This can be done by using ast.literal_eval ( see more here).
Then, in your product serializer, you must specify the source for each field, like this:
class ProductSerializer(serializers.ModelSerializer):
color = serializer.Field(source='value_dict.color')
size = serializer.Field(source='value_dict.size')
type = serializer.Field(source='type.name')
class Meta:
model = Product
fields = (
'id',
'color',
'size',
'type',
)
This should work just fine for creating the representation that you want. However, this will not create automatically the product config, because DRF doesn't yet allow nested object creation.
This leads us to the next step:
For creating a product with a configuration from JSON, you must override the post method in your view, and create it yourself. This part shouldn't be so hard, but if you need an example, just ask.
This is more of a suggestion: if the json fields are already defined, wouldn't it be easier to define them as separate fields in your productConfig model?