Dynamic field on Django model - django

I have my models.py
class Restaurant(models.Model):
name = models.CharField(max_length=100, blank=False)
opening_time = models.TimeField(blank=False)
closing_time = models.TimeField(blank=False)
def __str__(self):
return self.name
#property
def is_open(self):
return (
True
if self.opening_time <= datetime.now().time() < self.closing_time
else False
)
And, my serializer.py:
class RestaurantSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Restaurant
fields = ('pk', 'name', 'opening_time', 'closing_time')
I have the is_open property in the model that checks if the restaurant is open. How can I have the is_open property logic run and update this field, when the object is retrieved using a query on when the user makes a GET request to the serializer API.
Right now, it works when the object is created. Is there a retrieve method on the model where I can put this logic?
I was thinking about using Celery to check if it's open, but it sounds like an overkill solution. Of course, I would like this change to affect the serializer, so it should be something done on the model, I would think.

You can add is_open as a SerializerMethodField:
class RestaurantSerializer(serializers.HyperlinkedModelSerializer):
is_open = serializers.SerializerMethodField()
class Meta:
model = Restaurant
fields = ('pk', 'name', 'opening_time', 'closing_time', 'is_open')
def get_is_open(self, instance):
return instance.is_open

Related

Property field not appearing in django serializer

I have a property inside a Django model, I have to show it inside the serializer. I put the field inside the serializer, but it's not coming up in the response.
class Example(models.Model):
field_1 = models.ForeignKey(
Modelabc, on_delete=models.CASCADE, null=True, related_name="abc"
)
field_2 = models.ForeignKey(
Modelxyz,
on_delete=models.CASCADE,
null=True,
related_name="xyz",
)
name = models.CharField(max_length=25, blank=True)
#property
def fullname(self):
if self.name is not None:
return "%s%s%s" % (self.field_1.name, self.field_2.name, self.name)
return "%s%s" % (self.field_1.name, self.field_2.name)
Serializer is like this:
class ExampleSerializer(serializers.ModelSerializer):
fullname = serializers.ReadonlyField()
class Meta:
model = Example
fields = [
"id",
"fullname",]
When I call the get API for this, the fullname is not being displayed in the api response. What is the issue?
#property attributes are not included in Django Serializer fields as only Django model fields are shown. I normally use the following workaround for this.
Create a SerializerMethodField.
Return object.property from the method.
So, your Serializer class would be:
class ExampleSerializer(serializers.ModelSerializer):
fullname = serializers.SerializerMethodField()
class Meta:
model = OnlineClass
fields = [
"id",
"fullname",
]
def get_fullname(self, object):
return object.fullname
I think, in ExampleSerializer class, the model should be Example not OnlineClass and the fields should contain all the fields inside the model.

Use parameters in subfields with graphene-django without relay for pagination purpose

