django rest framework change primary key to use a unqiue field - django

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'

Related

Django Rest Framework - Filter by custom field

I have Serializer which defines a custom column, in the serializers.py which I am trying to filter a query on.
The itemsoutstanding column example below, I am trying to filter it from my view but it returns "is not defined"
class OverView(serializers.ModelSerializer):
itemsoutstanding = serializers.SerializerMethodField()
class Meta:
model = Order
fields = ['id','total_price', 'created_at','itemsoutstanding']
def get_itemsoutstanding(self, obj):
count= Items.objects.filter(order=obj.id).count()
return count
In my view I am trying to filter on the serializer column, but it says it's not defined
queryset = Order.objects.all()
serializer_class = OverView
queryset = Order.objects.filter(shop=shop)
queryset = queryset.filter(itemsoutstanding> 0)
Is there any way to filter based on the serializer columns?
You will need to annotate the queryset in the definition, you may need to check the django docs for annotations and probably adjust this code to your models but the idea is this:
from django.db.models import Count
queryset = Order.objects.annotate(itemsoutstanding=Count("items"))
Then you can use the itemsoutstanting in the filter and in the serializer as a field:
class OverView(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id','total_price', 'created_at','itemsoutstanding']
The queryset has nothing to do with the serializer, so it does not know of your defined field in the serializer. That custom field is only added to the serialized data and not the queryset. What you need to do is annotate the field in the view when you get the queryset.

How to access an object from PK of another object in ModelViewSet

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

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)

use django-filter with object url_params

I have successfully implemented django-filter on my Django-rest-framework server.
I have the following filter_class
filters.py
class EmploymentFilter(filters.FilterSet):
class Meta:
model = EmploymentCheck
fields = ['instructions',]
views.py
class EmploymentCheckViewSet(viewsets.ModelViewSet):
pagination_class = ContentRangeHeaderPagination
serializer_class = EmploymentCheckSerializer
queryset = EmploymentCheck.objects.all()
filter_class = EmploymentFilter
the filter works when i send a get request as
/employmentcheck/?instructions=2
However, I have implemented a front end with react-admin.
My front-end, sends a request with the url_params as objects
/employmentcheck/?filter={"instruction_id":"2"}&range=[0,24]&sort=["id","DESC"]/
Notice how the URL specifies a filter object, in which, it defines the parameters to filter against.
My question is, how and where can I filter my model without changing the URL pattern from my client?
Any other advise that spans the scope of my question is equally welcomed
Models.py
class EmploymentCheck(models.Model):
instructions = models.ForeignKey(Instruction, on_delete=models.CASCADE, null=True)
I simply omitted the filter_class, and instead overrode the get_queryset() method of the viewset.
Allow me to make some changes, the commented out code would fail if you sent a request with no 'filter' query params. e.g GET:/api/employee/ as opposed to GET:/api/employee/?filter{"instruction"=""}
So I opted to check if the query_params.get('key') was none, if so, i pass an empty string.
Any better pythonic way of handling this is encouraged
ViewSet
class EmploymentCheckViewSet(viewsets.ModelViewSet):
serializer_class = EmploymentCheckSerializer
def get_queryset(self):
queryset = EmploymentCheck.objects.all()
if self.request.query_params.get('filter') is None:
return queryset #return the queryset as is
else:
_instructions = self.request.query_params.get('filter') #returned as a string
_instructions = json.loads(_instructions)#convert to dictionary
if queryset and any(_instructions):
queryset = queryset.filter(instructions = _instructions['instruction_id'])
return queryset
Note: When you override the get_queryset, you must explicitly define the base_name argument of the router.register method in your app.urls.py. Refer to django-rest-framework routers.
urls.py
router = DefaultRouter()
router.register(r'employmentcheck', views.EmploymentCheckViewSet, 'EmploymentCheck')#explicitly set the base_name attribute when the viewset defines a custom get_queryset method

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

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.