Polymorphic models serializer - django

I'm using a Polymorphic model for setting up notifications:
My models:
class Notification(PolymorphicModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="creatednotifications")
created_on = models.DateTimeField(default=timezone.now)
created_for = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="receivednotifications")
read = models.DateTimeField(default=None, null=True, blank=True)
message = models.CharField(default=None, blank=True, null=True, max_length=800)
#property
def total(self):
return self.objects.filter(created_for=self.request.user).count()
#property
def unread(self):
return self.objects.filter(created_for=self.request.user,read=None).count()
#property
def read(self):
return self.objects.filter(created_for=self.request.user).exclude(read=None).count()
class WorkflowNotification(Notification):
# permission_transition = models.ForeignKey(WorkflowStatePermissionTransition, on_delete=models.CASCADE)
action = models.ForeignKey(UserAction, on_delete=models.CASCADE)
Currently i have just one model WorkFlowNotification inheriting from the Polymorphic model,but many would be there in the future.
Im trying to get the count(total) of notifications for the logged in user in the API ..total is given as property field to help in the same
my serializer:
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.ReadOnlyField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['id', 'total','read', 'unread']
In the view:
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
queryset = Notification.objects.all()
When i try to run the server it shows:
Got AttributeError when attempting to get a value for field `total` on serializer `NotificationSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `WorkflowNotification` instance.
Original exception text was: Manager isn't accessible via WorkflowNotification instances.

Since you need the 'meta data' only, what is the use of making a model serializer? Or any serializer, for that matter? Serializers will give you serialized instances of the objects of your model. So if you have multiple objects, you will get multiple serialized objects in response.
Just make your view a normal APIView. Since there is no necessity of serializing anything.
class NotificationsMeta(APIView):
def get(self, request, format=None):
qs = Notification.objects.filter(created_for=self.request.user)
response = {
'total': qs.count(),
'read': qs.filter(read=None).count(),
'unread': qs.exclude(read=None).count()
}
return Response(response)
Now remove those property functions from your model.
I didn't test your queries, just copied them from your model. You will need to check if they are working properly. Hope this helps.

I am not sure about how calling a model property who is responsible for querying in model can give appropriate data from serializer. Unfortunately i do have knowledge gap about that. I am thinking about an alternative solution. I hope following should work.
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.serializers.SerializerMethodField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['read', 'unread']
def get_total(self, obj):
user = self.context['request'].user
return Notification.objects.filter(created_for=user).count()
If this work then you can able to do similar kind of thing for read and unread too.
In order to get notification for current_user we need to overwrite get_queryset from view.
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
return Notification.objects.filter(created_for=self.request.user)

Related

display only some fields in get api response django serializer

I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples

Error after overriding create method in serializer

I'm Overriding create method of serializer in order to manipulate validated_data and create object in a model, Although it works, in the end I get below error, i am not able to figure out why after lot of research.
AttributeError: Got AttributeError when attempting to get a value for field `shift_time` on serializer `PunchRawDataAndroidSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `PunchRawData` instance.
Original exception text was: 'PunchRawData' object has no attribute 'shift_time'.
class PunchRawDataAndroidSerializer(serializers.ModelSerializer):
employee_id = serializers.CharField()
shift_id = serializers.CharField()
work_location_id = serializers.CharField()
shift_time = serializers.TimeField()
class Meta:
model = PunchRawData
fields = ['employee_id', 'shift_id','work_location_id', 'punch_type', 'actual_clock_datetime',
'emp_photo', 'created_at', 'updated_at','shift_time']
def create(self, validated_data):
validated_data.pop('shift_time')
request_data = self.context.get('request')
user = request_data.user
validated_data['user'] = user
data = validated_data
return PunchRawData.objects.create(**data)
class PunchRawDataAndroidViewSet(viewsets.ModelViewSet):
serializer_class = PunchRawDataAndroidSerializer
parser_classes = (MultiPartParser, FileUploadParser)
edit:
class PunchRawData(models.Model):
PUNCH_TYPES = [("in", "Punch IN"), ("out", "Punch Out")]
employee = models.ForeignKey(Employee, related_name="punch_employee", on_delete=models.CASCADE)
shift = models.ForeignKey(WorkShift, on_delete=models.CASCADE)
work_location = models.ForeignKey(HRMLocation, blank=True, on_delete=models.CASCADE,
null=True, related_name="punch_work_location")
punch_type = models.CharField(max_length=255, null=True, blank=True, choices=PUNCH_TYPES)
user = models.ForeignKey("useraccounts.User", on_delete=models.CASCADE)
actual_clock_datetime = models.DateTimeField(null=True, blank=True)
emp_photo = models.ImageField(upload_to="selfies/%Y/%m/%d/%I/%M/%S/")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
strl = "{emp_id} [{shift_id}]".format(emp_id=self.employee.emp_id,
shift_id=self.shift.shift_id)
return strl
class Meta:
verbose_name = "Punch Raw Data"
verbose_name_plural = "Punch Raw Data"
I get shift_time from frontend and it is not from model, hence i'm poping it out from validated_data in create method. is error related to modelviewset?
Your model doesn't have the shift_time attribute. So if you try to save it, you will end with
PunchRawData() got an unexpected keyword argument 'shift_time'
At the other hand you are getting AttributeError, because serializers.to_representation() tries to get a non-existing attribute when showing your freshly saved object.
If this should be a read-only attribute, you may do the following:
shift_time = serializers.TimeField(read_only=True)
and than remove the
validated_data.pop('shift_time')
from PunchRawDataAndroidSerializer.create(). You don't need this any more, because it is never submitted from your client.
If you need the opposite – your client should provide you that field, but you don't want it saved in your model, than the only thing, you should do, is:
shift_time = serializers.TimeField(write_only=True)
And if you need it to be bidirectional, than you should add it to your model.
Hope this helps.
Adding to #wankata's answer we can override __init__ method to have write_only field for only create method.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['view'].action == 'create':
self.fields['shift_time'].write_only = True
Generic viewsets of django-rest-framework return the serialized representation of the model in response, so it's try to serialize the model including the shift_time key.
To avoid this problem you can specify the shift_time field as write_only. documentation
modify the Meta class on your model
class Meta:
model = PunchRawData
fields = ['employee_id', 'shift_id','work_location_id', 'punch_type', 'actual_clock_datetime',
'emp_photo', 'created_at', 'updated_at','shift_time']
extra_kwargs = {'shift_time': {'write_only': True}}

