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)
Related
I am new Django, I try make REST API. Now face one issue. I created 2 models Account & Transaction
class Account(models.Model):
id = models.UUIDField(default=uuid.uuid4, unique=True,primary_key=True,editable=False)
user = models.ForeignKey(User,on_delete=models.CASCADE)
account_name = models.CharField(max_length=100)
Account have ForeignKey with user model
class Transaction(models.Model):
id = models.UUIDField(default=uuid.uuid4(),primary_key=True,editable=False)
account = models.ForeignKey(Account,on_delete=models.CASCADE,related_name='account')
transaction_no = models.CharField(default=str(uuid.uuid4())[:8],max_length=100)
Transaction have ForeignKey with Account model. then get JWT token & pass on API. In view.py I filtered by requested user
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getAccount(request,pk):
account = Account.objects.filter(user=request.user).get(id=pk)
serializer = AccountSerializer(account, many=False)
return Response(serializer.data)
now how will filter Transaction only by auth User
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getTransactions(request,account_id):
transactions = Transaction.objects.filter(account=account_id)
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
You filter with:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getTransactions(request):
transactions = Transaction.objects.filter(account__user=request.user)
serializer = TransactionSerializer(transactions, many=True)
return Response(serializer.data)
Here we thus retrieve Transactions for which the account refers to an Account object with request.user as user.
Note: It is normally better to make use of the settings.AUTH_USER_MODELĀ [Django-doc] to refer to the user model, than to use the User modelĀ [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
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
How to use POST to retrieve single data on models?
My idea, at the beginning, was to pass a map of parameters. Then the view, on the server side, would take care of reading the needed parameters in the map and return the response.
When I tested this in Postman, I send request body with email and password, but then it returns an error: 'name is required'
I want this api to work like generics. Retrieve but not with url, but with POST instead
Models.py
class Member(models.Model):
name = models.CharField(max_length=100)
password = models.CharField(max_length=100)
email = models.EmailField(unique=True)
phone = models.IntegerField(default=9999)
serializer.py
class LoginMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields =[
'name',
'password',
'email',
'phone',
]
view.py
class LoginMemberAPI(APIView):
def get_queryset(self):
return Member.objects.all()
def post(self, request, format=None):
serializer = LoginMemberSerializer(data=request.data)
if serializer.is_valid():
print(serializer.validated_data['email'])
member = Member.objects.get(name = str(serializer.validated_data['name']))
# serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
Well, indeed the serializer is not valid, as it is supposed to be a complete representation of a model and you're only sending a single field.
It doesn't really make sense to use a serializer for this. Just use the data to query the db and then create a serializer for the response:
member = Member.objects.get(**request.POST)
serializer = LoginMemberSerializer(member)
return Response(serializer.data)
I would say, the fact that you are struggling with this should be able indication that this isn't the right thing to do. POST is meant for sending data that updates the db, not for retrieving data.
Your serializer is using fields of Model and in your 'Member' model, all fields are required. You can not do this with same Serializer. You can create separate serializers for validation and for returning serialized response. Something like this.
Serializer for response:
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
# '__all__' will include all fields of models
fields = '__all__'
Serializer for validation of this Api:
class LoginMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields =[
'name',
'password',
]
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})
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.