Saving User foreign key upon creation of related model - python

My Category model is related to User, but I can't find a way to store the logged in user's id into user_id field in Category.
Category models.py:
class Category(models.Model):
user = models.ForeignKey(User, default=None)
name = models.CharField(max_length=32, unique=True)
views.py:
class CategoryList(APIView):
...
def post(self, request):
"""
Create a new Category
:param request:
:return: Category
"""
user_id = request.user.pk
serializer = CategorySerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
I can access request.user.pk and see it's correctly shown, but I can't figure out how I can store this value when creating a new category.

To add current user id to new record you can pass current user as save argument:
if serializer.is_valid():
serializer.save(user=request.user)
Or you can change serializer and use CurrentUserDefault:
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
from docs:
A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer.
So to use CurrentUserDefault you need to pass request to serializer in view:
serializer = CategorySerializer(data=request.data, context={'request': request})

Related

Create model instance only if its user id field is equal to the logged-in user's id

In a django rest framework app, there's a TextViewSet. The Text object structure is as follows:
{
text: text_value,
author: author_id
}
When creating a new Text instance, I want to check if the supplied author_id equals the currently logged-in user's id.
I've read this question: When to use Serializer's create() and ModelViewset's perform_create(), but still can't decide whether to override Serializer's create(), ModelViewset's create() or perform_create() methods. What's the right method to override?
UPD:
models.py:
class Text(models.Model):
text = models.TextField()
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
serializers.py:
class TextSerializer(serializers.ModelSerializer):
class Meta:
model = Text
fields = ['author', 'text']
The question is in which of these methods should one perform this check if self.request.user.id != self.request.data['author']:?
You can override create() the method of your TextViewSet
views.py
from rest_framework.response import Response
class TextViewSet(ModelViewSet):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if request.user.id == request.data['author']:
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
else:
return Response("Unauthorized", status=status.HTTP_401_UNAUTHORIZED
The real question is why not just set author to the logged in user and not let the client send in any ID at all?
The normal way of doing this is by:
class MyViewSet(ModelViewSet):
...
def perform_create(self, serializer):
'''The logged in user is always the author'''
return serializer.save(author=self.request.user)
def get_queryset(self):
'''Limit the queryset to the author, i.e the logged in user, for fetching/updating data'''
return self.queryset.filter(author=self.request.user)
But if you really want to send in author-id then you can also use a custom Permission for it to have it re-usable.
from rest_framework.permissions import BasePermission
class UserIsAuthor(BasePermission):
default_author_field = "author"
def has_permission(self, request, view):
author_field = getattr(
view,
"permission_author_field",
self.default_author_field
)
return request.user.is_authenticated and (
request.user.pk == request.data.get(author_field)
)
used as:
class ExampleViewSet(ModelViewSet):
permission_classes = [UserIsAuthor]
# optional if the default "author" isnt what you want.
permission_author_field = "some_field_in_request_data"

Add extra parameter to serializer.data

I get three fields from rest api i.e name , phone number and email id in serializer but i want to store the datetime as well when i receive the api . in database i have created 4 columns i.e name , phone number, email id , created data .
How can i add current date timeparameter to my serializer.data before saving it to posgtresql database table "table_userdetails". please help me with the answers because i am new to it
Below is the code for views.py
#api_view(['GET', 'POST'])
def UserInfo(request):
if request.method == 'GET':
snippets = UserDetails.objects.all()
serializer=UserDetailsSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method =='POST':
context=datetime.datetime.now()
serializer = UserDetailsSerializer(data=request.data)
print("serializer",serializer)
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)
Below is the code for Models.py
class UserDetails(models.Model):
name=models.CharField(max_length=255)
mobile_no=models.CharField(max_length=255)
email_id=models.CharField(max_length=255)
user_id=models.IntegerField(primary_key=True)
created_date=models.CharField(max_length=255)
class Meta:
db_table=table_userdetails
def __str__(self):
return self.response
Below is my code for serializer.py
class UserDetailsSerializer(serializers.ModelSerializer):
class Meta:
model=UserDetails
fields='__all__'
I have added the context part in post method but how to add in serializer.data .please help me with the solution and what am i doing wrong here .any kind of help is appreciated .
Thank you
Try this:
#api_view(['GET', 'POST'])
def UserInfo(request):
if request.method == 'GET':
snippets = UserDetails.objects.all()
serializer=UserDetailsSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method =='POST':
serializer = UserDetailsSerializer(data=request.data)
print("serializer",serializer)
data = serializer.data #fetch the serializer data
#add current time as key-value pair in data as serializer.data returns a dictionary
data['current_time'] = datetime.datetime.now()
if serializer.is_valid():
serializer.save()
#return the data object/dictionary
return Response(data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You don't need to set now to time directly as you could use auto_add_now on DateTimeField/DateField model field
There is no sense to keep date in CharField
For further reference you could update() your request.data dictionary with additional attribute as just setting new variable called context does nothing
You can pass extra parameters to the serializers save method.
serializer.save(created_at=datetime.datetime.now())
Doing so you will have to take care of validating the data manually, as the serializer will directly save this to the database.
As per your requirement, for created_at field, you can use the auto_now_add=True attribute in your model field, this will automatically assign the current time to the created_at field. only during the time of creation. Similary for a updated_at field you can use auto_now=True.
Now if you want to pass extra context to the serializer, you can do so by overriding the __init__ method.
class UserDetailsSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
time = kwargs.pop('time')
super().__init__(*args, **kwargs)
self.time = time
class Meta:
model=UserDetails
fields='__all__'
Then while initializing the serializer you can pass the value
serializer = UserDetailsSerializer(data=request.data, time=datetime.datetime.now())
After that you can use this in the serializer methods. For your requirement, setting the auto_now_add attribute will be the best solution.
Edit: If you are using USE_TZ=True in your settings.py you should be using timezone.now()
from django.utils import timezone.now()
serializer.save(created_at=timezone.now())
Django uses UTC time saved to the database for a timezone aware object, which is converted to the localtime of the user as per the settings.
Refer to docs for more information

Django REST Framework - OneToOne Relation through JSON

I want to create an API that allows to send JSON through a POST request.
The JSON should then get passed on to a serializer which takes care of creating a new object and populate it with the existing data.
It works fine for the 'simple' cases such as basic character-only inputs like usernames and alike, but I am seriously stuck when it comes to creating a OneToOne relation. Here's the sample code.
Function called employee_list in views.py - data['account'] is a valid username, a User instance is successfully being selected!
data = JSONParser().parse(request)
user_object = User.objects.get(username=data['account'])
data['account'] = user_object # this is now a valid User object
serializer = EmployeeSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
The model
class Employee(models.Model)
id = models.AutoField(primary_key=True)
name = models.CharField(...)
surname = models.CharField...
account = models.OneToOneField(User)
role = ...
salary = ...
picture = ...
And the serializer
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = (...) # all fields of the `Employee` model
So far so good, however, the serializer never validates! When I remove the need for a OneToOne relation, it works..
How can I create a new Employee objects with a working OneToOne relationship to a User object?
Thanks in advance.
You need to pass the pk of the User object, rather than the User object.
Solution:
data['account'] = user_object.pk
The issue is because a user object is being passed to the serializer, when it can be just passed to the serializer, while saving the object. Try something like this:
data = JSONParser().parse(request)
user_object = User.objects.get(username=data['account'])
serializer = EmployeeSerializer(data=data)
if serializer.is_valid():
# pass object to save, instead of saving in the data dictionary
serializer.save(account=user_object)
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)