Django - Get objects from the table which do not have a Foreignkey in another table

I am Django rest framework to return the list of objects who do not have a foreign key in another table. what queryset should I write to get those objects.
models.py
class Event(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=100,default='')
description = models.TextField(blank=True,default='', max_length=1000)
link = models.URLField(null=True)
image = models.ImageField(null=True, blank=True)
organizer = models.CharField(max_length=100, default='')
timings = models.DateTimeField(default=None)
cost = models.IntegerField(default=1,null=True,blank=True)
def __str__(self):
return self.title
class Featured(models.Model):
event = models.ForeignKey(Event, null=True ,on_delete=models.PROTECT, related_name="event")
def __str__(self):
return self.event.title
class Meta:
verbose_name_plural = 'Featured'
views.py
class Upcoming2EventsViewSet(mixins.RetrieveModelMixin,mixins.ListModelMixin,viewsets.GenericViewSet):
serializer_class = Upcoming2Events
def get_queryset(self):
featured_events = Featured.objects.all().values_list('id')
return Event.objects.filter(id__in=featured_events)
# return Event.objects.exclude(id__in=featured_events.event.id)
# # return Event.objects.exclude(id__in = [featured_events.id])
serializers.py
class Upcoming2Events(serializers.ModelSerializer):
id = serializers.CharField(source='event.id')
title = serializers.CharField(source='event.title')
timings = serializers.DateTimeField(source='event.timings')
organizer = serializers.CharField(source='event.organizer')
class Meta:
model = Featured
fields = ['id','title','organizer','timings']
Error
Got AttributeError when attempting to get a value for field `id` on serializer `Upcoming2Events`.
The serializer field might be named incorrectly and not match any attribute or key on the `Event` instance.
Original exception text was: 'RelatedManager' object has no attribute 'id'.
Can you tell me what queryset should I write to get the only objects which are not present in the table Featured?
Also, what should I do to get only the upcoming 2 events from the Event table which are not present in the Featured table?
Note I am not supposed to use any flag value, can you provide some other solutions?
Based on the Informations you wrote here, i would suggest using a flag to determine a featured event. A second Model is useful if you want to provide more Informations on this specific for a featured event
like this:
class Event(models.Model):
id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=100,default='')
description = models.TextField(blank=True,default='', max_length=1000)
link = models.URLField(null=True)
image = models.ImageField(null=True, blank=True)
organizer = models.CharField(max_length=100, default='')
timings = models.DateTimeField(default=None)
cost = models.IntegerField(default=1,null=True,blank=True)
featured = models.BooleanField(default=False)
so you can directly use querysets to get what you want:
Event.objects.exclude(featured=True)
Event.objects.exclude(featured=True).order_by('-timings')[:2]
I would use ModelViewsets directly, hence you will use your model here.
views and serializers would look like this:
views.py
class Upcoming2EventsViewSet(viewesets.ReadyOnlyModelViewSet):
serializer_class = EventSerializer
queryset = Event.objects.exclude(featured=True).order_by('-timings')[:2]
serializers.py
class EventSerializer(serializers.ModelSerilizer):
class Meta:
model = Event
fields = ['id', 'title', 'organizer', 'timings']
As improvement i would provide filters instead of setting up different ViewSets for just filtering querysets.

