Can django prefetch related work with 3 lookups? - python

I want to prefetch 3 tables plus the initial table. Here are model examples of the project i am working on
class ExampleOne(models.Model):
name = models.Charfield()
option = models.BooleanField()
money = MoneyField()
that_date = models.DateTimeField()
class ExampleTwo(models.Model):
word = models.Charfield()
example_one = models.ManyToManyField(ExampleOne)
number = models.IntegerField()
class ExampleThree(models.Model):
number = models.IntegerField()
example_two = models.ForeignKey(ExampleTwo)
this_date = models.DateTimeField()
#property
def get_calculation_one(self):
if self.example_two.example_one.exists():
for example1 in self.example_two.example_one.all():
if example1.option is not None:
if self.this_date >= example1.that_date):
return self.number * example1.money.amount
else:
if self.this_date >= datetime.today()):
return self.number * example1.money.amount
else:
return 0
class ExampleFour(models.Model):
date = models.DateField()
example_three = models.ManyToManyField(ExampleThree)
#property
def get_calculation_two(self):
cost = 0
if self.example_three.exists():
for example3 in self.example_three.all():
cost += example3.get_calculation_one
return cost
return cost
Now I want to know if it is possible to retrieve data from all these models with as little hits to the database as possible because when I try to retrieve data it takes over one minute to retrieve the data and send it to the frontend
The calculation might be making many database hits in order to get the data and calculate and I think that is why it is taking more than a minute to retrieve the data
my view looks like this
qs = ExampleFour.objects.prefetch_related(
Prefetch('example_three__example_two__example_one')
).filter(...)
Is there a way to use prefetch to make the retrieval faster or is there another suggestion on I can rewrite my code in order to avoid this long retrieval time?
Note I am still new to Django
I am also using DRF so here are how the serializers look
class ExampleOneSerializer(serializers.ModelSerializer):
class Meta:
model = ExampleOne
fields = (
"name",
"option",
"money",
"that_date",
)
class ExampleTwoSerializer(serializers.ModelSerializer):
example_1_value = serializers.SerializerMethodField()
class Meta:
model = ExampleOne
fields = (
"word",
"example_one",
"number",
)
read_only_fields =(
"example_1_value",
)
def get_example_1_value(self, obj):
return ExampleOneSerializer(obj.example_one.latest('that_date')
).data
class ExampleThreeSerializer(serializers.ModelSerializer):
example_2_data = serializers.SerializerMethodField()
get_calculation_one = serializers.ReadOnlyField()
class Meta:
model = ExampleOne
fields = (
"number",
"example_two",
"this_date",
"example_2_data",
"get_calculation_one",
)
read_only_fields =(
"example_2_data",
"get_calculation_one",
)
def get_example_2_data(self, obj):
return ExampleTwoSerializer(obj.example_two).data
class ExampleFourSerializer(serializers.ModelSerializer):
example_3_data = serializers.SerializerMethodField()
get_calculation_two = serializers.ReadOnlyField()
class Meta:
model = ExampleOne
fields = (
"date",
"example_three",
"example_3_data",
"get_calculation_two",
)
read_only_fields =(
"example_3_data",
"get_calculation_two",
)
def get_example_3_data(self, obj):
return ExampleThreeSerializer(obj.example_three.all(),
many=True).data
I wonder if would prefetch work on the serializer side or would it be redundant

Related

AssertionError: The field '' was declared on serializer '' but has not been included in the 'fields' option. optional

