Upgrade to Pro — share decks privately, control downloads, hide ads and more …

API-Driven Django

API-Driven Django

A tutorial on making API-Driven Django apps.

Philip James

May 10, 2018
Tweet

More Decks by Philip James

Other Decks in Programming

Transcript

  1. API-Driven Django
    Philip James
    PyCon 2018
    @phildini

    View full-size slide

  2. Format
    • Buddy system
    • Switching pairs
    • SHcky Notes
    • Learn by trying first
    • You can get this all at the end

    View full-size slide

  3. Some Pairing Best PracHces
    • Less familiar person should type first
    • Switching pairs at different sessions
    • One machine, one set of hands
    • No grabbing the keyboard

    View full-size slide

  4. Goals
    • Build a working Django API-First App for collecHng civic
    government data
    • Have that App use the same code for API endpoints as well
    as rendered templates
    • Have that app let anyone read, but only authorized users
    write

    View full-size slide

  5. https://www.djangoproject.com/

    View full-size slide

  6. http://www.django-rest-framework.org/

    View full-size slide

  7. CivicAPI
    • Django app to track votes taken at civic government
    meeHngs
    • Needs to support rich frontends and API clients
    • CRUD app on votes taken

    View full-size slide

  8. Create
    Retrieve
    Update
    Destroy

    View full-size slide

  9. GeXng started

    View full-size slide

  10. Pipenv
    https://docs.pipenv.org/
    GeXng started

    View full-size slide

  11. mkdir civicapi && cd civicapi
    pipenv --three install django
    GeXng started

    View full-size slide

  12. pipenv run
    vs.
    pipenv shell
    GeXng started

    View full-size slide

  13. pipenv shell
    django-admin.py startproject civicapi .
    GeXng started

    View full-size slide

  14. django-admin.py startapp votes
    GeXng started

    View full-size slide

  15. python manage.py migrate
    GeXng started

    View full-size slide

  16. python manage.py createsuperuser
    GeXng started

    View full-size slide

  17. python manage.py runserver
    GeXng started

    View full-size slide

  18. {Check data in admin}

    View full-size slide

  19. •subject: string, required
    •vote_taken: date and Hme, required
    •ayes: integer, not required
    •nays: integer, not required
    Vote Model
    https://docs.djangoproject.com/en/2.0/ref/models/fields/
    google: django model field reference

    View full-size slide

  20. Pair Exercise - 10 min
    1. Make sure reqs are installed and project is set up
    2. Try to build the model in votes/models.py
    3. Add votes to installed_apps in civicapi/seXngs.py
    4. python manage.py migrate
    5. (extra - see it in admin)
    https://docs.djangoproject.com/en/2.0/ref/models/fields/
    google: django model field reference

    View full-size slide

  21. Django Generic Views?

    View full-size slide

  22. To create:
    • View that lists and creates Votes
    We need:
    1. django.views.generic.list.ListView
    2. django.views.generic.edit.CreateView
    3. Template file
    4. URL update
    5. (Possibly) a ModelForm

    View full-size slide

  23. What about an API?

    View full-size slide

  24. Two op&ons:
    1. Add separate API views
    2. Munge JSON responses into exisHng views

    View full-size slide

  25. What’s beker for the client?
    What’s beker for the dev?

    View full-size slide

  26. Rich frontends, client-first
    ==
    API-Driven, API First

    View full-size slide

  27. StarHng with

    View full-size slide

  28. To create:
    • APIView that lists and creates Votes
    We need:
    1. ModelSerializer for Vote
    2. rest_framework.generics.ListCreateAPIView

    View full-size slide

  29. DRF ModelSerializers
    http://www.django-rest-framework.org/api-guide/serializers/#modelserializer

    View full-size slide

  30. DRF ModelSerializers
    class AccountSerializer(serializers.ModelSerializer):
    class Meta:
    model = Account
    fields = ('id', 'account_name', 'users', 'created')
    http://www.django-rest-framework.org/api-guide/serializers/#modelserializer

    View full-size slide

  31. Pair Exercise - 10 min
    1. pipenv install djangorestframework
    2. add rest_framework to the Django app
    3. Try to make the ModelSerializer for Vote
    in the votes app
    4. write a test to verify the serialized data
    http://www.django-rest-framework.org/api-guide/serializers/#modelserializer

    View full-size slide

  32. DRF ListCreateAPIView

    View full-size slide

  33. DRF ListCreateAPIView
    class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    View full-size slide

  34. Pair Exercise - 10 min
    1. add a VoteList ListCreateAPIView to views
    2. add the view to urls.py at /votes/
    3. python manage.py runserver
    4. open a browser, browse to localhost:8000
    5. play around, try to create some votes
    6. (opHonal: write a test for the view)
    http://www.django-rest-framework.org/api-guide/generic-views/#examples

    View full-size slide

  35. DRF Renderers

    View full-size slide

  36. DRF JSONRenderer
    OrderedDict([('count', 3), ('next', None), ('previous', None),
    ('results', [OrderedDict([('id', 1), ('subject', 'More apps
    should be built in Django!'), ('vote_taken',
    '2018-04-14T19:51:09Z'), ('ayes', 100), ('nays', 0)]),…])])
    {'count': 3, 'next': None, 'previous': None, 'results': [{'id': 1,
    'subject': 'More apps should be built in Django!',
    'vote_taken': '2018-04-14T19:51:09Z', 'ayes': 100, 'nays':
    0},...]}

    View full-size slide

  37. DRF TemplateHTMLRenderer
    OrderedDict([('count', 3), ('next', None), ('previous', None),
    ('results', [OrderedDict([('id', 1), ('subject', 'More apps
    should be built in Django!'), ('vote_taken',
    '2018-04-14T19:51:09Z'), ('ayes', 100), ('nays', 0)]),…])])


    More apps should be built in Django! - 100/0 on
    2018-04-14T19:51:09Z



    View full-size slide

  38. DRF BrowsableAPIRenderer
    OrderedDict([('count', 3), ('next', None), ('previous', None),
    ('results', [OrderedDict([('id', 1), ('subject', 'More apps
    should be built in Django!'), ('vote_taken',
    '2018-04-14T19:51:09Z'), ('ayes', 100), ('nays', 0)]),…])])

    View full-size slide

  39. Django TemplaHng

    {% for user in results %}
    {{ user.name }}
    {% endfor %}

    View full-size slide

  40. DRF Template Gotcha!
    In se,ngs.py:
    REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.paginaHon.PageNumberPaginaHon',
    'PAGE_SIZE': 10,
    }

    View full-size slide

  41. Pair Exercise - 20 min
    1. add a templates directory to votes
    2. add a vote_list.html template
    3. add the TemplateHTMLRenderer to VoteList’s
    renderer_classes, along with JSONRenderer and
    BrowsableAPIRenderer
    4. point the template_name to “vote_list.html"
    5. Create votes/templates/vote_list.html
    6. Browse to /votes/ and see the list.
    http://www.django-rest-framework.org/api-guide/renderers/#templatehtmlrenderer

    View full-size slide

  42. Tip!
    ?format=

    View full-size slide

  43. Why not use the
    BrowseableAPIRenderer
    for everything?

    View full-size slide

  44. CreaHng a Vote with
    the rendered template

    View full-size slide

  45. CreaHng a Vote with the
    rendered template

    {% csrf_token %}

    Subject type="text">


    Vote taken name="vote_taken" type="datetime-local">


    Ayes


    Nays



    View full-size slide

  46. CreaHng a Vote with the
    rendered template
    def create(self, request, *args, **kwargs):
    response = super(VoteList, self).create(request, *args, **kwargs)
    if request.accepted_renderer.format == 'html' and response.status_code == 201:
    return redirect('/votes/')
    return response

    View full-size slide

  47. Pair Exercise - 5 min
    1. add the code snippets to vote_list.html and
    views.py
    2. Browse to /votes/ and add a vote via the interface.

    View full-size slide

  48. {Review Code}

    View full-size slide

  49. DRF RetrieveUpdateDestroyAPIView
    class UserDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    View full-size slide

  50. Pair Exercise - 10 min
    1. add a VoteDetail view, using
    generics.RetrieveUpdateDestroyAPIView
    2. Don’t forget the right renderers!
    3. add that view to urls.py
    4. add a vote.html template
    5. point the template_name to “vote.html"
    6. Wire up the links in “vote_list.html”
    7. Browse to /votes/ and see the list, make sure the
    links are clickable.
    http://www.django-rest-framework.org/tutorial/3-class-based-views/#using-generic-class-
    based-views

    View full-size slide

  51. AuthenHcaHon

    View full-size slide

  52. Reading vs. WriHng

    View full-size slide

  53. Session, Token, OAuth

    View full-size slide

  54. DRF AuthenHcaHon Classes
    &&
    DRF Permissions Classes

    View full-size slide

  55. Pair Exercise - 10 min
    1. add permissions.IsAuthenHcatedOrReadOnly to
    VoteList view and VoteDetail view
    2. Browse to /votes/ try adding a vote
    3. Open an anonymous browser window, try doing the
    same thing
    4. (opHonal: add some tests for the authed and non-
    authed requests)
    http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#adding-
    required-permissions-to-views

    View full-size slide

  56. Keeping things DRY

    View full-size slide

  57. Keeping things DRY
    •We can use mixins for common funcHonality
    •Capture auth, permissions, renderers in one
    place
    •While we’re here, only allow the
    BrowseableAPIRenderer to staff users

    View full-size slide

  58. def get_renderers(self):
    renderer_classes = self.renderer_classes
    if self.request.user.is_staff:
    renderer_classes += [BrowsableAPIRenderer]
    return [renderer() for renderer in renderer_classes]

    View full-size slide

  59. Pair Exercise - 10 min
    1. add a VoteAPIMixin that captures renderer classes,
    queryset, serializer_class, and permission classes
    2. Make sure the get_renderers snippet is defined in
    the mixin
    3. Add the mixin to both view classes, remove the
    duplicate code from the views
    4. Browse the api and make sure things sHll work.
    5. Run tests and make sure things sHll work

    View full-size slide

  60. Pair Exercise - 10 min
    1. add ‘rest_framework.authtoken' to seXngs.py
    2. Add correct authenHcaHon classes to seXngs.py
    ‘DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework.authentication.BasicAuthentication',
    'rest_framework.authentication.SessionAuthentication',
    'rest_framework.authentication.TokenAuthentication',
    )
    http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

    View full-size slide

  61. TesHng
    • http://www.django-rest-framework.org/
    api-guide/testing/
    • https://docs.djangoproject.com/en/2.0/
    topics/testing/
    •“Code without tests is broken as designed.”
    - Jacob Kaplan-Moss

    View full-size slide

  62. Pair Exercise - 10 min
    1. Add more tests for VoteList and VoteDetail views
    2. Pay special akenHon to edge cases with
    authenHcaHon and renderer type
    3. Any more tests you can think of?
    4. (opHonal: add ability to edit votes from VoteDetail)

    View full-size slide

  63. Where to go from
    here?

    View full-size slide

  64. Deploying
    OAuth
    Django Channels
    Working with Web Frontends

    View full-size slide

  65. Thanks!
    Philip James
    [email protected]
    @[email protected]
    https://github.com/phildini/api-driven-django

    View full-size slide