method to serialize comment(model) data using parent field to get comment's reply in Django

I have made FoodComment model like this in Django using Foreignkey. It uses username to specify user who owns the comment, and food is for menu which to collect comments of users. And parent points other FoodComment object if it is comment's reply.
class FoodComment(models.Model):
username = models.ForeignKey(Account, on_delete=models.CASCADE)
food = models.ForeignKey(Food, on_delete=models.CASCADE)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
body = models.CharField(max_length=30)
star = models.IntegerField(default=5)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.food.menuname + ':' + self.username.username
I am having problem with serializer getting jsoned comments data with hierarchy of comments and that of replies.
class FoodCommentList(APIView):
serializer_class = CommentSerializer
def get(self, request, foodname):
food = Food.objects.get(menuname=foodname)
data = FoodComment.objects.select_related('food').filter(food=food, parent=None)
serialized = CommentSerializer(data, many=True)
return Response(serialized.data, HTTP_200_OK)
If i use views.py's FoodCommentList function as above, REST returns serialized.data as below.
[{"id":1,"body":"delicious!","star":5,"created":"2020-03-26T20:56:49.111307","username":2,"food":19,"parent":null}]
And CommentSerializer used above looks like this.
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = FoodComment
fields = '__all__'
What I am trying to do is not just serialize 'parent=None' data but with parent field, I want to get json data with comment's reply like this using serializer.
{
id:1,
body:"delicious!",
...
childs: {
id:2,
body:"ty!",
...
}
]
I have tried code by other users but couldn't solve it. Is it possible to call CommentSerializer once and get comment's reply in recursive way using serializer?
Thank You!

Nested objects save in DRF

Models:
class Owner(models.Model):
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class SomeThing(models.Model):
own_id = models.IntegerField(unique=True)
description = models.CharField(max_length=255, blank=True)
owner = models.ForeignKey(Owner, blank=True, null=True)
def __unicode__(self):
return self.description
Serializers:
class OwnerNameField(serializers.RelatedField):
def to_internal_value(self, data):
pass
def to_representation(self, value):
return value.name
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, (QuerySet, Manager)):
queryset = queryset.all()
lista = [Owner(name="------------")]
lista.extend(queryset)
return lista
class OwnerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Owner
fields = ('name', 'id')
class ThingSerializer(serializers.ModelSerializer):
owner = OwnerNameField(queryset=Owner.objects.all())
class Meta:
model = SomeThing
fields = ('own_id', 'description', 'owner')
Basically it works as intended. But when i add some fields to Owner class i would like to see all these fields in output of ThingSerializer (and be able to parse them - string doesn't suit here). I could change field owner to owner = OwnerSerializer() which gives me what i need. But when i want to add SomeThing object (tested in API browser) i also need add new Owner object - and i don't want it, i want use existing Owner object. How can i achieve it?
Finally i got it. This question describes exactly my problem and provided answers work as a charm!