I'm using Django 2.0 and Django REST Framework.
I have two models contact and transaction as below
contact model
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
amount given model
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
interest_rate = models.FloatField(blank=True, default=None, null=True, help_text='% of interest to be calculated')
_given_date = models.DateTimeField(
db_column='given_date',
default=timezone.now,
help_text='Date and time when amount was given to the contact'
)
def __str__(self):
return str(self.amount)
#property
def given_date(self):
return self._given_date
#given_date.setter
def given_date(self, value):
self._given_date = value
#property
def interest_to_pay(self):
if self.interest_rate:
datetime_diff = datetime.now(get_localzone()) - self.given_date
days = datetime_diff.days
duration_in_year = days/365
simple_interest_amount = (self.amount * duration_in_year * self.interest_rate)/100
return simple_interest_amount
return 0
#property
def total_payable(self):
return self.amount + self.interest_to_pay
#property
def amount_due(self):
returned_amount = 0
for returned in self.amountreturned_set.all():
returned_amount += returned.amount
return self.total_payable - returned_amount
and ContactSerializer
class ContactSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail',
read_only=True
)
user = serializers.CurrentUserDefault()
amount_due = ReadOnlyField(source='amountgiven__amount_due')
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'full_name', 'amount_due')
and in views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
But there is no field as amount_due and url in the response returned while making the request to /contacts/ endpoint with GET method.
Based on your comment, you want the sum of all the amounts(please edit your question). so you should use annotate in your queryset:
from django.db.models import Sum
def get_queryset(self):
return Contact.objects.filter(user=self.request.user).annotate(amount_due=Sum('amountgiven_set__amount'))
(I recommend using modelManager for the queryset and the filtering instead of doing it here)
and add a field like this to your serializer:
amount_due = serializer.IntegerFiled()
Your modeling doesn't allow you to access amount_due in the way which you'd like.
Your Contact model does not have amountgiven attribute.
It does however have amountgiven_set which you can use to obtain a queryset of amountgiven for given contact.
But there can be multiple ones so you need to decide which amount_due you want to display in your serializer.
You can use SerializerMethodField to serializer the value of amount_due which you would like:
class ContactSerializer(serializers.HyperlinkedModelSerializer):
amount_due = serializers.SerializerMethodField()
def get_amount_due(self, obj):
amountgiven = obj.amountgiven_set.first()
return amountgiven.amount_due
But again, as i already mentioned - amountgiven_set returns a queryset where you can have multiple objects.
In case you are sure you have only one for given contact, you can use first() as in my example to get it.
Related
I've been trying to create an api endpoint to update my "lead" objects and add a list of facilities to them when sending a put request (each time a different amount of facilities). The lead objects already exist inside the database so do the facility objects. Since i need a date and time associated to each facility when they are being added to a lead i created the "LeadFacilityAssign" class.
Since i wasn't able to get it to work i tried to do it just with a post request for now, during the lead creation process. I was told that i need to use bulk_create if i need to add more than one facility this way. I couldn't find anything on bulk_create inside the drf documentation so i decided to do this for now just with one facility and improve my code from there one issue at a time since i'm new to drf.
Does anyone know what is causing this error? I tried a few different things but nothing worked so far.
ValueError: Cannot assign "1": "LeadFacilityAssign.assigned_facilities" must be a "Facility" instance.
serializers.py
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.IntegerField(required=True)
datetime = serializers.DateTimeField(required=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"assigned_facilities",
"datetime",
)
read_only_fields = ("id", "created_at", "agent", "is_owner")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
assigned_facilities = validated_data.pop("assigned_facilities")
datetime = validated_data.pop("datetime")
instance = Lead.objects.create(**validated_data)
instance.leadfacility.create(assigned_facilities=assigned_facilities,datetime=datetime)
print(instance)
return instance
models.py
class Facility(models.Model):
name = models.CharField(max_length=150, null=True, blank=False)
def __str__(self):
return self.name
class Lead(models.Model):
first_name = models.CharField(max_length=40, null=True, blank=True)
last_name = models.CharField(max_length=40, null=True, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class LeadFacilityAssign(models.Model):
assigned_facilities = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name='leadfacility')
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='leadfacility')
datetime = models.DateTimeField()
views.py
class LeadCreateView(CreateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def perform_create(self, serializer):
serializer.save(agent=self.request.user)
class LeadUpdateView(UpdateAPIView):
permission_classes = [IsAuthenticated, IsLeadOwner]
serializer_class = LeadUpdateSerializer
def get_queryset(self):
return Lead.objects.all()
You are trying to add Integer value into FK field.
You have 2 options. You can change the serializer field.
assigned_facilities = serializers.PrimaryKeyRelatedField(queryset=Facility.objects.all(), required=True, write_only=True)
OR
assigned_facilities = serializers.IntegerField(required=True, write_only=True)
instance.leadfacility.create(assigned_facilities_id=assigned_facilities,datetime=datetime)
I would rather use 1 option.
Another potential solution you can apply:
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.IntegerField(required=True)
datetime = serializers.DateTimeField(required=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"assigned_facilities",
"datetime",
)
read_only_fields = ("id", "created_at", "agent", "is_owner")
def validate_assigned_facility(self, facility_pk)->:
assigned_facility = Facility.objects.filter(pk=facility_pk).first()
if assigned_facility:
return assigned_facility
raise ValidationError('Facility not found, provide a valid pk')
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
assigned_facilities = validated_data.pop("assigned_facilities")
datetime = validated_data.pop("datetime")
instance = Lead.objects.create(**validated_data)
instance.leadfacility.create(assigned_facilities=assigned_facilities,datetime=datetime)
print(instance)
return instance
This solution is kind of big but is so flexible 'cause give you the opportunity to add more business logic around the input and the expected data in the model or datasource.
I have EndPoint that let user booking halls. But when i try to booking hall it give me error NOT NULL constraint failed. I lock at the problem 'NOT NULL constraint failed' after adding to models.py. they suggest to put null in the filed. But in my case i must save the hall id otherwise I can not know the hall that user is booking (it save null in the hall)
at the beginning it show this Error: NOT NULL constraint failed: api_bookingmodel.user_id and I solve it by putting in the function perform_create ... serializer.save(user=self.request.user)
This is my Code....
Model classes
class HallModel(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='hall_owner')
name = models.CharField(max_length=100)
price = models.FloatField()
phone = models.CharField(max_length=9, blank=True)
size = models.CharField(max_length=50)
region = models.CharField(max_length=100)
state = models.CharField(max_length=100)
image_1 = models.ImageField(upload_to='halls_image')
image_2 = models.ImageField(upload_to='halls_image')
image_3 = models.ImageField(upload_to='halls_image')
created_at = models.DateTimeField(auto_now_add=True)
class BookingModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_booking')
hall = models.ForeignKey(HallModel, on_delete=models.CASCADE, related_name='hall_owner')
time_date = models.DateTimeField(auto_now_add=True)
booking_method = models.IntegerField()
Serializer
class HallSerializer(serializers.ModelSerializer):
booking = serializers.SerializerMethodField()
class Meta:
model = HallModel
fields = '__all__'
def get_booking(self, obj):
booking = BookingSerializer(obj.hall_owner.all(),many=True).data
return booking
def perform_create(self, serializer):
serializer.save()
class BookingSerializer(serializers.ModelSerializer):
hall = serializers.SerializerMethodField()
class Meta:
model = BookingModel
fields = '__all__'
depth = 1
def get_hall(self, obj):
serializer_data = HallSerializer(obj.hall).data
return serializer_data
View Sets
class HallViewSet(viewsets.ModelViewSet):
queryset = HallModel.objects.all()
serializer_class = HallSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class BookingViewSet(viewsets.ModelViewSet):
serializer_class = BookingSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
return self.request.user.user_booking.all()
it show this Error: IntegrityError at /api/booking/
NOT NULL constraint failed: api_bookingmodel.hall_id... I think i must save the hall id in the perform_create function but I don't able to do it. the stange things is when i delete the depth = 1 in the booking serializer it not show me the Error ... any one have a solution.
You can override def perform_create in BookingViewSet class, like below:
def perform_create(self, serializer):
serializer.save(
user=self.request.user,
hall=self.request.data['hall_id']
)
make sure that hall_id is ready in post request. Also def perform_create in HallSerializer is useless. read cdrf.co
I have an API that can list several buildings. Each building belongs to several building groups and each building group contains several buildings.
I want to show single fields of one building group. More specifically I want to show all buildings of one building group in my RetrieveAPIView.
I can list a single BuildingGroup instance using the generic view like so:
class BuildingGroupRetrieveAPIView(RetrieveAPIView):
serializer_class = BuildingGroupSerializer
queryset = BuildingGroup.buildings.all()
I assume that I can overwrite the get method to only display a single field of that retrieved object. Specifically I want to display all the objects that are in my many to many relation. Or better to say, I want to retrieve all the complete data within my m2m relation.
Here are my models:
class Building(models.Model):
name = models.CharField(max_length=120, null=True, blank=True)
def __str__(self):
return self.name
class BuildingGroup(models.Model):
description = models.CharField(max_length=500, null=True, blank=True)
buildings = models.ManyToManyField(Building, default=None, blank=True)
I tried this without success:
def get(self):
building_group = BuildingGroup.objects.get(id='id')
qs = building_group.buildings.all()
return qs
my serializer
class BuildingGroupSerializer(serializers.ModelSerializer):
class Meta:
model = BuildingGroup
fields = (
'description',
.....
)
I can attach a screenshot to be more clear.
Any help is highly appreciated. Thanks in advance
Here is my full view:
class BuildingGroupAPIView(ListAPIView):
permission_classes = [permissions.IsAdminUser]
authentication_classes = [SessionAuthentication]
serializer_class = BuildingGroupSerializer
passed_id = None
def get_queryset(self):
qs = BuildingGroup.objects.all()
query = self.request.GET.get('q')
if query is not None:
qs = qs.filter(name=query)
return qs
class BuildingGroupRetrieveAPIView(RetrieveAPIView):
serializer_class = BuildingGroupSerializer
queryset = BuildingGroup.buildings.all()
def get(self):
building_group = BuildingGroup.objects.get(id='id')
qs = building_group.buildings.all()
return qs
I'm using Django Rest Framework.
Here is model:
class TimeSlot(models.Model):
date_start = models.DateTimeField(default=None, null=True, blank=True)
booked = models.BooleanField(default=False)
user = models.ForeignKey(User, related_name='time_slots', on_delete=models.SET_NULL, default=None)
Here is my serializer:
class CreateBookingSerializer(serializers.ModelSerializer):
class Meta:
model = TimeSlot
fields = ('id','booked','user',)
extra_kwargs = {'booked': {'required': True}}
Here is my view:
class CreateBookingViewSet(viewsets.ModelViewSet):
queryset = TimeSlot.objects.all()
serializer_class = CreateBookingSerializer
http_method_names = ['put']
def perform_update(self, serializer):
if (self.request.data['booked'] == 1):
serializer.save(user=self.request.user, booked=True)
I want to set booked to true only if the current value is False.
I'm using PUT method. so my url is /create_booking/id/
If I could get the id in my view, I would check the current booked value! How to get the id?
You can try to use self.kwargs['id'].
class CreateBookingViewSet(viewsets.ModelViewSet):
queryset = TimeSlot.objects.all()
serializer_class = CreateBookingSerializer
http_method_names = ['put']
def perform_update(self, serializer):
id = self.kwargs['id']
if (self.request.data['booked'] == 1):
serializer.save(user=self.request.user, booked=True)
I'm using Django 2.x.
I have two models
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
interest_rate = models.FloatField(blank=True, default=None, null=True)
given_date = models.DateField(default=timezone.now)
total_due = models.FloatField(blank=True, default=0.0, editable=False)
class AmountReturned(models.Model):
amount_given = models.ForeignKey(AmountGiven, on_delete=models.CASCADE, blank=True)
amount = models.FloatField()
return_date = models.DateField(default=date.today)
Use case
There can be multiple records of the amount given to a contact
There can be multiple records of the returned amount for an amount given
Now, I want to get total_due amount for a particular contact. This includes
total_payable = total_given + interest
total_due = total_payable - total_returned
To calculate total_due and interest, I have defined few property methods in the AmountGiven model.
#property
def interest_to_pay(self):
if self.interest_rate:
simple_interest_amount = ...
return simple_interest_amount
return 0
#property
def total_payable(self):
return self.amount + self.interest_to_pay
#property
def amount_due(self):
total_due = self.total_payable - self.total_returned
self.total_due = total_due
self.save()
return total_due
#property
def total_returned(self):
returned_amount = self.amountreturned_set.aggregate(total_returned=Sum('amount'))['total_returned']
if not returned_amount:
returned_amount = 0
return returned_amount
In Contact model, there is a property method to get the total due amount for the contact.
#property
def amount_due(self):
total_due = 0
for due in self.amountgiven_set.all():
total_due += due.amount_due
return total_due
Query
ContactSerializer
class ContactMinSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = (
'id', 'first_name', 'amount_due', 'created', 'modified'
)
Since amount_due property is being used in the ContactSerializer, amount_due property is called everytime a contact is call and thus results in nested DB queries.
How can I optimise the above scenario in the application to reduce the DB queries while getting contact or list of contacts? Specially two properties amount_due and total_returned.
amount_due() updates the total_due field in the table, every time it is called.
Edit 2
class ContactViewSet(LoggingMixin, viewsets.ModelViewSet):
serializer_class = ContactMinSerializer
def get_queryset(self):
return Contact.objects.filter(user=self.request.user).annotate(
total_due=Sum(
F('amountgiven_set__total_payable')
- F('amountgiven_set__total_returned')
)
).order_by('first_name')
You're looking for annotations.
Your viewset should define a queryset as follows:
from django.db.models import F, Sum
Contact.objects.annotate(
total_due=Sum(
F('amountgiven_set__total_payable')
- F('amountgiven_set__total_returned')
)
)
Then define a MethodSerializer field on your serializer to account for it.
class ContactMinSerializer(serializers.ModelSerializer):
total_due = serializers.SerializerMethodField()
def get_total_due(self, obj):
return return self.total_due
class Meta:
model = Contact
fields = (
'id', 'first_name', 'created', 'modified',
'total_due',
)