how to add data to child model along with parent mode? - django

I am working with Django REST framework. I want to add data to child model along with the parent model. Basically creating two records at a time.
models.py
class Visit(models.Model):
name = models.CharField(max_length=200)
gender = models.CharField(choices=GENDER_CHOICE, max_length=1)
mobile = models.CharField(max_length=18,default="")
email = models.CharField(max_length=256, null=True, blank=True)
address = models.TextField(null=True, blank=True)
visit_type = models.IntegerField(choices=VISIT_TYPE)
visit_purpose = models.CharField(max_length=250)
visitor_photo = models.FileField(upload_to="visitor/",null=True, blank=True)
id_photo = models.FileField(upload_to="id_card/",null=True, blank=True)
date_created = models.DateTimeField(default=timezone.now, editable=False)
class Status(models.Model):
visit = models.ForeignKey(Visit,on_delete=models.CASCADE)
description = models.CharField(max_length=200)
from_time = models.DateField()
to_time = models.DateTimeField(null=True, blank=True)
aproved = models.BooleanField(default=False)
visit_complete = models.BooleanField(default=False)
exit_time = models.DateTimeField(null=True, blank=True)
date_created = models.DateTimeField(default=timezone.now, editable=False)
serializers.py
class StatusSerializers(serializers.ModelSerializer):
class Meta:
model = Status
fields = "__all__"
class VisitSerializers(serializers.ModelSerializer):
visit = StatusSerializers(many=True)
class Meta:
model = Visit
fields = "__all__"
def create(self, validated_data):
print(validated_data)
model_b = Visit.objects.create(**validated_data)
# print(self.context.request.data.get('description'))
child_model_data = {
'description': self.context.request.data.get('description'),
}
child_model_serializer = StatusSerializers(data=child_model_data)
child_model_serializer.is_valid(raise_exception=True)
child_model_serializer.save(status=model_b)
return model_b
views.py
#api_view(['GET', 'POST'])
def create_visitor(request):
if request.method == 'GET':
visitor = Visit.objects.all()
serializer = VisitSerializers(visitor,context={'request': request},many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = VisitSerializers(data=request.data,context={'request': request})
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)
In the above code I am able to create child record i.e. Status model record but I don't know how to add other fields to it during its creation.

In your serializer child model data can be found in self.context.request.data and you can simply use child serializer to validate and save in db.
class VisitSerializers(serializers.ModelSerializer):
class Meta:
model = Visit
fields = "__all__"
def create(self, validated_data):
print(validated_data)
model_b = Visit.objects.create(**validated_data)
child_model_data = {
'field': self.context['request'].data.get('your_field');
.....
}
child_model_serializer = ChildSerializer(data=child_model_data)
child_model_serializer.is_valid(raise_exception=True)
child_model_serializer.save()
return model_b
Update you need to change in multiple places
First in your view, in post method serializer initialization you need to pass context object.
#api_view(['GET', 'POST'])
def create_visitor(request):
if request.method == 'GET':
visitor = Visit.objects.all()
serializer = VisitSerializers(visitor,context={'request': request},many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = VisitSerializers(data=request.data, context={'request': request})
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)
And then in your VisitSerializer
class VisitSerializers(serializers.ModelSerializer):
visit = StatusSerializers(many=True) # not sure why you did this, this should be removed i guess
class Meta:
model = Visit
fields = "__all__"
def create(self, validated_data):
print(validated_data)
model_b = Visit.objects.create(**validated_data)
# print(self.context.request.data.get('description'))
child_model_data = {
'description': self.context.request.data.get('description'),
'status': model_b
# and make sure all other necessary fields.
}
child_model_serializer = StatusSerializers(data=child_model_data)
child_model_serializer.is_valid(raise_exception=True)
child_model_serializer.save()
return model_b

You can have extra fields in your serializer along with default fields. For reverse relations, you can add a field like this
class VisitSerializers(serializers.ModelSerializer):
status = StatusSerializer(many=True)
class Meta:
model = Visit
You can pass data to VisitSerializer like this. {, status: [, ]}

Related

How do i get a nested field to be read_only in modelserializer?

