I am running into an issue while deserializing JSON data. One of the field is the customerID and i cannot find a way to user a Serializer class properly.
Here is my code:
class UserProfileData(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
captureDateTime = models.CharField(_('Capture datetime'), blank=True, null=True, max_length=100)
class UserProfileDataSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfileData
fields = "__all__"
The JSON i receive is the following:
{ "customerID": "someUUID", "captureDateTime": "..." }
Here is the current state of my view:
#api_view(['POST'])
def register_profile(request):
data = JSONParser().parse(request)
serializer = UserProfileDataSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
It fails with the following error:
{'user': [ErrorDetail(string='This field is required.', code='required')]}
I understand i am missing something here, but can't figure out what... Also, i almost forgot to mention the User object has a customerId field. Thanks for your help.
You can pass the source argument to a field to map it to an attribute with a different name. Something like this should work
class UserProfileDataSerializer(serializers.ModelSerializer):
# May want to use a UUIDField based on your question
# either 'user_id' or 'user' as source
customerID = serializers.CharField(source='user') # 'user'
class Meta:
model = UserProfileData
fields = ('captureDateTime', 'customerID')
Here is the working version:
class UserProfileDataSerializer(serializers.ModelSerializer):
customerId = serializers.SlugRelatedField(source='user', queryset=get_user_model().objects.all(), many=False, slug_field='customerId')
class Meta:
model = UserProfileData
fields = ('captureDateTime', 'customerId')
Related
I have a model for a "gym", and a model for a "workout":
class Gym(models.Model):
name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
def __str__(self):
return self.name
class Workout(models.Model):
gym = models.ForeignKey(Gym, on_delete=models.CASCADE)
time = models.DateTimeField()
I will also show the 'WorkoutSerializer':
class WorkoutSerializer(serializers.ModelSerializer):
gym = serializers.StringRelatedField()
class Meta:
model = Workout
fields = ['gym','time']
as you can see, gym is represented in the workout json as a string of the field 'name'.
here is the view for workout:
#api_view(['GET','POST'])
def workout_list(request):
if request.method == 'GET':
queryset = Workout.objects.select_related('gym').all()
serializer = WorkoutSerializer(queryset, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = WorkoutSerializer(data=request.data)
serializer.is_valid(raise_exception=True) # rasis 400 bad request if needed
serializer.save()
return Response('ok')
when I try to test a POST request with (I want to use the gym's str representation in POST as well):
{
"gym": "gym 2",
"activity": "Yuga",
"time": "2022-03-07T06:00:00Z",
"participants": [
"Anna Boing"
]
}
I get the error:
StringRelatedField.to_internal_value() must be implemented for field
any idea why, and what can I do to fix this?
StringRelatedField is a read-only field. If you want to make gym field writable you can use SlugRelatedField instead. Please note that the slug field corresponds to a model field with unique=True.
I think StringRelatedField won't work for object creation,
Also, Gym needs an address and name both and you are just passing the name.
You can use SlugRelatedField for that.
I'm implementing some voting functionality in an application, where a logged-in user specifies a post that they would like to vote for using a payload like this:
{
"post": 1,
"value": 1
}
As you can tell, the a user field is absent - this is because it gets set in my viewset's perform_create method. I've done this to ensure the vote's user gets set server side. This is what the viewset looks like:
class CreateVoteView(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = VoteSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Here is what the model looks like:
class Vote(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='votes', null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='votes', null=False)
class Values(models.IntegerChoices):
UP = 1, _('Up')
DOWN = -1, _('Down')
value = models.IntegerField(choices=Values.choices, null=False)
class Meta:
unique_together = ('post', 'user')
and finally, the serializer:
class VoteSerializer(serializers.ModelSerializer):
class Meta:
model = Vote
fields = ['post', 'value']
From what I understand, in order for DRF to enforce a unique together validation, both fields (in my case, user and post) must be included in the serializer's fields. As I've mentioned, I'd like to avoid this. Is there any other way of implementing this type of validation logic?
EDIT:
To clarify: the records do not save - I receive this error:
django.db.utils.IntegrityError: (1062, "Duplicate entry '1-3' for key 'api_vote.api_vote_post_id_user_id_73614533_uniq'")
However, my goal is to return a Bad Request instead of an Internal Server Error much like I would when traditionally using a DRF serializer and excluding required fields from a payload.
To output a custom error message due to the IntegrityError, you can override the create method in your serializer:
from django.db import IntegrityError
class VoteSerializer(serializers.ModelSerializer):
class Meta:
model = Vote
fields = ['post', 'value']
def create(self, validated_data):
try:
validated_data['user'] = self.context['request'].user
return super().create(validated_data)
except IntegrityError:
error_msg = {'error': 'IntegrityError message'}
raise serializers.ValidationError(error_msg)
You can try this on your views
try:
MoviesWatchList.objects.create(user=request.user, content=movie)
return response.Response({'message': f'{movie} added in watchlist.'}, status=status.HTTP_201_CREATED)
except:
return response.Response({'message': f'{movie} already added to watchlist.'}, status=status.HTTP_304_NOT_MODIFIED)
In the serializer used to create a model I want to rename my model field to (field_name)_id so it's clearer for API consumers that this field is an ID field. The model also has a unique_together constraint on some fields. However when validation runs in the serializer, it fails with a KeyError that the field does not exist:
...rest_framework/utils/serializer_helpers.py", line 148, in __getitem__
return self.fields[key]
KeyError: 'question'
Is there a simple way to get this to work? Minimal example code below.
Model
class MyModel(Model):
question = ForeignKey('uppley.Question', null=False, on_delete=PROTECT)
user = ForeignKey('catalystlab.User', null=False, on_delete=PROTECT)
class Meta:
unique_together = ('question', 'user',)
Serializer
class MyCreateSerializer(ModelSerializer):
question_id = PrimaryKeyRelatedField(
write_only=True,
source='question',
queryset=Question.objects.all(),
)
user_id = PrimaryKeyRelatedField(
write_only=True,
source='user',
queryset=User.objects.all(),
)
class Meta:
model = MyModel
fields = ('question_id', 'user_id',)
test.py - test for demonstration purposes
question = QuestionFactory()
user = UserFactory()
data = {
'question_id': question.id,
'user_id': user.id,
}
serializer = MyCreateSerializer(data=data, write_only=True)
is_valid = serializer.is_valid(raise_exception=True) #KeyError exception raised here.
Previously with DRF 3.10.3 this all worked fine, however with 3.11.0 this now throws a KeyError as mentioned above.
What I have tried
Removing the source field on PrimaryKeyRelatedField for user_id and question_id in the Serializer actually results in bypassing the unique_together validation in DRF and the KeyError is avoided. However the validated data is not mapped back to the original field names (user and question). In this case we have to manually change the keys back to their original names before we can create an instance of the Model from the validated data.
Is there a better way to do this?
You can make a custom serializer like :-
class MyCreateSerializer(serializers.Serializer):
question_id = serializers.PrimaryKeyRelatedField(
write_only=True,
queryset=Question.objects.all(),
)
user_id = PrimaryKeyRelatedField(
write_only=True,
queryset=User.objects.all(),
)
and make custom create function in it for creating object. like :-
def create(self, validated_data):
try:
question = validated_data.get('question_id')
user = validated_data.get('user_id')
instance = MyModel.objects.create(question=question, user=user)
except TypeError:
raise TypeError("Something went wrong while creating objects")
return instance
Using the following models:
class Ticket(models.Model):
[some irrelevant fields]
class TicketComment(models.Model):
text = models.TextField()
creator = models.CharField(max_length=255)
ticket = models.ForeignKey(Ticket, models.CASCADE, related_name='comments')
I created the following serializers:
class TicketSerializer(serializers.ModelSerializer):
[irrelevant]
class TicketCommentSerializer(serializers.ModelSerializer):
class Meta:
model = TicketComment
fields = '__all__'
def create(self, validated_data):
return TicketComment.objects.create(**validated_data)
A view:
class TicketCommentView(APIView):
lookup_url_kwarg = 'ticket_id'
def post(self, request, ticket_id):
data = request.data
data['creator'] = 'joe'
try:
data['ticket'] = Ticket.objects.get(pk=ticket_id)
except Ticket.DoesNotExist:
raise NotFound('Ticket {} does not exist.'.format(ticket_id))
serializer = TicketCommentSerializer(data=data)
serializer.is_valid(raise_exception=True)
comment = serializer.save()
return Response(comment, status=HTTP_201_CREATED)
And URL pattern:
urlpatterns = [
path('ticket/<int:ticket_id>/comment', TicketCommentView.as_view()),
]
However, when trying to POST the data {"text": "test"}, it fails with:
"ticket": ["Incorrect type. Expected pk value, received Ticket."]
If I change the view to pass the ticket_id integer instead of the ticket instance, it complains about duplicate keys:
django.db.utils.IntegrityError: duplicate key value violates unique constraint "ticketcomment_pkey"
DETAIL: Key (id)=(41993) already exists.
How can I create a resource and attach it to an existing related object?
Instead of passing ticket as serializer data, pass it to serializer's save method directly(related part of docs):
def post(self, request, ticket_id):
data = request.data
try:
ticket = Ticket.objects.get(pk=ticket_id)
except Ticket.DoesNotExist:
raise NotFound('Ticket {} does not exist.'.format(ticket_id))
serializer = TicketCommentSerializer(data=data)
serializer.is_valid(raise_exception=True)
comment = serializer.save(ticket=ticket, creator='joe')
return Response(comment, status=HTTP_201_CREATED)
Note in TicketCommentSerializer you should leave only text field:
class TicketCommentSerializer(serializers.ModelSerializer):
class Meta:
model = TicketComment
fields = ['text']
First you should not include id in serializer, cause mostly it will be a auto increment value. That is why you are getting an Integrity Error.
Short description: I have a problem with ModelSerializer. It is not filling id field of object on save.
Useful details.
My model:
class Platform(models.Model):
identifier = models.CharField(max_length=32, unique=True)
description = models.TextField()
My serializer:
class PlatformSerializer(serializers.ModelSerializer):
identifier = serializers.CharField(required=True, max_length=32)
description = serializers.CharField(max_length=100000, required=False)
class Meta:
model=Platform
fields=('id', 'identifier', 'description',)
read_only_fields=('id',)
And APIView:
class PlatformView(APIView):
def post(self, request, id_platform=None, format=None):
serializer = PlatformSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(form_error(u"Ошибка при создании КА: ", serializer.errors), status=status.HTTP_400_BAD_REQUEST)
The line serializer.save() returns Platform object with id==None. Is it a bug or am I missing something?
I think this serializer class should work:
class PlatformSerializer(serializers.ModelSerializer):
class Meta:
model=Platform
ModelSerializer:
By default, all the model fields on the class will be mapped to corresponding serializer fields.
Ouch. I'm sorry for this question.
Somebody created pk field for this table as integer with default value from sequence (without owning this sequence).
Django performs pk selection as SELECT CURRVAL(pg_get_serial_sequence('tablename','id')). But pg_get_serial_sequence returns Null.
I don't know why django-rest-framework doesn't performselect ... returning id;`. I'll try to fix the database.