I'm trying to show a the result of Django query using the module django-ajax-datatable. According to the documentation (Ajax datatable documentation)the attributes model und column_defs are at least needed to define the table.
The problem is that I wanted to show a query based on some joins where the result is not based on a specific model but on a lot of attributes coming from different tables and even calculation which are only present in the query and in no table at all. As the model is mandatory, but I cannot/don't want to define a model, i don't know what to assign for the model. Everything a tried with generic names or datatypes etc. failed. For example:
Query:
query_result = (Model1.objects.filter(model2__id=3)
.order_by("id")
.values("id", "name", "model2__start", "model3__number",
)
)
Datatable class:
class ExampleDatatableView(AjaxDatatableView):
"""."""
model = *Shouldbeaquery*
title = datatable_example
initial_order = [["id", "asc"], ]
length_menu = [[10, 20], [10, 20]]
search_values_separator = '+'
column_defs = [
#AjaxDatatableView.render_row_tools_column_def(),
{'name': 'id', 'visible': True, },
{"name": "name", "visible": True, },
{'name': 'start', 'visible': True, },
{'name': 'number', 'visible': True, },
]
Related
in my post requests to OrderProduct model, i want to only have to pass order.id and product.id and it works... untill i add a serializer to retrieve product.name. It might be because i didnt understand documentation about nested requests, but im unable to advance further into my project :(
[
{
"id": 2,
"order": 1,
"product": 1,
}
]
^ here's how it looks without nested serializer, and thats the data that i wanna have to input
[
{
"id": 2,
"order": 1,
"product": {
"id": 1,
"name": "gloomhaven",
},
},
^ here's how it looks after i add an additional serializer. I pretty much want these nested fields to be read only, with me still being able to send simple post requests
here are my serializers
class OrderProductSerializer(serializers.ModelSerializer):
product = Product()
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
class Product(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"id",
"name")
Is there any way for me to accomplish this? Thank you for trying to help!
Just overwrite to_representation method of the serializer
def to_representation(self, instance):
response = super().to_representation(instance)
response['other_field'] = instance.id# also response['other_field'] = otherSerializer(instance.model)
return response
This can solve your problem
I think you are missing many=True
class OrderProductSerializer(serializers.ModelSerializer):
product = Product(many=True)
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
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)
})
Is it possible to specify in which order fields will appear in a serialised model?
To make sure there is no confusion, while searching answers for this I have found lots of suggestions for ordering objects in a list view but this is not what I am looking for.
I really mean for a given model, I'd like their fields, once serialized to appear in a specific order. I have a fairly complex serialized object containing a lot of nested serializers, which appear first. I'd prefer instead key identifying fields such as name and slug to show up first, for readability.
Apologies in advance if this question is a duplicate, but I didn't find any relevant responses.
Solution
Based on #Toni-Sredanović solution I have implemented the following solution
def promote_fields(model: models.Model, *fields):
promoted_fields = list(fields)
other_fields = [field.name for field in model._meta.fields if field.name not in promoted_fields]
return promoted_fields + other_fields
class MySerializer(serializers.ModelSerializer):
...
class Meta:
model = MyModel
fields = promote_fields(model, 'id', 'field1', 'field2')
For that you can specify which fields you want to show and their order in class Meta:
class Meta:
fields = ('id', 'name', 'slug', 'field_1', 'field_2', ..., )
Here is a full example:
class TeamWithGamesSerializer(serializers.ModelSerializer):
"""
Team ModelSerializer with home and away games.
Home and away games are nested lists serialized with GameWithTeamNamesSerializer.
League is object serialized with LeagueSerializer instead of pk integer.
Current players is a nested list serialized with PlayerSerializer.
"""
league = LeagueSerializer(many=False, read_only=True)
home_games = GameWithTeamNamesSerializer(many=True, read_only=True)
away_games = GameWithTeamNamesSerializer(many=True, read_only=True)
current_players = PlayerSerializer(many=True, read_only=True)
class Meta:
model = Team
fields = ('id', 'name', 'head_coach', 'league', 'current_players', 'home_games', 'away_games', 'gender')
And the result:
{
"id": 1,
"name": "Glendale Desert Dogs",
"head_coach": "Coach Desert Dog",
"league": {
"id": 1,
"name": "Test league 1"
},
"current_players": [
{
"id": "rodriem02",
"first_name": "Emanuel",
"last_name": "Rodriguez",
"current_team": 1
},
{
"id": "ruthba01",
"first_name": "Babe",
"last_name": "Ruth",
"current_team": 1
}
],
"home_games": [
{
"id": 6,
"team_home": {
"id": 1,
"name": "Glendale Desert Dogs"
},
"team_away": {
"id": 2,
"name": "Mesa Solar Sox"
},
"status": "canceled",
"date": "2019-10-01"
},
{
"id": 7,
"team_home": {
"id": 1,
"name": "Glendale Desert Dogs"
},
"team_away": {
"id": 2,
"name": "Mesa Solar Sox"
},
"status": "",
"date": "2019-10-04"
}
],
"away_games": [
{
"id": 3,
"team_home": {
"id": 2,
"name": "Mesa Solar Sox"
},
"team_away": {
"id": 1,
"name": "Glendale Desert Dogs"
},
"status": "canceled",
"date": "2019-10-02"
}
],
"gender": "M"
}
If you would just use fields = '__all__' default ordering would be used which is:
object id
fields specified in the serializer
fields specified in the model
Best i can think of right now regarding your comment about generating fields is getting the fields in model, not really sure how to access what you've defined in the serializer so you would still need to write that manually.
Here is how you could do it with my example (this would make the name and gender appear on top):
class Meta:
model = Team
fields = ('name', 'gender')\
+ tuple([field.name for field in model._meta.fields if field.name not in ('name', 'gender')])\
+ ('league', 'home_games', 'away_games', 'current_players')
I'm trying to optimize the queries for my moderation system, build with Django and DRF.
I'm currently stuck with the duplicates retrieval: currently, I have something like
class AdminSerializer(ModelSerializer):
duplicates = SerializerMethodField()
def get_duplicates(self, item):
if item.allowed:
qs = []
else:
qs = Item.objects.filter(
allowed=True,
related_stuff__language=item.related_stuff.language
).annotate(
similarity=TrigramSimilarity('name', item.name)
).filter(similarity__gt=0.2).order_by('-similarity')[:10]
return AdminMinimalSerializer(qs, many=True).data
which works fine, but does at least one additional query for each item to display. In addition, if there are duplicates, I'll do additional queries to fill the AdminMinimalSerializer, which contains fields and related objects of the duplicated item. I can probably reduce the overhead by using a prefetch_related inside the serializer, but that doesn't prevent me from making several queries per item (assuming I have only one related item to prefetch in AdminMinimalSerializer, I'd still have ~2N + 1 queries: 1 for the items, N for the duplicates, N for the related items of the duplicates).
I've already looked at Subquery, but I can't retrieve an object, only an id, and this is not enough in my case. I tried to use it in both a Prefetch object and a .annotate.
I also tried something like Item.filter(allowed=False).prefetch(Prefetch("related_stuff__language__related_stuff_set__items", queryset=Items.filter..., to_attr="duplicates")), but the duplicates property is added to "related_stuff__language__related_stuff_set", so I can't really use it...
I'll welcome any idea ;)
Edit: the real code lives here. Toy example below:
# models.py
from django.db.models import Model, CharField, ForeignKey, CASCADE, BooleanField
class Book(Model):
title = CharField(max_length=250)
serie = ForeignKey(Serie, on_delete=CASCADE, related_name="books")
allowed = BooleanField(default=False)
class Serie(Model):
title = CharField(max_length=250)
language = ForeignKey(Language, on_delete=CASCADE, related_name="series")
class Language(Model):
name = CharField(max_length=100)
# serializers.py
from django.contrib.postgres.search import TrigramSimilarity
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from .models import Book, Language, Serie
class BookAdminSerializer(ModelSerializer):
class Meta:
model = Book
fields = ("id", "title", "serie", "duplicates", )
serie = SerieAdminAuxSerializer()
duplicates = SerializerMethodField()
def get_duplicates(self, book):
"""Retrieve duplicates for book"""
if book.allowed:
qs = []
else:
qs = (
Book.objects.filter(
allowed=True, serie__language=book.serie.language)
.annotate(similarity=TrigramSimilarity("title", book.title))
.filter(similarity__gt=0.2)
.order_by("-similarity")[:10]
)
return BookAdminMinimalSerializer(qs, many=True).data
class BookAdminMinimalSerializer(ModelSerializer):
class Meta:
model = Book
fields = ("id", "title", "serie")
serie = SerieAdminAuxSerializer()
class SerieAdminAuxSerializer(ModelSerializer):
class Meta:
model = Serie
fields = ("id", "language", "title")
language = LanguageSerializer()
class LanguageSerializer(ModelSerializer):
class Meta:
model = Language
fields = ('id', 'name')
I'm trying to find a way to prefetch related objects and duplicates so that I can get rid of the get_duplicates method in BookSerializer, with the N+1 queries it causes, and have only a duplicates field in my BookSerializer.
Regarding data, here would be an expected output:
[
{
"id": 2,
"title": "test2",
"serie": {
"id": 2,
"language": {
"id": 1,
"name": "English"
},
"title": "series title"
},
"duplicates": [
{
"id": 1,
"title": "test",
"serie": {
"id": 1,
"language": {
"id": 1,
"name": "English"
},
"title": "first series title"
}
}
]
},
{
"id": 3,
"title": "random",
"serie": {
"id": 3,
"language": {
"id": 1,
"name": "English"
},
"title": "random series title"
},
"duplicates": []
}
]
I have the following situation
class MyModel(models.Model):
key = models.CharField(max_length=255)
value = models.TextField(max_length=255)
category = models.CharField(max_length=4)
mode = models.CharField(max_length=4)
the fields key, category and mode are unique together. I have the following objects:
m1 = MyModel(key='MODEL_KEY', value='1', category='CAT_1' mode='MODE_1')
m2 = MyModel(key='MODEL_KEY', value='2', category='CAT_1' mode='MODE_2')
m3 = MyModel(key='MODEL_KEY', value='1', category='CAT_2' mode='MODE_1')
m4 = MyModel(key='MODEL_KEY', value='2', category='CAT_2' mode='MODE_2')
I want to expose an API that will group by key and category so the serialized data will look something like this:
{
"key": "MODEL_KEY",
"category": "CAT_1"
"MODE_1": { "id": 1, "value": "1" }
"MODE_2": { "id": 2, "value": "2" }
},
{
"key": "MODEL_KEY",
"category": "CAT_2"
"MODE_1": { "id": 3, "value": "1" }
"MODE_2": { "id": 4, "value": "2" }
}
Is there any way of doing this in django rest framework with ModelSerializer.
There is module that allows you to group Django models and still work with a QuerySet in the result: https://github.com/kako-nawao/django-group-by
Using the above to form your queryset:
# Postgres specific!
from django.contrib.postgres.aggregates.general import ArrayAgg
qs = MyModel.objects.group_by('key', 'category').annotate(
mode_list=ArrayAgg('mode')).order_by(
'key', 'category').distinct()
You can then access the properties key, category and mode_list on the resulting QuerySet items as attributes like qs[0].mode_list. Therefore, in your serializer you can simply name them as fields.
The model_list field might require a SerializerMethodField with some custom code to transform the list.
Note that you need an aggregation if you don't want to group by mode, as well.