How to represent `self` url in django-rest-framework - django

I want to add a link to a single resource representation which is an URL to itself, self. Like (taken from documentation):
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Album
fields = ('album_name', 'artist', 'track_listing')
{
'album_name': 'The Eraser',
'artist': 'Thom Yorke',
'self': 'http://www.example.com/api/album/2/',
}
How should this be done?

If you inherit serializers.HyperlinkedModelSerializer all you need to do is pass a url field to fields. See the docs here:
http://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/

Alright, this solved my problem but if you have a better solution please post an answer:
from django.urls import reverse
from rest_framework import serializers
self_url = serializers.SerializerMethodField('get_self')
def get_self(self, obj):
request = self.context['request']
return reverse('album-detail', kwargs={'id': obj.id}, request=request)

here is my solution,
in your view methods create serilizer object like this:
album = AlbumSerializer(data=data, {"request":request})
in your serilizer class override to_representation method (you can read about this method on DRF docs
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
def to_representation(self, obj):
data = super().to_representation(obj)
request = self.context["request"]
return data

According to this issue, you can just add 'url' in the list of fields.

Here is a little more context than you got in the other answers so far. The key is the context argument passed to the serializer constructor and the 'url' in fields.
http://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/
In your viewset:
class AlbumViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Album.objects.all()
serializer = AlbumSerializer(queryset, many=True,
context={'request': request})
return Response(serializer.data)
In the serializer:
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Album
fields = ('album_name', 'artist', 'track_listing', 'url')

As stated above the HyperlinkedModelSerializer will convert all your related fields and remove the ID of the resource as well. I use one of the following solutions, depending on the situation.
Solution 1: import api settings and add the url field:
For more details see URL_FIELD_NAME.
from rest_framework.settings import api_settings
class AlbumSerializer(SelfFieldMixin, serializers.ModelSerializer):
class Meta:
model = Album
fields = ('album_name', 'artist', 'track_listing', api_settings.URL_FIELD_NAME)
Solution 2: a simple mixin, which only works if default fields are used:
class SelfFieldMixin:
"""
Adds the self link without converting all relations to HyperlinkedRelatedField
"""
def get_default_field_names(self, declared_fields, model_info):
"""
Return the default list of field names that will be used if the
`Meta.fields` option is not specified.
"""
default_fields = super().get_default_field_names(declared_fields, model_info)
return [self.url_field_name, *default_fields]
And it can be used like
class AlbumSerializer(SelfFieldMixin, serializers.ModelSerializer):
class Meta:
model = Album
fields = '__all__'
NOTE: It requires Python 3 due to the super() call, the a mixin must be placed before any of the serializer classes!
P.S.: To achieve the required response in the question one must also set the URL_FIELD_NAME to 'self'.
Edit: get_default_field_names must return a list object for Meta.exclude to work on ModelSerializers.

You can use the HyperlinkedIdentityField like so:
class ThingSerializer(ModelSerializer):
class Meta:
model = Thing
fields = ['self_link', ...]
self_link = HyperlinkedIdentityField(view_name='thing-detail')
You need to have your routes named appropriately but the Default routers do so automatically (documented here).
As others have pointed out the HyperlinkedModelSerializer also works. This is because it uses this field automatically. See here.

Related

Django REST API Listview

Currently I'm trying to develop personal blog with Django/REST API, and I have trouble for that.
There are a number of posts in blog and I want to control those posts with Hyperlink. I made it by using ModelViewSet, however, whole data in detailView is also shown in ListView.
The thing is, I only want "url" and "title" of posts to be shown in ListView while DetailView contains full data.
Here is my code and current results given by REST framework.
Don't mind IndexView
# serializers
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
fields = '__all__'
# views
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = serializers.PostSerializer
permission_classes = (IsAdminUser, )
Post List in REST API:
Post instance in REST API:
As far as I'm aware, you need a separate serializer for the list view.
You could create a custom serializer that takes in a fields arg to select specific fields. But its probably simpler to just have a separate one for the ListView. Also, for the list view, if you are only showing a subset of the model fields, you can use the only() function on the queryset to only return the model data that you need. For example:
qs = MyModel.objects.all().only('field_a', 'field_b', 'field_c')
Here is the custom serializer if you decide to go that way:
class CustomSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
selected_fields = kwargs.pop('selected_fields', None)
# used pop function so selected_fields is not passed to superclass
super().__init__(*args, **kwargs)
if selected_fields:
# make sure only fields for the model are allowed
fields = set(selected_fields)
current_fields = set(self.fields.keys())
for field in current_fields - fields:
self.fields.pop(field)
class MyModelSerializer(CustomSerializer):
class Meta:
model = MyModel
fields = '__all__'
In the list view:
required_fields = ('field_a', 'field_b', 'field_c')
data_to_return = MyModelSerializer(model_queryset, many=True, fields=required_fields).data
return Response(data)

Custom error messages in Django Rest Framework serializer

The scenario is quite straight-forward:
I have a model with some fields that are required. Let's say one of them is a TextField which can't be blank.
I also have a ModelSerializer (Django Rest Framework) that represents that model.
When an empty string is used to set that field through the serializer the error returned comes from the model itself (This field can't be blank).
I would like to override the error messages only in the serializer level, without the need to explicitly re-specifying every field in the serializer (which I believe is against the DRY principle), having to write a validate_ method for each field and raise my own ValidationError or having to change the error messages in the Model level (because sometimes the context of the error message matters to my use-case and the error message should be given accordingly).
In other words, is there a way to override error messages in the serializer level as easy as it is for a ModelForm:
class MyModelForm(ModelForm):
class Meta:
model = MyModel
error_messages = {"field1": {"required": _("For some reason this is a custom error message overriding the model's default")}}
EDIT: I see that this question still receives some views, so it is important to note that there's another approach, much cleaner than the original answer I posted here.
You can just use the extra_kwargs attribute of the serializer's Meta class, like so:
class UserSerializer(ModelSerializer):
class Meta:
model = User
extra_kwargs = {"username": {"error_messages": {"required": "Give yourself a username"}}}
Original answer:
Using #mariodev 's answer I created a new class in my project that does that:
from rest_framework.serializers import ModelSerializer, ModelSerializerOptions
class CustomErrorMessagesModelSerializerOptions(ModelSerializerOptions):
"""
Meta class options for CustomErrorMessagesModelSerializerOptions
"""
def __init__(self, meta):
super(CustomErrorMessagesModelSerializerOptions, self).__init__(meta)
self.error_messages = getattr(meta, 'error_messages', {})
class CustomErrorMessagesModelSerializer(ModelSerializer):
_options_class = CustomErrorMessagesModelSerializerOptions
def __init__(self, *args, **kwargs):
super(CustomErrorMessagesModelSerializer, self).__init__(*args, **kwargs)
# Run through all error messages provided in the Meta class and update
for field_name, err_dict in self.opts.error_messages.iteritems():
self.fields[field_name].error_messages.update(err_dict)
The first one gives the possibility to add a new Meta class attribute to the serializer as with the ModelForm.
The second one inherits from ModelSerializer and uses #mariodev's technique to update the error messages.
All is left to do, is just inherit it, and do something like that:
class UserSerializer(CustomErrorMessagesModelSerializer):
class Meta:
model = User
error_messages = {"username": {"required": "Give yourself a username"}}
I tried to create a simple Serializer rather than a ModelSerializer. Probably because of that the accepted answer with extra_kwargs by Gabriel Amram didn't work for me. Another top answer by #mariodev did work but I was looking for a more elegant solution and found one. Turns out that the Field class accepts error_messages as a parameter, which is a dictionary that overrides the default error messages. Here is the reference to the docs. It's the same format as described in the accepted answers. Here is an example:
from rest_framework import serializers
class MySerializer(serializers.Serializer):
client_id = serializers.IntegerField(required=True, error_messages={'required': 'Custom error message'})
In your serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(UserSerializer, self).__init__(*args, **kwargs)
self.fields['username'].error_messages['required'] = u'My custom required msg'
Please notice that some error messages consist of %s placeholders like:
'invalid': _("'%s' value must be either True or False."),
for BooleanField.
So you need to go over default_error_messages part in each field type in the DRF's fields.py, to use it properly.
unique seemed to be ignored from error_messages, so I had to take a different approach.
email = serializers.EmailField(validators=[
UniqueValidator(
queryset=models.Client.objects.all(),
message="My custom error",
)]
)
It's simpler (yet less flexible, less reusable) than #gabriel-amram's, but far less hacky than #mariodev's.
Another approach for UniqueValidator (for using with ModelSerializer):
def __init__(self, *args, **kwargs):
super(UserSerializer, self).__init__(*args, **kwargs)
# Find UniqueValidator and set custom message
for validator in self.fields['email'].validators:
if isinstance(validator, validators.UniqueValidator):
validator.message = _('This email already exist on this site')
I just spent an hour ripping my hair out over this, so figured I'd post an update here in case anyone else finds it useful.
I'm using djangorestframework version 3.10.3, and for whatever reason, it seems that drf no longer uses the 'required' key in the error_messages dict to allow customization of an error message for a missing value. Instead it uses 'blank'.
class SampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SampleModel
fields = (
'description',
)
extra_kwargs = {
'description': {'error_messages': {'blank': "Please provide a description"}},
}
DRF3.0 expects us to explicitly define the validators for fields if we wish to override the default model validators. This can be done by passing extra_kwargs and explicitly defining the validators for whichever field
you seem necessary. Also you can even specify your own custom validator which can be reused again for different fields or even other serializers
http://www.django-rest-framework.org/api-guide/serializers/#validation
http://www.django-rest-framework.org/api-guide/validators/#validation-in-rest-framework
# my_app/validators.py
def validate_required(value):
# whatever validation logic you need
if value == '' or value is None:
raise serializers.ValidationError('This field is required.')
# my_app/serializers.py
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
extra_kwargs = {"field1": {"validators": [validators.validate_required,]}}
Just a note since I played with this for awhile, if you're using something like a URLField that just adds a URLValidator, it doesn't seem to use the error_messages, so I did something similar to #Hugo's answer:
class Meta:
extra_kwargs = {"url_field": {"validators": [validators.URLValidator(message="My error message")]}}
You can create separate function in serializers.py and call it from serializer class
def checkFields(fields):
for field in fields:
fields[field].error_messages['blank']=fields[field].error_messages['required'] = 'Please enter %s'%field
This is the code that inherits the model's error message.
There is also a module, so download it if you want.
If there's a problem, leave it in the comments.
https://pypi.org/project/django-rest-inherits-error-messages/#files
from rest_framework import serializers
from rest_framework.relations import HyperlinkedRelatedField
from rest_framework.utils.field_mapping import get_nested_relation_kwargs
class InheritsModelSerializer(serializers.ModelSerializer):
def build_field(self, field_name, info, model_class, nested_depth):
'''
inherits the error_messages of the model
'''
result: tuple = super().build_field(field_name, info, model_class, nested_depth)
field = model_class._meta.get_field(field_name)
error_messages = field.error_messages
if error_messages:
result[1]['error_messages'] = field.error_messages
return result

django rest framework change primary key to use a unqiue field

I have a model which is called GameProfile, which is a one to one relation with User model. I used HyperlinkedModelSerializer across all my design.
For the GameProfile, the user field is suppose to be the primary key for querying, it is unique but I did not set it up as a primary key. Is there a way to change the default behavior of django serializer to point to user__id as the primary key and always use it for retreiving the profile in the detail view?
class GameProfileSerializer(serializers.HyperlinkedModelSerializer):
"""
"""
user_pk = serializers.Field(source='user.id')
class Meta:
model = GameProfile
class GameProfileViewSet(viewsets.ModelViewSet):
"""
"""
queryset = GameProfile.objects.all()
serializer_class = GameProfileSerializer
def get_queryset(self):
""" get_queryset
"""
queryset = super(GameProfileViewSet, self).get_queryset()
if not queryset.exists():
raise Http404
if self.request.user.is_authenticated() and not self.request.user.is_superuser:
return queryset.filter(user=self.request.user)
return queryset
please advise, thanks in advance:)
Assuming your GameProfile model looks like:
class GameProfile(models.Model)
user = models.OneToOneField('User')
The serializer will be:
class GameProfileSerializer(serializers.HyperlinkedModelSerializer):
user_id = serializers.Field(source='user.id')
class Meta:
model = GameProfile
Set the .lookup_field attribute on the view correctly:
lookup_field = 'user_id'
Url will be:
/gameprofile/<user_id>
In order to get the URLs to work, you might need to add lookup_field on the ViewSet, not just on the serializer. So you would have:
class GameProfileViewSet(viewsets.ModelViewSet):
queryset = GameProfile.objects.all()
serializer_class = GameProfileSerializer
lookup_field = 'user__id'
In this case the lookup_field uses the double-underscore notation rather than the dot notation (dots won't work in the regular expressions in the URL patterns). I was unable to get the solution proposed by #almalki and #patsweet to work; according to the documentation on serializers, "The value of this option [lookup_field] should correspond both with a kwarg in the URL conf, and with a field on the model", so it's possible that it doesn't work with RelatedFields.
If I'm understanding your question correctly, you want a url structure like so:
/api/<GameProfile-resource>/<user-pk>
If that is the case, you should checkout the lookup_field option. Link
You're Serializer class would look something like:
class GameProfileSerializer(serializers.HyperlinkedModelSerializer):
"""
"""
user_pk = serializers.Field(source='user.id')
class Meta:
model = GameProfile
lookup_field = 'user_pk' # Might have to use 'user__id'

Django Rest Framework - Incorrect source object on nested serialization when using proxy model

I have a question concerning using proxy models with the Django Rest Framework and nested serialization.
My proxy models are as follows:
class MyField(Field):
class Meta:
proxy = True
def field_type_name(self):
# logic that computes the field type name here
return "the result"
class MyForm(Form):
class Meta:
proxy = True
The Field model is defined in another app that I've included in my project. I wanted to add my own method to it without modifying the model so I made a proxy.
These are the serializers for the proxy models:
class MyFieldSerializer(serializers.HyperlinkedModelSerializer):
field_type = serializers.ChoiceField(source='field_type_name',
choices=form_fields.NAMES)
class Meta:
model = MyField
fields = ('url', 'field_type',)
class MyFormSerializer(serializers.HyperlinkedModelSerializer):
fields = MyFieldSerializer(many=True)
class Meta:
model = MyForm
fields = ('url', 'fields')
And the viewsets:
class MyFieldViewSet(viewsets.ModelViewSet):
queryset = MyField.objects.all()
serializer_class = MyFieldSerializer
class MyFormViewSet(viewsets.ModelViewSet):
queryset = MyForm.objects.all()
serializer_class = MyFormSerializer
urls.py:
router.register(r'fields', views.MyFieldViewSet)
router.register(r'forms', views.MyFormViewSet)
If I go to /fields/ it works fine. The method I added in the proxy model is executed correctly.
[
{
"url": "http://127.0.0.1:8000/fields/1/",
"field_type": "the result",
},
{ ...
But if I go to /forms/ I get the following error:
AttributeError at /forms/
'Field' object has no attribute 'field_type_name'
/Users/..../lib/python2.7/site-packages/rest_framework/fields.py in get_component
"""
Given an object, and an attribute name,
return that attribute on the object.
"""
if isinstance(obj, dict):
val = obj.get(attr_name)
else:
**val = getattr(obj, attr_name)**
if is_simple_callable(val):
return val()
return val
▼ Local vars
Variable Value
attr_name u'field_type_name'
obj <Field: Cools2>
As you can see the obj is Field instead of MyField which is why it's not able to call field_type_name. This only happens on the nested serialization. If anyone has a suggestion on how I can best fix this I'd greatly appreciate it.
EDIT:
Based on Kevin's response I'm editing the proxy models to try to fix this.
Here are the base models for reference:
class Form(AbstractForm):
pass
class Field(AbstractField):
form = models.ForeignKey("Form", related_name="fields")
Here is my attempt to fix the problem (using examples from Django proxy model and ForeignKey):
class MyField(Field):
class Meta:
proxy = True
def field_type_name(self):
# logic that computes the field type name here
return "the result"
# this works
#property
def form(self):
return MyForm.objects.get(id=self.form_id)
class MyForm(Form):
class Meta:
proxy = True
# this does not work
#property
def fields(self):
qs = super(MyForm, self).fields
qs.model = MyField
return qs
Now I can get MyForm from MyField but not MyField from MyForm (the reverse):
>>> MyField.objects.get(pk=1).form
<MyForm: Cool Form>
>>> MyForm.objects.get(pk=1).fields.all()
[]
I
This is because your model Form (or MyForm) isn't configured to return MyField objects when you access the field attribute on the form. It's not configured to substitute your proxied-version.
Try it yourself, open ./manage.py shell and try to read the fields related manager, it will return a collection of Field objects.
>>> form = MyForm.objects.all()[0].fields.all()
(Btw, I have to guess on the actual model structure since the original Field and Form models weren't included in your example).
If it's a read-only field, you could use serializers.SerializerMethodField to add a method to the serializer (your field_type_name(). If you want to be able to edit it, you're better off writing your own field sub-class that handles the conversion.

Django REST Framework: adding additional field to ModelSerializer

I want to serialize a model, but want to include an additional field that requires doing some database lookups on the model instance to be serialized:
class FooSerializer(serializers.ModelSerializer):
my_field = ... # result of some database queries on the input Foo object
class Meta:
model = Foo
fields = ('id', 'name', 'myfield')
What is the right way to do this? I see that you can pass in extra "context" to the serializer, is the right answer to pass in the additional field in a context dictionary?
With that approach, the logic of getting the field I need would not be self-contained with the serializer definition, which is ideal since every serialized instance will need my_field. Elsewhere in the DRF serializers documentation it says "extra fields can correspond to any property or callable on the model". Are "extra fields" what I'm talking about?
Should I define a function in Foo's model definition that returns my_field value, and in the serializer I hook up my_field to that callable? What does that look like?
Happy to clarify the question if necessary.
I think SerializerMethodField is what you're looking for:
class FooSerializer(serializers.ModelSerializer):
my_field = serializers.SerializerMethodField('is_named_bar')
def is_named_bar(self, foo):
return foo.name == "bar"
class Meta:
model = Foo
fields = ('id', 'name', 'my_field')
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
You can change your model method to property and use it in serializer with this approach.
class Foo(models.Model):
. . .
#property
def my_field(self):
return stuff
. . .
class FooSerializer(ModelSerializer):
my_field = serializers.ReadOnlyField(source='my_field')
class Meta:
model = Foo
fields = ('my_field',)
Edit: With recent versions of rest framework (I tried 3.3.3), you don't need to change to property. Model method will just work fine.
With the last version of Django Rest Framework, you need to create a method in your model with the name of the field you want to add. No need for #property and source='field' raise an error.
class Foo(models.Model):
. . .
def foo(self):
return 'stuff'
. . .
class FooSerializer(ModelSerializer):
foo = serializers.ReadOnlyField()
class Meta:
model = Foo
fields = ('foo',)
if you want read and write on your extra field, you can use a new custom serializer, that extends serializers.Serializer, and use it like this
class ExtraFieldSerializer(serializers.Serializer):
def to_representation(self, instance):
# this would have the same as body as in a SerializerMethodField
return 'my logic here'
def to_internal_value(self, data):
# This must return a dictionary that will be used to
# update the caller's validation data, i.e. if the result
# produced should just be set back into the field that this
# serializer is set to, return the following:
return {
self.field_name: 'Any python object made with data: %s' % data
}
class MyModelSerializer(serializers.ModelSerializer):
my_extra_field = ExtraFieldSerializer(source='*')
class Meta:
model = MyModel
fields = ['id', 'my_extra_field']
i use this in related nested fields with some custom logic
My response to a similar question (here) might be useful.
If you have a Model Method defined in the following way:
class MyModel(models.Model):
...
def model_method(self):
return "some_calculated_result"
You can add the result of calling said method to your serializer like so:
class MyModelSerializer(serializers.ModelSerializer):
model_method_field = serializers.CharField(source='model_method')
p.s. Since the custom field isn't really a field in your model, you'll usually want to make it read-only, like so:
class Meta:
model = MyModel
read_only_fields = (
'model_method_field',
)
If you want to add field dynamically for each object u can use to_represention.
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ('id', 'name',)
def to_representation(self, instance):
representation = super().to_representation(instance)
if instance.name!='': #condition
representation['email']=instance.name+"#xyz.com"#adding key and value
representation['currency']=instance.task.profile.currency #adding key and value some other relation field
return representation
return representation
In this way you can add key and value for each obj dynamically
hope u like it
This worked for me.
If we want to just add an additional field in ModelSerializer, we can
do it like below, and also the field can be assigned some val after
some calculations of lookup. Or in some cases, if we want to send the
parameters in API response.
In model.py
class Foo(models.Model):
"""Model Foo"""
name = models.CharField(max_length=30, help_text="Customer Name")
In serializer.py
class FooSerializer(serializers.ModelSerializer):
retrieved_time = serializers.SerializerMethodField()
#classmethod
def get_retrieved_time(self, object):
"""getter method to add field retrieved_time"""
return None
class Meta:
model = Foo
fields = ('id', 'name', 'retrieved_time ')
Hope this could help someone.
class Demo(models.Model):
...
#property
def property_name(self):
...
If you want to use the same property name:
class DemoSerializer(serializers.ModelSerializer):
property_name = serializers.ReadOnlyField()
class Meta:
model = Product
fields = '__all__' # or you can choose your own fields
If you want to use different property name, just change this:
new_property_name = serializers.ReadOnlyField(source='property_name')
As Chemical Programer said in this comment, in latest DRF you can just do it like this:
class FooSerializer(serializers.ModelSerializer):
extra_field = serializers.SerializerMethodField()
def get_extra_field(self, foo_instance):
return foo_instance.a + foo_instance.b
class Meta:
model = Foo
fields = ('extra_field', ...)
DRF docs source
Even though, this is not what author has wanted, it still can be considered useful for people here:
If you are using .save() ModelSerializer's method, you can pass **kwargs into it. By this, you can save multiple dynamic values.
i.e. .save(**{'foo':'bar', 'lorem':'ipsum'})
Add the following in serializer class:
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['package_id'] = "custom value"
return representation