currently when i send a get request i get what i want. But when i do a Post it throws an error saying {"user": ["This field is required."]} even though i put 'user' in read_only_fields.
heres the code:
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email']
class SaleHistorySerializier(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = SaleHistory
fields =['id', 'user', 'product', 'date_bought']
read_only_fields = ('user',)
depth = 1
models.py
class SaleHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owner')
product = models.ForeignKey(SaleItems, on_delete=models.RESTRICT, default=None)
date_bought = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f' {self.product}, {self.user}, {self.id}'
api.py create part
class SaleHistoryVS(viewsets.ViewSet):
permission_classes = [permissions.IsAuthenticated]
def create(self, request):
serializer = SaleHistorySerializier(data=request.data, many=True)
if serializer.is_valid():
serializer.save(user = request.user)
return Response(serializer.data, status= status.HTTP_201_CREATED)
return Response(serializer.errors, status= status.HTTP_400_BAD_REQUEST)
how do i make it so that i get to create post request without having to user in my post data.
Replace user = UserSerializer() with user = UserSerializer(read_only=True)

Django RF, validation of input data in view layer

I wanted to get the date or tablerequireDate argument from the POST requests, how do I achieve in the below view layer ?
VIEWS.PY
class Tablecreateview(generics.CreateAPIView):
queryset = Tables.objects.all()
serializer_class = Tableserializer
def perform_create(self, serializer):
request_user = self.request.user
serializer.save(author=request_user)
MODELS.PY
class Tables(models.Model):
tablerequiretime = models.TimeField()
tablerequireDate = models.DateField()
created = models.DateTimeField(auto_now=True)
updatedat = models.DateTimeField(auto_now_add=True)
foodliketoeat = models.CharField(max_length=200)
totalpersons = models.PositiveIntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(20)])
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.author.username
SERIALIZERS.PY
class Tableserializer(serializers.ModelSerializer):
class Meta:
model = Tables
exclude=('author',)
Okay you can access this field before perform_create takes place
class Tablecreateview(generics.CreateAPIView):
queryset = Tables.objects.all()
serializer_class = Tableserializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
tablerequireDate = serializer.data['tablerequireDate']
# Do you magic then perform creation
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Another Round of a solution
class Tableserializer(ModelSerializer):
class Meta:
model = Tables
exclude=('author',)
def validate_tablerequireDate(self, data):
try:
# do validation
except ValidationError:
raise Validation.....
else:
return validated_data

DRF Foreignkey serialization

