How to solve PUT method is not allowed in drf? - django

I've a model:
class ListingPrice(Timestamps):
price = models.ForeignKey("Price", on_delete=models.CASCADE)
location = models.ForeignKey("location", on_delete=models.CASCADE)
class Meta:
unique_together = ["price", "location"]
class Price(Timestamps):
package = models.ForeignKey("products.Package", on_delete=models.CASCADE)
locations = models.ManyToManyField("location", through="ListingPrice")
price = models.DecimalField(max_digits=11, decimal_places=3)
with a serializer:
class LocationSerializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = location
fields = ['id', 'name']
class PriceSerializer(serializers.ModelSerializer):
locations = LocationSerializer(many=True, read_only=True)
class Meta:
model = Price
fields = ['package', 'locations', 'price']
def create(self, validated_data):
print("validated_data, validated_data)
and viewset:
class PriceViewSet(ModelViewSet):
queryset = Price.objects.all()
serializer_class = PriceSerializer
ordering = ['id']
permissions = {
"GET": ["view_minimum_listing_price", ],
"POST": ["add_minimum_listing_price", ],
'PUT': ['update_minimum_listing_price', ],
'DELETE': ['delete_minimum_listing_price', ],
}
In testing I'mm using the following:
data = {
"price": 15,
}
response = self.client.put(path=self.url, data=data, format='json', args=[1])
I'm trying to update the price in the instance with id 1, but neither put or update is not allowed? How to overcome this and update it?
edit: urls.py
router = SimpleRouter()
router.register('listing_price', PriceViewSet, basename='listing_price')

Related

How can I get a list of "ProductoSeralizer" in my "UsuarioSerializer" Django

I need the products that each user has ordered, like in the "UsuarioSerializer" i was thinking to put a field "productos" which is a list of "ProductoSeralizer", but the model "Usuario" do not have a direct relation with "ProductoSeralizer", so it give me an error when I try that, how can solve this? I need a JSON response like this:
[
{
"id": 1,
"cantidad_de_productos": 12,
"fecha": "2021-03-21T06:26:26.981487Z",
"correo": "user1#email.com",
"password": "pass",
"productos" : [
{},
{}
]
},
{
"id": 2,
"cantidad_de_productos": 0,
"fecha": "2021-03-21T06:26:56.700399Z",
"correo": "user2#email.com",
"password": "pass",
"productos" : [
{},
{}
]
}
]
models.py
class Entidad(models.Model):
fecha = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Usuario(Entidad):
correo = models.EmailField(unique=True)
password = models.CharField(max_length=20)
def __str__(self) -> str:
return self.correo
class Orden(Entidad):
solicitante = models.ForeignKey(Usuario, related_name='ordenes', on_delete=models.CASCADE)
productos = models.ManyToManyField('Producto', through='Detalle')
class Producto(Entidad):
nombre = models.CharField(max_length=20, unique=True)
ordenes = models.ManyToManyField('Orden', through='Detalle')
def __str__(self) -> str:
return self.nombre
class Detalle(Entidad):
cantidad = models.PositiveIntegerField()
orden = models.ForeignKey(Orden, related_name='detalles', on_delete=models.CASCADE)
producto = models.ForeignKey(Producto, related_name='detalles', on_delete=models.CASCADE)
serializers.py
class ProductoSerializer(serializers.ModelSerializer):
class Meta:
model = Producto
fields = '__all__'
class ProductosUsuarioSerializer(serializers.ModelSerializer):
cantidad = models.IntegerField()
class Meta:
model = Producto
fields = '__all__'
class DetalleOrdenSerializer(serializers.ModelSerializer):
class Meta:
model = Detalle
exclude = ['orden']
class OrdenSerializer(serializers.ModelSerializer):
detalles = DetalleOrdenSerializer(many=True)
class Meta:
model = Orden
exclude = ['productos']
def create(self, validated_data):
detalles_data = validated_data.pop('detalles')
orden = Orden.objects.create(**validated_data)
for detalle_data in detalles_data:
Detalle.objects.create(orden=orden, **detalle_data)
return orden
class DetalleSerializer(serializers.ModelSerializer):
class Meta:
model = Detalle
fields = '__all__'
class UsuarioSerializer(serializers.ModelSerializer):
cantidad_de_productos = serializers.IntegerField()
class Meta:
model = Usuario
fields = '__all__'
views.py
class Usuario(ListCreateAPIView):
queryset = Usuario.objects.annotate(cantidad_de_productos=Coalesce(Sum('ordenes__detalles__cantidad'), 0))
serializer_class = UsuarioSerializer
You can add a SerializerMethodField field to serializer.
from models import Orden, Producto
class UsuarioSerializer(serializers.ModelSerializer):
cantidad_de_productos = serializers.IntegerField()
productos = serializers.SerializerMethodField()
class Meta:
model = Usuario
fields = '__all__'
def get_productos(self, obj):
ordenes = Orden.objects.filter(solicitante=obj.id)
# get all product ids from orders
product_ids = [product.id for orden in ordenes for product in orden.productos.all()]
# find products (It does not matter if there are duplicate ids, it will not repeat products.)
user_products = Producto.objects.filter(id__in=product_ids)
# format the response
products = [{
'id': product.id,
'name': product.nombre
} for product in user_products]
return products
The method is called by each returned user

nested objects in Django rest framework

I want to design solution for ordering items. I have endpoint that create orders BUT I need to to have items object in the order. let me show you the code
class ItemModel(models.Model):
name = models.CharField(max_length=50)
price = models.FloatField()
discretion = models.CharField(max_length=500)
available = models.BooleanField(default=True)
class OrderModel(models.Model):
phone = models.CharField(max_length=20)
delevary_time = models.DateTimeField()
class CartModel(models.Model):
order = models.ForeignKey(OrderModel, on_delete=models.CASCADE, related_name='order_m')
item = models.ForeignKey(ItemModel, on_delete=models.CASCADE, related_name='item_m')
I need endpoint that create order to me. her what I did
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = CartModel
exclude = ['order',]
depth = 2
class OrderSerializer(serializers.ModelSerializer):
cart = serializers.SerializerMethodField()
class Meta:
model = OrderModel
fields = ['phone', 'state', 'delevary_time', 'cart']
def get_cart(self, obj):
cart = CartModel.objects.filter(order__id=obj.id)
serializer = CartSerializer(cart, many=True)
return serializer.data
this is the endpoint
router.register('order', OrderViewSet, 'api-order')
{
"phone": 124997988698,
"delevary_time": "2020-07-17T19:34:00",
"cart": [
{
"item": 1
},
{
"item": 2
}
]
}
when I post the json it don't save the cart it only save the oder phone and delevary_time. How I can save the cart at the same time
class CartSerializer(serializers.ModelSerializer):
class Meta:
model = CartModel
exclude = ['order',]
depth = 2
class OrderSerializer(serializers.ModelSerializer):
order_m = CartSerializer(many=True) # adding this
class Meta:
model = OrderModel
fields = ['phone', 'state', 'delevary_time', 'order_m']
def create(self, validated_data):
cart_data = validated_data.pop('order_m')
order = OrderModel.objects.create(**validated_data)
for c in cart_data:
CartModel.objects.create(order=order, **c)
return order

Django nested Serializer filter to only one field, not all fields

I have two serializers like below. The output for the below snippet is Workers and with associated Ticket Counter details with all fields (ticket_counter,ticket_counter_name,worker). But I just need only one field that is ticket_counter_name.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = WorkerToCounterSerializer(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
class WorkerToCounterSerializer(serializers.ModelSerializer):
ticket_counter = SerializerMethodField()
ticket_counter_name = serializers.CharField(source='ticket_counter.ticket_counter_name')
class Meta:
model = WorkerToTicketCounter
list_serializer_class = FilteredListSerializer
fields = (
'ticket_counter',
'ticket_counter_name',
'worker',
)
def get_ticket_counter(self, obj):
return obj.ticket_counter.pk
class FilteredListSerializer(ListSerializer):
def to_representation(self, data):
data = data.filter(worker_to_ticket_counter_is_deleted=False)[:1]
return super(FilteredListSerializer, self).to_representation(data)
What above snippet outputs
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
{
"ticket_counter": 7,
"ticket_counter_name": "Entrance Counter",
"worker": 4,
}
]
}
But What I want is
{
"username": "xxxxxxxxxxx",
"ticket_counter": "Entrance Counter"
}
I just need the name of the ticket_counter_name. In my case, there can't be two ticket_counters for a worker. Obviously, it gives only one ticket_counter. Is it possible?
EDIT: using string StringRelatedField
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
"Entrance Counter",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx"
]
}
EDIT: WorkerToTicketCounter Model
class WorkerToTicketCounter(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ticket_counter = models.ForeignKey(TicketCounter, related_name="workers")
worker = models.ForeignKey(User, related_name='ticket_counter')
worker_to_ticket_counter_is_deleted = models.BooleanField(default=False)
If I'm understood correctly, you only need a SerializerMethodField to perform both filtering and string represantion.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = serializers.SerializerMethodField(read_only=True)
def get_ticket_counter(self, user):
qs = user.ticket_counter.filter(worker_to_ticket_counter_is_deleted=False)
if qs.exists() and hasattr(qs.first().ticket_counter, 'ticket_counter_name'):
return qs.first().ticket_counter.ticket_counter_name
return None
class Meta:
model = User
fields = ('username', 'ticket_counter',)
You can use StringRelatedField:
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = StringRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
Note to use StringRelatedField you should add __str__ method to your WorkerToTicketCounter model:
class WorkerToTicketCounter:
...
def __str__(self):
return self.ticket_counter.ticket_counter_name

django rest framework return a custom object using ModelSerializer and ModelViewSet

I have three models, three serializers, one modelviewset below.
I am using django-rest-framework to make a rest api for android.
The restaurant model was created first. Then I created a star model and an image model.
What I want to do is to add star and image objects into restaurant objects.
finally I've got what I want result but I think my viewset code looks like wrong..
Is there another way not to use "for loop"?
Models
class Restaurant(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
weather = models.ForeignKey(Weather, on_delete=models.CASCADE)
distance = models.ForeignKey(Distance, on_delete=models.CASCADE)
description = models.TextField('DESCRIPTION')
def __str__(self):
return self.name
class Star(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.IntegerField('RATING')
def __str__(self):
return self.restaurant
class RestaurantImage(models.Model):
id = models.AutoField(primary_key=True)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
path = models.CharField(max_length=255)
Serializer
class StarSerializer(serializers.ModelSerializer):
class Meta:
model = Star
fields = ('id', 'restaurant', 'user', 'rating', )
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', )
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantImage
fields = ('id', 'path', 'restaurant')
ViewSet
class RestaurantDetailInfoViewSet(viewsets.ModelViewSet):
queryset = Restaurant.objects.all()
serializer_class = RestaurantSerializer
def list(self, request, *args, **kwargs):
restaurant_list = Restaurant.objects.all()
restaurant_result = []
for restaurant in restaurant_list:
restaurantInfo = Restaurant.objects.filter(id=restaurant.pk)
restaurant_serializer = RestaurantDetailSerializer(restaurantInfo, many=True)
ratingAverageValue = Star.objects.filter(restaurant=restaurant.pk).aggregate(Avg('rating'))
images = RestaurantImage.objects.filter(restaurant=restaurant.pk)
image_serializer = ImageSerializer(images, many=True)
restaurant_dic = {
'restaurant': restaurant_serializer.data,
'ratingAverage': ratingAverageValue['rating__avg']
if ratingAverageValue['rating__avg'] is not None else 0,
'images': image_serializer.data
}
restaurant_result.append(restaurant_dic)
return Response(restaurant_result)
Result
[
{
"restaurant": [
{
"id": 1,
"name": "restaurant1",
"address": "address1",
"category": {
"c_id": 1,
"name": "foodtype1"
},
"weather": {
"w_id": 1,
"name": "sunny"
},
"distance": {
"d_id": 1,
"name": "inside"
},
"description": "description1"
}
],
"ratingAverage": 2.6667,
"images": [
{
"id": 1,
"path": "imagepath",
"restaurant": 1
}
]
},
Solution:
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
images = ImageSerializer(many=True, read_only=True)
ratingAverage = serializers.SerializerMethodField(read_only=True)
def get_ratingAverage(self, restaurant):
ratingAvgVal = Star.objects.filter(
restaurant=restaurant
).aggregate(Avg('rating'))['rating__avg']
return ratingAvgVal if ratingAvgVal is not None else 0
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', 'images', 'ratingAverage', )
Explanation:
Here, I have nested the ImageSerializer in the RestaurantSerializer class, since you needed all the fields you've defined in ImageSerializer.
Then, for ratingAverage, I have used the SerializerMethodField which returns the value calculated (your logic) in the method I've defined for it, i.e. get_ratingAverage, which takes the Restaurant instance reference passed as an argument to the method for the field.

Django Rest Framework: writable nested serializer

I need to manage a JSON like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": [
{
"value": "Coca-Cola",
"synonyms": [
"coca cola",
"coke"
]
},
{
"value": "Beer",
"synonyms": [
"beer"
]
}
]
}
models.py:
class Entity(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class Value(models.Model):
entity = models.ForeignKey(Entity, related_name='values', on_delete=models.CASCADE)
value = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
class Synonymous(models.Model):
value = models.ForeignKey(Value, related_name='synonyms', on_delete=models.CASCADE)
synonymous = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add=True)
serializers.py:
class ValueSerializer(serializers.ModelSerializer):
synonyms = serializers.SlugRelatedField(
many=True,
slug_field='synonymous',
queryset=Synonymous.objects.all()
)
class Meta:
model = Value
fields = ('value', 'synonyms')
def create(self, validated_data):
synonyms_data = validated_data.pop('synonyms')
value = Value.objects.create(**validated_data)
for synonyomous_data in synonyms_data:
Synonymous.objects.create(value=value, **synonyomous_data)
return value
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id', 'name', 'description', 'values')
def create(self, validated_data):
values_data = validated_data.pop('values')
entity = Entity.objects.create(**validated_data)
for value_data in values_data:
# How can I call the create method of values?
pass
return entity
views.py
#api_view(['GET', 'POST'])
def entity_list(request, format=None):
"""
List all entities, or create a new entity.
"""
if request.method == 'GET':
entities = Entity.objects.all()
serializer = EntitySerializer(entities, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = EntitySerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The serializers works fine when the GET view is called, but when I try to create a new Entity using the POST view, I'm not able to call the create method of ValueSerializer Class and the data created is like this:
{
"name": "drink_type",
"description": "A type of drink",
"values": []
}
Someone can help me?
Thank you!
class SynonymousSerializer(serializers.ModelSerializer):
class Meta:
model = Synonymous
fields = '__all__'
class ValueSerializer(serializers.ModelSerializer):
entity = serializers.PrimaryKeyRelatedField(queryset = Entity.objects.all())
synonyms = ValueSerializer(many=True)
class Meta:
model = Value
fields = ('id', 'synonyms')
class EntitySerializer(serializers.ModelSerializer):
values = ValueSerializer(many=True)
class Meta:
model = Entity
fields = ('id','values')
q = Entity.objects.all()
serializer = EntitySerializer(q)
print serializer.data