Im, using Django Rest Framework to make an APIRest but I got this error when trying to make a simple crud for the discount resource
This is my model
class Discount(BaseModel):
""" Discount Model. """
code = models.CharField(max_length=50)
rate = models.DecimalField( max_digits=3, decimal_places=2)
class DiscountStatus(models.TextChoices):
ENABLED = 'ENABLED'
DISABLED = 'DISABLED'
REDEEMED = 'REDEEMED'
status = models.CharField("status", choices=DiscountStatus.choices, default=DiscountStatus.ENABLED, max_length=10)
insurance_company = models.ForeignKey("sanitas.InsuranceCompany", on_delete=models.CASCADE)
appointment = models.ForeignKey("sanitas.Appointment", verbose_name="appointment", on_delete=models.CASCADE, blank=True, null=True)
class Meta():
""" Meta class. """
db_table = 'discounts'
This is my serializer
class DiscountModelSerializer(serializers.ModelSerializer):
""" Insurance company model serializer. """
insurance_company = InsuranceCompanyModelSerializer(many=False, read_only=True, allow_null=True, required=False)
insurance_company_id = serializers.IntegerField(allow_null=True, required=False,)
appointment = AppointmentModelSerializer(many=False, read_only=True, allow_null=True, required=False)
appointment_id = serializers.IntegerField(allow_null=True, required=False)
class Meta():
""" Meta class. """
model = Discount
fields = (
'id',
'code',
'rate',
'status',
'appointment'
'insurance_company'
'insurance_company_id'
'appointment_id'
)
And this is my Viewset
class DiscountViewset(viewsets.ModelViewSet):
""" Schedule items will be the time unit to manage appointments. """
queryset = Discount.objects.all()
serializer_class = DiscountModelSerializer
#action(methods=['post'], detail=False)
def generate(self, request):
""" Create dicounts in bulk. """
rate = 1 - request.data['rate'] / 100
insurance_company = get_object_or_404(InsuranceCompany, pk=request.data['insurance_company_id'])
count = 0
amount = request.data['amount']
response = []
while count < amount:
code = insurance_company.code + randomString(8)
discount = Discount()
discount.rate = rate
discount.code = code
discount.insurance_company = insurance_company
discount.save()
serializer = DiscountModelSerializer(discount)
response.append(serializer.data)
count = count + 1
return Response(response, status=status.HTTP_200_OK)
My guess is that the error appears because I have an optional field (is not really optional, but when I create the discount codes there is no appointment yet so it will be added later) and the error appears because I didn't add the field "appointment" and when trying to retrieve the resources I make It shows this error
AssertionError at /discounts/generate/ The field 'appointment_id' was declared on serializer DiscountModelSerializer, but has not been included in the 'fields' option.

Django model reference and manipulation

I have the following models in Django that have a structure as follows:
class Office_Accounts(models.Model):
accountid = models.EmailField(max_length=200, unique=True)
validtill = models.DateField(default=datetime.now)
limit = models.CharField(max_length=2)
class Device(models.Model):
device_type = models.ForeignKey(DeviceType,to_field='device_type')
serial_number = models.CharField(max_length=200,unique=True)
in_use_by = models.ForeignKey(User,to_field='username')
brand = models.CharField(max_length=200,default="-", null=False)
model = models.CharField(max_length=200,default="-", null=False)
type_number = models.CharField(max_length=200,blank=True,null=True, default = None)
mac_address = models.CharField(max_length=200,blank=True,null=True, default = None)
invoice = models.FileField(upload_to='Device_Invoice', null=True, blank = True)
msofficeaccount = models.ForeignKey(Office_Accounts, to_field="accountid")
class Meta:
verbose_name_plural = "Devices"
def full_name(self):
return self.device_type + self.serial_number + self.brand
I will display both of the models in admin.py.
Now, I want to display the count of each accountid present in the field "msofficeaccount" (present in Device Models) in my admin page of Office_Accounts model. For an example if xyz#abc.com appears in 10 rows of msofficeaccount field then, the count should be displayed as 10 in Office_Accounts admin page. Can anyone please guide me how should I approach this problem to solve it?
You could add a method to your admin class that returns the count of related devices for each office_account, but that would be very inefficient. Instead you can override get_queryset to annotate the count from a database aggregation function:
from django.db.models import Count
class Office_AccountsAdmin(admin.ModelAdmin):
list_display = (..., 'device_count')
...
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(device_count=Count('device'))
(On a minor note, Python style is always to use CamelCase for class names, and Django style is to use singular model names, so your model should really be called OfficeAccount.)

Nested Django REST

