I have User and Item models, and am having an issue with nested Items in a Item.objects.all() view. Specifically, I'm getting the following in the ItemListView resource:
[ {
"id": 3,
"description": "Some test item description",
"user": {
"id": 10,
"username": "jason",
"email": "test#test.com",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0aW1lIjoiRnJpIE1hciAyNCAyMDo1NDo1OSAyMDE3IiwidXNlcm5hbWUiOiJqYXNvbiJ9.x4qdTF5eVKGLnrkcunm63n4d_X8xEzEYM0z48E5HKh4",
"items": [
{
"id": 3,
"description": "Some item description",
"timestamp": "2017-03-25T15:50:08.265780Z",
"user": 10
},
{
"id": 2,
"description": "test item description",
"timestamp": "2017-03-24T22:28:49.904198Z",
"user": 10
}
]
},
"timestamp": "2017-03-25T15:50:08.265780Z"
},
What I want is the User.items excluded from the output. How can I do that with the serializers and models below:
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only = True, required = False)
confirm_password = serializers.CharField(required = False)
class Meta:
model = User
fields = ('id', 'username', 'email', 'password', 'confirm_password', 'token', 'posts')
read_only_fields = ('confirm_password', )
depth = 1
def create(self, validated_data):
return User.objects.create_user(**validated_data)
def validate(self, data):
if data['password'] != data['confirm_password']:
raise ValidationError('Passwords do not match')
return data
class ItemSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only = True)
def create(self, validated_data):
return Item.objects.create(**validated_data)
class Meta:
fields = ('id', 'content', 'user', 'timestamp')
read_only_fields = ('timestamp', )
model = Item
depth = 1
Models:
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length = 50,
unique = True)
email = models.EmailField(unique = True, blank = False, null = False)
token = models.CharField(max_length = 255,
default = '')
objects = UserManager()
USERNAME_FIELD = 'username'
def generate_token(self):
self.token = User.objects.generate_token(user = self)
self.save()
#python_2_unicode_compatible
def __str__(self):
return '{} [Username: {}] [Email: {}]'.format(self.pk,
self.username,
self.email)
class Meta:
verbose_name = 'user'
verbose_name_plural = 'users'
class Item(models.Model):
description = models.CharField(db_index = True, max_length = 1000)
timestamp = models.DateTimeField(auto_now_add = True, db_index = True)
user = models.ForeignKey(User,
on_delete = models.CASCADE,
related_name = 'items')
def __str__(self):
return '{} [User: {}] [Timestamp: {}] [Slug {}]'.format(self.pk, self.user.pk, self.timestamp, self.description[:20])
class Meta:
verbose_name = 'item'
verbose_name_plural = 'items'
ordering = ['-timestamp']
There is no out of the box solution to dynamically set which fields you want to serialize on related models.
You either need to define a stripped copy of UserSerializer and use it inside ItemSerializer (can define it right inside ItemSerializer class to not pollute the namespace), or extend the ModelSerializer and manually implement some support for dynamic field serialization, see here for some ideas (this could get tricky if you want to go a few layers deep I would imagine).
Related
I need to get the ItemModel instances from the item model and store them in the foreign key field in the OrderModel but I am not sure how. I've tried to iterate through the item order list and add one but it doesn't display correctly. Any help would be much appreciated.
My goal is to display the data like this:
{
"payment_method": "test",
"confirmation": "14087147WA285750M",
"total_price": "15.00",
"is_paid": "True",
"order_created": "2021-07-09T19:51:18Z",
"item_order": [
{
"id": 2,
"name": "Carrots",
"image": "image link",
"slug": "carrots",
"price": "5.00",
"itemID": "ct1",
"quantity": 1
},
{
"id": 8,
"name": "Dog Food",
"image": "image link",
"slug": "dog-food",
"price": "10.00",
"itemID": "df4",
"quantity": 1
}
]
}
View:
#api_view(['POST'])
def create_order(request):
user = request.user
order = request.data
order_item = OrderModel.objects.create(
user=user,
payment_method=order['payment_method'],
confirmation=order['confirmation'],
total_price=order['total_price'],
is_paid=order['is_paid'],
order_created=order['order_created'],
item_order= """ The item model instance """
)
ordered_items = OrderSerializer(order_item, many=True).data
return Response(ordered_items)
Order Model:
class OrderModel(models.Model):
user = models.ForeignKey(CustomerModel, on_delete=models.CASCADE)
item_order = models.ForeignKey(
ItemModel, on_delete=models.CASCADE)
payment_method = models.CharField(max_length=50)
confirmation = models.CharField(max_length=255)
total_price = models.DecimalField(
max_digits=5, decimal_places=2)
is_paid = models.BooleanField(default=False)
has_been_sent = models.BooleanField(default=False)
order_created = models.DateTimeField()
def __str__(self):
return str(self.id)
Order Serializer:
class OrderSerializer(serializers.ModelSerializer):
item_order = ItemSerializer(many=True)
class Meta:
model = OrderModel
fields = ['payment_method', 'confirmation',
'total_price', 'is_paid', 'has_been_sent', 'order_created',
'item_order']
read_only_fields = ['id']
I am building an api for CRUD operations on a user table which has association with country and state tables as given model definitions:
class Country(models.Model):
""" Model for Country"""
country_abbreviation = models.CharField(max_length=80)
country_name = models.CharField(max_length=80)
is_active = models.SmallIntegerField()
def __str__(self):
return self.country_name
class State(models.Model):
""" model for saving state"""
state_name = models.CharField(max_length=32)
state_abbreviation = models.CharField(max_length=8)
country = models.ForeignKey(Country, related_name='states', on_delete=models.CASCADE)
is_active = models.SmallIntegerField(default=1, blank=True)
def __str__(self):
return self.state_name
class Meta:
""" meta class"""
ordering = ('state_name', )
class User(models.Model):
""" model for saving user information """
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
country = models.ForeignKey(Country, related_name='user_country', on_delete=models.CASCADE)
state = models.ForeignKey(State, related_name='user_state', on_delete=models.CASCADE)
address = models.CharField(max_length=150)
def __str__(self):
return '%d: %s %s' % (self.id, self.first_name, self.last_name)
class Meta:
""" meta class """
ordering = ('first_name', )
I am writing serializers in a way that while I am getting records from user table, for every row in the table there must be available all the country and state info associated with that particular row instead of just their ids respectively:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'country', 'state', 'address']
Expected response :
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"first_name": "Satish",
"last_name": "Kumar",
"country": {
"id": 23,
"country_name": "India"
},
"state": {
"id": 22,
"state_name": "Delhi"
},
"address": "New Delhi"
}
],
"page_size": 10,
"model_type": "User"
}
I am getting:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"first_name": "Satish",
"last_name": "Kumar",
"country": 23,
"state": 22,
"address": "New Delhi"
}
],
"page_size": 10,
"model_type": "User"
}
In the views.py the codes look like:
class UserList(generics.ListCreateAPIView):
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['first_name', 'last_name']
ordering_fields = ['id', 'first_name', 'last_name']
ordering = ['first_name']
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
def list(self, request, *args, **kwargs):
page_size_req = self.request.query_params.get('page_size', None)
if page_size_req is not None:
records_per_page = page_size_req
pagination.PageNumberPagination.page_size = int(page_size_req)
else:
records_per_page = 10
response = super().list(request, args, kwargs)
# Add additional info required:
response.data['page_size'] = records_per_page
response.data['model_type'] = 'User'
return response
Can someone please help me figure out, how I can achieve the desired results in this case? Thanks for your time in advance.
In that case, add depth to your serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'country', 'state', 'address']
depth = 1
I wonder how to handle POST request to properly save the incoming data, having such models:
class Recipe(models.Model):
author = models.ForeignKey('auth.user', related_name='recipes', on_delete=models.CASCADE)
image = models.TextField(default='None')
name = models.CharField(max_length=100)
description = models.TextField(default='No description')
votes = models.IntegerField(default=0)
def __str__(self):
return self.name
class Ingredient(models.Model):
image = models.TextField(default='None')
name = models.CharField(max_length=100)
description = models.TextField(default='No description')
price = models.DecimalField(max_digits=8, decimal_places=3)
unit_price = models.DecimalField(max_digits=8, decimal_places=3)
unit_quantity = models.CharField(max_length=20)
def __str__(self):
return self.name
I wanted to avoid duplicating Ingredient objects, so to provide quantity of specific Ingredient in Recipe I've created a RecipesIngredient model that binds Ingredient with Recipe, but also contains a quantity of this Ingredient:
class RecipesIngredient(models.Model):
recipe = models.ForeignKey(Recipe, related_name='ingredients', on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
quantity = models.CharField(max_length=100)
def __str__(self):
return self.quantity
I've also prepared some serializers for these models:
class IngredientSerializer(HyperlinkedModelSerializer):
class Meta:
model = Ingredient
fields = (
'url',
'image',
'name',
'description',
'price',
'unit_price',
'unit_quantity'
)
class RecipesIngredientSerializer(HyperlinkedModelSerializer):
ingredient_name = ReadOnlyField(source='ingredient.name')
ingredient_price = ReadOnlyField(source='ingredient.price')
ingredient_unit_price = ReadOnlyField(source='ingredient.unit_price')
ingredient_unit_quantity = ReadOnlyField(source='ingredient.unit_quantity')
class Meta:
model = RecipesIngredient
fields = (
'url',
'ingredient_name',
'quantity',
'ingredient_price',
'ingredient_unit_price',
'ingredient_unit_quantity'
)
class RecipeListSerializer(HyperlinkedModelSerializer):
author = ReadOnlyField(source='author.username')
author_url = ReadOnlyField(source='author.url')
class Meta:
model = Recipe
fields = (
'url',
'author',
'author_url',
'image',
'name',
'description',
'votes'
)
class RecipeDetailSerializer(HyperlinkedModelSerializer):
author = ReadOnlyField(source='author.username')
author_url = ReadOnlyField(source='author.url')
ingredients = RecipesIngredientSerializer(many=True)
class Meta:
model = Recipe
fields = (
'url',
'author',
'author_url',
'image',
'name',
'description',
'ingredients',
'votes'
)
But in this case, I have to first create a Recipe instance and save it to DB, then do the same with Ingredient to be able to "bind" them in RecipesIngredient. Is this possible to handle this case with only one POST request to view below?
#
# path('recipes/', views.RecipeList.as_view(), name='recipe-list')
#
class RecipeList(generics.ListCreateAPIView):
queryset = Recipe.objects.all()
serializer_class = RecipeListSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
#EDIT
I forgot 'bout this thread, but the problem is solved now. I've prepared another serializer for CREATE purposes only, and overridden the 'create' function of this serializer.:
class RecipeCreateSerializer(HyperlinkedModelSerializer):
#author = ReadOnlyField(source='author.username')
#author_url = ReadOnlyField(source='author.url')
recipes_ingredients = RecipesIngredientCreateSerializer(many=True)
def create(self, validated_data):
recipes_ingredients = validated_data.pop('recipes_ingredients')
recipe_instance = super().create(validated_data)
for recipe_ingredient in recipes_ingredients:
ingredient_data = recipe_ingredient.pop('ingredient')
ingredient_instance = Ingredient(
image=ingredient_data['image'],
name=ingredient_data['name'],
description=ingredient_data['description'],
price=ingredient_data['price'],
unit_price=ingredient_data['unit_price'],
unit_quantity=ingredient_data['unit_quantity'],
)
ingredient_instance.save()
recipes_ingredient_instance = RecipesIngredient(
recipe=recipe_instance,
ingredient=ingredient_instance,
quantity=recipe_ingredient['quantity']
)
recipes_ingredient_instance.save()
return recipe_instance
class Meta:
model = Recipe
fields = (
'url',
'image',
'name',
'description',
'votes',
'recipes_ingredients',
)
Also the JSON file looks a bit different now, but everything works just fine:
{
"image": "image-url",
"name": "recipes-name",
"description": "recipes-description",
"votes": 0,
"recipes_ingredients": [
{
"quantity": "ingredients-quantity",
"ingredient": {
"image": "image-url",
"name": ingredient-name",
"description": "ingredient-description",
"price": 3.6,
"unit_price": 0.36,
"unit_quantity": "100ML"
}
},
{
"quantity": "ingredients-quantity",
"ingredient": {
"image": "image-url",
"name": ingredient-name",
"description": "ingredient-description",
"price": 0.3,
"unit_price": 0.3,
"unit_quantity": "EACH"
}
},
{
"quantity": "ingredients-quantity",
"ingredient": {
"image": "image-url",
"name": ingredient-name",
"description": "ingredient-description",
"price": 2.0,
"unit_price": 0.8,
"unit_quantity": "KG"
}
}
]
}
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
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.