How to aggregate annotate fields of related models in django - python

My problem is a little more complicated, but I'm posting the question in the simplest possible way.
Annotated total_score in Photo.
I would like to annotate max_total_score in Person.
I wrote get_queryset of PersonManager, but the following error occurred.
Is there a new way?
models.py
from django.db import models
class PersonManager(models.Manager):
def get_queryset(self):
photos_prefetch = models.Prefetch(
'photos',
Photo.objects.annotate(total_score=models.Sum('features__score'))
)
return super().get_queryset() \
.prefetch_related(photos_prefetch) \
.annotate(max_total_score=models.Max('photos__total_score'))
class Person(models.Model):
objects = PersonManager()
class Photo(models.Model):
person = models.ForeignKey(Person, models.CASCADE, related_name='photos')
class Feature(models.Model):
photo = models.ForeignKey(Photo, models.CASCADE, related_name='features')
name = models.CharField(max_length=100)
score = models.IntegerField()
shell
>> Person.objects.all()
# Unsupported lookup 'total_score' for BigAutoField or join on the field not permitted
I solved it by using the values ​​method of the queryset.
class PersonManager(models.Manager):
def get_queryset(self):
photos = Photo.objects.annotate(total_score=models.Sum('features__score'))
photos_prefetch = models.Prefetch(
'photos',
photos
)
return super().get_queryset() \
.prefetch_related(photos_prefetch) \
.annotate(max_total_score=models.Max(photos.values('total_score')))

Related

Django , how to show 'secondary property' of parent in TabularInline