It seems very simple to do nested serializer in Django REST.
But I can not find anything wrong in my code.carts does not shown up
I had followed http://www.django-rest-framework.org/api-guide/relations/#nested-relationships
Only one thing different is I use JSONField.
It should not be a problem.
class MailAPIOrderItemSerializer(serializers.ModelSerializer):
product = serializers.SerializerMethodField()
category = serializers.SerializerMethodField()
variant = serializers.SerializerMethodField()
class Meta:
model = OrderItem
fields = ('category', 'product', 'variant')
def get_variant(self, obj: OrderItem):
return obj.product.get('level_two')
def get_category(self, obj: OrderItem):
return obj.product.get('service_name')
def get_product(self, obj: OrderItem):
return obj.product.get('product_name')
class MailAPIOrderSerializer(serializers.ModelSerializer):
...
carts = MailAPIOrderItemSerializer(many=True, read_only=True)
class Meta:
model = Order
fields = (
...
'carts'
)
def get_staff_name(self, order: Order):
return order.staff.full_name
class OrderItem(AbstractSoftModelController):
order = models.ForeignKey(Order, related_name='order_items', verbose_name=_('Order'))
product = JSONField(verbose_name=_('Product'))
My Temporal Solution:
Right now. I am working around by replacing
carts = serializers.SerializerMethodField()
And in the method I use
def get_carts(self, order: Order):
qs = order.order_items.all()
serializer = MailAPIOrderItemSerializer(qs, many=True)
return serializer.data
In model Order you have backref field order_items not carts. So try change field name in MailAPIOrderSerializer:
order_items = MailAPIOrderItemSerializer(many=True, read_only=True)
class Meta:
model = Order
fields = (
...
'order_items'
)

Alter django rest framework field before serialization and deserialization

what I'm trying to do, is to convert unix_epoch into python dateTime and vice versa while serialization and desiarilization. What I've done so far:
My model Track:
class Track(models.Model):
uuid = models.UUIDField(null=True)
distance = models.FloatField(null=True)
dateCreated = models.DateTimeField(null=True)
dateClosed = models.DateTimeField(null=True)
def date_created_to_epoch(self):
return time.mktime(self.dateCreated.timetuple())
def date_closed_to_epoch(self):
return time.mktime(self.dateClosed.timetuple())
My model Point:
class Point(models.Model):
uuid = models.UUIDField(null=True)
track_uuid = models.UUIDField(null=True)
dateCreated = models.DateTimeField(null=True);
track = models.ForeignKey(Track, related_name='points')
def date_created_to_epoch(self):
return time.mktime(self.dateCreated.timetuple())
My serializers:
class PointSerializer(serializers.ModelSerializer):
dateCreated = serializers.ReadOnlyField(source='date_created_to_epoch')
class Meta:
model = Point
fields = ('uuid', 'lat', 'lng')
class TrackSerializer(serializers.ModelSerializer):
points = PointSerializer(many=True)
dateCreated = serializers.ReadOnlyField(source='date_created_to_epoch')
dateClosed = serializers.ReadOnlyField(source='date_closed_to_epoch')
class Meta:
model = Track
fields = ('uuid', 'distance', 'dateCreated', 'dateClosed', 'comments', 'type', 'status', 'points')
def create(self, validated_data):
points_data = validated_data.pop('points')
track = Track.objects.create(**validated_data)
for point_data in points_data:
Point.objects.create(track=track, **point_data)
return track
As you see, I've made from database to json convertion with def date_created_to_epoch(self): method. What I need is to implement json(unix time) to database (date time) convertion. I use nested models. I think my methods are incorrect and there is a way to make it better. Please help.
I would use Django Rest Framework's built in DateTime fields instead of the raw .ReadOnlyField. You can make any field readonly by passing read_only=True to the field's constructor.

Django REST -- How to "modify" value before returning the REST response?

I have done some research, but I'm not quite sure what I'm technically looking for so I didn't find much information. I am using Django REST.
model
class Car(models.Model)
name = name = models.CharField(blank=True, null=True, db_index=True)
speed = models.IntegerField(default=SPEED.FAST)
view
class CarViewSet(viewsets.ModelViewSet):
queryset = Car.objects.all()
serializer_class = CarSerializer
serializer
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
Question: My speed field in Car model is an integer, when I access the REST endpoint API, I don't want to return an integer, but instead a string. Basically:
if speed is 0:
return "slow"
else:
return "fast"
So an example JSON response is:
{ name: "ferrari", speed: "fast" }
You can use SerializerMethodField:
class CarSerializer(serializers.ModelSerializer):
speed = serializers.SerializerMethodField()
def get_speed(self, obj):
if obj.speed == 0:
return "slow"
else:
return "fast"
class Meta:
model = Car
fields = ('name', 'speed')
Adding another approach -
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('name', 'speed')
def to_representation(self, data):
data = super(CarSerializer, self).to_representation(data)
data['speed'] = 'slow' if data.get('speed') == 0 else 'fast'
return data
Also, one improvement in #Gocht answer.
Better practice to omit extra else statement while using conditional return in method get_speed -
def get_speed(self, obj):
if obj.speed == 0:
return "slow"
return "fast"

Categories

Resources