I am a beginner of django-rest-framework. I am trying hard to understand the serializer and good api design. I dont want to dive directly into viewset and generics view like ListView, RetrieveAPIView and etc. I want to understand the serializer and APIView clearly so i drew the following criteria to solve them. However i could only solve 2 of the problem from below.
Here is the list of problems i drew to hone my rest api skill
"""
return a list of rent
"""
"""
return a list of rent or specific rent if token is give n
"""
"""
return a list of rent contacted by specific buyer
"""
"""
return a list of buyer that has contacted a specific rent
"""
Here is my model of Rent, Galleries and Contact. Rental and contact are separate app. Contact is to contact a rent owner to buy or rent the space listed by that owner.
class Rental(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=300, blank=False, null=False)
phone_number = models.PositiveIntegerField(null=False, blank=False)
rate = models.FloatField()
class Gallery(models.Model):
rent = models.ForeignKey(Rental, related_name="galleries")
image = models.FileField(upload_to=user_directory_path, null=True, blank=True)
tag = models.CharField(max_length=1, choices=TAGS, null=True, blank=True)
BUYER_CHOICES = (
('B', 'Buy'),
('R', 'Rent'),
)
class Contact(models.Model):
buyer = models.ForeignKey(User)
rental = models.ForeignKey(Rental, related_name="rent")
email_id = models.EmailField(blank=False, null=False)
buyer_choice = models.CharField(choices=BUYER_CHOICES, max_length=1, null=False, blank=False)
class GallerySerializer(serializers.ModelSerializer):
class Meta:
model = Gallery
fields = ('image', 'tag',)
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
galleries = GallerySerializer(many=True)
class Meta:
model = Rental
fields = ('__all__')
UPDATED RentAPIView CODE AS PER #Remi
class RentAPIView(APIView):
serializer_class = RentalSerializer
parser_classes = (FormParser, MultiPartParser,)
def get(self, request, token=None, format=None):
"""
Returns a list of rents (or just rent if token is given)
"""
reply = {}
try:
rents = Rental.objects.filter(user=request.user)
# code is updated here
buyer_id = self.request.query_params.get('buyer_id', None)
if buyer_id:
rents = rents.filter(user__contact_set__buyer_id=buyer_id)
if token:
rent = rents.get(id=token)
reply['data'] = RentalSerializer(rent).data
else:
reply['data'] = RentalSerializer(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
Sorry for posting long code. I just wanted to show i tried my best to design effective rest api but could not solve the last 2 problem.
Can anyone kindly help me to understand on how to handle the following criteria?
I would strongly suggest using the viewsets. In your case, the ModelViewSet would do, if you override your get_queryset function in order to prefilter your queryset by anything you want. For Example:
class RentAPIViewset(viewsets.ModelViewSet):
serializer_class = RentalSerializer
def get_queryset(self):
# if requesting something like /api/rent?buyer_id=1
buyer_id = self.request.query_params.get('buyer_id', None)
if buyer_id:
return Rental.objects.filter(rent__buyer__id=buyer_id)
return Rental.objects.all()
This would be much cleaner uses the core-functionality of DRF. That being said, back to your question, you could use some of the logic above to extend your api view:
with query params (you could also go with the kwargs approach like you did with the token):
class RentAPIView(APIView):
serializer_class = RentalSerializer
parser_classes = (FormParser, MultiPartParser,)
def get(self, request, token=None, format=None):
"""
Returns a list of rents (or just rent if token is given)
"""
reply = {}
try:
# first of your two remaining problems
# if requesting something like /api/rent?buyer_id=1
buyer_id = self.request.query_params.get('buyer_id', None)
rents = Rental.objects.filter(user=request.user)
if buyer_id:
rents = rents.filter(rent__buyer__id=buyer_id)
if token:
rent = rents.get(id=token)
reply['data'] = RentalSerializer(rent).data
else:
reply['data'] = RentalSerializer(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
The last of your problems doesn't return Rent-Objects, but rather User objects, so this should be done in a differen API view.
Related
I don't know how to actually write the logic but I want to check user inputs especially price form field against Product model (price property).
I have model the django form as below:
class SalesForm(forms.ModelForm):
class Meta:
model = Sales
fields = ['product', 'customer', 'quantity', 'price']
Here is the Product Model
class Product(models.Model):
name = models.CharField(max_length=100, null=True)
category = models.CharField(max_length=100, choices=CATEGORY, null=True)
cost = models.PositiveIntegerField(null=True)
price = models.PositiveIntegerField(null=True)
quantity = models.PositiveIntegerField(null=True)
Here is what I am trying to do logically in views.py
def user_add_sales(request):
sales = Sales.objects.all().order_by('-salesdate')[:5]
if request.method == 'POST':
sales_form = SalesForm(request.POST)
if sales_form.is_valid:
sales_price = sales_form.cleaned_data('price')
try:
user_input = Product.objects.get(price = sales_price)
sales_form.save()
messages.success(request, 'Sales Added Successfully')
return redirect('dashboard-user-sales')
except user_input.DoesNotExist:
sales_form = SalesForm()
else:
sales_form = SalesForm()
context = {
'sales' : sales,
'sales_form' : sales_form,
}
return render(request, 'dashboard/user_sales.html', context)
When Try running the code, says 'SalesForm' object has no attribute 'cleaned_data'. Someone should please help me on how I can check whether what the user enters in price field of the SalesForm is not less than the price set for that product in Product Model.
The form is validated by the sales_form.is_valid() method, not by an attribute, the if condition is thus:
if sales_form.is_valid():
# …
# …
In your form, you can check if the given price is at least the price in the related attribute with:
class SalesForm(forms.ModelForm):
# …
def clean(self, *args, **kwargs):
data = super().clean(*args, **kwargs)
if data['price'] < data['product'].price:
raise ValidationError('The price of the sale is below the price of the product')
return data
I am creating ecommerce api using Django Rest Framework.
This is my order model. I have 2 many to many fields for address and order items.
class Order(models.Model):
payment_options= (
('COD', 'Cash on Delivery'),
('BANK', 'Bank Transfer'),
('WALLET', 'Wallet Transfer'),
)
delivery_options= (
('Placed', 'Placed'),
('Processing', 'Processing'),
('Delivered', 'Delivered'),
('Cancelled', 'Cancelled'),
)
order_number = models.AutoField(primary_key=True)
items = models.ManyToManyField(OrderItem)
order_address = models.ManyToManyField(Address)
delivery_date = models.DateTimeField(auto_now_add=False)
price = models.FloatField()
payment_method = models.CharField(max_length=6, choices=payment_options)
delivery_status = models.CharField(max_length=16, choices=delivery_options, default='Placed')
class Meta:
ordering = ['order_number']
def __str__(self):
ord = str(self.order_number)
ord_text = "Order #"+ord
return ord_text
Here is my Serializer:
class OrderItemSerializer(serializers.ModelSerializer):
prod = ProductSerializer()
class Meta:
model = OrderItem
fields = ["quantity", "prod"]
class AdressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields =[
"address_1",
"address_2",
"city",
"state",
"postcode",
"country"
]
class CompleteOrderSerializer(serializers.ModelSerializer):
items = OrderItemSerializer(many=True)
order_address = AdressSerializer(many=True)
class Meta:
model = Order
fields =[
"order_number",
"items",
"delivery_date",
"price",
"payment_method",
"delivery_status",
"order_address",
]
def create(self, validated_data):
ord_id = []
address_data = validated_data.pop('order_address')
print(address_data)
for j in range(len(address_data)):
address = Address.objects.create(
address_1=address_data[j]['address_1'],
address_2=address_data[j]['address_2'],
city=address_data[j]['city'],
state = address_data[j]['state'],
postcode=address_data[j]['postcode'],
country=address_data[j]['country']
)
ord_id.append(address.id)
item = validated_data.pop('items')
ids = []
for i in range(len(item)):
prod = item[i]['prod']['name']
count = item[i]['quantity']
product = OrderItem.objects.create(
prod=Product.objects.get(name=prod),
quantity=count
)
ids.append(product.id)
Order.order_address = Address.objects.filter(id__in=(ord_id))
Order.items = OrderItem.objects.filter(id__in=(ids))
Order.objects.create(**validated_data)
return Order
And the POST and GET requests
class OrderApiView(APIView):
"""
A simple ViewSet for viewing and posting orders.
"""
def get(self, request):
order = Order.objects.all()
serializer = CompleteOrderSerializer(order, many=True)
return Response(serializer.data)
def post(self, request):
data = JSONParser().parse(request)
serializer = CompleteOrderSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Thank you so much for your any help. Whenever I make post request, data are saved in Database but when it comes to show Post Response it says
int() argument must be a string, a bytes-like object or a number, not 'DeferredAttribute' .
What's the issue here?
Try with this fixed serializer
class CompleteOrderSerializer(serializers.ModelSerializer):
items = OrderItemSerializer(many=True)
order_address = AdressSerializer(many=True)
class Meta:
model = Order
fields =[
"order_number",
"items",
"delivery_date",
"price",
"payment_method",
"delivery_status",
"order_address",
]
def create(self, validated_data):
addresses = []
address_data = validated_data.pop('order_address')
print(address_data)
for add_d in address_data:
address = Address.objects.create(
address_1=add_d['address_1'],
address_2=aadd_d['address_2'],
city=add_d['city'],
state = add_d['state'],
postcode=add_d['postcode'],
country=add_d['country']
)
# if your address_data only includes required data you can do this instead
# address = Address.objects.create(**add_d)
addresses.append(address)
items = validated_data.pop('items')
order_items = []
for item in items:
prod = Product.objects.get(name=item['prod']['name']) # if you have the prod id it's better to use than to look up by name
quantity = item['quantity']
order_item = OrderItem.objects.create(
prod=prod,
quantity=quantity
)
order_items.append(order_item)
order = Order.objects.create(**validated_data)
order.items.set(order_items)
order.order_address.set(addresses)
return order
I don't get why you might have multiple addresses for the same order, maybe it's a bug from your side or a feature ?
You were also trying to set Order.order_address which is not a valid operation, you'll need to create a new Order object and give it the required data.
Also since you are creating the related objects, you have access to them so no need to keep track of ids only then fetch them again.
note that you can also use your other serializers to create your objects (if you want to do that) in place of model.Objects.Create for example
address = AdressSerializer(
address_1=add_d['address_1'],
address_2=aadd_d['address_2'],
city=add_d['city'],
state = add_d['state'],
postcode=add_d['postcode'],
country=add_d['country']
)
which is useful if you need to override the serializer's create method for them too
I have made a Django model form but the problem is in my logic I am using something else and now I want to figure out a way to validate it by either defining a Meta class and choosing the fields that I want to display to the user but of course this won't validate the form.
Now I want to know if there is a way to validate the form without touching the models and pass the data required for the logic and after take care of the information needed for the data of the model to be saved.
Here is the models:
from django.db import models
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class RoomCategory(models.Model):
name = models.CharField(max_length=59)
price = models.IntegerField()
beds = models.PositiveIntegerField()
capacity = models.PositiveIntegerField()
size = models.CharField(max_length=59)
def __str__(self):
return self.name
class Room(models.Model):
room_number = models.CharField(max_length=60)
room_category = models.ForeignKey(RoomCategory, on_delete=models.CASCADE)
def __str__(self):
return f"The room {self.room_number} {self.room_category} has a maximum of {self.room_category.capacity} person and cost {self.room_category.price}/night "
class Booking(models.Model):
customer = models.ForeignKey(User, on_delete=models.CASCADE)
room = models.ForeignKey(RoomCategory, on_delete=models.CASCADE)
check_in = models.DateField()
check_out = models.DateField()
adults = models.PositiveSmallIntegerField()
children = models.PositiveSmallIntegerField()
def __str__(self):
return f"{self.customer} has booked for {self.room} from {self.check_in} to {self.check_out}"
Here is the form:
class BookingForm(forms.ModelForm):
class Meta:
model = Booking
fields = ['room', 'check_in', 'check_out', 'adults', 'children']
here is the views.py
data = form.cleaned_data
roomlist = Room.objects.filter(room_category__name=data['room'])
available_rooms = []
for room in roomlist:
if data['adults'] + data['children'] > room.room_category.capacity:
return HttpResponse(f'Sorry !! But this category of room cannot handle more than {room.room_category.capacity}')
else:
if check_availability(room.room_category.name, data['check_in'], data['check_out'], data['adults'], data['children']):
available_rooms.append(room)
if len(available_rooms) > 0:
room = available_rooms[0]
new_booking = Booking.objects.create(
customer=self.request.user,
room=room,
check_in=data['check_in'],
check_out=data['check_out'],
adults=data['adults'],
children=data['children']
)
new_booking.save()
return HttpResponse(new_booking)
else:
return HttpResponse('All the rooms of this type are not available')
It is not printing the data means that the form is not valid and it fall down to the else statement.
You can validate any field in the form by writing a method in this way : def clean_(field_name) i.e def clean_room(self) read more:
https://docs.djangoproject.com/en/3.1/ref/forms/validation/#cleaning-a-specific-field-attribute
I have a class called Team that has wins and losses.
I want to be able to sort teams by a win_pct which should be the ratio of wins to total games played (i.e. wins.count()/(wins.count() + losses.count())).
class Team(TimestampModerated):
name = models.CharField(max_length=80, blank=False, null=False)
wins = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='related_wins')
losses = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='related_losses')
def num_wins(self):
return self.wins.count()
def num_losses(self):
return self.losses.count()
def win_pct(self):
return self.wins.count()/(self.wins.count() + self.losses.count())
So, in my view I have something like this:
#list_route
def teams_list(self, request):
teams = Team.objects.all().order_by('-win_pct')
page = self.paginate_queryset(teams)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(teams, many=True)
return Response(
data=serializer.data
)
But it just gives an error:
django.core.exceptions.FieldError: Cannot resolve keyword 'win_pct' into a field. Choices are: created, id, losses, moderation_code, related_losses, related_wins, name, updated, wins
Try below code
from django.db.models import Count
teams = Team.objects.all().annotate(
win_pct=(Count('wins')/(Count('wins')+Count('losses')))).order_by('win_pct')
I have a django rest api which an android client is connected to. One of the models on the django rest framework api is called Debt:
class Debt(models.Model):
paying_user = models.ForeignKey(User, related_name="paying_user")
receiving_user = models.ForeignKey(User, related_name="reeceiving_user")
amount = models.FloatField()
currency = models.CharField(max_length=10, default="USD")
description = models.CharField(max_length=100, blank=True)
date_incurred = models.DateTimeField(default=timezone.now)
deadline = models.DateTimeField()
payed = models.BooleanField(default=False)
overdue = models.BooleanField(default=False)
class Meta:
verbose_name = "Debt"
verbose_name_plural = "Debts"
objects = DebtManager()
def save(self, *args, **kwargs):
if self.paying_user == self.receiving_user:
raise ValidationError("Users cannot be in debt with themselves.")
super(Debt, self).save(*args, **kwargs)
In the DebtManager I have a function named all_debt:
def all_debts(self, user):
''' Returns a queryset of all a user's debts '''
all_debt_queryset = ... # What do I write here?
return all_debt_queryset
To retrieve all a user's debts, I would need to get all Debt objects where the paying_user attribute equals the user parameter (current user) and all Debt objects where the receiving_user attribute equals the user parameter.
How do I get a queryset with all a user's debts?
You can use filter and simply return the queryset. I am assuming that you are sending the user object in the method argument.
from django.db.models import Q
def all_debts(self, user):
return self.objects.filter(Q(paying_user=user) | Q(receiving_user=user))
Note: I would suggest remove all_debts function from DebtManager. It is not suited to be a manager's function. Although its better suited for Debt model's function