I have an architecture in a way that when user registers, his/her settings are initialized. So we need GET and PUT to get or update the settings respectively on settings API.
I am using model serializers of django rest framework for storing and getting the settings. It works well in giving response but it doesn't save that to models.
serializers.py
class UserSettingsSerializer(serializers.ModelSerializer):
class Meta:
model = UserSettings
fields = ('group_notifications', 'updates', 'goal_remind_me',
'goal_days', 'goal_time_interval', 'user')
models.py
class UserSettings(models.Model):
class Meta:
db_table = 'user_settings'
user = models.ForeignKey('User')
group_notifications = models.BooleanField(default=True)
updates = models.BooleanField(default=False)
goal_remind_me = models.BooleanField(default=False)
goal_days = ListField()
goal_time_interval = models.IntegerField(null=True)
views.py
def settings(request, pk):
if request.method == 'PUT':
request.data['user'] = user.id
serializer = UserSettingsSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
serializer_dict = serializer.data
serializer_dict["message"] = "Settings updated successfully."
return Response(serializer_dict, status=status.HTTP_200_OK)
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
I get response as expected but it's not storing data in user_settings table and I am using PostgreSql.
In order for a ModelSerializer to preform an update, you need to pass in the instance that you want updated to the constructor.
def settings(request, pk):
# query for the UserSettings object
instance = get_object_or_404(UserSettings.objects.all(), pk=pk)
if request.method == 'PUT':
request.data['user'] = user.id
# pass in the instance we want to update
serializer = UserSettingsSerializer(instance, data=request.data)
# validate and update
if serializer.is_valid():
serializer.save()
serializer_dict = serializer.data
serializer_dict["message"] = "Settings updated successfully."
return Response(serializer_dict, status=status.HTTP_200_OK)
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Related
I'm trying to update boolean fields using PATCH request and partial-update in DRF. However, only the name field gets updated and not the others
Model:
class UserModel(models.CustomModel):
name = models.CharField(max_length=64)
enabled = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
Serializer
class UserSerializer(serializers.CustomModelSerializer):
class Meta:
model = UserModel
fields = (
'id', 'name', 'enabled', 'admin'
)
read_only_fields = ('id',)
View and function
class UserView(viewsets.CustomModelViewSet):
def get_queryset(self):
return User.users.for_company(company=self.request.company.pk)
def get_serializer_class(self):
return UserSerializer
def update(self, request, id):
try:
instance = self.get_queryset().get(id=id)
except User.DoesNotExist:
return Response('User not in DB', status=status.HTTP_404_NOT_FOUND)
if 'name' in request.data and self.get_queryset().filter(name=request.data['name']).exists():
return Response('Duplicated user name', status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Url
urlpatterns = [ url(r'^(?P<id>${uuid})/$', UserView.as_view(
{'patch': 'update'}), name='user-update') ]
Currently if I send a PATCH request with a user_id and the following data in the body, ONLY the name field gets updated in the DB.
name = user2
enabled = True
admin = True
However if I change the update method as follows, the DB gets updated correctly
def update(self, request, id):
try:
instance = self.get_queryset().get(id=id)
except User.DoesNotExist:
return Response('User not in DB', status=status.HTTP_404_NOT_FOUND)
if 'name' in request.data and self.get_queryset().filter(name=request.data['name']).exists():
return Response('Duplicated user name', status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(instance, data=request.data, partial=True)
if 'enabled' or 'admin' in request.data:
instance.enabled = request.data.get('enabled', instance.enabled)
instance.admin = request.data.get('admin', instance.admin)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Is this the correct way to update the fields by modifying the instance? Shouldn't the Django partial-update method take care of this if I'm passing the other boolean fields in the request and update them correctly in the database?
I investigated further and understand that the serializer.save() calls an instance.save() under the hood, and validates the data, only the name fields gets through as validated data and not the other two boolean fields.
views.py
def productslist(request):
products = Products.objects.all()
context = {'products':products}
return render(request,'productslist.html',context)
def productsform(request):
return render(request,'productscreate.html')
#api_view(['GET','POST'])
def products_list(request):
if request.method == 'GET':
product = Products.objects.all()
serializer = Productserialize(product,many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = Productserialize(data=request.data)
if serializer.is_valid():
def create(serializer):
return Products.objects.create(serializer)
return Response(serializer.data)
return Response(serializer.errors)
#api_view(['GET','PUT'])
def products_detail(request, pk):
products = Products.objects.get(pk=pk)
if request.method == 'GET':
serializer = Productserialize(products)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = Productserialize(products, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
In views.py I have done create function but it is not working not saving data
I want to save serializer data using create function
I want to pass serializer.data into create function in post method api
serializer.py
class Productserialize(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
description = serializers.CharField(required=False, allow_blank=True, max_length=100)
image = serializers.FileField()
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Products.objects.create(**validated_data)
Just use a ListCreateAPIView and use a ModelSerializer instead of a Serializer.
# Serializer
class ProductSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'title', 'description', 'image')
model = Product
# View
from rest_framework.generics import ListCreateAPIView
class ProductListCreate(ListCreateAPIView):
serializer_class = ProductSerializer
queryset = Product.objects.all()
ListCreateAPIView handle GET to list and POST to create new objects, you don't need to override anything else.
you can try to call it
if serializer.is_valid():
def create(serializer):
return Products.objects.create(serializer.data)
crerate(serializer)
return Response(serializer.data)
I have a model which has user as a foreign key:
class Task(models.Model):
user = models.ForeignKey(User)
what_task = models.CharField(max_length=100, )
#This helps to print in admin interface
def __str__(self):
return u"%s" % (self.what_task)
It's serializer:
class TaskSerializer(serializers.ModelSerializer):
steps = StepSerializer(many=True)
class Meta:
model = Task
fields = '__all__'
def create(self, validated_data):
steps_data = validated_data.pop('steps')
task = Task.objects.create(**validated_data)
for step_data in steps_data:
Step.objects.create(task=task, **step_data)
return task
And in my view I have a function that handles GET and POST request. GET is correct it returns me all the tasks of a specific user-I use request.user.id for it.
I am not sure about my POST, how can I save task against a specific user in this case:
#api_view(['GET', 'POST'])
def task_list(request):
"""
List all tasks, or create a new task.
"""
if request.method == 'GET':
tasks = Task.objects.filter(user=request.user.id)
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = TaskSerializer(data=request.data)
print(request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Where I must make change, in serializer or in view?
Change in your models,
user = models.ForeignKey(User, blank=True)
Then, migrate your changes.
Then, In your views,
if serializer.is_valid():
serializer.save(user=request.user)
Suppose I want to register a user (I'm using the User model located in django.contrib.auth.models). Assume this is my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password', 'email', )
What's the difference between the following views, and which one is recommended when it comes to creating a user?
View A:
def createUser(request):
if request.method == 'POST':
serializer = UserSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
View B:
def createUser(request):
serializer = UserSerializer(data=request.DATA)
if serializer.is_valid():
user = User.objects.create_user(
email = serializer.init_data['email'],
username = serializer.init_data['username'],
password = serializer.init_data['password'],
)
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Neither of those is perfect. But View A looks promising.
View A is a good start, but it is incomplete solution. Because User creation is not simply User.save, but rather you have to call User.create_user method.
View B is the correct way to create user by calling User.create_user, however, the views contains a logic which should actually be abstracted within the Serializer.save() method.
To solve this, you have to change the behavior of Serializer.save() method. Looking at the documentation, Serializer.save(), will call either Serializer.create() or Serializer.update().
In summary, we have to override the Serializer.create() for user creation and use View A.
# File: serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
def create(self, validated_data):
user = User.objects.create_user(
email = validated_data['email'],
username = validated_data['username'],
password = validated_data['password'],
)
return user
I have a Location model and I need to create a Location without specifying a user, if user in empty then the user will be placed in request.user in the viewset.
This is my model:
class Location(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
serializer:
class LocationSerializer(serializers.ModelSerializer):
def is_valid(self):
if not 'user' in self.init_data:
# avoid this validation.. I manage this in the viewset
pass
return not self.errors
class Meta:
model = Location
and viewset
class LocationViewSet(ModelViewSet):
"""
API endpoint that allows location to be created or viewed.
"""
model = Location
serializer_class = LocationSerializer
renderer_classes = (JSONRenderer, JSONPRenderer)
def get_queryset(self):
return self.request.user.locations.filter(deleted=False)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
self.pre_save(serializer.object)
if not self.object.user:
self.object.user = request.user
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
Thanks for any suggestion
You can use required=False in serializer:
class LocationSerializer(serializers.ModelSerializer):
user = serializers.RelatedField(required=False)
EDIT
Also you can simplify your viewset with:
class LocationViewSet(ModelViewSet):
def pre_save(self, obj):
if obj.user_id is None:
obj.user = self.request.user
This avoid copying code from DRF core.