I'm using graphene with django and I'm struggling to do something that in my head should be very simple, but I don't find it documented anywhere in graphene docs or github nor did I see similar question here. The closest to it I found was:
https://www.howtographql.com/graphql-python/8-pagination/ but as you can see I'd have to declare the parameters in the parent resolver which I don't want to.
I have a query like this
getUser(id: $userIdTarget) {
id
username
trainings{
id
name
sessions{
id
name
}
}
}
}
I would like to implement a pagination in the sessions subfield. So this is what I would like:
getUser(id: $userIdTarget) {
id
username
trainings{
id
name
sessions(first:10){
id
name
}
}
}
}
and in the resolver I'd implement something like this:
def resolve_sessions(root, info, first=None, skip=None):
if skip:
return gql_optimizer.query(Session.objects.all().order_by('-id')[skip:], info)
elif first:
return gql_optimizer.query(Session.objects.all().order_by('-id')[:first], info)
else:
return gql_optimizer.query(Session.objects.all().order_by('-id'), info)
(gql_optimizer is just an optimization wrapper library I use)
However this doesn't work as the field sessions correspond to a list of a model Session that is a fk to Training according to my django models, so this is automatically resolved by graphene because these types are DjangoObjectType , so I'm not really sure how can one customize these resolvers (or if it's even possible).
I'll leave the relevant models and types below:
Session model
class Session(models.Model):
name = models.CharField(max_length=200, help_text='Session\'s name')
category = models.CharField(max_length=240, choices=SESSION_CATEGORIES, default="practice",
help_text='Session type. Can be of \'assessment\''
'or \'practice\'')
total_steps = models.IntegerField(default=1, help_text='Amount of steps for this session')
created_at = models.DateTimeField(editable=False, default=timezone.now, help_text='Time the session was created'
'(Optional - default=now)')
completed_at = models.DateTimeField(editable=False, null=True, blank=True, help_text='Time the session was finished'
'(Optional - default=null)')
is_complete = models.BooleanField(default=0)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="training_sessions", on_delete=models.DO_NOTHING)
training = models.ForeignKey("Training", related_name="sessions", on_delete=models.CASCADE)
def __str__(self):
return self.name
UserType
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
fields = "__all__"
#classmethod
def get_queryset(cls, queryset, info, **kwargs):
if info.variable_values.get('orgId') and info.variable_values.get('orgId') is not None:
return queryset.order_by('username')
return queryset
SessionType
class SessionType(DjangoObjectType):
class Meta:
model = Session
fields = "__all__"
convert_choices_to_enum = False
#classmethod
def get_queryset(cls, queryset, info, **kwargs):
if info.variable_values.get('userId') and info.variable_values.get('userId') is not None:
return queryset.filter(Q(user_id=info.variable_values.get('userId'))).order_by('-id')
return queryset
TrainingType
class TrainingType(gql_optimizer.OptimizedDjangoObjectType):
class Meta:
model = Training
fields = "__all__"
convert_choices_to_enum = False
It's possible to extend your types to add more fields that aren't in the Django model -- perhaps that is the technique you are looking for to inject more data into the query?
class TrainingType(gql_optimizer.OptimizedDjangoObjectType):
my_extra_field = graphene.Int() # for example
class Meta:
model = Training
fields = "__all__"
convert_choices_to_enum = False
You can also override the default resolvers that are created with DjangoObjectType.

How can I add my custom model function's value in my API

