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)
Related
I want to overwrite the __str__ method in Django admin when using the autocomplete_fields = () but the returned values are using __str__.
I have a form something like
class MyAdminForm(forms.ModelForm):
placement = forms.Select(
choices = Organisation.objects.active(),
)
class Meta:
model = Lead
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['placement'].label_from_instance = lambda obj: f'{str(obj)} {obj.post_code}'
This will provide back a Select with the organisation name and post code in the dropdown fields. But there are some 80k choices so I need to using autocomplete. Within within admin.py I have
class LeadAdmin(admin.ModelAdmin):
form = LeadAdminForm
autocomplete_fields = ('placement',)
As soon as I add the autocomplete_fields I lose my postcode and it reverts to just showing the __str__
Hoa can I used autocomplete_fields and overwrite the __str__ method?
This question is answered through Benbb96 comment above which I've copied here so I can close it
So maybe this answer can help you :
stackoverflow.com/a/56865950/8439435 – Benbb96
I have a custom queryset on a model manager:
class TenantManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(myfield=myvalue)
class TenantModel(TenantModelMixin, models.Model):
objects = TenantManager()
class Meta:
abstract = True
I use the abstract TenantModel as a mixin with another model to apply the TenantManager. E.g.
class MyModel(TenantModel):
This works as expected, applying the TenantManager filter every time MyModel.objects.all() is called when inside a view.
However, when I create a ModelForm with the model, the filter is not applied and all results (without the filter are returned. For example:
class AddPersonForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('person', )
Why is this and how to I ensure the ModelManager is applied to the queryset in ModelForm?
Edit
#Willem suggests the reason is forms use ._base_manager and not .objects (although I can not find this in the Django source code), however the docs say not to filter this kind of manager, so how does one filter form queries?
Don’t filter away any results in this type of manager subclass
This
manager is used to access objects that are related to from some other
model. In those situations, Django has to be able to see all the
objects for the model it is fetching, so that anything which is
referred to can be retrieved.
If you override the get_queryset() method and filter out any rows,
Django will return incorrect results. Don’t do that. A manager that
filters results in get_queryset() is not appropriate for use as a base
manager.
You can do it in two ways:
First: When creating the form instance, add the queryset for the desired field.
person_form = AddPersonForm()
person_form.fields["myfield"].queryset = TenantModel.objects.filter(myfield="myvalue")
Second: Override the field's queryset in the AddPersonForm itself.
class AddPersonForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('person', )
def __init__(self, *args, **kwargs):
super(AddPersonForm, self).__init__(*args, **kwargs)
self.fields['myfield'].queryset = TenantModel.objects.filter(myfield="myvalue")
I'm not sure why your code doesn't properly works. Probably you haven't reload django app. You could load queryset in __init__ of your form class
class AddPersonForm(forms.ModelForm):
person = forms.ModelMultipleChoiceField(queryset=None)
class Meta:
model = MyOtherModel
fields = ('person', )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['person'].queryset = MyModel.objects.all()
The generic structure of the models is that there are teachers and devices, each device has a ForeignKey relationship with the teachers ID/PK.
I'm trying to create my API in such a way that when going to the detail view for a teacher, all of the associated devices are displayed. I've overridden get_serializer_class() to specify which serializer to use at the appropriate time, but can't figure out how to correctly change the Queryset based on detail view or not. Error posted below.
Got AttributeError when attempting to get a value for field `brand` on serializer `DeviceSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Teacher` instance.
Original exception text was: 'Teacher' object has no attribute 'brand'.
class TeacherViewSet(viewsets.ModelViewSet):
queryset = Teacher.objects.order_by('campus','name')
serializer_class = TeacherSerializer
detail_serializer_class = DeviceSerializer
def get_serializer_class(self):
if self.action == 'retrieve':
if hasattr(self, 'detail_serializer_class'):
return self.detail_serializer_class
return super(TeacherViewSet, self).get_serializer_class()
def get_queryset(self, pk=None):
if pk is not None:
return Device.objects.filter(device__owner=self.kwargs.get('pk')
return Teacher.objects.all()
I was able to get the desired output by adding a nested DeviceSerializer in my TeacherSerializer that parses the device object list.
class TeacherSerializer(serializers.ModelSerializer):
devices = DeviceSerializer(many=True)
class Meta:
model = Teacher
fields = ('id', 'name', 'campus', 'email', 'devices')
I assume you are using DRF. If that is the case, just tweak TeacherSerializer to something like:
def TeachSearializer(serializer.ModelSerializer):
devices = serializers.SerializerMethodField()
class Meta:
model = Teacher
fields = '__all__'
def get_devices(self, obj):
return Devices.objects.filter(teacher=obj)
And that is it, everytime you use the serializer on a teacher object, their devices will be added on a field devices
I'm using Django 2.1, DRF 3.7.7.
I've some models and their relative (model) serializers: these models are nested, and so are the serializers.
Let me give an example:
# models.py
class City(models.Model):
name = models.CharField(max_length=50)
class Person(models.Model):
surname = models.CharField(max_length=30)
birth_place = models.ForeignKey(City)
# serializers.py
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = models.CitySerializer
fields = "__all__"
class PersonSerializer(serializers.ModelSerializer):
birth_place = CitySerializer()
class Meta:
model = models.Person
fields = "__all__"
If I submit an AJAX request with a json like:
{'surname': 'smith', 'birth_place': 42}
I get back a Bad Request response, containing: Invalid data. Expected a dictionary, but got int.
If I submit a nested json like:
{'surname': 'smith', 'birth_place': {'id': 42, 'name': 'Toronto'}}
the relation is not converted, the id field is ignored and the rest is parsed to:
OrderedDict([('birth_place', OrderedDict([('name', 'Toronto')]))])
The following is the post method I'm using on a class-based view:
def post(self, request):
print("Original data:", request.data)
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
self.data = serializer.validated_data
print("Parsed data:", self.data)
...
I only need to get data from the endpoints connected to the serializers, I don't need to write/save anything through the REST interface, since the POST processing of the form is done by Django.
TL;DR: How should I correctly submit a JSON request to a nested serializer, without having to write handmade conversions? Did I commit errors in setting up the serializers?
Edit: I've discovered that by adding id = serializers.IntegerField() to the serializer parent class (e.g. City), the serializer parser now processes the id. At least now I'm able to perform actions in the backend with django.
Generic writing for nested serializers is not available by default. And there is a reason for that:
Consider, you are creating a person with a birthplace, using a POST request. It is not clear if the submitted city is a new one or an existing one. Should it return an error if there isn't such a city? Or should it be created?
This is why, if you want to handle this kind of relationship in your serializer, you need to write your own create() and update() methods of your serializer.
Here is the relevant part of the DRF docs: http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
It's definitely not clearly put into the docs of django-rest. If you follow the process of serializers processing the data for creation then it becomes clear that django manages m2m by saving the parent instance first and then adding the m2m values, but somehow the m2m fields don't go through the validation if you mark them as read_only.
The solution to this is to overr run_validation method of the serializer. The serializer should look like this:
class ExampleSerializer(serializers.ModelSerializer):
queryset = SomeModel.objects.all()
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = SomeModel
fields = ['pk', 'name', 'tags']
def run_validation(self, data):
validated_data = super(StudyResourceSerializer, self).run_validation(data)
validated_data['tags'] = data['tags']
return validated_data
The request body should look like this:
{
"tags": [51, 54],
"name": "inheritance is a mess"
}
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.