Django Rest Framework ModelSerializer Set attribute on create

When creating an object initially I use the currently logged-in user to assign the model field 'owner'.
The model:
class Account(models.Model):
id = models.AutoField(primary_key=True)
owner = models.ForeignKey(User)
name = models.CharField(max_length=32, unique=True)
description = models.CharField(max_length=250, blank=True)
Serializer to set owner:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = models.Account
fields = ('name', 'description')
def restore_object(self, attrs, instance=None):
instance = super().restore_object(attrs, instance)
request = self.context.get('request', None)
setattr(instance, 'owner', request.user)
return instance
It is possible for a different user in my system to update another's Account object, but the ownership should remain with the original user. Obviously the above breaks this as the ownership would get overwritten upon update with the currently logged in user.
So I've updated it like this:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = models.Account
fields = ('name', 'description')
def restore_object(self, attrs, instance=None):
new_instance = False
if not instance:
new_instance = True
instance = super().restore_object(attrs, instance)
# Only set the owner if this is a new instance
if new_instance:
request = self.context.get('request', None)
setattr(instance, 'owner', request.user)
return instance
Is this the recommended way to do something like this? I can't see any other way, but I have very limited experience so far.
Thanks
From reviewing #zaphod100.10's answer. Alternatively, in the view code (with custom restore_object method in above serializer removed):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.object.owner = request.user
self.pre_save(serializer.object)
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)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Basically you want the owner to be set on creation and not on subsequent updates. For this I think you should set the owner in the POST view. I think it is more logical and robust that way. Update is done via PUT view so your data should always be correct since no way on updation the owner can be changed if the owner is not editable on PUT.
For making the views you can use DRF's generic class based views. Use the RetrieveUpdateDeleteView as it is. For ListCreateView override the post method. Use a django model form for validating the data and creating an account instance.
You will have to copy the request.DATA dict and insert 'owner' as the current user.
The code for the POST method can be:
def post(self, request, *args, **kwargs):
data = deepcopy(request.DATA)
data['owner'] = request.user
form = AccountForm(data=data)
if form.is_valid():
instance = form.save(commit=false)
instance.save()
return Response(dict(id=instance.pk), status=status.HTTP_201_CREATED)
return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)
Potential other option using pre_save which I think seems to be intended for just this kind of thing.
class AccountList(generics.ListCreateAPIView):
serializer_class = serializers.AccountSerializer
permission_classes = (permissions.IsAuthenticated)
def get_queryset(self):
"""
This view should return a list of all the accounts
for the currently authenticated user.
"""
user = self.request.user
return models.Account.objects.filter(owner=user)
def pre_save(self, obj):
"""
Set the owner of the object to the currently logged in user as this
field is not populated by the serializer as the user can not set it
"""
# Throw a 404 error if there is no authenticated user to use although
# in my case this is assured more properly by the permission_class
# specified above, but this could be any criteria.
if not self.request.user.is_authenticated():
raise Http404()
# In the case of ListCreateAPIView this is not necessary, but
# if doing this on RetrieveUpdateDestroyAPIView then this may
# be an update, but if it doesn't exist will be a create. In the
# case of the update, we don't wish to overwrite the owner.
# obj.owner will not exist so the way to test if the owner is
# already assigned for a ForeignKey relation is to check for
# the owner_id attribute
if not obj.owner_id:
setattr(obj, 'owner', self.request.user)
I think this is the purpose of pre_save and it is quite concise.
Responsibilities should be split here, as the serializer/view only receives/clean the data and make sure all the needed data is provided, then it should be the model responsibility to set the owner field accordingly. It's important to separate these two goals as the model might be updated from elsewhere (like from an admin form).
views.py
class AccountCreateView(generics.CreateAPIView):
serializer_class = serializers.AccountSerializer
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, *args, **kwargs):
# only need this
request.data['owner'] = request.user.id
return super(AccountCreateView, self).post(request, *args, **kwargs)
models.py
class Account(models.Model):
# The id field is provided by django models.
# id = models.AutoField(primary_key=True)
# you may want to name the reverse relation with 'related_name' param.
owner = models.ForeignKey(User, related_name='accounts')
name = models.CharField(max_length=32, unique=True)
description = models.CharField(max_length=250, blank=True)
def save(self, *args, **kwargs):
if not self.id:
# only triggers on creation
super(Account, self).save(*args, **kwargs)
# when updating, remove the "owner" field from the list
super(Account, self).save(update_fields=['name', 'description'], *args, **kwargs)

