Add extra value before save serializer - django

My form sends data to django-rest-framework, but the form contains two fields, and I want to save 5 fields in the database, other fields I calculate on my own (they are not sent by the form). How can I add additional values before saving?
so, form send 'user' and 'comment' values, I want add 'article', 'ip_address' before save to DB
models.py
class Comments(models.Model):
article = models.ForeignKey(Articles, on_delete=models.CASCADE)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
comment = models.TextField(verbose_name=_('Comment'))
submit_date = models.DateTimeField(_('Created'), auto_now_add=True)
ip_address = models.CharField(_('IP address'), max_length=50)
is_public = models.BooleanField(verbose_name=_('Publish'), default=False)
serializers.py
class CommentsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.first_name')
class Meta:
model = Comments
fields = ('user', 'comment')
views.py
class AddCommentViewSet(viewsets.ModelViewSet):
queryset = Comments.objects.all()
serializer_class = CommentsSerializer

You have to override create() method:
class CommentsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.first_name')
class Meta:
model = Comments
fields = ('user', 'comment')
def create(self, validated_data):
new_comment = models.Comment()
new_comment.user = validated_data['user']
new_comment.comment = validated_data['comment']
new_comment.article = get_your_article_somehow()
new_comment.ip_address = get_your_ip_address_somehow()
new_comment.save()
return new_comment

Related

How to post manytomany field value in Postman for API

