Here's my views.py:
class LanguageViewSet(viewsets.ModelViewSet):
queryset = Language.objects.all().order_by('name')
serializer_class = LanguageSerializer
class FrameworkViewSet(viewsets.ModelViewSet):
queryset = Framework.objects.all()
serializer_class = FrameworkSerializer
class SelectedLanguageViewSet(viewsets.ModelViewSet):
queryset = Language.objects.all()
serializer_class = FrameworkSerializer
def get_queryset(self):
request = self.request
language_id = request.query_params.get('language_id')
language = Language.objects.filter(id=language_id)
self.queryset = language.framework_set.all()
return self.queryset
And my urls.py:
router = routers.DefaultRouter()
router.register(r'languages', views.LanguageViewSet)
router.register(r'frameworks', views.FrameworkViewSet)
router.register(r'language/<int:language_id>', views.SelectedLanguageViewSet)
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
However, only languages and frameworks work. language doesn't exists. Is this because of the get_queryset? I even tried removing the <int:language_id> in the url params but it still won't show up.
EDIT:
Forgive my naiveness, I'm quite new to django and DRF
Update
What I meant to do was like this (without DRF):
In the views.py:
def frameworks_from_language(request, language_id):
language = Language.objects.get(pk=language_id)
if language == None:
# Do some stuffs
frameworks = language.framework_set.all()
template = 'app/language.html'
context = {
'frameworks': frameworks
}
return render(request, template, context)
And in urls.py:
path('language/<int:language_id>', views.frameworks_from_language, name='getframeworks')
So the whole flow would be:
User clicks a language
Fire a get request based on the id of the selected language
Return an object of frameworks that is related to the language
This is working on a normal template-based Django. However I have no idea how to execute something like this on DRF, with all the viewsets etc.
Yes, it's because of your get_queryset (and your URL definitions).
At first, drop <int:language_id> from URL prefix, as DRF router generates the list and detail URL endpoints for you automatically. Moreover, as you're using Regex path language/<int:language_id> is taken literally (<int:language_id> has a meaning while using path, not re_path).
In your SelectedLanguageViewSet.get_queryset, you're trying to return all Framework instances related with a certain Language (you thought you would take that from language_id query param). The viewset is for Language model, and at most you should do some filtering on the default queryset inside get_queryset; absolutely don't return a whole different queryset from another model. What will happen (after fixing your URL) when you pass /language/1/? (Will 1 be a Language ID or a Framework ID ? Hint: as per you current design it would refer a Framework instance ID).
FWIW, the URL captures come as kwargs attribute in a viewset instance (i.e. self.kwargs), not via the query string.
Answer to edits:
To implement that in DRF, you can define a serializer for language with only the frameworks field:
from viewsets import ReadOnlyModelViewSet
class SelectedLanguageViewSet(ReadOnlyModelViewSet):
queryset = Language.objects.all()
serializer_class = LangaugeRelationSerializer
only list and retrieve actions should be supported, hence inherited from ReadOnlyModelViewSet.
Now, the serializer (with only one field -- frameworks):
class LangaugeRelationSerializer(serializers.ModelSerializer):
frameworks = FrameworkSerializer(source='framework_set', many=True)
class Meta:
model = Language
fields = ('frameworks',)
By default ModelSerializer sets the related fields as PrimaryKeyRelatedField, so the related objects are represented as their primary keys. If you want that instead of using the FrameworkSerializer:
class LangaugeRelationSerializer(serializers.ModelSerializer):
frameworks = serializers.PrimaryKeyRelatedField(source='framework_set', many=True)
class Meta:
model = Language
fields = ('frameworks',)
As PrimaryKeyRelatedField is the default, you can also define the source in Meta.extra_kwargs (to save you from writing the field definition yourself):
class LangaugeRelationSerializer(serializers.ModelSerializer):
class Meta:
model = Language
fields = ('frameworks',)
extra_kwargs = {
'frameworks': {
'source': 'framework_set',
'many': True,
},
}
Have you tried passing url as /language/id-of-language in the place of id-of-language pass integer i.e. the id for language in the db.
Related
I am using DRF to generate API for my django app.
I am using the ViewSet class and exposing API endpoints for most of my models at their own path.
I want to allow viewing of my Endpoint and TLSCertificate models at an /assets/ path. As they are both children of an Organisation entity, I want to allow the results to be filtered Organisation.
So far I have:
serializers.py
class AssetSerializer(serializers.Serializer):
endpoints = EndpointSerializer(many=True)
certificates = TLSCertificateSerializer(many=True)
views.py
class AssetFilterSet(filters.FilterSet):
organisation = filters.ModelChoiceFilter(
name='organisation', queryset=Organisation.objects.all())
project = filters.ModelChoiceFilter(
name='project', queryset=Project.objects.all())
class Meta:
model = Endpoint
fields = ['organisation', 'project']
# the object type to be passed into the AssetSerializer
Asset = namedtuple('Asset', ('endpoints', 'certificates'))
class AssetViewSet(CacheResponseAndETAGMixin, viewsets.ViewSet):
"""
A simple ViewSet for listing the Endpoints and Certificates in the Asset list.
Adapted from https://stackoverflow.com/questions/44978045/serialize-multiple-models-and-send-all-in-one-json-response-django-rest-framewor
"""
# TODO filtering not functional yet
filter_class = AssetFilterSet
filter_fields = ('organisation', 'project',)
queryset = Endpoint.objects.all()
def list(self, request):
assets = Asset(
endpoints=Endpoint.objects.all(),
certificates=TLSCertificate.objects.all(), )
serializer = AssetSerializer(assets, context={'request': request})
return Response(serializer.data)
This works in returning the objects but does not allow for filtering to take place.
I would appreciate any guidance in how to enable the filtering in this situation?
I have a model that more or less looks like this:
class Starship(models.Model):
id = models.UUIDField(default=uuid4, editable=False, primary_key=True)
name = models.CharField(max_length=128)
hull_no = models.CharField(max_length=12, unique=True)
I have an unremarkable StarshipDetailSerialiser and StarshipListSerialiser (I want to eventually show different fields but for now they're identical), both subclassing serializers.ModelSerializer. It has a HyperlinkedIdentityField that refers back to the (UU)ID, using a home-brew class very similar to the original HyperlinkedIdentityField but with capability to normalise and handle UUIDs:
class StarshipListSerializer(HyperlinkedModelSerializer):
uri = UUIDHyperlinkedIdentityField(view_name='starships:starship-detail', format='html')
class Meta:
model = Starship
fields = ('uri', 'name', 'hull_no')
Finally, there's a list view (a ListAPIView) and a detail view that looks like this:
class StarshipDetail(APIView):
"""
Retrieves a single starship by UUID primary key.
"""
def get_object(self, pk):
try:
return Starship.objects.get(pk=pk)
except Starship.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
vessel = self.get_object(pk)
serializer = StarshipDetailSerialiser(vessel, context={'request': request})
return Response(serializer.data)
The detail view's URL schema is currently invoking the view based on the UUID:
...
url(r'vessels/id/(?P<pk>[0-9A-Fa-f\-]+)/$', StarshipDetail.as_view(), name='starship-detail'),
...
I now want users to be able to navigate and find the same vessel not just by UUID but also by their hull number, so that e.g. vessels/id/abcde1345...and so on.../ and vessels/hull/H1025/ would be able to resolve to the same entity. And ideally, regardless of whether one arrived at the detail view from ID or hull number, the serialiser, which is used with slight alterations in lists as well, should be able to have the ID hyperlinked to the ID-based link and the hull hyperlinked to a hull number based link (vessels/hull/H1025/). Is this at all possible? And if so, how would I go about it?
1. Add the new routes
# in urls.py
urlpatterns = [
...,
url(r'vessels/id/(?P<pk>[0-9A-Fa-f\-]+)/$', StarshipDetail.as_view(), name='starship-detail-pk'),
url(r'vessels/hull/(?P<hull_no>[0-9A-Za-z]+)/$', StarshipDetail.as_view(), name='starship-detail-hull'),
]
Tweak the regex for hull_no as you want. Note that I gave distinct names to each route, starship-detail-pk and starship-detail-hull.
2. Add the hull field in the serializer
# in serializers.py
class StarshipListSerialiser(HyperlinkedModelSerializer):
uri = UUIDHyperlinkedIdentityField(view_name='starship-detail-pk', format='html')
hull_no = UUIDHyperlinkedIdentityField(view_name='starship-detail-hull', format='html', lookup_field='hull_no')
class Meta:
model = Starship
fields = ('uri', 'name', 'hull_no')
3. Modify the view so it can also resolve objects based on hull
# in serializers.py
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
from starwars.serializers import StarshipDetailSerialiser
from starwars.models import Starship
class StarshipDetail(APIView):
def get(self, request, pk=None, hull_no=None, format=None):
lookup = {'hull_no': hull_no} if pk is None else {'pk': pk}
vessel = get_object_or_404(Starship, **lookup)
serializer = StarshipDetailSerialiser(vessel, context={'request': request})
return Response(serializer.data)
That should be enough to get you going with the detail view:
As a final note, you should be aware that it's not RESTful for the same resource to be available at two different URLs like this. Perhaps, as an alternate design decision, you might like to consider just defining the "one true route" for a resource, and adding in a "convenience" redirect from the other locator to the canonical URL.
I've been writing a game picking style webapp with Django and recently decided to implement my views as API endpoints with DRF, to give me more flexibility when it comes to frontend approaches. I have basic serializers and ViewSets for each of my models, and I can browse them normally with the (excellent) browsable API. Here are a couple:
class SheetSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer(read_only = True)
league = LeagueSerializer(read_only = True)
picks = serializers.HyperlinkedRelatedField(
source='pick_set',
many=True,
view_name='pick-detail',
read_only = True
)
class Meta:
model = Sheet
fields = ('url', 'id', 'league_week', 'user', 'league', 'picks')
class GameSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Game
fields = ('url', 'home_team', 'away_team', 'week', 'home_team_score', 'away_team_score')
class PickSerializer(serializers.HyperlinkedModelSerializer):
sheet = SheetSerializer()
game = GameSerializer()
class Meta:
model = Pick
fields = ('url', 'sheet', 'amount', 'spread', 'pick_type', 'pick_team', 'game')
With respective ViewSets:
class PickViewset(viewsets.ModelViewSet):
queryset = Pick.objects.all()
serializer_class = PickSerializer
class GameViewset(viewsets.ModelViewSet):
queryset = Game.objects.all()
serializer_class = GameSerializer
class SheetViewset(viewsets.ModelViewSet):
queryset = Sheet.objects.all()
serializer_class = SheetSerializer
What I'm currently having trouble with is how to represent more complex endpoints than single-object or list of same-type object CRUD operations. For example, I currently have a regular Django view for matchups which pulls the users Sheet (collection of Picks), another users Sheet, and displays the nested Picks against each other. I'm also planning to display other data on the same page from the other users in whatever League they're a part of. The implementation for the user + opponent data in vanilla Django looks like this:
class MatchupDetail(DetailView):
template_name = 'app/matchups.html'
context_object_name = 'pick_sheet'
def get_object(self):
#logic to find and return object
def get_opponent(self,username,schedule,week, **kwargs):
#logic to find and return the opponent in the matchup
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#logic to pull the opponents details and set them in the context
I've been struggling on how to represent this in a ViewSet. With a regular Django view, it's easy to write the get_object (or get_list) as well as get_context_data, include the user's data + any other desired objects from the database, and pass all of that to the template. Currently, the (early) API endpoint version of the above looks like this:
class MatchupViewset(viewsets.ReadOnlyModelViewSet):
serializer_class = SheetSerializer
def get_queryset(self):
user = self.request.user
return Sheet.objects.filter(user=self.request.user)
def list(self, request, format=None):
sheets = self.get_queryset()
serializer = SheetSerializer(sheets, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk, format=None):
sheet = Sheet.objects.get(user=self.request.user, league_week=pk)
serializer = SheetSerializer(sheet, context={'request':request})
return Response(serializer.data)
This code works, but only returns a limited subset of objects- a single users Sheet(s). My aim in this Viewset is to return a list of all the matchups (whi are just a collection of user Sheets) for a given league with list() and given league + week with retrieve(), but I'm not sure how to return that data. With the normal Django context variable, extra data you include can be named in any manner you like- how is the goal of collecting and returning an arbitrary number of potentially different objects accomplished with DRF? edit: this wasn't very clear initially- I've gotten nested representation working, but I'm wondering if there's a way to name/label the different serialized objects in the manner that information in the normal context can be set with any name
Say I want to pass the requesting users serialized Sheet, along with their opponents Sheet and all the other players Sheets for this particular league and week. Will the logic to determine which belongs to the user, which belongs to their opponent, and which are the other players' have to live in the frontend code?
Also, how would I configure the router so that the list() method always requires a URL parameter (for the league)? Anytime I change the registration from
router.register(r'matchup', MatchupViewset, base_name = 'matchup')
to something like
router.register(r'matchup/(?P<league>[0-9]+)/$', MatchupViewset, base_name = 'matchup')
the endpoint disappears from the browsable API root.
how is the goal of collecting and returning an arbitrary number of potentially different objects accomplished with DRF?
I'm not sure what the question is exactly here. I assume you'll want nested representations which is explained here.
the endpoint disappears from the browsable API root.
This is because the browsable API doesn't know how to resolve the extra kwargs. Since no url matches, he won't display the link
I've got a relationship between User's and Image's where every user can have multiple images. I need to define an endpoint that retrieves all images for a specific user:
GET /users/:id/images
I've done it like this:
urls.py
router = routers.DefaultRouter()
router.register(r'images', ImageViewSet)
image_list = ImageViewSet.as_view({
'get': 'list'
})
urlpatterns = patterns('',
...
url(r'^', include(router.urls)),
url(r'^users/(?P<user_id>[^/]+)/images/$', image_list),
...
)
image/views.py
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
def get_queryset(self):
user_id = self.kwargs.get('user_id', None)
if user_id:
return Image.objects.filter(user_id=user_id)
return super(ImageViewSet, self).get_queryset()
It works but I'm not happy with it. Imagine a few additional endpoints that are analogous to /users/:user_id/images/, i.e. something along the lines of /categories/:category_id/images/, etc. Having get_queryset as an entry-point for both of those, letting it distinguish between them based on kwargs, doesn't seem very appealing. Is there a better way to do it?
In the basic case you end up specifying a model and a serializer class. There's no reason why you couldn't put both of these in some kind of table and use kwargs to do a lookup.
LOOK_UP_TABLE = {
'users' : {
'model': ...,
'serializer_class': ...,
},
'categories' : {
...
},
}
If you did this, as well as overriding get_queryset, you could override get_serializer_class too. You'd then have something pretty generic.
I'm not how much typing you'd really be saving (especially if you use your editor's code snippets features). Or how flexible the resultant system would be. But it's an interesting idea.
You really should use super() in get_queryset() to get the unfiltered query set and just filter it down based on the kwargs.
if user_id:
super(ImageViewSet, self).get_queryset().filter(user_id=user_id)
You'll notice that this becomes very generic and can be included into each view as a mixin:
class FilterByUserMixin(object):
def get_queryset(self):
user_id = self.kwargs.get('user_id', None)
queryset = super(FilterByUserMixin, self).get_queryset()
if user_id:
queryset = queryset.filter(user_id=user_id)
return querset
class ImageViewSet(FilterByUserMixin, viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
class CategoryViewSet(FilterbyUserMixin, viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
I have three models — articles, authors and tweets. I'm ultimately needing to use Django REST Framework to construct a feed that aggregates all the objects using the Article and Tweet models into one reverse chronological feed.
Any idea how I'd do that? I get the feeling I need to create a new serializer, but I'm really not sure.
Thanks!
Edit: Here's what I've done thus far.
app/serializers.py:
class TimelineSerializer(serializers.Serializer):
pk = serializers.Field()
title = serializers.CharField()
author = serializers.RelatedField()
pub_date = serializers.DateTimeField()
app/views.py:
class TimelineViewSet(viewsets.ModelViewSet):
"""
API endpoint that lists all tweet/article objects in rev-chrono.
"""
queryset = itertools.chain(Tweet.objects.all(), Article.objects.all())
serializer_class = TimelineSerializer
It looks pretty close to me. I haven't used ViewSets in DRF personally, but I think if you change your code to this you should get somewhere (sorry - not tested either of these):
class TimelineViewSet(viewsets.ModelViewSet):
"""
API endpoint that lists all tweet/article objects in rev-chrono.
"""
def list(self, request):
queryset = list(itertools.chain(Tweet.objects.all(), Article.objects.all()))
serializer = TimelineSerializer(queryset, many=True)
return Response(serializer.data)
If you're not wedded to using a ViewSet then a generics.ListAPIView would be a little simpler:
class TimeLineList(generics.ListAPIView):
serializer_class = TimeLineSerializer
def get_queryset(self):
return list(itertools.chain(Tweet.objects.all(), Article.objects.all()))
Note you have to convert the output of chain to a list for this to work.