Django REST Framework - Serialize ForeignKey fields

In my app, users have a wall, similar to the old Facebook wall. A user is able to post comments on other users walls. I have a serializer with a base structure like this:
class UserWallCommentSerializer(serializers.ModelSerializer):
class Meta:
model = UserWallComment
fields = ('uid', 'uidcommenter', 'idwall', 'created', 'description')
read_only_fields = ('uid', 'uidcommenter', 'idwall', 'created')
uid and uidcommenter are foreignkeys to the user model, idwall is the PK, and description is the comment itself.
When a comment is created/edited, uid and uidcommenter needs to be set by the backend. A user can not be allowed to change these fields.
Lets say I have the variables uid and uidcommenter in my view that is calling the serializer - how can I pass these variables along to the serializer so that a UserWallComment is created?
I have tried setting uid and uidcommenter using the SerializerMethodField (passing the PK's in the context variable), but the database says I am passing NULL PK's:
class UserWallCommentSerializer(serializers.ModelSerializer):
uid = serializers.SerializerMethodField('setUid')
class Meta:
model = UserWallComment
fields = ('uid', 'uidcommenter', 'idwall', 'created', 'description')
read_only_fields = ('uidcommenter', 'idwall', 'created')
def setUid(self):
return self.context['uid']
My view code (idwall is the pk of the wall):
class MemberWall(APIView):
def post(self, request, requestUid, idwall):
uid = request.user.uid
serializer = UserWallCommentSerializer(data=request.DATA, context={'uid': requestUid, 'uidcommenter': uid})
if serializer.is_valid():
serializer.save()
return Response(serializer.data['uid'], status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
the documentation says that the SerializerMethodField is used only for representation of the object. Which means it is used only when you return your data as a response.
By default the serializer get's the request passed:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
This means that you can overwrite de default save, update methods of the serializer and set the relevant fields. You should be able to access then using: self._context.request.user.uid
I didn't try this but it should work.

Categories

Resources