I have a field which is ManyToMany. I would like to enter the value in POSTMAN for API post operation. But everytime It says: "This field is required." even though I provided the value.
Models:
class Day(models.Model):
day_name = models.CharField(
_("Day Name"), max_length=255, null=True, blank=True)
def __str__(self):
return self.day_name
class TutorProfile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availablility = models.ManyToManyField(
Day,blank=True)
Serializer:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
Viewsets:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
With the following models.py (notice that your current Day.__str__ can raise an exception if day_name does not exist):
class Day(models.Model):
day_name = models.CharField(_("Day Name"), max_length=255, blank=True, null=True)
def __str__(self):
return self.day_name if self.day_name else "Unnamed"
class TutorProfile(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availability = models.ManyToManyField(Day, blank=True)
You do not need to explicitly add tutor_availability nor user as serializer fields:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = "__all__"
class TutorProfileSerializer(serializers.ModelSerializer):
# Omitting `image_url` as not reflected in `models.py`
# image_url = serializers.SerializerMethodField('get_image_url')
class Meta:
model = TutorProfile
fields = "__all__"
With this viewset:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorProfileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
Then, after creating days with IDs 1 and 2 in admin, by sending the tutor_availability field as you are doing it, it should work. Request:
{
"user": 1,
"tutor_availability": [1, 2]
}
Response:
{
"id": 1,
"user": 1,
"tutor_availability": [
1,
2
]
}
Notice as well that I've changed availablility to availability and that it may be unsafe to allow authenticated users to pass the user field in the request, you may want to infer that from the user who makes the request.
In your TutorProfileSerializer you are using the DaySerializer for tutor_availablility field so when you do a post request your post action will wait for a list of dict, what you you need to do in first is to delete this line : from your TutorProfileSerializer and it will works.
tutor_availablility = DaySerializer(many=True)
If you still have the problem then you need to verify the validate method of the TutorProfileSerializer.
And if it works but you want a list of dict(of Day object) for GET request, you need to override the get_serializer_class() of your ViewSet and create two serializers one for post request and a second for get request:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
def get_serializer_class(self):
if self.action.method == 'GET':
return TutorGETProfileSerializer
return super(TutorprofileViewSet, self).get_serializer_class()
and the 2 serializers:
class TutorGETProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = TutorProfile
fields = '__all__'
read_only_fields = ('user',)

Django don't save ModelMultipleChoiceField in admin

I want to filter skill_ids fields and create 3 "abstract" fields for every SkillType, but now it's not saving this hard_skills field in admin site.
Model
class Task(models.Model):
name = models.CharField(max_length=255)
category_id = models.ForeignKey('Category', on_delete=models.RESTRICT, null=True)
level_id = models.ForeignKey('Level', on_delete=models.RESTRICT, null=True)
permission_ids = models.ManyToManyField('Permission', blank=True)
skill_ids = models.ManyToManyField('Skill', blank=True)
Form
class TaskForm(ModelForm):
hard_skills = ModelMultipleChoiceField(Skill.objects.filter(skill_type=SkillType.HARD_SKILL),
widget=FilteredSelectMultiple("Hard Skills", False), required=False)
class Meta:
model = Task
exclude = ['skill_ids']
Admin
#admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_per_page = 25
list_display = ['name', 'category_id', 'level_id', 'get_permissions']
list_filter = ['category_id']
filter_horizontal = ['permission_ids', 'skill_ids']
form = TaskForm
def save_model(self, request, obj, form, change):
for hard_skill in form.cleaned_data.get('hard_skills'):
obj.skill_ids.set(hard_skill)
super().save_model(request, obj, form, change)
ManyToManyField method set requires a list, so this should do:
obj.skill_ids.set(form.cleaned_data.get('hard_skills'))

How to add and update data into foreign key field in django model

I have two models which is related to a forignkey.
class BikeModel(models.Model):
City = models.ForeignKey(CityForBike, on_delete=models.CASCADE)
BikeModels = models.CharField(default='', max_length=50)
Image = models.ImageField(upload_to='bike_images', blank=True, null=True, default='https://www.urbandrive.co.in/Admin/API/UploadedFiles/CarImage/44b150b3-f61a-41b5-afd8-34ded18fa980.png')
class Meta:
verbose_name_plural = 'BikeModels'
def __str__(self):
return self.BikeModels
class CityForBike(models.Model):
CityForCars = models.CharField(default="",max_length=50)
class Meta:
verbose_name_plural = 'CityForBikes'
def __str__(self):
return self.CityForCars
and i want to insert data to BikeModel. but it gives me error.
AssertionError: You cannot call `.save()` on a serializer with invalid data.
I want to know how to insert data to forign key field.
if request.method == 'POST':
import pdb;
pdb.set_trace()
input_data = json.loads(request.read().decode('utf-8'))
print(input_data)
serializer = serializers.BikeModelSerializer(data=input_data)
if serializer.is_valid():
serializer.save()
return render(request, "bike_model_add.html", {'car_city': car_city}, status=200)
And this is the data i am sending.
{'City': 'testo', 'BikeModels': 'ds'}
this is my serializers
class CityForBikeSerializer(serializers.ModelSerializer):
class Meta:
model = models.CityForBike
fields = '__all__'
class BikeModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.BikeModel
fields = '__all__'
If you want to identify cities by their name in the bike serializer, you need to use SlugRelatedField.
class BikeModelSerializer(serializers.ModelSerializer):
City = serializers.SlugRelatedField(slug_field='CityForCars')
class Meta:
model = models.BikeModel
fields = '__all__'

Django rest ModelViewSet many-to-many create

I have my model relationships as following: A Reader will have a Wishlist and a Wishlist will have many Books:
class Reader(models.Model):
user = models.OneToOneField(User)
...
# A library has many readers
which_library = models.ForeignKey('Library', related_name='readers', on_delete=models.CASCADE)
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=30)
...
# A library has many books
which_library = models.ForeignKey('Library', related_name='books', on_delete=models.CASCADE)
# Record the date whenever a new book is added, it will be helpful for showing new arrivals
when_added = models.DateTimeField(auto_now_add=True, blank=True, null= True)
reader = models.ManyToManyField('Reader', related_name='wishlist')
My serializers:
class ReaderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
password = serializers.CharField(source='user.password')
class Meta:
model = Reader
#fields = '__all__'
#depth = 1
fields = ('id', 'username', 'email', 'password', 'phone', 'address', 'dob', 'which_library')
def update(self, instance, validated_data):
...
instance.which_library = validated_data.get('which_library', instance.which_library)
instance.save()
return instance
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.set_password(user_data['password'])
user.save()
reader = Reader.objects.create(user=user, **validated_data)
return reader
class BookSerializer(serializers.ModelSerializer):
wishlist = ReaderSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = '__all__'
I can already perform CRUD operations with Reader, I want to now add books to a specific Reader's wishlist. My view:
class ReaderViewSet(viewsets.ModelViewSet):
serializer_class = ReaderSerializer
def get_queryset(self):
readers = Reader.objects.filter(which_library=self.kwargs.get('library_id'))
return readers
#detail_route(methods=['post'])
def wishlist(self):
return Response('OK')
URL that I hit:
router.register(r'readers/(?P<library_id>[0-9]+)', ReaderViewSet, base_name='readers')
Here I am expecting that on hitting api/readers/<library_id>/<book_id>/wishlist/addI will be able to perform
add operation to the Wishlist.
How can I achieve that?
You can use detail_route's argument url_path to change url of endpoint. Also you can add additional arguments like book_id directly to detail_routed method, so your method can look like this:
#detail_route(methods=['post'], url_path='(?P<book_id>[0-9]+)/wishlist/add')
def wishlist(self, library_id=None, book_id=None):
reader = self.request.user.reader
book = Book.objects.get(pk=book_id)
reader.wishlist.add(book)
return Response('OK')
And it should be accessible from api/readers/<library_id>/<book_id>/wishlist/add url.

