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
Related
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
I am trying to update Contact model fields while creating the new fields of UpdateInfo model and add them to the existing model.
But I am getting this error
contacts.models.Contacts.DoesNotExist: Contacts matching query does not exist.
I know the contact object with the id 19 exists because I can see it when I try the get contacts API.
I am sending data like this.
My models:
class Contacts(models.Model):
full_name = models.CharField(max_length=100, blank=True)
.........
def __str__(self):
return self.full_name
class Meta:
verbose_name_plural = 'Potential Clients'
class UpdateInfo(models.Model):
contacts = models.ForeignKey(Contacts,on_delete=models.CASCADE, related_name='update_info')
updated_at = models.DateTimeField(auto_now=True)
modified_by = models.CharField(max_length=100, blank=True)
def __str__(self):
return f"Last modified by {self.modified_by} at {self.updated_at}"
My views:
class EditContactView(RetrieveUpdateDestroyAPIView):
permission_classes = [IsAuthenticated]
queryset = Contacts.objects.all()
serializer_class = ContactsUpdateSeializer
My serializers:
class UpdateInfoSerializer(serializers.ModelSerializer):
contacts= serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = UpdateInfo
fields = ['id','contacts','updated_at','modified_by']
class ContactsUpdateSeializer(serializers.ModelSerializer):
update_info = UpdateInfoSerializer(many=True)
id = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Contacts
fields = ['id', 'full_name', 'lead_source', 'email', 'phone', 'contact_owner',
'contact_status', 'company_name', 'job_position', 'tasks',
'notes', 'email_list', 'created_by', 'created_at', 'update_info']
def update(self, instance, validated_data):
update_data = validated_data.pop('update_info')
id = validated_data.get('id')
contacts = Contacts.objects.get(id=id)
#contacts.save()
#contact_only_update_logic
instance.full_name = validated_data.get('full_name')
instance.lead_source = validated_data.get('lead_source')
instance.email = validated_data.get('email')
instance.phone = validated_data.get('phone')
instance.contact_owner = validated_data.get('contact_owner')
instance.contact_status = validated_data.get('contact_status')
instance.company_name = validated_data.get('company_name')
instance.job_position = validated_data.get('job_position')
instance.tasks = validated_data.get('tasks')
instance.notes = validated_data.get('notes')
instance.email_list = validated_data.get('email_list')
instance.save()
#add_update_info_logic
for update_data in update_data:
abc = UpdateInfo.objects.create(contacts=contacts,**update_data)
instance.update_info.add(abc)
instance.save()
return instance
You have to change your serializer
class ContactsUpdateSeializer(serializers.ModelSerializer):
update_info = UpdateInfoSerializer(many=True)
class Meta:
model = Contacts
fields = ['id', 'full_name', 'lead_source', 'email', 'phone', 'contact_owner',
'contact_status', 'company_name', 'job_position', 'tasks',
'notes', 'email_list', 'created_by', 'created_at', 'update_info']
def update(self, instance, validated_data):
update_data = validated_data.pop('update_info')
instance = super(ContactsUpdateSeializer, self).update(instance, validated_data)
for update_datum in update_data:
abc = UpdateInfo.objects.create(contacts=instance,**update_datum)
return instance
PrimaryKeyRelatedField is used for foreign key purpose and there we have to define queryset also.
Don't need to add the updateinfo in contact, it is already done throgh django.
Moreover, it would be better if you use bulk_create instead of running save each time if you there is no signals exists for that.
You can do it as:-
UpdateInfo.objects.bulk_create([UpdateInfo(contacts=instance,**update_datum) for update_datum in update_data])
When I send post request with data in profile model at that time this error show.
Error
{
"user_name": [
"Incorrect type. Expected pk value, received str."
] }
I saw this answer but don't know how to implement SlugRelatedField in serializers(OneToOneField)
models.py:
class CustomUser(AbstractUser):
# username_validator = UnicodeUsernameValidator()
username = models.CharField(
max_length=80,
unique=True,
)
email = models.EmailField(
unique=True,
blank=True,
)
.....
def __str__(self):
return '%s' %(self.username)
class Profile(models.Model):
user_name = models.OneToOneField(to = CustomUser,on_delete=models.CASCADE)
full_name = models.CharField(null=True,max_length=15, blank=True)
public_name = models.CharField(null=True,max_length=15, blank=True)
....
serializers.py:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['user_name','full_name','public_name']
views.py:
class ProfileApiview(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
#Pradip - Have a look at this.
class ProfileSerializer(serializers.ModelSerializer):
user_name = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Profile
fields = ['user_name','full_name','public_name']
def create(self, validated_data):
user = self.context['request'].user
profile = Profile.objects.create(
user_name=user, full_name=validated_data['full_name'].............)
return profile
AttributeError:Got AttributeError when attempting to get a value for field phone_number on serializer ListBusCompanyStaffSerializer.
Bascially I have two models User and BusCompanyStaff, User consists of phone_number field BusCompanyStaff consists of following models fields
class BusCompanyStaff(BaseModel):
user = models.OneToOneField(
BusCompanyUser,
on_delete=models.CASCADE
)
position = models.ForeignKey(
StaffPosition,
on_delete=models.SET_NULL,
null=True,
related_name='position'
)
created_by = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='created_by'
)
staff_of = models.ForeignKey(
BusCompany,
on_delete=models.CASCADE
)
I basically wants to list particular BusCompanyStaff from BusCompany so here is my serializer I tried till now
class ListBusCompanyStaffSerializer(serializers.ModelSerializer):
position = serializers.CharField()
class Meta:
model = BusCompanyStaff
fields = (
'id',
'phone_number',
'position',
'email',
)
there is defintely Error as BusCompanyStaff dont consist of phone_number field but requirement is to put User phone number and email
Here is my rest of code in views.py and usecases.py
#usecases.py
class ListBusCompanyStaffUseCase(BaseUseCase):
def __init__(self, bus_company: BusCompany):
self._bus_company = bus_company
def execute(self):
self._factory()
return self._bus_company_staffs
def _factory(self):
self._bus_company_staffs = BusCompanyStaff.objects.filter(staff_of=self._bus_company)
#views.py
class ListBusCompanyStaffView(generics.ListAPIView):
serializer_class = bus_company_user_serializers.ListBusCompanyStaffSerializer
def get_bus_company(self):
return GetBusCompanyUseCase(
bus_company_id=self.kwargs.get('bus_company_id')
).execute()
def get_queryset(self):
return ListBusCompanyStaffUseCase(
bus_company=self.get_bus_company()
).execute()
how can I serialize in this format
id ,
phone_number ,
position,
email,
You can do it this way:
class ListBusCompanyStaffSerializer(serializers.ModelSerializer):
...
phone_number = serializers.CharField(source='user.phone_number')
class Meta:
model = BusCompanyStaff
fields = (
...
'phone_number',
)
I am trying to use nested writable serializer in django-rest-framework. When I send a POST request with following data:
{
"acc_name": "Salary Card",
"acc_type_id": {
"id": 2,
"acc_type_name": "Debit Card"
},
"credit_amt": null,
"bill_dt": null,
"due_dt": null,
"balance": "0.00",
"comments": null
}
I got an error:
Cannot assign "OrderedDict([('acc_type_name', 'Debit Card')])": "Accounts.acc_type_id" must be a "AccountTypes" instance.
I actually passed id for AccountTypes, but why restframework remove it automatically? How can I resolve this problem? How can I create a new account with existing account type?
Views:
class AccountTypesViewSet(ListAPIView):
name = __name__
pagination_class = None
queryset = AccountTypes.objects.filter(active='Y')
serializer_class = AccountTypesSerializer
class AccountsViewSet(viewsets.ModelViewSet):
pagination_class = None
queryset = Accounts.objects.all()
serializer_class = AccountsSerializer
Models:
class AccountTypes(models.Model):
id = models.AutoField(primary_key=True, editable=False)
acc_type_name = models.CharField(max_length=50)
active = models.CharField(max_length=1, default='Y')
class Meta:
db_table = f'"{schema}"."taccount_types"'
managed = False
class Accounts(models.Model):
id = models.AutoField(primary_key=True, editable=False)
acc_name = models.CharField(max_length=1024)
acc_type_id = models.ForeignKey(
to=AccountTypes,
db_column='acc_type_id',
related_name='accounts',
on_delete=models.DO_NOTHING
)
credit_amt = models.DecimalField(max_digits=11, decimal_places=2, null=True)
bill_dt = models.DateField(null=True)
due_dt = models.DateField(null=True)
balance = models.DecimalField(max_digits=11, decimal_places=2, default=0)
comments = models.CharField(max_length=1024, null=True)
class Meta:
db_table = f'"{schema}"."taccounts"'
managed = False
Serializers:
class AccountTypesSerializer(serializers.ModelSerializer):
class Meta:
model = AccountTypes
fields = ('id', 'acc_type_name')
class AccountsSerializer(serializers.ModelSerializer):
# acc_type = serializers.SlugRelatedField(slug_field='acc_type_name', source='acc_type_id', read_only=True)
acc_type_id = AccountTypesSerializer()
class Meta:
model = Accounts
fields = ('id', 'acc_name', 'acc_type_id', 'credit_amt',
'bill_dt', 'due_dt', 'balance', 'comments')
def create(self, validated_data):
accounts_instance = Accounts.objects.create(**validated_data)
return accounts_instance
You don't need to set id field because Django will do it by itself, so remove that lines:
id = models.AutoField(primary_key=True, editable=False)
Next, you need to send an id of AccountTypes model to create new account and you give Django a dict fields = ('id', 'acc_type_name'). It doesn't know what to do with that. So just send id as number. And you don't need AccountTypesSerializer and that line acc_type_id = AccountTypesSerializer() also.
Next, you should create account in view, not in serializer, and you have to save it after creating.