I'm trying to build my own poll app and the models have a lot of relation with each other and I need to count how many objects are in relation with some other objects so I need these custom function for it
Here's the model with the custom functions:
class Poll(models.Model):
title = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_available = models.BooleanField(default=True)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
#property
def total_votes(self):
votes = 0
for o in self.option_set.all():
votes = votes + o.how_much_vote
return votes
#property
def users_involved(self):
list = []
for o in self.option_set.all():
for v in o.vote:
list.append(v.user)
return list
#property
def users_involved(self):
users = []
for o in self.option_set.all():
for v in o.who_voted:
users.append(v)
return users
#property
def total_votes_each_option(self):
dct = {}
for o in self.option_set.all():
dct[o.title]= o.how_much_vote
return dct
My question is how do you include all of those custom functions total_votes, users_involved, etc to my api? because right now my api looks something like this:
{
"id": 1,
"title": "Which is the best frontend framework?",
"is_active": true,
"is_available": true,
"date_created": "2021-07-13T14:08:17.709054Z"
}
which is expected but I want to know how do I add those extra value to make it look like this
{
"id": 1,
"title": "Which is the best frontend framework?",
"is_active": true,
"is_available": true,
"date_created": "2021-07-13T14:08:17.709054Z",
"total_votes": "some number",
"users_involved": [...users]
}
Edit: here's my serializer class
class PollSerializer(serializers.ModelSerializer):
class Meta:
model = Poll
fields = '__all__'
Thank you very much
Simple way we can use SerializerMethodField
from rest_framework import serializers
class PollSerializer(serializers.ModelSerializer):
total_votes = serializers.SerializerMethodField()
users_involved = serializers.SerializerMethodField()
class Meta:
model = Poll
fields = (
'id',
'title',
'is_active',
'is_available',
'date_created',
'total_votes',
'users_involved',
)
def get_total_votes(self, poll):
return poll.total_votes
def get_users_involved(self, poll)
return poll.users_involved
Please note that SerializerMethodField is read_only, cannot for write.
Also make sure your custom model functions well for loop processing, using select_related or prefetch_related.
This one should be easy, you can add addition fields in Serializers by mentioning the field_name and its type before class Meta: something like this.
Model Upload
class Upload(models.Model):
.
.
total_files = models.IntegerField(default=0,null=True,blank=True)
.
.
def add(self):
return (self.total_files + 2)
Serializer FileSerializer
class FileSerializer(serializers.ModelSerializer):
#This is the line you need to add
extra_add = serializers.IntegerField(source = 'add')
class Meta:
model = Upload
fields= ['id','name','uploaded','total_files','created_on','updated_on','extra_add']
Then mention your extra_add field name in fields[..,..,'extra_add'].
You can change the type of field while mentioning the field thereserializer.<field_type>(source='<method_name>').
Result of the Above field i added:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username')
class PollSerializer(serializers.ModelSerializer):
total_votes = serializers.IntegerField(read_only=True)
users_involved = UserSerializer(read_only=True, many=True)
class Meta:
model = Poll
fields = '__all__'
#staticmethod
def setup_eager_loading(queryset):
return queryset.prefetch_related('option_set')
Also note that you defined users_involved twice in your Pool class.
You can use PoolSerializer.setup_eager_loading function in your view/viewset for better db query performance but it's not necessary (I do it in viewset.get_queryset method like self.queryset = self.serializer_class.setup_eager_loading(self.queryset).

Django update number of items on each save or delete

I am new to Django and still learning. I am looking to keep track of how many events I have under a test. My current model looks like
class Test(models.Model):
name = models.CharField(max_length=255)
description = models.CharField(max_length=255, blank=True)
num_of_events = models.IntegerField(default=0)
class Meta:
verbose_name = 'Test'
verbose_name_plural = 'Tests'
def __str__(self):
return self.name
class Event(models.Model):
name = models.CharField(max_length=255)
test = models.ForeignKey(Test,on_delete=models.CASCADE)
class Meta:
verbose_name = 'Event'
verbose_name_plural = 'Events'
def __str__(self):
return self.name
def save(self):
obj, created = Test.objects.update_or_create(name=self.test)
obj.num_of_events += 1
super().save()
def delete(self):
self.test.num_of_events -= 1
super().delete()
I thought I could just override the save() function but it does not update on the admin panel and still shows 0.
I am trying to figure out what I am doing wrong.
EDIT: admin.py
class TestAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'num_of_events')
fieldsets = [
(None, {'fields': ('name', 'description')})
]
class EventsAdmin(admin.ModelAdmin):
pass
class PropertyAdmin(admin.ModelAdmin):
list_display = ('name', 'property_type', 'expected_value')
admin.site.register(Test, TestAdmin)
admin.site.register(Event, EventsAdmin)
admin.site.register(Property, PropertyAdmin)
You forget to save the Test object. For example with:
class Event(models.Model):
# …
def save(self):
if self.test_id is not None:
obj = self.test
obj.num_of_events += 1
obj.save()
super().save()
def delete(self):
if self.test_id is not None:
self.test.num_of_events -= 1
self.test.save()
super().delete()
But regardless, storing the number of items is usually not a good idea. Say that you change the .test of a given Event, then you need to subtract from the old Test and add to the new Test. Furthermore ORM operations in bulk, like .update(..) circumvent .save() and signals, so it will be hard or impossible to keep this correct.
The point is that you do not need to store the number of Events. Indeed, you can simply obtain these with:
from django.db.models import Count
Test.objects.annotate(number_of_events=Count('event'))
The Test objects that arise from this queryset will have an extra attribute .number_of_events that contains the number of related Event ojbects.

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!