I have a class Object with a method state that takes a datetime parameter dt. How do I pass the datetime parameter from the URL to Object.state()?
The model:
class Object(models.Model):
def state(self, dt=datetime.datetime.now()) -> dict:
...stuff...
return {'dt': dt, 'other_stuff': stuff}
The view:
class ObjectDetail(generics.RetrieveAPIView):
queryset = models.Object.objects.all()
serializer_class = serializers.ObjectSerializer
def get_serializer_context(self):
return {'dt': self.request.query_params.get('dt', datetime.datetime.now())}
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
And the serializer classes:
class ObjectSerializer(serializers.HyperlinkedModelSerializer):
state = StateSerializer(read_only=True, context={'dt': self.context['dt']})
class Meta:
model = models.Object
fields = ('state')
class StateSerializer(serializers.Serializer):
dt = serializers.DateTimeField()
... other stuff...
As you can see I am trying to pass dt as extra context in the line state = StateSerializer(read_only=True, context={'dt': dt}) having set the context earlier in the view. The problem here is that when ObjectSerializer is initialized dt is not accessible via self.context['dt'] as self is not defined.
The solution is to make state a serializers.SerializerMethodField() and then define a get_state method. The new ObjectSeializer looks like this:
class ObjectSerializer(serializers.HyperlinkedModelSerializer):
state = serializers.SerializerMethodField()
class Meta:
model = models.Object
fields = ('state')
def get_state(self, obj):
state = obj.state(self.context['dt'])
state_serializer = StateSerializer(state)
return state_serializer.data
Related
I have a model like this:
class Camper(models.Model):
location = models.PointField()
name = models.CharField(max_length=255)
and a viewset like this:
class CamperViewSet(viewsets.ModelViewSet):
...
def retrieve(self, request, *args, **kwargs):
"""Retrieve a Camper instance."""
show_weather = request.query_params.get('showWeather', False)
instance = self.get_object()
if show_weather:
lat = instance.location.y
lon = instance.location.x
instance.weather = getWeatherFromLatLon(lat, lon)
serializer = self.get_serializer(instance)
return Response(serializer.data)
So when I request /api/campers/8?showWeather=true I make another request in my view to get the weather from the current position.
How do I add it to my serializer ? It's an optional field so I need to manage this and it's only used in /campers/id so it will not be used in list/create/put/etc
My serializer looks like this:
class CamperSerializer(serializers.ModelSerializer):
camper_id = serializers.IntegerField(source='id')
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location')
you can add custom serializer for retrive only todo it. I called CamperRetriveSerializer.
Inside CamperRetriveSerializer, you can use SerializerMethodField for define field not have in database.
And you want check param show_weather from request, best is pass value of it to context and get it in serializer.
Like this:
class CamperRetriveSerializer(serializers.ModelSerializer):
weather = serializers.SerializerMethodField()
camper_id = serializers.IntegerField(source='id')
def get_weather(self, obj):
show_weather = self.context.get('show_weather')
if show_weather:
lat = obj.location.y
lon = obj.location.x
return getWeatherFromLatLon(lat, lon)
# define default value if not show_weather in this
return ''
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location', 'weather')
class CamperViewSet(viewsets.ModelViewSet):
...
def retrieve(self, request, *args, **kwargs):
"""Retrieve a Camper instance."""
instance = self.get_object()
show_weather = self.request.query_params.get('showWeather', False)
context = {
'show_weather': show_weather
}
serializer = CamperRetriveSerializer(instance, context=context)
return Response(serializer.data)
You can use two different serializers for this.
class CamperViewSet(viewsets.ModelViewSet):
serializer_class = CamperSerializer
def get_serializer_class(self):
serializer_class = self.serialzier_class
if self.request.method == 'GET':
serializer_class = CamperSerializerGet
return serializer_class
#Serializer for GET request
class CamperSerializerGet(serializers.ModelSerializer):
weather = serialziers.SerializerMethodField()
camper_id = serializers.IntegerField(source='id')
def get_weather(self, obj):
return obj.weather
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location', 'weather')
#For other requests call this
class CamperSerializer(serializers.ModelSerializer):
camper_id = serializers.IntegerField(source='id')
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location')
I need help with REST Framework. I have to do my test for internship position and I have two models with circular import 'Pokemon' model and 'Team' mode. In serializer of 'Team' I have this code
class TeamDetailsSerializer(ModelSerializer):
"""Serializer for details of Team instances"""
pokemon_1 = SerializerMethodField()
pokemon_2 = SerializerMethodField()
pokemon_3 = SerializerMethodField()
pokemon_4 = SerializerMethodField()
pokemon_5 = SerializerMethodField()
trainer = UserSerializer()
class Meta:
model = Team
fields = (
"trainer",
"name",
"pokemon_1",
"pokemon_2",
"pokemon_3",
"pokemon_4",
"pokemon_5",
)
read_only_fields = ("id",)
# Methods to relate each Pokemon object
def get_pokemon_1(self, obj):
pokemon_1 = obj.pokemon_1
if not pokemon_1:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_1)
return serializer.data
def get_pokemon_2(self, obj):
pokemon_2 = obj.pokemon_2
if not pokemon_2:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_2)
return serializer.data
def get_pokemon_3(self, obj):
pokemon_3 = obj.pokemon_3
if not pokemon_3:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_3)
return serializer.data
def get_pokemon_4(self, obj):
pokemon_4 = obj.pokemon_4
if not pokemon_4:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_4)
return serializer.data
def get_pokemon_5(self, obj):
pokemon_5 = obj.pokemon_5
if not pokemon_5:
return None
serializer = pokemon.serializers.PokemonDetailsSerializer(pokemon_5)
return serializer.data
and problem that I get this kind of schema
name* [...]
trainer* User{...}
pokemon_1 integer
nullable: true
pokemon_2 [...]
pokemon_3 [...]
pokemon_4 [...]
pokemon_5 [...]
but I would like to get object type, what kind of solutions I can apply?
Thank a lot
I'm wanting to know how you would pass nested data to a ModelSerializer if the child of the nested data is not a model on its own.
The data that I'm working with looks like this:
{
'leadId': 12345,
'updateTime': 1651250096821,
'changeInfo': {
'oldstage': 'New Leads',
'newstage': 'Attempting Contact'
}
}
From previous experience, I know that if I was only working with the leadId and the updateTime, my serializer would look like this:
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
class Meta:
model = Log
fields = ["leadId", "updateTime"]
Which would then make it possible to do this:
data = {
'leadId': 12345,
'updateTime': 1651250096821
}
serializer = LogSerializer(data=data)
serializer.is_valid()
serializer.save()
If I'm not wanting to turn changeInfo into its own model, is it possible to map the fields to the nested data? Something that might look like this (but this obviously doesn't work):
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
oldstage = serializers.IntegerField(source="oldstage")
newstage = serializers.IntegerField(source="newstage")
class Meta:
model = Log
fields = ["leadId", "updateTime", "oldstage", "newstage]
You can use a custom serializer for your changeInfo field (you don't need to create a model for that):
class ChangeInfoSerializer(serializers.Serializer):
oldstage = serializers.CharField(max_length=100, source="old_stage") # Set max_length to a value that suits your needs
newstage = serializers.CharField(max_length=100, source="new_stage")
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class LogSerializer(serializers.ModelSerializer):
leadId = serializers.IntegerField(source="lead_id")
updateTime = serializers.IntegerField(source="update_time")
changeInfo = ChangeInfoSerializer(required=False) # Change to required=True if you want this field to be mandatory
class Meta:
model = Log
fields = ["leadId", "updateTime", "changeInfo"]
def create(self, validated_data):
change_info = validated_data.pop('changeInfo')
for key, value in change_info.items():
if key == "old_stage":
validated_data['old_stage'] = value
elif key == "new_stage":
validated_data['new_stage'] = value
log = Log.objects.create(**validated_data)
return log
def update(self, instance, validated_data):
change_info = validated_data.pop('changeInfo')
instance.lead_id = validated_data.get('leadId', instance.lead_id)
instance.update_time = validated_data.get('updateTime', instance.update_time)
# Here you can use change_info['oldstage'] and change_info['newstage'] if 'changeInfo' is sent (otherwise you'll get a KeyError)
instance.save()
return instance
As mentioned in the comments, a SerializerMethodfield is a good way to go:
serializers.py
class LogSerializer(...):
...
changeInfo = serializers.SerializerMethodField()
def get_changeInfo(self, obj): return {
"leadId" : obj.lead_id,
"updateTime": obj.update_time
}
class Meta:
fields = ["changeInfo", ...]
...
Im trying to create a Question Model Serializer. The Question model has several other models that inherit from it like RangeQuestion, YesNoQuestion, etc..
This is my view:
class QuestionViewSet(generics.ListCreateAPIView):
"""
A viewset that provides `create` actions.
"""
queryset = Question.objects.all()
def get_serializer_class(self):
if 'type' in self.request.data:
if self.request.data['type'] == 'range':
return serializers.RangeQuestionSerializer
elif self.request.data['type'] == 'open':
return serializers.QuestionSerializer
elif self.request.data['type'] == 'multiple_choice':
return serializers.MultipleChoiceQuestionSerializer
elif self.request.data['type'] == 'yes_no':
return serializers.YesNoAnswerSerializer
elif self.request.data['type'] == 'object':
return serializers.ObjectQuestionSerializer
else:
return serializers.QuestionSerializer
def create(self, request):
if 'type' in self.request.data:
serializer = self.get_serializer(data=self.request.data)
print serializer
serializer.is_valid(raise_exception=True)
serializer.save()
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
else:
return Response(status=HTTP_400_BAD_REQUEST, data="Must specify a question type")
And this are my serializers:
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
def get_type(self):
return settings.QUESTION_TYPES['open']
class Meta:
model = Question
exclude = []
class YesNoQuestionSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
def get_type(self):
return settings.QUESTION_TYPES['yes_no']
class Meta:
model = YesNoQuestion
exclude = []
class RangeQuestionSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
def get_type(self):
return settings.QUESTION_TYPES['range']
class Meta:
model = RangeQuestion
exclude = []
class QuestionChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = QuestionChoice
exclude = []
class MultipleChoiceQuestionSerializer(serializers.ModelSerializer):
choices = QuestionChoiceSerializer()
type = serializers.SerializerMethodField()
def get_type(self):
return settings.QUESTION_TYPES['multiple_choice']
class Meta:
model = MultipleChoiceQuestion
exclude = []
When I acces the url of the list action I get the error:
get_type() takes exactly 1 argument (2 given)
Can anyone tell me how to make the list view show all the question serialized according to the subclass?
Your def get_type(self) method should be defined def get_type(self, instance) because rest framework serializers will pass in the instance as the first argument.
models.py
class MEMBER(models.Model):
LB_NAME = models.CharField(max_length=40,null=True)
LB_FIRST_NAME = models.CharField(max_length=40,null=True)
DT_DAT_BIRTH = models.DateField(,null=True)
CO_ID = models.CharField(max_length=6,null=True)
class MEMBER_DECLARE_RECEPT(models.Model):
SY_MEMBER=models.ForeignKey(MEMBER,verbose_name='Name member ',null=True,related_name='Member')
DT_DECLARATION_RECEPT=models.DateField(('Date received',null=True)
serializers.py
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = MEMBER
fields=('id','LB_NAME','LB_FIRST_NAME','CO_ID')
class MemberDeclareSerializer(serializers.ModelSerializer):
class Meta:
model = MEMBER_DECLARE_RECEPT
fields=('id','SY_MEMBRE','DT_DECLARATION_RECEPT','FL_RECEPT_RECEPT_DEFAUT')
views.py
class MemberDeclareDetail(generics.ListCreateAPIView):
queryset=MEMBER_DECLARE_RECEPT.objects.all()
serializer_class =MemberDeclareSerializer
def get_object(self,pk):
try:
return self.queryset.get(pk=pk)
except MEMBER_DECLARE_RECEPT.DoesNotExist:
raise Http404
def get(self, request, pk,format=None):
entries = self.get_object(pk)
serializer = MemberDeclareSerializer(entries)
return Response(serializer.data)
result :
{
"id": 1,
"SY_MEMBER": 1,
"DT_DECLARATION_RECEPT": "2014-12-31",
"FL_RECEPT_RECEPT_DEFAUT": "1"
}
but not the related table data !
What did I miss here ?
You need Serializer relations
class MemberDeclareSerializer(serializers.ModelSerializer):
...
members = serializers.StringRelatedField(many=True)
...
Also, try to follow python naming conventions
class MemberDeclareRecept(models.Model):
member=models.ForeignKey(Member,verbose_name='Name member ',null=True,related_name='members')