I have two models related through a one to one field, and I want to create a REST services that manages them both as they where one. The post works perfectly so far, and new instances are created in both models as they where one, but the put method just does nothing. It raises no error or anything, it just leaves the data unchanged.
These are my models:
class Company(models.Model):
name = models.CharField(max_length=40)
legal_name = models.CharField(max_length=100)
url = models.URLField()
address = models.TextField(max_length=400)
def __str__(self):
return "[{}]{}".format(self.id, self.name)
class Hotel(models.Model):
company = models.OneToOneField('Company', on_delete=models.CASCADE, primary_key=True)
code = models.CharField(max_length=10)
city = models.ForeignKey('City', on_delete=models.PROTECT, related_name='city_hotels')
category = models.ForeignKey('HotelCategory', on_delete=models.PROTECT, related_name='category_hotels')
capacity = models.IntegerField()
position = models.DecimalField(max_digits=11, decimal_places=2, default=0.00)
in_pickup = models.BooleanField(default=False)
def __str__(self):
return self.company.name
This is my ViewSet:
class HotelViewSet(viewsets.ModelViewSet):
queryset = models.Hotel.objects.all()
serializer_class = serializers.HotelSerializer
These are my serializers:
class CompanySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Company
fields = ('id', 'name', 'legal_name', 'url', 'address')
class HotelSerializer(serializers.HyperlinkedModelSerializer):
company = CompanySerializer()
class Meta:
model = models.Hotel
fields = ('company', 'code', 'city', 'category', 'capacity', 'position', 'in_pickup')
def create(self, validated_data):
company_data = validated_data.pop('company')
new_company=models.Company.objects.create(**company_data)
hotel = models.Hotel.objects.create(company=new_company, **validated_data)
return hotel
def update(self, instance, validated_data):
company = models.Company(
id=instance.company.id,
name=instance.company.name,
legal_name=instance.company.legal_name,
url=instance.company.url,
address=instance.company.address
)
company.save()
instance.save()
return instance
I found that instance carries the original data, and validated_data carries the new data. I was saving the original data back.
I had to replace instance data with validated_data data and then save instance:
def update(self, instance, validated_data):
company_data = validated_data.pop('company')
company = models.Company.objects.get(id=instance.company.id)
company.name = company_data.get('name')
company.legal_name = company_data.get('legal_name')
company.tax_id = company_data.get('tax_id')
company.url = company_data.get('url')
company.address = company_data.get('address')
instance.company = company
instance.code = validated_data.get('code', instance.code)
instance.city = validated_data.get('city', instance.city)
instance.category = validated_data.get('category', instance.category)
instance.capacity = validated_data.get('capacity', instance.capacity)
instance.position = validated_data.get('position', instance.position)
instance.in_pickup = validated_data.get('in_pickup', instance.in_pickup)
instance.is_active = validated_data.get('is_active', instance.is_active)
company.save()
instance.save()
return instance
put method works handsomely now.
You are creating a new instance of company instead of updating the one that belongs to the Hotel instance.
company = Company.objects.get(id=instance.company.id)
company.name = instance.company.name
company.legal_name = instance.company.name
company.url = instance.company.url
company.address = instance.company.address
company.save()
instance.save()
Related
I am doing CRUD data which has foreign keys and serializers(since I am told to use serializers instead of Forms),even though I have put the correct model and it's names in the product_edit page, the data is showing blank instead of thier saved data ,the wrong sub_category name is coming,this is how the edit page currently looks
serializer:
class CategoriesSerializer(serializers.ModelSerializer):
class Meta:
model = Categories
fields = "__all__"
extra_kwargs = {'category_name': {'required': False}}
class ColorsSerializer(serializers.ModelSerializer):
class Meta:
model = Colors
fields = "__all__"
class POLLSerializer(serializers.ModelSerializer):
# categories = serializers.StringRelatedField(many=False)
# sub_categories = serializers.StringRelatedField(many=False)
# color = serializers.StringRelatedField(many=False)
# size = serializers.StringRelatedField(many=False)
class Meta:
model = Products
fields = "__all__"
class SizeSerializer(serializers.ModelSerializer):
class Meta:
model = Size
fields = "__all__"
class SUBCategoriesSerializer(serializers.ModelSerializer):
class Meta:
model = SUBCategories
fields = "__all__"
below are the models of my CRUD
class Products(models.Model):
categories = models.ForeignKey(Categories,on_delete=models.CASCADE)
sub_categories = models.ForeignKey(SUBCategories,on_delete=models.CASCADE)
color = models.ForeignKey(Colors,on_delete=models.CASCADE)
size = models.ForeignKey(Size,on_delete=models.CASCADE)
# image = models.ImageField(upload_to = 'media/',width_field=None,height_field=None,null=True)
title = models.CharField(max_length=50)
price = models.CharField(max_length=10)
sku_number = models.CharField(max_length=10)
product_details = models.CharField(max_length=300)
quantity = models.IntegerField(default=0)
isactive = models.BooleanField(default=True)
class Categories(models.Model):
#made changes to category_name for null and blank
category_name = models.CharField(max_length=20)
category_description = models.CharField(max_length=20)
isactive = models.BooleanField(default=True)
def __str__(self):
return self.category_name
class Colors(models.Model):
color_name = models.CharField(max_length=10)
color_description = models.CharField(max_length=10)
isactive = models.BooleanField(default=True)
def __str__(self):
return self.color_name
class Size(models.Model):
size_name = models.CharField(max_length=10)
size_description = models.CharField(max_length=20)
isactive = models.BooleanField(default=True)
def __str__(self):
return self.size_name
class SUBCategories(models.Model):
category_name = models.ForeignKey(Categories, on_delete=models.CASCADE)
sub_categories_name = models.CharField(max_length=20)
sub_categories_description = models.CharField(max_length=20)
isactive = models.BooleanField(default=True)
def __str__(self):
return self.sub_categories_name
update function
def update(request,id):
if request.method == 'GET':
print('GET',id)
edit_products = SUBCategories.objects.filter(id=id).first()
s= SUBCategoriesSerializer(edit_products)
category_dict = Categories.objects.filter(isactive=True)
category = CategoriesSerializer(category_dict, many=True)
sub_category_dict = SUBCategories.objects.filter(isactive=True)
sub_category = SUBCategoriesSerializer(sub_category_dict,many=True)
color_dict = Colors.objects.filter(isactive=True)
color = ColorsSerializer(color_dict,many=True)
size_dict = Size.objects.filter(isactive=True)
size = SizeSerializer(size_dict,many=True)
hm = {"context": category.data,"sub_context":sub_category.data,"color_context":color.data,"size_context":size.data,"SUBCategories":s.data}
return render(request,'polls/product_edit.html',hm)
else:
print('POST',id)
editproducts = {}
d = Products.objects.filter(id=id).first()
if d:
editproducts['categories']=request.POST.get('categories')
editproducts['sub_categories']=request.POST.get('sub_categories')
editproducts['color']=request.POST.get('color')
editproducts['size']=request.POST.get('size')
editproducts['title']=request.POST.get('title')
editproducts['price']=request.POST.get('price')
editproducts['sku_number']=request.POST.get('sku_number')
editproducts['product_details']=request.POST.get('product_details')
# print(editsubcategories)
form = SUBCategoriesSerializer(d,data= editproducts)
if form.is_valid():
form.save()
print("form data",form.data)
print('form error',form.errors)
messages.success(request,'Record Updated Successfully...!:)')
return redirect('polls:show')
else:
print(form.errors)
return redirect("polls:show")
where am I going wrong in the code?
you must create product serializer like below
class ProductSerial(ModelSerializer):
class Meta:
model = Products
fields = '__all__'
and pass editproducts to this serializer
and also you have to be careful that pass id's of
categories
sub_categories
color
size
into request.POST data
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])
I don't understand why i got this error, I just added a new foreign key property to Rest model and it doesn't work:
Error
TypeError: Direct assignment to the reverse side of a related set is prohibited. Use exercises.set() instead.
I don't know if the name "rest" is causing I already changed the name of the property but I can't make it work.
models.py
class Routine(models.Model):
name = models.CharField(max_length=30)
creation_date = models.DateField(auto_now_add=True)
class Workout(models.Model):
name = models.CharField(max_length=30)
creation_date = models.DateField(auto_now_add=True)
routine = models.ForeignKey(Routine, related_name='workouts', on_delete=models.CASCADE)
class Exercise(models.Model):
index = models.IntegerField()
video_url = models.URLField(max_length=200)
workout = models.ForeignKey(Workout, related_name='exercises', on_delete=models.CASCADE)
class Rest(models.Model):
index = models.IntegerField()
duration = models.IntegerField()
workout = models.ForeignKey(Workout, related_name='rests', on_delete=models.CASCADE)
routine = models.ForeignKey(Routine, related_name='rests', on_delete=models.CASCADE)
serializers.py
class WorkoutSerializer(serializers.ModelSerializer):
exercises = ExerciseSerializer(many=True)
rests = RestSerializer(many=True)
class Meta:
model = Workout
fields = ['id', 'name', 'creation_date', 'exercises', 'rests']
def create(self, validated_data):
exercises_data = validated_data.pop('exercises')
rests_data = validated_data.pop('rests')
workout = Workout.objects.create(**validated_data)
for exercise_data in exercises_data:
Exercise.objects.create(workout=workout, **exercise_data)
for rest_data in rests_data:
Rest.objects.create(workout=workout, **rest_data)
return workout
class RoutineSerializer(serializers.ModelSerializer):
workouts = WorkoutSerializer(many=True)
rests = RestSerializer(many=True)
class Meta:
model = Routine
fields = ['id', 'name', 'creation_date', 'workouts', 'rests']
def create(self, validated_data):
workouts_data = validated_data.pop('workouts')
rests_data = validated_data.pop('rests')
routine = Routine.objects.create(**validated_data)
for workout_data in workouts_data:
Workout.objects.create(routine=routine, **workout_data)
for rest_data in rests_data:
Rest.objects.create(routine=routine, **rest_data)
return routine
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'm looking into "nested-nested" serializers, where there's a nested serializer nested in another one. It's a cooking service where a recipe has multiple directions, each having multiple ingredients. I set that up with foreign keys.
Here are my models:
class Category(models.Model):
"""Model representing a recipe category"""
name = models.CharField(max_length=200, help_text="Enter a recipe category (e.g Baking)")
def __str__(self):
"""String for representing the Model object."""
return self.name
class Ingredient(models.Model):
"""Model representing an ingredient in a direction"""
name = models.CharField(max_length=250, help_text="The ingredient's name")
quantity = models.CharField(max_length=200, help_text="How much of this ingredient.")
direction = models.ForeignKey("Direction", help_text="This ingredient's direction", on_delete=models.CASCADE, related_name='ingredients')
def __str__(self):
"""String for representing this recipe ingredient"""
return f'{self.quantity} {self.ingredient}'
class Direction(models.Model):
"""Model representing a step in a recipe"""
title=models.CharField(max_length=200)
text=models.TextField(blank=True, help_text="Describe this step.")
recipe=models.ForeignKey("Recipe", help_text="This direction's recipe", on_delete=models.CASCADE, related_name='directions')
def __str__(self):
"""String for representing the Direction"""
return self.title
class Recipe(models.Model):
"""Model representing a recipe."""
title = models.CharField(max_length=200)
notes = models.TextField(max_length=1000, help_text="Enter notes, reviews, ...")
photos = models.ImageField(null=True, blank=True)
category = models.ForeignKey(Category, help_text="This recipe's category", on_delete=models.CASCADE, related_name='recipes')
def __str__(self):
"""String for representing the Model object."""
return self.title
def get_absolute_url(self):
"""Returns the url to access a detail record for this recipe."""
I read the docs and thought I could just use a nested serializer in another, which threw me an error stating "direct assignment to the reverse side of a related set is prohibited."
I then made it work by using these serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('name',)
def create(self, validated_data):
category = Category.objects.create(**validated_data)
return category
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ('quantity', 'name',)
def create(self, validated_data):
ingredient = Ingredient.objects.create(**validated_data)
return ingredient
def update(self, instance, validated_data):
instance.quantity = validated_data.get('quantity', instance.quantity)
instance.name = validated_data.get('name', instance.name)
instance.direction = validated_data.get('direction', instance.direction)
instance.save()
return instance
class DirectionSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(many=True)
class Meta:
model = Direction
fields = ('title', 'text', 'ingredients', )
def create(self, validated_data):
direction = Direction.objects.create(**validated_data)
return direction
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.text = validated_data.get('text', instance.text)
instance.recipe = validated_data.get('recipe', instance.recipe)
instance.save()
return instance
class RecipeSerializer(serializers.ModelSerializer):
directions = DirectionSerializer(many=True)
category = CategorySerializer()
class Meta:
model = Recipe
fields = ('title', 'notes', 'photos', 'category', 'directions')
def create(self, validated_data):
directions_data = validated_data.pop('directions')
category_data = validated_data.pop('category')
category = Category.objects.create(**category_data)
validated_data["category"] = category
recipe = Recipe.objects.create(**validated_data)
recipe.category = category
for direction_data in directions_data:
ingredients_data = direction_data.pop("ingredients")
direction = Direction.objects.create(recipe=recipe, **direction_data)
for ingredient_data in ingredients_data:
Ingredient.objects.create(direction=direction, **ingredient_data)
return recipe
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.notes = validated_data.get('notes', instance.notes)
instance.photos = validated_data.get('photos', instance.photos)
instance.category = validated_data.get('category', instance.category)
instance.save()
return instance
I am not sure, what of this is actually necessary at this point. Basically I create the nested structure in the create() function of the recipe.
Now to the real questions:
This does feel like a hack. Is is this correct and intended way of achieving multiple nested serializers?
If I do it this way, do I even the need create() and update() functions?
Thank you very much.