Django db field not displaying modified slug field value after serializing - django

I am trying get the user names instead of user id, in the created_by field of the Comment class. My model is below:
class Comment(models.Model):
thread_id = models.ForeignKey(Thread, on_delete=models.CASCADE)
content = models.TextField()
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
likes = models.IntegerField(default=0)
Have used slugfield in the serializer.
class CommentSerializer(serializers.ModelSerializer):
created_by = serializers.SlugRelatedField(
slug_field='email',
queryset=User.objects.all())
class Meta:
model = Comment
fields = ("id", "thread_id", "content", "created_by", "created_at", "likes")
Where as in the api below, the field created_by still has user id and not email.
#action(detail=True, methods=["GET"])
def list_comments(self, request, pk):
comments = Comment.objects.filter(
thread_id = pk
)
data = json.loads(serialize('json', comments))
print("**************data**************")
print(data)
return Response(data, status.HTTP_200_OK)
The data printed looks like this :
[{'model': 'blog.comment', 'pk': 20, 'fields': {'thread_id': 19, 'content': 'hi', 'created_by': 2, 'created_at': '2021-07-14T03:34:11.333Z', 'likes': 0}}]
Is this because of serialize? How do I correctly get the value of the created_by as user email and not id ?

Use to_representation() method on the serializer
def to_representation(self, instance):
rep = super(CommentSerializer, self).to_representation(instance)
rep['created_by'] = instance.created_by.email
return rep

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',)

How to use a ManyToMany field in JsonResponse?

I have this model in Django:
class Post(models.Model):
poster = models.ForeignKey('User', on_delete=models.CASCADE, related_name='posts')
body = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
likers = models.ManyToManyField('User', blank=True, null=True, related_name='liked_posts')
likes = models.IntegerField(default=0)
def serialize(self):
return {
'id': self.pk,
'poster': self.poster.username,
'body': self.body,
'timestamp': self.timestamp.strftime('%b %d %Y, %I:%M %p'),
'likes': self.likes
}
It works but when I try to add likers to it, I get an error which says I can't use manytomany fields. How can I do such thing?
I fetch it in JavaScript like this:
fetch('/posts')
.then(response => response.json())
.then(data => {
console.log(data);
});
Using this view:
def posts_view(request):
posts = Post.objects.all()
posts = posts.order_by('-timestamp').all()
return JsonResponse([post.serialize() for post in posts], safe=False)
You don't have to serialize your models manually, i.e. defining serialize(self) method for each model. Try to use django-rest-framework, everything is already made for you.
Define serializers for your models in serializers.py.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class PostSerializer(serializers.ModelSerializer):
poster = UserSerializer()
likers = UserSerializer(many=True, allow_null=True, default=None)
# if you want short info about likers (list of ids), use snippet below
# likers = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), many=True)
class Meta:
model = Post
fields = "__all__"
And then in your view:
def posts_view(request):
posts = Post.objects.all().order_by('-timestamp')
data = PostSerializer(posts, many=True).data
return JsonResponse(data)

validated_data is returning empty OrderedDict for nested fields with Django Rest Framework

