I'm using Django Rest Framework to write my API. I want to write different values than the the id (specifically the uuid) into my serializer.
Let me give you the basic setup first. I have a model called House which has amongst other values a pk and a uuid. And I have a second model called Citizen which also has a pk and a uuid. House and Citizen have a ManyToMany relationship with each other.
I would like to have a serializer that just gives back an array of it's citizen.
Here is the not working pseudo code that I tried (and failed):
class HouseSerializer(serializers.ModelSerializer):
address = AddressSerializer()
citizen = serializers.UUIDField(source="citizen.uuid")
class Meta:
model = Table
fields = [
"uuid",
"address",
"citizen",
...
]
This serializer throws the error:
AttributeError: Got AttributeError when attempting to get a value for field `citizen` on serializer `HouseListSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `House` instance.
Original exception text was: 'ManyRelatedManager' object has no attribute 'uuid'.
But on my model for the House I have explicitly citizen = models.ManyToManyField(Citizen).
If I just don't specify any serializer and just leave citizen in the fields array, I just get an array of the PKs which I can't use.
How can I get an array of the UUIDs here?
First you'll need a serializer class for your Citizen model.
class CitizenSerializer(serializers.ModelSerializer):
uuid = serializers.UUIDField(read_only=True)
class Meta:
model = Citizen
fields = ('uuid', )
We will then add the CitizenSerializer to your HouseSerializer. Note that we need the many=True argument for ManyToManyField relation.
class HouseSerializer(serializers.ModelSerializer):
address = AddressSerializer()
citizen = CitizenSerializer(read_only=True, many=True)
class Meta:
...
You can read more about this here
You can use SlugRelatedField like this:
class HouseSerializer(serializers.ModelSerializer):
address = AddressSerializer()
citizen = serializers.SlugRelatedField( many=True,
read_only=True,
slug_field='uuid')
read the drf doc for more information.
Related
I have three models in a django DRF project:
class ModelA(models.Model):
name = ....
other fields...
class ModelB(models.Model):
name = ....
other fields...
class ModelC(models.Model):
name = ....
model_a = FKField(ModelA)
model_b = FKField(ModelB)
I was using the default ModelViewSet serializers for each model.
On my react frontend, I'm displaying a table containing 100 objects of ModelC. The request took 300ms. The problem is that instead of displaying just the pk id of modelA and ModelB in my table, I want to display their names. I've tried the following ways to get that data when I use the list() method of the viewset (retreive all modelc objects), but it significantly increases call times:
Serializing the fields in ModelCSerializer
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelASerializer(read_only=True)
model_b = ModelBSerializer(read_only=True)
class Meta:
model = ModelC
fields = '__all__'
Creating a new serializer to only return the name of the FK object
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelANameSerializer(read_only=True) (serializer only returns id and name)
model_b = ModelBNameSerializer(read_only=True) (serializer only returns id and name)
class Meta:
model = ModelC
fields = '__all__'
StringRelatedField
class ModelCSerializer(serializers.ModelSerializer):
model_a = serializer.StringRelatedField()
model_b = serializer.StringRelatedField()
class Meta:
model = ModelC
fields = '__all__'
Every way returns the data I need (except number 3 takes more work to get the FKobject's id) but now my table request takes 5.5 seconds. Is there a way to do this without significantly increasing call times? I guess this is due to the DB looking up 3 objects for every object I retrieve.
Also I wouldn't be able to make the primary_key of ModelA & ModelB the name field because they aren't unique.
Thanks
EDIT Answer for my example thanks to bdbd below:
class ModelCViewSet(viewsets.ModelViewSet):
queryset = ModelC.objects.select_related('model_a', 'model_b').all()
# ...
You can use select_related for this to optimise your queries and make sure that every object in your ModelC does not do extra DB hits
Say I have a serializer A
class SerializerA(ModelSerializer):
some_field = CharField()
some_other_field = CharField()
field_require_other_model = SerializerMethodField()
class Meta:
model = ModelA
fields = ('some_field', 'some_other_field', 'field_require_other_model')
def get_field_require_other_model(self, instance):
other_model_qs = ModelB.objects.filter(email=instance.email)
# say I want to get whatever that comes first
return other_model_qs.first().useful_info
As seen above, SerializerA uses ModelA for getting all the fields except that one in ModelB. I can get the info from ModelB doing what I did, but I don't know if this is the best way getting the data. I'm not sure if I need to hit database so many times or if there's a way to lazily evaluate it.
Also, what if I have another SerializerMethodField() that utilizes ModelB but for different info. Is this way still the best way to get the data?
How about using .annotate, annotating the other field onto modelA from modelB and then defining it as a charfield(or whatever the type is) on the serializer?
Something like
queryset = ModelA.objects.all().annotate(other_field_on_model_b=F('ModelB__other_field_on_model_b'))
then in the seralizer
class SerializerA(ModelSerializer):
some_field = CharField()
some_other_field = CharField()
other_field_on_model_b = CharField(required=False) #or whatever the field type is.
class Meta:
model = ModelA
fields = ('some_field', 'some_other_field', 'other_field_on_model_b')
Could do the annotation in get_queryset() or in the end point itself.
I need to be able to do a post on an api endpoint to save an adgroup model.The model has a many to many field. I know I need to overwrite the create() method.But How is where I am stuck at . The incoming request data will have the id for the other model (creative). This id will already be present in the creative table.
Django creates another table called adgroup_creative to hold this M2M relationship.I need to populate that table when saving this adgroup object.
class AdGroup(models.Model):
adgroup_name = models.CharField(max_length=200, verbose_name="Name")
creative = models.ManyToManyField(Creative, verbose_name="Creative")
class Creative(models.Model):
creative_name= models.CharField(max_length=200, verbose_name="Name", default=0)
ad_type= models.PositiveIntegerField(max_length=1,verbose_name="Ad Type")
class AdGroupSerializer(serializers.ModelSerializer):
class Meta:
model = AdGroup
fields = ('id','adgroup_name','creative')
class CreativeSerializer(serializers.ModelSerializer):
class Meta:
model = Creative
fields = ('id','creative_name')
class AdGroupViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = AdGroup.objects.all().order_by('-id')
serializer_class = AdGroupSerializer
https://codereview.stackexchange.com/questions/46160/django-rest-framework-add-remove-to-a-list
Save a many-to-many model in Django/REST?
You should have a look at the serializer relation documentation.
You don't need anything special if you simply use ID to represent a M2M relation with DRF. You'll need to override the create/update methods only if you intend to provide non existing related objects or use nested serializers.
In the current case, you don't need nested serializers because you want to provide related instances' IDs.
How can I add a custom hyperlink field in a serializer? I would like to have a hyperlink field in my serializer that has query params in it. Since there is no way to pass query params from HyperlinkedRelatedField or HyperlinkedIdentityField as far as I know, I've tried using a SerializerMethodField. However, this only serializes to a string, and is not a clickable URL when I visit the API through my browser. My code looks something like this:
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('url', 'custom_field')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view'),
urllib.urlencode({'param': 'foo'})
)
return result
Also, I am having trouble understanding the difference between a HyperlinkedRelatedField and a HyperlinkedIdentityField, so a brief explanation would be appreciated.
This should do the trick:
from rest_framework.reverse import reverse
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('url', 'custom_field')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view', args=[obj.id], request=self.context['request']),
'param=foo'
)
return result
The reverse function in rest_framework takes a view name (whatever view you'd like to link to), either an args list (the object id, in this case) or kwargs, and a request object (which can be accessed inside the serializer at self.context['request']). It can additionally take a format parameter and any extra parameters (as a dictionary) that you want to pass to it.
The reverse function then builds a nice, fully-formed URL for you. You can add query params to it by simply adding as many ?{}&{}&{} to your result variable and then filling in the series of query params beneath the 'param=foo' inside your format function with whatever other params you want.
The HyperlinkedIdentityField is used on the object itself that is being serialized. So a HyperlinkedIdentifyField is being used in place of your primary key field on MyModel because you are using a HyperlinkedModelSerializer which creates a HyperlinkedIdentityField for the pk of the object itself being serialized.
The HyperlinkedRelatedField is used to define hyperlinked relationships to RELATED objects. So if there were a MySecondModel with a foreign key relationship to MyModel and you wanted to have a hyperlink on your MyModel serializer to all the related MySecondModel objects you would use a HyperlinkedRelatedField like so (remember to add the new field to your fields attribute in Meta):
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
mysecondmodels = serializers.HyperlinkedRelatedField(
many=True
read_only=True,
view_name='mysecondmodel-detail'
)
class Meta:
model = MyModel
fields = ('url', 'custom_field', 'mysecondmodels')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view', args=[obj.id], request=self.context['request']),
'param=foo'
)
return result
If it were a OneToOneField rather than ForeignKey field on MySecondModel then you would set many=False.
Hope this helps!
views.py
I'm creating a queryset that I want to serialize and return as JSON. The queryset looks like this:
all_objects = Program.objects.all()
test_data = serializers.serialize("json", all_objects, use_natural_keys=True)
This pulls back everything except for the 'User' model (which is linked across two models).
models.py
from django.db import models
from django.contrib.auth.models import User
class Time(models.Model):
user = models.ForeignKey(User)
...
class CostCode(models.Model):
program_name = models.TextField()
...
class Program(models.Model):
time = models.ForeignKey(Time)
program_select = models.ForeignKey(CostCode)
...
Question
My returned data has Time, Program, and CostCode information, but I'm unable to query back the 'User' table. How can I get back say the 'username' (from User Table) in the same queryset?
Note: I've changed my queryset to all_objects = Time.objects.all() and this gets User info, but then it doesn't pull in 'CostCode'. My models also have ModelManagers that return the get_by_natural_key so the relevant fields appear in my JSON.
Ultimately, I want data from all four models to appear in my serialized JSON fields, I'm just missing 'username'.
Here's a picture of how the JSON object currently appears in Firebug:
Thanks for any help!
It seems a bit heavyweight at first glance but you could look at using Django REST Framework:
http://www.django-rest-framework.org/api-guide/serializers#modelserializer
You can define and use the serializer classes without having to do anything else with the framework. The serializer returns a python dict which can then be easily dumped to JSON.
To get all fields from each related model as nested dicts you could do:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
depth = 2
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
To customise which fields are included for each model you will need to define a ModelSerializer class for each of your models, for example to output only the username for the time.user:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', )
class TimeSerializer(serializers.ModelSerializer):
"""
specifying the field here rather than relying on `depth` to automatically
render nested relations allows us to specify a custom serializer class
"""
user = UserSerializer()
class Meta:
model = Time
class ProgramSerializer(serializers.ModelSerializer):
time = TimeSerializer()
class Meta:
model = Program
depth = 1 # render nested CostCode with default output
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
What you really want is a "deep" serialization of objects which Django does not natively support. This is a common problem, and it is discussed in detail here: Serializing Foreign Key objects in Django. See that question for some alternatives.
Normally Django expects you to serialize the Time, CostCode, Program, and User objects separately (i.e. a separate JSON array for each) and to refer to them by IDs. The IDs can either be the numeric primary keys (PKs) or a "natural" key defined with natural_key.
You could use natural_key to return any fields you want, including user.username. Alternatively, you could define a custom serializer output whatever you want there. Either of these approaches will probably make it impossible to load the data back into a Django database, which may not be a problem for you.