Trouble POSTing multiple related objects at once to api created with django rest framework

I'm making a ToDo app but having difficulties getting the api to allow a user to create a new list with multiple items via one api call. Each list belongs to a specific "room".
I get 400 Bad Request. If I leave the 'todo_items' off the POST data it works fine to create the ToDoList object.
Also, if I remove "user" from the Meta fields attribute for the CreateToDoItemSerializer, it'll create both the ToDoList object and the ToDoItem objects, but the "content" for each ToDoItem will be an empty string. Inside the create method of NewToDoListSerializer, the validated_data is returning a list of empty OrderedDict() objects for the key "todo_items". I'm not sure what to make of that.
my models:
class Room(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
slug = models.SlugField(max_length=255, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="rooms")
class ToDoList(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, blank=True)
room = models.ForeignKey(Room, related_name="todo_lists")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="todo_lists")
class ToDoItem(models.Model):
todo_list = models.ForeignKey(ToDoList, related_name="todo_items")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="replies")
content = FroalaField(options={'placeholder': '''Just start writing...
Highlight any text to bring up the editor.'''})
my serializers
class CreateTodoItemSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=User.objects.all())
class Meta:
model = ToDoItem
fields = ['pk', 'user', 'content']
def create(self, validated_data):
reply = ToDo.objects.create(**validated_data)
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, read_only=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)
my viewset (the relevant bits):
class ToDoListViewSet(viewsets.ModelViewSet):
queryset = ToDoList.objects.all()
serializer_class = ToDoListSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated]
renderer_classes = (renderers.TemplateHTMLRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer)
template_name = "react_base.html"
lookup_field = "slug"
def create(self, request, **kwargs):
self.serializer_class = NewToDoListSerializer
return super(ToDoListViewSet, self).create(request, **kwargs)
def perform_create(self, serializer):
instance = serializer.save(user=self.request.user)
the data I'm POSTing:
todoListTitle, todoItemContent, moreTodoItemContent are all strings. this.props.room.pk is an integer. this.props.csrfmiddlewaretoken is the csrfmiddlewaretoken
var newToDoListData = {
"room": this.props.room.pk,
"title": todoListTitle,
"todo_items": [{"content": todoItemContent}, {"content": moreTodoItemContent}],
"csrfmiddlewaretoken": this.props.csrfmiddlewaretoken
};
You need to make the todo_items in your serializer required = false. I am not sure I understand what your second issue is.
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, required=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)