I can't save model with Foreignkey field.
Thanks to "azudo" problem solved. Solution below
For example I have simple models:
class User(AbstractUser):
class Meta:
pass
email_validator = EmailValidator()
username = models.CharField('Name', max_length=150, )
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField('Email', blank=True, unique=True, validators=[email_validator], )
...
class Package(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='packages')
description = models.CharField('Description', max_length=256, default='description')
weight = models.CharField('weight', max_length=256, default='weight')
...
View (the user is guaranteed to be in the request):
#api_view(["POST"])
def test(request):
data = request.data
data['user'] = User.objects.get(id=request.user.id)
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
My serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = '__all__'
class PackageSerializer(ModelSerializer):
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def to_representation(self, instance):
self.fields['user'] = UserSerializer(many=False, read_only=True)
self.fields['where_from'] = LocationSerializer(many=False, read_only=True)
self.fields['destination'] = LocationSerializer(many=False, read_only=True)
return super().to_representation(instance)
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user, **validated_data)
return package
json in request:
{
"description": "Some package",
"weight": "12",
}
So, I'have user in database, and want create package for him. But in overridden create in PackageSerializer, validated_data doesn't have user. Please explain what I'm doing wrong.
Versions of django and drf:
django==2.2.4
djangorestframework==3.10.2
Solution:
Serializer:
class PackageSerializer(ModelSerializer):
user = UserSerializer(many=False, read_only=True)
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user)
return package
View:
#api_view(["POST"])
def create_package(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
DRF will ignore included fields that are marked as read-only so the caller cannot include read-only data. If you want to include additional attributes simply pass them as keyword args to save:
https://www.django-rest-framework.org/api-guide/serializers/#passing-additional-attributes-to-save
e.g.
#api_view(["POST"])
def test(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)

Serializers vs views to retrieve object by foreign key field

I'd like to allow users to perform POST request to create a blog post, and to use the topic name instead of the topic id as an option.
A minimal model might look as follows.
models.py
class Topic(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=100)
class Post(models.Model):
name = models.CharField(max_length=30)
topic = models.ForeignKey(Topic, on_delete=models.PROTECT)
created_on = models.DateTimeField(blank=True, auto_now_add=True, editable=False)
Now there are 2 possible approaches that I've considered:
1) Keep the views simple.
views.py
class PostList(ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
serializers.py
class PostSerializer(serializers.ModelSerializer):
topic_name = serializers.CharField()
class Meta:
model = Topic
fields = ('name', 'topic_name', 'created_on')
read_only_fields = ('created_on',)
def validate_topic_name(self, value):
"""Verify that the topic exists."""
if not Topic.objects.filter(name=value).exists():
raise serializers.ValidationError("Specified Topic Name does not exist!")
else:
return value
def create(self, validated_data):
"""Create a Post."""
topic_name = validated_data.pop('topic_name', None)
topic = Topic.objects.get(name=topic_name)
return Post.objects.create(topic=topic, **validated_data)
2) Keep the serializers simple.
views.py
class PostList(ListCreateAPIView):
queryset = Post.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST)
topic = get_object_or_404(Topic, name=serializer.data['topic_name'])
post = Post.objects.create(
name=serializer.data['name'],
topic=topic)
return Response({'name': serializer.data['name'], 'description': serializer.data['topic_name']},
status=status.HTTP_201_CREATED)
serializers.py
class PostSerializer(serializers.ModelSerializer):
topic_name = serializers.CharField()
class Meta:
model = Topic
fields = ('name', 'topic_name', 'created_on')
read_only_fields = ('created_on',)
My question are:
Where should I put the complexity? In the serializer on in the views?
Is there a better way to proceed?
There's no need to put this complexity into your code at all, because this function is already provided by serializers.SlugRelatedField, which allows you to refer to a related field using a string instead of an id. Here's how it looks with your model:
views.py
class PostList(ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
serializers.py
class PostSerializer(serializers.ModelSerializer):
topic = serializers.SlugRelatedField(slug_field='name',
queryset=Topic.objects.all())
class Meta:
model = Post
fields = '__all__'

Django Rest Framework: How to associate the object with the user when posting the object

I'm new to creating REST API so I might misunderstand something.
I'm creating REST API using Django Rest Framework. And I'm trying to create an object and send it from my mobile app.
However, API returns 400. I think it still cannot associate the object with the request user and I'm wondering how to do it.
models.py
class Item(models.Model):
item_name = models.CharField()
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name', 'created_by')
and views.py
class ListItems(generics.ListCreateAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
What I want to know is how to associate the object with the request user when posting the object like as we do like
if form.is_valid():
item = form.save(commit=False)
item.created_by = request.user
item.save()
I think the easiest approach is like this:
class ItemSerializer(serializers.ModelSerializer):
created_by = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
Reference can be found here
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name',)
class ListItems(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
you can do this way
One of the possible way to overwrite serializer_create method. As user is not associated with request.data first we need to make sure, this is write_only field and also need to assign current user from modelSerializer's self.context.request.user. Following addition should solve the problem.
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name', 'created_by')
extra_kwargs = {'created_by': {'write_only': True}}
def create(self, validated_data):
item = Item(
item_name=validated_data['item_name'],
created_by=self.context.request.user
)
item.save()
return item
Reference link
It works for me
models.py
class Category(models.Model):
name = models.CharField('Category', max_length=200, unique=True, help_text='Name of the category')
slug = models.SlugField('Slug', max_length=100, db_index=True, unique=True, help_text='Name of the category in format URL')
def __str__(self):
return (self.name)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = [
'id', 'name', 'slug'
]
read_only_fields = [
'slug',
]
Finally, I get the user in the view, before to save the post.
views.py
class CategoryList(APIView):te a new category instance.
permission_classes = (IsAuthenticatedOrReadOnly,)
def get(self, request, format=None):
categories = Category.objects.all()
serializer = CategorySerializer(categories, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, format=None):
serializer = CategorySerializer(data=request.data)
if serializer.is_valid():
serializer.save(created_by=self.request.user)
Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)