Views¶
Now it’s time to make our data visible to the outside world.
django-nap builds on Django’s Class-Based Generic Views.
Question List¶
Now it’s time to add our question list endpoint.
First, we’ll define a common QuestionMixin class to help hold common
definitions for list and object views:
from nap.rest import views
from . import mappers, models
class QuestionMixin:
model = models.Question
mapper_class = mappers.QuestionMapper
Next we’ll define our QuestionListView, based on this and the
ListBaseView fron nap:
class QuestionListView(QuestionMixin,
views.ListBaseView):
pass
As it is, this view won’t do anything, as it has no get, post or other
methods. What it does provide is Django’s MultipleObjectMixin, along with
nap’s MapperMixin and NapView classes.
To add the default GET behavior for a list, we mix in the ListGetMixin:
class QuestionListView(QuestionMixin,
views.ListGetMixin,
views.ListBaseView):
pass
The ListGetMixin adds a simple get method, which will return a list of mapped instances of our model.
Let’s add our new view to the existing urls, but with a ‘api/’ prefix:
from django.conf.urls import url
from . import views
urlpatterns = [
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# ex: /polls/5/results/
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
url(r'^api/', include([
url(r'question/$', views.QuestionListView.as_view()),
]))
]
So we can now access our list of Questions at http://localhot:8000/api/question/ and should see something like this:
[
{
"id": 1,
"question_text": "What's new?",
"pub_date": "2017-06-17 05:30:58+00:00",
"age": "20\u00a0hours, 15\u00a0minutes"
}
]
Nested Records¶
That’s great, but a Question with no Choices isn’t much use, is it?
We can ask our mapper to render a list of related records using a ToMany field:
class QuestionMapper(mapper.ModelMapper):
class Meta:
model = models.Question
fields = '__all__'
@mapper.field
def age(self):
return timesince(self.pub_date)
choices = mapper.ToManyField('choice_set')
The ToManyField will check if its value is a django.db.models.Manager,
and call .all() on it if it is.
And now out output will look something like this:
[
{
"id": 1,
"age": "20\u00a0hours, 19\u00a0minutes",
"question_text": "What's new?",
"pub_date": "2017-06-17 05:30:58+00:00",
"choices": [1, 2]
}
]
By default, a ToManyField will only render the primary keys of the related objects. If you want to control how it’s serialised, just specify a mapper on the field.
choices = mapper.ToManyField('choice_set', mapper=ChoiceMapper)
Which will give us this output:
[
{
"pub_date": "2017-06-17 05:30:58+00:00",
"age": "20\u00a0hours, 22\u00a0minutes",
"question_text": "What's new?",
"id": 1,
"choices": [
{
"question": 1,
"choice_text": "First Choice",
"id": 1,
"votes": 0
},
{
"question": 1,
"choice_text": "Another Choice",
"id": 2,
"votes": 0
}
]
}
]
We really don’t need the question ID embedded there, so let’s define a new choice mapper which will exclude that.
class InlineChoiceMapper(mapper.ModelMapper):
class Meta:
model = models.Choice
fields = '__all__'
exclude = ('question',)
And finally we see:
[
{
"choices": [
{
"votes": 0,
"id": 1,
"choice_text": "First Choice"
},
{
"votes": 0,
"id": 2,
"choice_text": "Another Choice"
}
],
"question_text": "What's new?",
"age": "20\u00a0hours, 27\u00a0minutes",
"pub_date": "2017-06-17 05:30:58+00:00",
"id": 1
}
]