I try to do tabular inline admin.
In the child tab, if we include a ForeignKey field, it will show the str property of that foreign model.
But how to also show another property of that foreign model?
Here is my models.py
class RawMaterial(models.Model):
name = models.CharField(max_length=15)
ubuy = models.CharField(max_length=5)
usell = models.CharField(max_length=5)
uconv = models.DecimalField(max_digits = 5,decimal_places=2)
def __str__(self):
return self.name
class Coctail(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Ingredient(models.Model):
coctail = models.ForeignKey(Coctail,
related_name='ingredient',
on_delete=models.CASCADE)
rawmaterial = models.ForeignKey(RawMaterial,
related_name='ingredient',
on_delete=models.CASCADE)
qty = models.DecimalField(max_digits = 5,decimal_places=2)
def __str__(self):
return self.rawmaterial
def rawusell(self):
return self.rawmaterial.usell
rawusell.short_description = 'UOM'
Here is my admin.py
from django.contrib import admin
# Register your models here.
from .models import *
admin.site.register(RawMaterial)
class IngredientInline(admin.TabularInline):
model = Ingredient
list_display = ('rawmaterial', 'qty', 'rawusell')
class CoctailAdmin(admin.ModelAdmin):
inlines = [IngredientInline]
admin.site.register(Coctail, CoctailAdmin)
and here what I got
My question is : How to show rawmaterial.usell in Ingredient tab?
Sincerely
-bino-
You can show the rawmaterial.usell field in the ingredient tab but it will not be editable. Since, any field can only be editable if they are a field of that model (Without using any custom form and logic).
So, if you want rawmaterial.usell to be editable, you will have to make a rawmaterial admin
You can show the rawmaterial.usell in IngredientInline by doing this.
class IngredientInline(admin.TabularInline):
model = Ingredient
readonly_fields = ('rawusell', )
list_display = ('rawmaterial', 'qty', 'rawusell')
def rawusell(self, obj):
return obj.rawmaterial.usell
This will start showing usell in the inline admin.

DRF select_related and prefetch_related doesn't work

I'm try to decrease query counts for using prefetch_related and select_related. However, it seems doesn't work.
in Match Model have 5 ForeignKey fields, so when i get the query counts it will return 5. Also when i delete def get_queryset method in MatchDetailAPIView. The Api still work. ( e.g 127.0.0.1:8000/game/match/match-1 is working whether or not the get_queryset method.
I can't find where I'm doing wrong.
Models.py
class Game(models.Model):
name = models.CharField(max_length=255)
...
class Match(models.Model):
name = models.TextField(blank=False, null=False)
game = models.ForeignKey(Game, on_delete=models.SET_NULL, null=True)
tournament = models.ForeignKey(Tournament, on_delete=models.SET_NULL, null=True, blank=True)
....
serializers.py
class MatchSerializer(serializers.ModelSerializer):
class Meta:
model = Match
fields = '__all__'
#exclude = ['participant', ]
views.py
class MatchDetailAPIView(RetrieveAPIView):
serializer_class = MatchSerializer
def get_queryset(self):
queryset =Match.objects.all().prefetch_related('game_id')
return queryset
def get_object(self):
gameslug = self.kwargs.get('gameslug')
slug = self.kwargs.get('slug')
# find the user
game = Game.objects.get(slug=gameslug)
return Match.objects.get(slug=slug, game__slug=game.slug)
def get_serilizer_context(self, *args, **kwargs):
return {'request': self.request}
You should use select_related for ForeignKey fields, since prefetch_related does the joining in Python, and select_related creates an SQL join.
You should also refer to the relationship by it's name, not the id of the ForeignKey ('game_id' vs. 'game'):
def get_queryset(self):
queryset =Match.objects.all().select_related('game')
return queryset
As mentioned in django docs, select_related should be used for foeign-key relationships. Also for RetrieveAPIView override the get method.
def get(self, request, *args, **kwargs):
gameslug = self.kwargs.get('gameslug')
slug = self.kwargs.get('slug')
game = Game.objects.get(slug=gameslug)
return Match.objects.filter(slug=slug, game__slug=gameslug).select_related('game').first()

Django REST: Serializer lookup by UUID

I'm creating this simple shopping API in Django REST.
Internally I'm using IDs for foreign key constraints, while guuids are brought to the outside world.
For the checkout procedure, the user provides a list of article IDs he is willing to purchase. The object in the POST data thus looks as follows:
{
assets: [
{
'product': 'd9d5044d-2284-4d15-aa76-2eee3675035b',
'amount': 4
},
....
]
}
I'm using the following ticket/asset models:
# Ticket
class Ticket(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='tickets', on_delete=models.CASCADE)
# Assets
class Asset(models.Model):
ticket = models.ForeignKey(Ticket, related_name='assets', on_delete=models.CASCADE)
stock_item = models.ForeignKey(Stock, related_name='stock_item', on_delete=models.SET_NULL, null=True)
amount = models.IntegerField(validators=[MinValueValidator(0)])
And the serializers look as follows:
# Asset serializer
class AssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ('stock_item', 'amount')
# Ticket serializer
class TicketSerializer(WritableNestedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
assets = AssetSerializer(many=True)
class Meta:
model = Ticket
fields = ('uuid', 'owner', 'assets', )
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
When posting an object of the type specified above, the following error is presented:
{"assets":[{"stock_item": ["Invalid type. Expected PK, received string"]}]}
Which I can't seem to solve, how do I instruct the serializer to use the uuid as the lookup value? I solved a similar problem on view-level earlier by using the lookup_field member, but that doesn't seem to solve it. Any suggestions?
Enter code here
If I have understood you correctly, a SlugRelatedField should be able to find the correct related object.
class AssetSerializer(serializers.ModelSerializer):
ticket = serializers.SlugRelatedField(
read_only=True,
slug_field='uuid',
queryset=Ticket.objects.all() # Might be redundant with read_only=True
)
class Meta:
model = Asset
fields = ('ticket', 'stock_item', 'amount')
Elaborating on #BjornW's comment:
class UUIDRelatedField(serializers.SlugRelatedField):
slug_field = 'uuid'
def __init__(self, **kwargs):
super().__init__(slug_field=self.slug_field, **kwargs)
def to_representation(self, obj):
return getattr(obj, self.slug_field).hex

Populate Django ManyToManyField Options Based on Other Field in Admin

I am having problems filtering options for a ManyToManyField on the Django Admin Add screen based on input to another field on the same form. I am new to Django and have been unable to use any of the generic fixes described elsewhere because they are all slightly different than my situation. Here is my situation:
I have three models in my project: Class, Student, and AttendanceRecord. In the Django Admin, when adding an attendance record, I would like to change the options for the field Absent_Students based on the selection made for the field Associated_Class. So, for example, if Associated_Class "CS 450" is selected, the options for Absent_Students should change to only students whose class_list includes CS 450.
Here are my models:
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
from django.utils.encoding import python_2_unicode_compatible
import random, string
# Create your models here.
#This is the model for a student
#python_2_unicode_compatible
class Student(models.Model):
pass
Student_First_Name = models.CharField(max_length=200)
Student_Last_Name = models.CharField(max_length=200)
Student_ID_Number = models.CharField(max_length=200)
Student_Class = models.ForeignKey('Class', null=True)
def __str__(self):
return self.Student_Last_Name + ',' + self.Student_First_Name
# This is the model for a class
#python_2_unicode_compatible
class Class(models.Model):
class Meta:
verbose_name_plural = "Classes"
Class_Name = models.CharField(max_length=200)
Student_List = models.ManyToManyField('Student', related_name='class_list')
Professor = models.ForeignKey(User,null=True)
AddCode = models.IntegerField
pass
def __str__(self):
return self.Class_Name
def getName(self):
return self.Class_Name
def getProfessor(self):
return self.Professor.id
def getProf(self):
return self.Professor
def getStudents(self):
return self.Student_List
#This is the model for attendance records
class AttendanceRecord(models.Model):
class Meta:
verbose_name = "Attendance Record"
Associated_Class = models.ForeignKey(Class, on_delete=models.CASCADE, related_name='Attendance_Records')
Date = models.DateField()
Absent_Students = models.ManyToManyField('Student', blank=True)
Present_Students = models.ManyToManyField('Student', related_name='a')
def get_associated_class_id(self):
return self.Associated_Class
def __str__(self):
return self.Associated_Class.__str__() + ' on date ' + self.Date.__str__(self)
I have tried doing this by editing the AttendanceRecordAdminForm class and AttendanceRecordAdmin class. My problem is that when setting the self.fields['Absent_Students].queryset I do not know how to access the currently selected Associated_Class on the form. I keep getting an error that "AttendanceRecord has no Associated_Class". Here are those classes just discussed in their entirety:
class AttendanceRecordAdminForm(forms.ModelForm):
class Meta:
model = AttendanceRecord
fields = '__all__'
def __init__(self, *args, **kwargs):
super(AttendanceRecordAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
self.fields['Absent_Students'].queryset = Student.objects.filter(class_list__id=self.instance.get_associated_class_id())
self.fields['Present_Students'].queryset = Student.objects.filter(class_list__id=1)
class AttendanceRecordAdmin(admin.ModelAdmin):
form = AttendanceRecordAdminForm
filter_horizontal = ('Absent_Students', 'Present_Students',)
Basically, I am looking for a way to access the currently entered Associated_Class on the admin form so I can properly filter the queryset.
After hours more of online searching I finally found what I needed. A chained ManyToMany from the smart_select app makes this very easy. This link: How to use django-smart-select describes the install process and also links to the documentation for using it once it is installed. Hopefully this helps some others as well.

Django Rest Framework: dynamic database on POST - RelatedField or PrimaryKeyRelatedField

I'm developing RESTFul services with DRF and I have multiple databases depending on the country (see my last question here)
I'm having a problem now with relationships, I have two models: Category and SubCategory:
class SubCategory(models.Model):
objects = CountryQuerySet.as_manager()
id = models.AutoField(primary_key=True,db_column='sub_category_id')
name = models.TextField()
female_items_in_category = models.BooleanField()
male_items_in_category = models.BooleanField()
kids_items_in_category = models.BooleanField()
category = models.ForeignKey('Category')
class Meta:
managed = True
db_table = Constants().SUBCATEGORY
And the serializer is:
class SubCategorySerializer(serializers.ModelSerializer):
category = PrimaryKeyRelatedField(queryset=Category.objects.using('es').all())
class Meta:
model = SubCategory
fields = ('id', 'name','female_items_in_category','male_items_in_category','kids_items_in_category','category')
If I don't set the queryset with the proper country it fails, because it doesn't know where to get the category.
Here the problem
I already set the country in the serializer context (in the ModelViewSet):
def get_serializer_context(self):
return {Constants().COUNTRY: self.kwargs.get(Constants().COUNTRY)}
But I can not find the proper way to get the self.context.get(Constants().COUNTRY) in the serializer.
Do you any have an idea to solve this? Thanks!
Well, I found a solution to my problem: I overwrite the method get_fields in the serializer:
def get_fields(self, *args, **kwargs):
fields = super(SubCategorySerializer, self).get_fields()
country = self.context.get(Constants().COUNTRY)
qs = Category.objects.using(country).all()
fields['category'].queryset = qs
return fields
And that works!

Categories

Resources