Please help! I'm trying to implement a recipe creation function. The data is created and then you can get it, with a post request, changes are also made. But when creating a site, a redirect does not occur and gives such an error.
AttributeError: Got AttributeError when attempting to get a value for field amount on serializer AmountIngredientForRecipePostSerializer. The serializer field might be named incorrectly and not match any attribute or key on the Ingredient instance. Original exception text was: 'Ingredient' object has no attribute 'amount'.
view
class RecipesViewSet(viewsets.ModelViewSet):
queryset = Recipe.objects.all().order_by('id')
filter_backends = (DjangoFilterBackend,)
filter_class = RecipeFilter
pagination_class = PagePagination
permission_classes = (OwnerOrAdminOrSafeMethods,)
def get_serializer_class(self):
if self.request.method == 'GET':
return RecipeGetSerializer
return RecipePostSerializer
#staticmethod
def post_or_delete(request, model, serializer, pk):
if request.method != 'POST':
get_object_or_404(
model,
user=request.user,
recipe=get_object_or_404(Recipe, id=pk)
).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
serializer = serializer(
data={'user': request.user.id, 'recipe': pk},
context={'request': request})
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
serialaizer
class AmountIngredientForRecipePostSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(queryset=Ingredient.objects.all())
amount = serializers.IntegerField(min_value=1)
class Meta:
model = AmountIngredient
fields = ('id', 'amount')
class RecipePostSerializer(serializers.ModelSerializer):
author = CostomUserSerializer(read_only=True)
ingredients = AmountIngredientForRecipePostSerializer(many=True)
tags = serializers.PrimaryKeyRelatedField(
queryset=Tags.objects.all(), many=True)
image = Base64ImageField()
class Meta:
model = Recipe
fields = ('id', 'author', 'ingredients', 'tags',
'image', 'name', 'text', 'cooking_time')
#staticmethod
def create_ingredients_tags(recipe, ingredients, tags):
for ingredient in ingredients:
AmountIngredient.objects.create(
recipe=recipe,
ingredient=ingredient['id'],
amount=ingredient['amount']
)
for tag in tags:
recipe.tags.add(tag)
def create(self, validated_data):
ingredients = validated_data.pop('ingredients')
tags = validated_data.pop('tags')
recipe = Recipe.objects.create(
author=self.context.get('request').user,
**validated_data
)
self.create_ingredients_tags(recipe, ingredients, tags)
return recipe
def update(self, recipe, validated_data):
recipe.tags.clear()
AmountIngredient.objects.filter(recipe=recipe).delete()
ingredients = validated_data.pop('ingredients')
tags = validated_data.pop('tags')
self.create_ingredients_tags(recipe, ingredients, tags)
return super().update(recipe, validated_data)
def validate(self, data):
ingredients = self.initial_data.get('ingredients')
ingredients_list = []
for ingredient in ingredients:
ingredient_id = ingredient['id']
if ingredient_id in ingredients_list:
raise serializers.ValidationError({
'ingredient': 'already have'
})
ingredients_list.append(ingredient_id)
return data
def to_representation(self, object):
data = super().to_representation(object)
data["image"] = object.image.url
return data
models
class Recipe(models.Model):
tags = models.ManyToManyField(Tags, verbose_name='Теги')
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='author',
)
ingredients = models.ManyToManyField(
Ingredient,
through='AmountIngredient',
through_fields=('recipe', 'ingredient'),
related_name='ingredients',
)
name = models.CharField(
max_length=150,
db_index=True,
)
image = models.ImageField(
upload_to='recipes/',
text = models.TextField()
cooking_time = models.PositiveSmallIntegerField(
validators=[MinValueValidator(
1,
message='min 1 minute'
)],
verbose_name='time to cook'
)
def __str__(self):
return self.name
class AmountIngredient(models.Model):
recipe = models.ForeignKey(
Recipe,
on_delete=models.CASCADE,
)
ingredient = models.ForeignKey(
Ingredient,
on_delete=models.CASCADE,
)
amount = models.PositiveSmallIntegerField(
validators=(
MinValueValidator(
1, 'Min 1.'
),
),
verbose_name='Ingredients count'
)
class Meta:
constraints = (
models.UniqueConstraint(
fields=['ingredient', 'recipe'],
name='unique_ingredient'
),
)
class Ingredient(models.Model):
name = models.CharField(
max_length=200,
db_index=True,
)
measurement_unit = models.CharField(
max_length=200,
)
def __str__(self):
return self.name
I tried to solve it by changing the field in the serializer, but it didn't work
Related
As declared in question title, i got task to filter results by field not presented in model but calculated by serializer.
The model:
class Recipe(models.Model):
tags = models.ManyToManyField(
Tag,
related_name='recipe_tags'
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='author_recipes'
)
ingredients = models.ManyToManyField(
Ingredient,
related_name='recipe_ingredients'
)
name = models.CharField(max_length=200)
image = models.ImageField()
text = models.TextField()
cooking_time = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)]
)
class Meta:
ordering = ("-id",)
verbose_name = "Recipe"
verbose_name_plural = "Recipes"
def __str__(self):
return self.name
Here is the view code:
class RecipeViewSet(ModelViewSet):
queryset = Recipe.objects.all()
permission_classes = [IsAdminOrAuthorOrReadOnly, ]
serializer_class = RecipeInSerializer
pagination_class = LimitPageNumberPagination
filter_backends = [DjangoFilterBackend]
filterset_fields = ['tags', ]
filter_class = RecipeFilter
Serializer:
class RecipeOutSerializer(serializers.ModelSerializer):
tags = ManyRelatedField(child_relation=TagSerializer())
author = CustomUserSerializer()
ingredients = serializers.SerializerMethodField()
is_favorite = serializers.SerializerMethodField()
is_in_shopping_cart = serializers.SerializerMethodField()
class Meta:
fields = '__all__'
model = Recipe
def get_ingredients(self, obj):
ingredients = IngredientAmount.objects.filter(recipe=obj)
return GetIngredientSerializer(ingredients, many=True).data
def get_is_favorite(self, obj):
request = self.context.get("request")
if request.user.is_anonymous:
return False
return Favorite.objects.filter(recipe=obj, user=request.user).exists()
def get_is_in_shopping_cart(self, obj):
request = self.context.get("request")
if not request or request.user.is_anonymous:
return False
return ShoppingCart.objects.filter(recipe=obj, user=request.user).exists()
And custom filter code:
class RecipeFilter(rest_framework.FilterSet):
tags = ModelMultipleChoiceFilter(
field_name='tags__slug',
to_field_name="slug",
queryset=Tag.objects.all()
)
favorite = BooleanFilter(field_name='is_favorite', method='filter_favorite')
def filter_favorite(self, queryset, name, value):
return queryset.filter(is_favorite__exact=True)
class Meta:
model = Recipe
fields = ['tags', ]
Target is is_favorited field that return boolean value. I tried writing func in custom filter class that return queryset but didnt work, neither documentation helped me with examples. Hope for your help.
We can use queryset annotate:
from django.db import models
from rest_framework import serializers
class RecipeViewSet(ModelViewSet):
def get_queryset(self):
user = self.request.user
user_id = user.id if not user.is_anonymous else None
return Recipe.objects.all().annotate(
total_favorite=models.Count(
"favorite",
filter=models.Q(favorite__user_id=user_id)
),
is_favorite=models.Case(
models.When(total_favorite__gte=1, then=True),
default=False,
output_field=BooleanField()
)
)
class RecipeOutSerializer(serializers.ModelSerializer)
is_favorite = serializers.BooleanField(read_only=True)
class Meta:
model = Recipe
fields = (
# ...
is_favorite,
)
class RecipeFilter(rest_framework.FilterSet):
favorite = BooleanFilter(field_name='is_favorite')
Id like to know if there is a way to display on my admin section the number of party that an author have in the Author admin section call num_party ? To me i have to loop through the model and count how many party an author has, it's seems easy to say but not easy to do, so someone could help me ?
Here is my Party Model:
class Party(models.Model):
title = models.CharField(max_length=200)
place = models.ForeignKey('Place', on_delete=models.CASCADE, null=True)
author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
date = models.DateField(null=True, blank=True)
genre = models.ManyToManyField(Genre, help_text='Select a genre for this partty')
ticket_available = models.IntegerField()
ticket_price = models.PositiveIntegerField(default=5)
language = models.ManyToManyField('Language')
insider_secret = models.ManyToManyField('InsiderSecret')
#benef = models.IntegerField(default=5)
def get_benef(self):
return self.ticket_available * self.ticket_price
benef = property(get_benef)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('book-detail', args=[str(self.id)])
def display_genre(self):
return ', '.join(genre.name for genre in self.genre.all()[:3])
display_genre.short_description = 'Genre'
def display_secret(self):
return ', '.join(insider_secret.secret for insider_secret in self.insider_secret.all()[:3])
display_secret.short_description = 'Insider Secret'
class Author(models.Model):
"""Model representing an author."""
username = models.CharField(max_length=100, help_text="How do you want to be call by people as organisor ? ", default="Bestpartyorganisorintown")
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
class Meta:
ordering = ['last_name', 'first_name']
def get_absolute_url(self):
"""Returns the url to access a particular author instance."""
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
"""String for representing the Model object."""
return f'{self.username}'
Here is my admin code:
#admin.register(Party)
class PartyAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'display_genre', 'ticket_price','ticket_available','display_secret','benef')
readonly_fields = ('benef',)
class AuthorAdmin(admin.ModelAdmin):
list_display = ('username', 'first_name', 'date_of_birth')
fields = [('first_name', 'last_name'),'username', 'date_of_birth']
# Register the admin class with the associated model
admin.site.register(Author, AuthorAdmin)
class InsiderSecretAdmin(admin.ModelAdmin):
list_display = ('secret',)
You can add custom methods to list_display like this:
class AuthorAdmin(admin.ModelAdmin):
list_display = ('username', 'first_name', 'date_of_birth', 'party_count')
fields = [('first_name', 'last_name'),'username', 'date_of_birth']
def party_count(obj):
return obj.party_set.count()
However this will make a count query for every author object. Instead you can override the default queryset and annotate the count:
class AuthorAdmin(admin.ModelAdmin):
list_display = ('username', 'first_name', 'date_of_birth', 'party_count')
fields = [('first_name', 'last_name'),'username', 'date_of_birth']
def get_queryset(self, request):
qs = super(AuthorAdmin, self).get_queryset(request)
return qs.annotate(party_count=Count('party_set'))
def party_count(obj):
return obj.party_count
i tried what you did, and now it display me 1 for all my Authors, i don't really understand what i did wrong. Id like it to display me the num of party for each individual user, someone can help ?
here is the code
class AuthorAdmin(admin.ModelAdmin):
list_display = ('username', 'first_name', 'date_of_birth','party_count')
fields = [('first_name', 'last_name'),'username', 'date_of_birth']
def get_queryset(self, request):
return Author.objects.annotate(party_count=Count('party'))
def party_count(self, Author):
return Author.party_count
def __str__(self):
return self.party_count
[enter image description here][1]
[1]: https://i.stack.imgur.com/PVhYQ.png
I am trying to assign an owner as an object and I must be doing it wrong because it is still raising
Cannot assign "<User: kdkd#gmail.com>": "Movie.owner" must be a "Suppliers" instance.
Request Method: POST
This is my serializer for my Movie model.
class MovieTicketSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ['owner', 'title', 'price', 'date', 'description', 'seat', 'choice', 'active']
def create(self, validated_data):
owner = self.context['request'].user
movie = Movie.objects.create(owner=owner, active=True, **validated_data)
return movie
And this is the View
#api_view(['POST', ])
#permission_classes([IsAuthenticated, ])
def movie_ticket_detail(request):
if request.method == 'POST':
serializer = MoveTicketSerializer(data=request.data, context={'request': request})
data = {}
if serializer.is_valid():
ticket = serializer.save()
data['request'] = ' ticket is instantiated '
data['title'] = ticket.title
data['owner'] = ticket.owner
else:
data = serializer.errors
return Response(data)
class TicketModel(models.Model):
owner = models.ForeignKey(Suppliers, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=30, default='TICKET')
price = models.IntegerField()
date = models.CharField(max_length=20)
description = models.TextField(max_length=300, blank=False)
posted = models.DateTimeField(default=now, editable=False)
active = models.BooleanField(default=False)
class Meta:
abstract = True
class Movie(TicketModel):
seat = models.IntegerField(null=True)
choice = models.IntegerField(choices=TYPE_OF_MOVIES_CONCERT_OTHERS, default=1)
def __repr__(self):
return {'title': self.title, 'price': self.price, 'description': self.description, 'date': self.date}
You can not return model instances or queryet instead you need to return native python data type in the format json, xml or other content types. First Create Serialzier
class SupplierSerializer(ModelSerializer):
class Meta:
model = Supplier
fields = '__all__' # Or sepecific fields ['id','name']
Then Use serializer as below.
# you need to get Supplier object
supplier_obj = get_object_or_404(Supplier,user=ticket.owner)
data['owner'] = SupplierSerializer(supplier_obj).data
OR
supplier_obj = get_object_or_404(Supplier,user=ticket.owner)
data['owner'] = supplier_obj.id # just return id
def create(self, validated_data):
owner = Suppliers.objects.get(email=self.context['request'].user)
movie = Movie.objects.create(owner=owner, active=True, **validated_data)
return HttpResponse(movie, content_type="text/json-comment-filtered")
is there something wrong with implementing it like this. it works.
I can't get a valid response, always get this error
{"required": "This field is required.", "null": "This field may not be null.", "not_a_list": "Expected a list of items but got type \"{input_type}\".", "empty": "This list may not be empty."}
Here is some code (models, serializers, views)
models
class Task(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=500, blank=True, default='')
pub_datetime = models.DateTimeField()
priority = models.CharField(choices=PRIORITY_CHOICES, max_length=1)
status = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, blank=True, default=None, null=True)
def __str__(self):
return self.name
serializers
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
agenda = serializers.ReadOnlyField(source='agenda.name')
class Meta:
model = Task
fields = ('id', 'name', 'description', 'pub_datetime', 'priority', 'status', 'owner', 'agenda')
views
class TaskListView(generics.ListCreateAPIView):
serializer_class = TaskSerializer
permission_classes = (permissions.IsAuthenticated,)
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
template_name = 'task_list.html'
def get(self, request, *args, **kwargs):
queryset = Task.objects.filter(owner=self.request.user)
if self.request.accepted_renderer.format == 'html':
return Response({'tasks': queryset})
else:
serializer = TaskSerializer(data=queryset, many=True)
if serializer.is_valid():
return JsonResponse(serializer.data, safe=False)
else:
return JsonResponse(serializer.error_messages)
def post(self, request, *args, **kwargs):
name = request.data['name']
description = request.data['description']
priority = request.data['priority']
new_task = Task.objects.create(name=name, description=description, pub_datetime=datetime.datetime.now(),
priority=priority, status=False, owner=self.request.user)
new_task.save()
return redirect('task-list')
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
I'm not sure, at what place I'm wrong. Can someone help?
just text to avoid errors
The queryset or object instance should be passed to serializer as instance, not data, so:
queryset = Task.objects.filter(owner=self.request.user)
serializer = TaskSerializer(instance=queryset, many=True)
Also, I believe that serializer can't validate instance, so in this case you should be able to just return return JsonResponse(serializer.data)
I'm new to Django Rest Framework and nested serializers. I have a ModelSerializer named OrderSerialiser which contains two nested ModelSerializers : ProductSerializer and ClientSerializer.
I want that new instances of the model client and the model product are created (Only if there is no already existing ones) when a post request is sent to the Order CreateAPI.
The solution I have found is to override the create method of the OrderSerializer.
It works fine when there is no instances of the client and the product having the same email and sku, but it returns an error saying that there is already existing objects ( client with the same email and a product with the same sku ) in the other case and does not get those existing objects,I noted that the create method in this case is not called , I think that I have to override the serializers.is_valid() method but I didn't figure out what I should do exactly .
models.py
class Client(models.Model):
email = models.EmailField(
verbose_name=_('Email address'),
max_length=255,
unique=True,
primary_key=True
)
first_name = models.CharField(_('first name'), max_length=30)
last_name = models.CharField(_('last name'), max_length=30)
class Product(models.Model):
sku = models.CharField(
verbose_name=_('SKU'),
unique=True,
max_length=120,
primary_key=True
)
name = models.CharField(
verbose_name=_('Name'),
max_length=150
)
url = models.URLField(
verbose_name=_('URL'),
blank=True,
null=True
)
class Order(models.Model):
client = models.ForeignKey(Client)
products = models.ManyToManyField(
Product,
related_name= "orders",
null=True,
blank=True,
)
serializers.py
class ProductSerialiser(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('sku', 'name', 'url')
class ClientSerialiser(serializers.ModelSerializer):
class Meta:
model = Client
fields = ('email','first_name', 'last_name')
class OrderSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
client = ClientSerialiser()
products = ProductSerialiser(many=True, required=False)
class Meta:
model = Order
fields = ('id', 'client', products')
def create(self, validated_data):
client_data = validated_data.pop('client')
try:
client_instance = Client.objects.get(email=client_data['email'])
except ObjectDoesNotExist:
client_instance = Client.objects.create(**client_data)
if 'products' in validated_data:
products_data = validated_data.pop('products')
order_instance = Order.objects.create(client=client_instance, **validated_data)
for product_data in products_data:
try :
product = Product.objects.get(sku=product_data['sku'])
except ObjectDoesNotExist:
product = Product.objects.create(**product_data)
product.orders.add(order_instance)
return order_instance
order_instance = Order.objects.create(client=client_instance, **validated_data)
return order_instance
Akamee,
one way I am used to solve this sort of problems is basically to, inside your serializers.py, do
def validate(self, data):
data = super(YourSerializer, self).validate(data)
try:
data['product'] = Product.objects.get(sku='bar')
except Product.DoesNotExist:
data['product'] = Product.object.create(sku='bar')
This isn't ideal, but I did find a solution that solved my problem (I'm waiting to accept it as the answer, hoping someone else can do better)
I have eliminated the validators for the field email in ClientSerializer and the field sku in Productserializer , and then checked the existence of objects manually.
modification on serialzers.py
class ProductSerialiser(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('sku', 'name', 'url')
extra_kwargs = {
'sku': {
'validators': []
}
}
class ClientSerialiser(serializers.ModelSerializer):
class Meta:
model = Client
fields = ('email', 'first_name', 'last_name')
extra_kwargs = {
'email': {
'validators': []
}
}
class OrderSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.email')
client = ClientSerialiser(partial=True)
products = ProductSerialiser(many=True, required=False, partial=True)
class Meta:
model = Order
fields = ('id','client', products')
def create(self, validated_data):
client_data = validated_data.pop('client')
try:
print "**************** client exists ***********************"
client_instance = Client.objects.get(email=client_data['email'])
except ObjectDoesNotExist:
print "**************** creating a client ***********************"
client_instance = Client.objects.create(**client_data)
if 'products' in validated_data:
products_data = validated_data.pop('products')
order_instance = Order.objects.create(client=client_instance, **validated_data)
for product_data in products_data:
try :
print "**************** Product exists ***********************"
product = Product.objects.get(sku=product_data['sku'])
except ObjectDoesNotExist:
print "**************** creating object product ***********************"
product = Product.objects.create(**product_data)
product.orders.add(order_instance)
return order_instance
order_instance = Order.objects.create(client=client_instance, **validated_data)
return order_instance