Can someone help me figure out why some fields are not parsed correctly using nested serializers with Django and Django-rest-framework?
I've researched the issue on SO, and the only cause for this happening I've found is that the request is sent as Form-data and not Json, but I've checked that response.content_type equals application/json - so this shouldn't be the issue here.
This is what my validated_data looks like (note that 3 of the fields only contain an empty OrderedDict):
{'author': OrderedDict(),
'disclosed_at': datetime.datetime(2021, 10, 19, 12, 0, tzinfo=<DstTzInfo 'Europe/Stockholm' CEST+2:00:00 DST>),
'event_date': datetime.date(2021, 10, 20),
'event_type': OrderedDict(),
'subject_companies': [OrderedDict()]}
This is what request.data looks like (where you can see that all fields are there, and having the fields that each serializer have specified, provided below this paragraph):
{'event_type': {'pk': 1}, 'author': {'pk': 1}, 'event_date': '2021-10-20', 'disclosed_at': '2021-10-19 12:00:00', 'subject_companies': [{'pk': 1}]}
This is where I'm sending the request from (test.py):
def test_authenticated_creating_event_for_own_organisation(self):
view = NewsEventList.as_view()
url = reverse('news_event_list', kwargs={'pk': self.organisation.pk})
request = self.client.post(url, data=json.dumps(self.payload_event), content_type='application/json')
force_authenticate(request, user=self.user)
response = view(request, pk=self.organisation.pk)
json_data = json.dumps(response.data, indent=4)
json_ = (json.loads(json_data))
self.assertEqual(response.status_code, 201, 'Should return 201 - Created')
return response
Models
class NewsEvent(TimeStampedModel):
event_type = models.ForeignKey('publication.eventtype', on_delete=models.SET_NULL, related_name='events_type', null=True)
author = models.ForeignKey('core.organisation', on_delete=models.CASCADE, related_name='events_author', null=True)
subject_companies = models.ManyToManyField('core.organisation', related_name='events_companies')
legacy_id = models.CharField(max_length=128, blank=True)
event_date = models.DateField(null=True, blank=True)
event_time = models.TimeField(null=True, blank=True)
disclosed_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return '{}: {}'.format(self.author, self.event_type)
class Meta:
ordering = ('pk',)
class EventType(models.Model):
language = models.ForeignKey(
'core.Language',
default=get_default_language,
null=True,
on_delete=models.SET_NULL,
related_name='event_contents'
)
name = models.CharField(max_length=64, default=None)
key = models.CharField(max_length=64, default=None)
def __str__(self):
return '{}'.format(self.name)
class Meta:
ordering = ('pk',)
The view
class NewsEventList(generics.ListCreateAPIView):
permission_classes = (IsAuthenticatedAndOfSameOrganisationEvents,)
serializer_class = NewsEventSerializer
def get_queryset(self):
org_pk = self.kwargs.get('pk', None)
try:
org_obj = Organisation.objects.get(pk=org_pk)
except Organisation.DoesNotExist:
return ValidationError('Organisation does not exist')
news_events = NewsEvent.objects.filter(author=org_obj)
return news_events
Serializers
class OrganisationNameSerializer(ModelSerializer):
class Meta:
model = Organisation
fields = ['pk']
class EventTypeSerializer(ModelSerializer):
class Meta:
model = EventType
fields = ['pk']
class HeadlineSerializer(ModelSerializer):
class Meta:
model = EventHeadline
fields = ['news_event', 'language', 'headline']
class NewsEventSerializer(ModelSerializer):
event_type = EventTypeSerializer()
author = OrganisationNameSerializer()
subject_companies = OrganisationNameSerializer(many=True)
class Meta:
model = NewsEvent
fields = ['pk', 'event_type', 'author', 'event_date', 'event_time', 'disclosed_at', 'subject_companies', 'created_at', 'updated_at']
def create(self, validated_data):
# Get PK for organisation from URL
org_pk = self.context.get('request').parser_context.get('kwargs', {}).get('pk', {})
org_obj = Organisation.objects.get(pk=org_pk)
print(self.context.get('request').data)
pprint(validated_data)
Also for reference I printed the serializer.data for an already existing instance of a NewsEvent:
news_event_test = NewsEvent.objects.all()[0]
serializer = NewsEventSerializer(news_event_test)
print(serializer.data)
{'pk': 1, 'event_type': OrderedDict([('pk', 1)]), 'author': OrderedDict([('pk', 1)]), 'event_date': None, 'event_time': None, 'disclosed_at': None, 'subject_companies': [OrderedDict([('pk', 1)])], 'created_at': '2021-10-25T09:32:41.562428+02:00', 'updated_at': '2021-10-25T09:32:41.562487+02:00'}
I've also tried doing a "pop" of each of the fields from the validated_object, but only the ones that don't is an empty OrderedDict work, such as disclosed_at, but if I try to do:
event_type = validated_data.pop('event_type')
I get:
KeyError: "Got KeyError when attempting to get a value for field `event_type` on serializer `NewsEventSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'event_type'."
event_type = models.ForeignKey('publication.EventType', on_delete=models.SET_NULL, related_name='events_type', null=True)
author = models.ForeignKey('core.Organisation', on_delete=models.CASCADE, related_name='events_author', null=True)
subject_companies = models.ManyToManyField('core.Organisation', related_name='events_companies')
Replace those lines and delete previous migrations and create new ones and try.
The reason was that I was doing a write operation (POST request, create action in Django/DRF terms) and the pk field on the serializers is read_only by default.
To validate that data during write ops, you'll need to set the field explicitly and set read_only=False. For example:
class RedditTestSerializer(serializers.ModelSerializer):
class Meta:
model = models.Task
fields = ("pk",)
This solved the problem, but you may want to consider using
PrimaryKeyRelatedField if your use case is only setting the primary key
of a related object.

Django: Cannot update foreign key values in a model

When I create this model, the values are nulls.
class TestRequest(models.Model):
class Meta:
verbose_name_plural = "TestRequest"
title = models.CharField(max_length=256, null=True, blank=True)
testConfiguration = models.ForeignKey(
TestConfiguration, on_delete=models.PROTECT, null=True, blank=True)
testDescription = models.ForeignKey(
TestDescription, on_delete=models.PROTECT, null=True, blank=True)
The serializer:
class TestRequestSerializer(serializers.ModelSerializer):
class Meta:
model = TestRequest
fields = [
'id',
'title',
'testConfiguration',
'testDescription',
]
depth = 2
The view:
#api_view(['PUT'])
def TestRequestUpdate(request, pk):
testRequest = TestRequest.objects.get(id=pk)
serializer = TestRequestSerializer(instance=testRequest, data=request.data)
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)
And when I want to update them later from the front-end with this state:
id: 98
title: "title"
testConfiguration: 31
testDescription: 32
I get this response:
{
"id": 98,
"title": "title",
"testConfiguration": null,
"testDescription": null
}
Why can't I update it?
EDIT: I added my solution as an answer.
You can modify your views with the following code:
testRequest = TestRequest.objects.get(id=pk)
import json
data = json.loads(json.dumps(request.data))
if data.get('testConfiguration', None) is None and testRequest.testConfiguration:
data.update({'testConfiguration': testRequest.testConfiguration.id})
if data.get('testDescription', None) is None and testRequest.testDescription:
data.update({'testDescription': testRequest.testDescription.id})
serializer = TestRequestSerializer(instance=testRequest, data=data)
My solution was that i removed the depth value from the serializer for the POST requests and added a separate serializer with the depth value for the GET requests.

Add extra value before save serializer

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