Full Code of agiliq/building-api-django for AI

master 2c14b45a3f80 cached
40 files
111.8 KB
27.6k tokens
35 symbols
1 requests
Download .txt
Repository: agiliq/building-api-django
Branch: master
Commit: 2c14b45a3f80
Files: 40
Total size: 111.8 KB

Directory structure:
gitextract_czqqekaq/

├── .circleci/
│   └── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs/
│   ├── Documenting-API-with-RAML.rst.draft
│   ├── Makefile
│   ├── access-control.rst
│   ├── apis-without-drf.rst
│   ├── conf.py
│   ├── index.rst
│   ├── introduction.rst
│   ├── make.bat
│   ├── more-views-and-viewsets.rst
│   ├── postman.rst
│   ├── serailizers.rst
│   ├── setup-models-admin.rst
│   ├── swagger.rst
│   ├── testing-and-ci.rst
│   └── views-and-generic-views.rst
├── pollsapi/
│   ├── .gitignore
│   ├── docs.raml
│   ├── docs.swagger.json
│   ├── manage.py
│   ├── polls/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apiviews.py
│   │   ├── apps.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── pollsapi/
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── requirements.txt
└── requirements.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/python:3.6.1


    working_directory: ~/repo

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "pollsapi/requirements.txt" }}
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-

      - run:
          name: install dependencies
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r pollsapi/requirements.txt

      - save_cache:
          paths:
            - ./venv
          key: v1-dependencies-{{ checksum "requirements.txt" }}

      # run tests!
      # this example uses Django's built-in test-runner
      # other common Python testing frameworks include pytest and nose
      # https://pytest.org
      # https://nose.readthedocs.io
      - run:
          name: run tests
          command: |
            . venv/bin/activate
            cd pollsapi
            python manage.py test

      - store_artifacts:
          path: test-reports
          destination: test-reports



================================================
FILE: .gitignore
================================================
# sphinx build folder
_build

# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so

# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip

# Logs and databases #
######################
*.log
*.sql
*.sqlite

# OS generated files #
######################
.DS_Store
ehthumbs.db
Icon?
Thumbs.db

# Editor backup files #
#######################
*~

================================================
FILE: LICENSE
================================================
This work is licensed under a CC-BY-SA 4.0 (Creative Commons Attribution-ShareAlike 4.0 International License) licence. 
The full text is available at: https://creativecommons.org/licenses/by-sa/4.0/


================================================
FILE: README.md
================================================
 Building APIs with Django and Django Rest Framework
==================================================================

[Read it online](http://books.agiliq.com/projects/django-api-polls-tutorial/)

An opinionated guide to building APIs with modern tools.



Dramatis personæ
================

* Python: A programming language
* Django: A framework for Python
* Django rest framework: A flexible toolkit for building APIs
* CircleCI: Keeps your documentation and API in sync


Chapters
========

* The importance of Documentation driven development and summary of API we will need
* What we will build and the API
* [Models and Admin (Probably the same as Django tutorial)](http://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/chapter1.html)
* [Building API with Django-Rest-Framework](http://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/chapter2.html)
* [Authentication and access control with DRF](http://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/chapter3.html)
* [Testing and CI: Introduction to Django testing, language agnostic API testing and CircleCI](http://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/chapter4.html)
* Consuming and collaborating with API: Introduce Postman, ngrok, angular


================================================
FILE: docs/Documenting-API-with-RAML.rst.draft
================================================
Documenting API with RAML
============================

In this chapter we will see how to use raml for all the views of our API.

RAML is an acronym for "RESTful API Modeling Language". It is a YAML based language for describing the RESTful APIs. RAML contains certain sections for describing the APIs. Each section has it's purpose and we'll look into each one of these by using our polls application.


1. Root
----------------------

Root section is specifies the basic things like title, baseUri etc. These are applied through out the rest of the API

.. code-block:: python

    #%RAML 0.8
        ---
        title: django polls API
        baseUri: http://api.example.com/{version}
        version: v1
        mediaType: application/json


2. Resources
---------------------

It's important to consider how your API consumers will use your API. So we'll list out all the resources that are available in our API.

.. code-block:: python

    /polls:
    /choices:
    /votes:

Notice that these resources all begin with a slash (/). In RAML, this is how you indicate a resource. Any methods and parameters nested under these top level resources belong to and act upon that resource.

Now, since each of these resources is a collection of individual objects (specific poll, choice), we'll need to define some sub-resources to fill out the collection.

To view all the API endpoints, we need to specify them in a JSON file with the following format. You may call it pollaspi.json.

.. code-block:: python

    /polls:
        /{pollId}:

This lets the API consumer interact with the key resource and its nested resources. For example a GET request to http://api.example.com/polls/1 returns details about the one particular poll whose pollId is 1.


3. Methods
--------------

The above mentioned resources can be accessed via 4 most common HTTP methods(Verbs).

GET - Retrieve the information defined in the request URI.

PUT - Replace the addressed collection. At the object-level, create or update it.

POST - Create a new entry in the collection. This method is generally not used at the object-level.

DELETE - Delete the information defined in the request URI.

You can add as many methods as you like to each resource of your BookMobile API, at any level. However, each HTTP method can only be used once per resource.

Nest the methods to allow developers to perform these actions under your resources. Note that you must use lower-case for methods in your RAML API definition.

.. code-block:: python

    /polls:
        get:
        post:


URI Parameters
---------------

The resources that we defined are collections of smaller, relevant objects.

.. code-block:: python

    /polls:
        /{pollId}:


Query Parameters:
--------------------

Query parameters are used for filtering a collection. We already have collections-based resource types that are further defined by object-based URI parameters. We'll see how we can use query paramters for them.

.. code-block:: python

    /polls:
        get:
            description: Get list of polls
            queryParameters:
                pollId:

An API's resources and methods often have a number of associated query parameters. Each query parameter may have any number of optional attributes to further define it.

Now, we'll specify attributes for the query parameters we defined above.

.. code-block:: python

    /polls:
        get:
            description: Get list of polls
            queryParameters:
                pollId:
                    description: Specify the poll id you want to retrieve
                    type: integer
                    example: 1


Responses:
-------------

Responses MUST be a map of one or more HTTP status codes, and each response may include descriptions, examples.

.. code-block:: python

    responses:
        200:
            body:
                application/json:
                example:
                {
                    "data":
                    {
                        "Id": 1,
                        "question": "Will A be the leader next time?",
                        "created_by": "user1",
                        "pub_date": "08:02:2014"
                    },
                    "success": true,
                    "status": 200
                }


================================================
FILE: docs/Makefile
================================================
# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
PAPER         =
BUILDDIR      = _build

# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext

help:
	@echo "Please use \`make <target>' where <target> is one of"
	@echo "  html       to make standalone HTML files"
	@echo "  dirhtml    to make HTML files named index.html in directories"
	@echo "  singlehtml to make a single large HTML file"
	@echo "  pickle     to make pickle files"
	@echo "  json       to make JSON files"
	@echo "  htmlhelp   to make HTML files and a HTML help project"
	@echo "  qthelp     to make HTML files and a qthelp project"
	@echo "  applehelp  to make an Apple Help Book"
	@echo "  devhelp    to make HTML files and a Devhelp project"
	@echo "  epub       to make an epub"
	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
	@echo "  text       to make text files"
	@echo "  man        to make manual pages"
	@echo "  texinfo    to make Texinfo files"
	@echo "  info       to make Texinfo files and run them through makeinfo"
	@echo "  gettext    to make PO message catalogs"
	@echo "  changes    to make an overview of all changed/added/deprecated items"
	@echo "  xml        to make Docutils-native XML files"
	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
	@echo "  linkcheck  to check all external links for integrity"
	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
	@echo "  coverage   to run coverage check of the documentation (if enabled)"

clean:
	rm -rf $(BUILDDIR)/*

html:
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

dirhtml:
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."

singlehtml:
	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
	@echo
	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."

pickle:
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
	@echo
	@echo "Build finished; now you can process the pickle files."

json:
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
	@echo
	@echo "Build finished; now you can process the JSON files."

htmlhelp:
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in $(BUILDDIR)/htmlhelp."

qthelp:
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
	@echo
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BuildingAPIDjango.qhcp"
	@echo "To view the help file:"
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BuildingAPIDjango.qhc"

applehelp:
	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
	@echo
	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
	@echo "N.B. You won't be able to view it unless you put it in" \
	      "~/Library/Documentation/Help or install it in your application" \
	      "bundle."

devhelp:
	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
	@echo
	@echo "Build finished."
	@echo "To view the help file:"
	@echo "# mkdir -p $$HOME/.local/share/devhelp/BuildingAPIDjango"
	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BuildingAPIDjango"
	@echo "# devhelp"

epub:
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
	@echo
	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."

latex:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
	@echo "Run \`make' in that directory to run these through (pdf)latex" \
	      "(use \`make latexpdf' here to do that automatically)."

latexpdf:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through pdflatex..."
	$(MAKE) -C $(BUILDDIR)/latex all-pdf
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

latexpdfja:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through platex and dvipdfmx..."
	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

text:
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
	@echo
	@echo "Build finished. The text files are in $(BUILDDIR)/text."

man:
	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
	@echo
	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."

texinfo:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo
	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
	@echo "Run \`make' in that directory to run these through makeinfo" \
	      "(use \`make info' here to do that automatically)."

info:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo "Running Texinfo files through makeinfo..."
	make -C $(BUILDDIR)/texinfo info
	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."

gettext:
	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
	@echo
	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."

changes:
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
	@echo
	@echo "The overview file is in $(BUILDDIR)/changes."

linkcheck:
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in $(BUILDDIR)/linkcheck/output.txt."

doctest:
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
	@echo "Testing of doctests in the sources finished, look at the " \
	      "results in $(BUILDDIR)/doctest/output.txt."

coverage:
	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
	@echo "Testing of coverage in the sources finished, look at the " \
	      "results in $(BUILDDIR)/coverage/python.txt."

xml:
	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
	@echo
	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."

pseudoxml:
	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
	@echo
	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."


================================================
FILE: docs/access-control.rst
================================================
Access Control
=================================

In this chapter, we will add access control to our APIs,
and add APIs to create and authenticate users.

Right now our APIs are completely permissive. Anyone can create, access and delete anything.
We want to add these access controls.


- A user must be authenticated to access a poll or the list of polls.
- Only an authenticated users can create a poll.
- Only an authenticated user can create a choice.
- Authenticated users can create choices only for polls they have created.
- Authenticated users can delete only polls they have created.
- Only an authenticated user can vote. Users can vote for other people's polls.

To enable the access control, we need to add two more APIs

- API to create a user, we will call this endpoint :code:`/users/`
- API to verify a user and get a token to identify them, we will call this endpoint :code:`/login/`



Creating a user
--------------------------


We will add an user serializer, which will allow creating. Add the following code to :code:`serializers.py`.

.. code-block:: python

    # ...
    from django.contrib.auth.models import User
    
    # ...
    class UserSerializer(serializers.ModelSerializer):

        class Meta:
            model = User
            fields = ('username', 'email', 'password')
            extra_kwargs = {'password': {'write_only': True}}

        def create(self, validated_data):
            user = User(
                email=validated_data['email'],
                username=validated_data['username']
            )
            user.set_password(validated_data['password'])
            user.save()
            return user

We have overriden the ModelSerializer method's :code:`create()` to save the :code:`User` instances. We ensure that we set the password correctly using :code:`user.set_password`, rather than setting the raw password as the hash. We also don't want to get back the password in response which we ensure using :code:`extra_kwargs = {'password': {'write_only': True}}`.

Let us also add views to the User Serializer for creating the user and connect it to the urls.py

.. code-block:: python

    # in apiviews.py
    # ...
    from .serializers import PollSerializer, ChoiceSerializer, VoteSerializer, UserSerializer

    # ...
    class UserCreate(generics.CreateAPIView):
        serializer_class = UserSerializer

    # in urls.py
    # ...
    from .apiviews import PollViewSet, ChoiceList, CreateVote, UserCreate


    urlpatterns = [
        # ...
        path("users/", UserCreate.as_view(), name="user_create"),
    ]

We can test this api by posting to :code:`/users/` with this json.

.. code-block:: json

    {
        "username": "nate.silver",
        "email": "nate.silver@example.com",
        "password": "FiveThirtyEight"
    }

Which give back this response.

.. code-block:: json

    {
        "username": "nate.silver",
        "email": "nate.silver@example.com"
    }

Try posting the same json, and you will get a error response (HTTP status code 400)

.. code-block:: json

    {
        "username": [
            "A user with that username already exists."
        ]
    }


Authentication scheme setup
-----------------------------

With Django Rest Framework, we can set up a default authentication scheme which is applied to all views using :code:`DEFAULT_AUTHENTICATION_CLASSES`. We will use the token authentication in this tutorial. In your settings.py, add this.

.. code-block:: python

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.TokenAuthentication',
            'rest_framework.authentication.SessionAuthentication',
        )
    }

You also need to enable :code:`rest_framework.authtoken` app, so update :code:`INSTALLED_APPS` in your settings.py.

.. code-block:: python

    INSTALLED_APPS = (
        ...
        'rest_framework.authtoken'
    )

Run :code:`python manage.py migrate` to create the new tables.

.. We want to ensure that, by default all apis are only allowed to authenticated users. Add this to your :code:`settings.py`.

.. code-block:: python

    REST_FRAMEWORK = {
        # ...
        'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',
        )
    }

Also, dont forget to give exemption to :code:`UserCreate` view for authentication by overriding the global setting. The :code:`UserCreate` in :code:`polls/apiviews.py` should look as follows.

.. code-block:: python

    class UserCreate(generics.CreateAPIView):
        authentication_classes = ()
        permission_classes = ()
        serializer_class = UserSerializer

Note the :code:`authentication_classes = ()` and :code:`permission_classes = ()` to exempt :code:`UserCreate` from global authentication scheme.

We want to ensure that tokens are created when user is created in :code:`UserCreate` view, so we update the :code:`UserSerializer`. Change your :code:`serializers.py` like this

.. code-block:: python

    from rest_framework.authtoken.models import Token

    class UserSerializer(serializers.ModelSerializer):

        class Meta:
            model = User
            fields = ('username', 'email', 'password')
            extra_kwargs = {'password': {'write_only': True}}

        def create(self, validated_data):
            user = User(
                email=validated_data['email'],
                username=validated_data['username']
            )
            user.set_password(validated_data['password'])
            user.save()
            Token.objects.create(user=user)
            return user



The login API
-----------------------------

Since we have added :code:`rest_framework.authentication.TokenAuthentication`, we will need to set a header like this :code:`Authorization: Token c2a84953f47288ac1943a3f389a6034e395ad940` to authenticate. We need an API where a user can give their username and password, and get a token back.

We will not be adding a serializer, because we never save a token using this API.

Add a view and connect it to urls.

.. code-block:: python

    # in apiviews.py
    # ...
    from django.contrib.auth import authenticate

    class LoginView(APIView):
        permission_classes = ()

        def post(self, request,):
            username = request.data.get("username")
            password = request.data.get("password")
            user = authenticate(username=username, password=password)
            if user:
                return Response({"token": user.auth_token.key})
            else:
                return Response({"error": "Wrong Credentials"}, status=status.HTTP_400_BAD_REQUEST)


    # in urls.py
    # ...

    from .apiviews import PollViewSet, ChoiceList, CreateVote, UserCreate, LoginView



    urlpatterns = [
        path("login/", LoginView.as_view(), name="login"),
        # ...
    ]

WARNING: You have to create a user using the :code:`/user/` endpoint before logging in using the :code:`/login/` endpoint. Using a previously existing user will result in a "User has no auth_token" error because we have not created a token for them. You can create tokens for them manually by using the django shell :code:`$ python manage.py shell`.

    >>> from django.contrib.auth.models import User
    >>> from rest_framework.authtoken.models import Token
    >>> user = User.objects.get(pk=pk_of_user_without_token)
    >>> Token.objects.create(user=user)
    <Token: e2b9fa2d4ae27fe1fdcf17b6e37711334d07e167>

Do a POST with a correct username and password, and you will get a response like this.

.. code-block:: json

    {
        "token": "c300998d0e2d1b8b4ed9215589df4497de12000c"
    }


POST with a incorrect username and password, and you will get a response like this, with a HTTP status of 400.

.. code-block:: json

    {
        "error": "Wrong Credentials"
    }

Another way to create this login endpoint is using :code:`obtain_auth_token` method provide by DRF

.. code-block:: python

    # in urls.py
    # ...
    from rest_framework.authtoken import views

    urlpatterns = [
        path("login/", views.obtain_auth_token, name="login"),
        # ...
    ]


Fine grained access control
-----------------------------

Try accessing the :code:`/polls/` API without any header. You will get an error with a http status code of :code:`HTTP 401 Unauthorized` like this.

.. code-block:: json

    {
        "detail": "Authentication credentials were not provided."
    }

Add an authorization header :code:`Authorization: Token <your token>`, and you can access the API.

From now onwards we will use a HTTP header like this, :code:`Authorization: Token <your token>` in all further requests.

We have two remaining things we need to enforce.

- Authenticated users can create choices only for polls they have created.
- Authenticated users can delete only polls they have created.

We will do that by overriding :code:`PollViewSet.destroy` and :code:`ChoiceList.post`.

.. code-block:: python

    # ...
    from rest_framework.exceptions import PermissionDenied


    class PollViewSet(viewsets.ModelViewSet):
        # ...

        def destroy(self, request, *args, **kwargs):
            poll = Poll.objects.get(pk=self.kwargs["pk"])
            if not request.user == poll.created_by:
                raise PermissionDenied("You can not delete this poll.")
            return super().destroy(request, *args, **kwargs)


    class ChoiceList(generics.ListCreateAPIView):
        # ...

        def post(self, request, *args, **kwargs):
            poll = Poll.objects.get(pk=self.kwargs["pk"])
            if not request.user == poll.created_by:
                raise PermissionDenied("You can not create choice for this poll.")
            return super().post(request, *args, **kwargs)

In both cases, we are checking :code:`request.user` against the expected user, and raising
a :code:`PermissionDenied` error if it does not match.

You can check this by doing a DELETE on someone elses :code:`Poll`. You will get an error with :code:`HTTP 403 Forbidden` and response.


.. code-block:: json

    {
        "detail": "You can not delete this poll."
    }


Similarly, trying to create choice for someone else's :code:`Poll` will get an error with :code:`HTTP 403 Forbidden` and response

.. code-block:: json

    {
        "detail": "You can not create choice for this poll."
    }


Next steps:
-----------------

In the next chapter we will look at adding tests for our API and serializers. We will also look at how to use :code:`flake8` and run our tests in a CI environment.


================================================
FILE: docs/apis-without-drf.rst
================================================
A simple API with pure Django
========================================

In this chapter, we will build an API with pure Django. We will not use Django Rest Framework (Or any other library).
To start add some :code:`Poll` using the admin.

The endpoints and the URLS
+++++++++++++++++++++++++++++++

Our API will have two endpoints returning data in JSON format.

* :code:`/polls/` GETs list of :code:`Poll`
* :code:`/polls/<id>/` GETs data of a specific :code:`Poll`

Connecting urls to the views
++++++++++++++++++++++++++++++

Write two place holder view functions and connect them in your :code:`urls.py`. We will finish :code:`polls_list` and :code:`polls_detail` shortly.

.. code-block:: python

    # In views.py
    def polls_list(request):
        pass

    def polls_detail(request, pk):
        pass


    # in urls.py
    from django.urls import path
    from .views import polls_list, polls_detail

    urlpatterns = [
        path("polls/", polls_list, name="polls_list"),
        path("polls/<int:pk>/", polls_detail, name="polls_detail")
    ]


Writing the views
++++++++++++++++++++++++

We will now write the :code:`polls_list` and :code:`polls_detail`

.. code-block:: python

    from django.shortcuts import render, get_object_or_404
    from django.http import JsonResponse

    from .models import Poll

    def polls_list(request):
        MAX_OBJECTS = 20
        polls = Poll.objects.all()[:MAX_OBJECTS]
        data = {"results": list(polls.values("question", "created_by__username", "pub_date"))}
        return JsonResponse(data)


    def polls_detail(request, pk):
        poll = get_object_or_404(Poll, pk=pk)
        data = {"results": {
            "question": poll.question,
            "created_by": poll.created_by.username,
            "pub_date": poll.pub_date
        }}
        return JsonResponse(data)

This should be standard Django for you. :code:`polls = Poll.objects.all()[:20]` gets us upto 20 :code:`Poll` objects.
We get a list of dictionaries using :code:`{"results": list(polls.values("question", "created_by__username", "pub_date"))}` and return it with a :code:`JsonResponse`. A :code:`JsonResponse` is a like :code:`HttpResponse` with :code:`content-type=application/json`.

Similarly, `polls_detail` gets a specific Poll using :code:`get_object_or_404(Poll, pk=pk)`, and returns it wrapped in :code:`JsonResponse`.


Using the API
++++++++++++++++++++++++

You can now access the API using curl, wget, postman, browser or any other API consuming tools. Here is the response with curl.

.. code-block:: bash

    $ curl http://localhost:8000/polls/

    {"results": [{"pk": 1, "question": "What is the weight of an unladen swallow?", "created_by__username": "shabda", "pub_date": "2018-03-12T10:14:19.002Z"}, {"pk": 2, "question": "What do you prefer, Flask or Django?", "created_by__username": "shabda", "pub_date": "2018-03-12T10:15:55.949Z"}, {"pk": 3, "question": "What is your favorite vacation spot?", "created_by__username": "shabda", "pub_date": "2018-03-12T10:16:11.998Z"}]}

You should consider using postman or a similar tool. This is how your API looks in Postman.

.. image:: postman_polls_detail.png


Why do we need DRF?
++++++++++++++++++++++++

**(DRF = Django Rest Framework)**

We were able to build the API with just Django, without using DRF, so why do we need DRF?
Almost always, you will need common tasks with your APIs, such as access control, serialization, rate limiting and more.

DRF provides a well thought out set of base components and convenient hook points for building APIs. We will be using DRF in the rest of the chapters.


================================================
FILE: docs/conf.py
================================================
# -*- coding: utf-8 -*-
#
# Building API Django documentation build configuration file, created by
# sphinx-quickstart on Thu Apr 23 19:31:51 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys
import os
import shlex

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'Building API Django'
copyright = u'2015-2018, Agiliq'
author = u'Agiliq'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# This will track the Django version against which this is written.
version = '2.0'
# The full version, including alpha/beta/rc tags.
release = '2.0'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False


# -- Options for HTML output ----------------------------------------------
html_theme = 'sphinx_rtd_theme'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'

# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}

# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'

# Output file base name for HTML help builder.
htmlhelp_basename = 'BuildingAPIDjangodoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',

# Latex figure (float) alignment
#'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  (master_doc, 'BuildingAPIDjango.tex', u'Building APIs with Django and Django Rest Framework',
   u'Agiliq', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True


# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'buildingapidjango', u'Building API Django Documentation',
     [author], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False


# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  (master_doc, 'BuildingAPIDjango', u'Building API Django Documentation',
   author, 'BuildingAPIDjango', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False


================================================
FILE: docs/index.rst
================================================
Building APIs with Django and Django Rest Framework
======================================================

Building APIs with Django and DRF takes over where the Django tutorials stop.
In the Django tutorials, you built a regular Django polls app. We will rebuild an API for a similar app.

In the chapters to come, we will build a REST(ish) api with authorization, rate limiting, first with pure Django and then with DRF.
We will cover testing, continuous integration, documentation tools and API collaboration tools.

.. image:: cover.jpg

Chapters:

.. toctree::
   :maxdepth: 2

   introduction
   setup-models-admin
   apis-without-drf
   serailizers
   views-and-generic-views
   more-views-and-viewsets
   access-control
   testing-and-ci


Appendix
==================

.. toctree::
   :maxdepth: 2

   postman
   swagger


* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`



================================================
FILE: docs/introduction.rst
================================================
Introductions
=================

*Building APIs with Django and Django Rest Framework* starts where the `Django "Polls" tutorial <https://docs.djangoproject.com/en/2.0/intro/tutorial01/>`_ stops, and takes you through building the polls app, but this time using APIs. You will learn the basics of Django Rest Framework including serialization, views, generic views, viewsets, testing, access control. You will also learn about API documentation using swagger and raml.

Who is this book for?
-------------------------

If you have finished the Django "Polls" tutorial, and want to learn using DRF to build APIs, this book is perfect for you. This book assumes some knowledge of Django and Python, which you should have built if you have finished the "Poll" turtorial. No existing knowledge of DRF is assumed.


How to read this book?
-------------------------

The chapters are meant to be read in order. If you have existing knowledge of some chapters, you can quickly go through that chapter, but I highly recommend reading them in order as each chapter builds on the previous.




================================================
FILE: docs/make.bat
================================================
@ECHO OFF

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
	set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)

if "%1" == "" goto help

if "%1" == "help" (
	:help
	echo.Please use `make ^<target^>` where ^<target^> is one of
	echo.  html       to make standalone HTML files
	echo.  dirhtml    to make HTML files named index.html in directories
	echo.  singlehtml to make a single large HTML file
	echo.  pickle     to make pickle files
	echo.  json       to make JSON files
	echo.  htmlhelp   to make HTML files and a HTML help project
	echo.  qthelp     to make HTML files and a qthelp project
	echo.  devhelp    to make HTML files and a Devhelp project
	echo.  epub       to make an epub
	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
	echo.  text       to make text files
	echo.  man        to make manual pages
	echo.  texinfo    to make Texinfo files
	echo.  gettext    to make PO message catalogs
	echo.  changes    to make an overview over all changed/added/deprecated items
	echo.  xml        to make Docutils-native XML files
	echo.  pseudoxml  to make pseudoxml-XML files for display purposes
	echo.  linkcheck  to check all external links for integrity
	echo.  doctest    to run all doctests embedded in the documentation if enabled
	echo.  coverage   to run coverage check of the documentation if enabled
	goto end
)

if "%1" == "clean" (
	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
	del /q /s %BUILDDIR%\*
	goto end
)


REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 2> nul
if errorlevel 9009 goto sphinx_python
goto sphinx_ok

:sphinx_python

set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
	echo.
	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
	echo.installed, then set the SPHINXBUILD environment variable to point
	echo.to the full path of the 'sphinx-build' executable. Alternatively you
	echo.may add the Sphinx directory to PATH.
	echo.
	echo.If you don't have Sphinx installed, grab it from
	echo.http://sphinx-doc.org/
	exit /b 1
)

:sphinx_ok


if "%1" == "html" (
	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
	goto end
)

if "%1" == "dirhtml" (
	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
	goto end
)

if "%1" == "singlehtml" (
	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
	goto end
)

if "%1" == "pickle" (
	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can process the pickle files.
	goto end
)

if "%1" == "json" (
	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can process the JSON files.
	goto end
)

if "%1" == "htmlhelp" (
	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
	goto end
)

if "%1" == "qthelp" (
	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\BuildingAPIDjango.qhcp
	echo.To view the help file:
	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\BuildingAPIDjango.ghc
	goto end
)

if "%1" == "devhelp" (
	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished.
	goto end
)

if "%1" == "epub" (
	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The epub file is in %BUILDDIR%/epub.
	goto end
)

if "%1" == "latex" (
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
	goto end
)

if "%1" == "latexpdf" (
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
	cd %BUILDDIR%/latex
	make all-pdf
	cd %~dp0
	echo.
	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
	goto end
)

if "%1" == "latexpdfja" (
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
	cd %BUILDDIR%/latex
	make all-pdf-ja
	cd %~dp0
	echo.
	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
	goto end
)

if "%1" == "text" (
	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The text files are in %BUILDDIR%/text.
	goto end
)

if "%1" == "man" (
	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The manual pages are in %BUILDDIR%/man.
	goto end
)

if "%1" == "texinfo" (
	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
	goto end
)

if "%1" == "gettext" (
	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
	goto end
)

if "%1" == "changes" (
	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
	if errorlevel 1 exit /b 1
	echo.
	echo.The overview file is in %BUILDDIR%/changes.
	goto end
)

if "%1" == "linkcheck" (
	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
	if errorlevel 1 exit /b 1
	echo.
	echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
	goto end
)

if "%1" == "doctest" (
	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
	if errorlevel 1 exit /b 1
	echo.
	echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
	goto end
)

if "%1" == "coverage" (
	%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
	if errorlevel 1 exit /b 1
	echo.
	echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
	goto end
)

if "%1" == "xml" (
	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The XML files are in %BUILDDIR%/xml.
	goto end
)

if "%1" == "pseudoxml" (
	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
	goto end
)

:end


================================================
FILE: docs/more-views-and-viewsets.rst
================================================
More views and viewsets
======================================

A better URL structure
-----------------------------

We have three API endpoints

- :code:`/polls/` and :code:`/polls/<pk>/`
- :code:`/choices/`
- :code:`/vote/`

They get the work done, but we can make our API more intuitive by nesting them correctly. Our redesigned urls look like this:

- :code:`/polls/` and :code:`/polls/<pk>`
- :code:`/polls/<pk>/choices/` to GET the choices for a specific poll, and to create choices for a specific poll. (Idenitfied by the :code:`<pk>`)
- :code:`/polls/<pk>/choices/<choice_pk>/vote/` - To vote for the choice identified by :code:`<choice_pk>` under poll with :code:`<pk>`.

Changing the views
-----------------------------

We will make changes to :code:`ChoiceList` and :code:`CreateVote`, because the :code:`/polls/` and :code:`/polls/<pk>` have not changed.

.. code-block:: python

    from rest_framework import generics
    from rest_framework.views import APIView
    from rest_framework import status
    from rest_framework.response import Response

    from .models import Poll, Choice
    from .serializers import PollSerializer, ChoiceSerializer, VoteSerializer

    # ...
    # PollList and PollDetail views

    class ChoiceList(generics.ListCreateAPIView):
        def get_queryset(self):
            queryset = Choice.objects.filter(poll_id=self.kwargs["pk"])
            return queryset
        serializer_class = ChoiceSerializer


    class CreateVote(APIView):
        serializer_class = VoteSerializer
        
        def post(self, request, pk, choice_pk):
            voted_by = request.data.get("voted_by")
            data = {'choice': choice_pk, 'poll': pk, 'voted_by': voted_by}
            serializer = VoteSerializer(data=data)
            if serializer.is_valid():
                vote = serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            else:
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

And change your urls.py to a nested structure.

.. code-block:: python

    #...
    urlpatterns = [
        path("polls/<int:pk>/choices/", ChoiceList.as_view(), name="choice_list"),
        path("polls/<int:pk>/choices/<int:choice_pk>/vote/", CreateVote.as_view(), name="create_vote"),

    ]


You can see the changes by doing a GET to :code:`http://localhost:8000/polls/1/choices/`, which should give you.

.. code-block:: json

    [
        {
            "id": 1,
            "votes": [],
            "choice_text": "Flask",
            "poll": 1
        },
        {
            "id": 2,
            "votes": [
            ],
            "choice_text": "Django",
            "poll": 1
        }
    ]

You can vote for choices 2, of poll 1 by doing a POST to :code:`http://localhost:8000/polls/1/choices/2/vote/` with data :code:`{"voted_by": 1}`.

.. code-block:: json

    {
        "id": 2,
        "choice": 2,
        "poll": 1,
        "voted_by": 1
    }

Lets get back to :code:`ChoiceList`.

.. code-block:: python

    # urls.py
    #...
    urlpatterns = [
        # ...
        path("polls/<int:pk>/choices/", ChoiceList.as_view(), name="choice_list"),
    ]

    # apiviews.py
    # ...

    class ChoiceList(generics.ListCreateAPIView):
        def get_queryset(self):
            queryset = Choice.objects.filter(poll_id=self.kwargs["pk"])
            return queryset
        serializer_class = ChoiceSerializer

From the urls, we pass on :code:`pk` to :code:`ChoiceList`. We override the :code:`get_queryset` method, to filter on choices with this :code:`poll_id`, and let DRF handle the rest.


And for :code:`CreateVote`,

.. code-block:: python

    # urls.py
    #...
    urlpatterns = [
        # ...
        path("polls/<int:pk>/choices/<int:choice_pk>/vote/", CreateVote.as_view(), name="create_vote"),
    ]

    # apiviews.py
    # ...

    class CreateVote(APIView):

        def post(self, request, pk, choice_pk):
            voted_by = request.data.get("voted_by")
            data = {'choice': choice_pk, 'poll': pk, 'voted_by': voted_by}
            serializer = VoteSerializer(data=data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            else:
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

We pass on poll id and choice id. We subclass this from :code:`APIView`, rather than a generic view, because we competely customize the behaviour. This is similar to our earlier :code:`APIView`, where in we are passing the data to a serializer, and saving or returning an error depending on whether the serializer is valid.

Introducing Viewsets and Routers
-----------------------------------

Our urls are looking good, and we have a views with very little code duplication, but we can do better.

The :code:`/polls/` and :code:`/polls/<pk>/` urls require two view classes, with the same serializer and base queryset. We can group them into a viewset, and connect them to the urls using a router.

This is what it will look like:

.. code-block:: python

    # urls.py
    # ...
    from rest_framework.routers import DefaultRouter
    from .apiviews import PollViewSet


    router = DefaultRouter()
    router.register('polls', PollViewSet, basename='polls')


    urlpatterns = [
        # ...
    ]

    urlpatterns += router.urls

    # apiviews.py
    # ...
    from rest_framework import viewsets

    from .models import Poll, Choice
    from .serializers import PollSerializer, ChoiceSerializer, VoteSerializer


    class PollViewSet(viewsets.ModelViewSet):
        queryset = Poll.objects.all()
        serializer_class = PollSerializer

There is no change at all to the urls or to the responses. You can verify this by doing a GET to
:code:`/polls/` and :code:`/polls/<pk>/`.


Choosing the base class to use
-----------------------------------

We have seen 4 ways to build API views until now

- Pure Django views
- :code:`APIView` subclasses
- :code:`generics.*` subclasses
- :code:`viewsets.ModelViewSet`

So which one should you use when? My rule of thumb is,

- Use :code:`viewsets.ModelViewSet` when you are going to allow all or most of CRUD operations on a model.
- Use :code:`generics.*` when you only want to allow some operations on a model
- Use :code:`APIView` when you want to completely customize the behaviour.

Next steps
-----------------

In the next chapter, we will look at adding access control to our apis.


================================================
FILE: docs/postman.rst
================================================
Testing and Using API with Postman
==========================================

In this chapter, we'll learn how to use the Postman app for testing our APIs.

Postman can be installed from `the Postman site <https://www.getpostman.com/>`_. It is a versatile tool for working with APIs.

In this books, you will be creating and using APIs. We'll see how we can make use of Postman for this.


Making HTTP request
------------------------

Postman is pretty intutive, but the image below should make the app easy to understand.

.. image:: postman.png

There are 4 key elements in making an HTTP request.

1. URL:
    This specifies to which URL we need to make a request for. In other terms where our API endpoint resides.

2. Method:
    Each API endpoint has a method which serves it's purpose. The methods for eg., can be GET for retrieving some data, POST for creating or updating, DELETE for deleting a record.

3. Headers:
    Headers provide required information about the request or the response or about the object sent in the body. Some times we use authentication headers too, in order to access the API endpoint.

4. Body:
    The request body is where we send the object. The object which may be required for the service.


Response
------------

Response is available in the bottom section, usually in a JSON format, but may also vary depending up on the API service.


Collections
--------------

We can save all the relative API endpoints to collections. In our example, we can save all our polls related endpoints as a collection or all the users related endpoints as another collection. This way all the APIs are organized.


Authentication
---------------

Postman also supports few authentication mechanisms like Basic Auth, Digest Auth and Oauth1. This allows us to use these authentication methods for the APIs.


================================================
FILE: docs/serailizers.rst
================================================
Serializing and Deserializing Data
========================================

DRF makes the process of building web API's simple and flexible. With batteries included,
it comes with well designed base classes which allows us to serialize and deserialize data.


Serialization and Deserialization
--------------------------------------

The first thing we need for our API is to provide a way to serialize model instances into representations. Serialization is the process of making a streamable representation of the data which we can transfer over the network. Deserialization is its reverse process.


Creating Serializers
-----------------------

Lets get started with creating serializer classes which will serialize and deserialize the model instances to json representations. Create a file named :code:`polls/serializers.py`. We will use :code:`ModelSerializer` which will reduce code duplication by automatically determing the set of fields and by creating implementations of the :code:`create()` and :code:`update()` methods.

Our :code:`polls/serializers.py` looks like this.

.. code-block:: python

    from rest_framework import serializers

    from .models import Poll, Choice, Vote


    class VoteSerializer(serializers.ModelSerializer):
        class Meta:
            model = Vote
            fields = '__all__'


    class ChoiceSerializer(serializers.ModelSerializer):
        votes = VoteSerializer(many=True, required=False)

        class Meta:
            model = Choice
            fields = '__all__'


    class PollSerializer(serializers.ModelSerializer):
        choices = ChoiceSerializer(many=True, read_only=True, required=False)

        class Meta:
            model = Poll
            fields = '__all__'


The :code:`PollSerializer` in detail
----------------------------------------

Our :code:`PollSerializer` looks like this.

.. code-block:: python

    ...

    class PollSerializer(serializers.ModelSerializer):
        choices = ChoiceSerializer(many=True, read_only=True, required=False)

        class Meta:
            model = Poll
            fields = '__all__'

What have we got with this? The :code:`PollSerializer` class has a number of methods,

* A :code:`is_valid(self, ..)` method which can tell if the data is sufficient and valid to create/update a model instance.
* A :code:`save(self, ..)` method, which knows how to create or update an instance.
* A :code:`create(self, validated_data, ..)` method which knows how to create an instance. This method can be overriden to customize the create behaviour.
* A :code:`update(self, instance, validated_data, ..)` method which knows how to update an instance. This method can be overriden to customize the update behaviour.


Using the :code:`PollSerializer`
----------------------------------------

Let's use the serializer to create a :code:`Poll` object.

.. code-block:: ipython

    In [1]: from polls.serializers import PollSerializer

    In [2]: from polls.models import Poll

    In [3]: poll_serializer = PollSerializer(data={"question": "Mojito or Caipirinha?", "created_by": 1})

    In [4]: poll_serializer.is_valid()
    Out[4]: True

    In [5]: poll = poll_serializer.save()

    In [6]: poll.pk
    Out[6]: 5


The :code:`poll.pk` line tells us that the object has been commited to the DB. You can also use the serializer to update a :code:`Poll` object. ::


    In [9]: poll_serializer = PollSerializer(instance=poll, data={"question": "Mojito, Caipirinha or margarita?", "created_by": 1})

    In [10]: poll_serializer.is_valid()
    Out[10]: True

    In [11]: poll_serializer.save()
    Out[11]: <Poll: Mojito, Caipirinha or margarita?>

    In [12]: Poll.objects.get(pk=5).question
    Out[12]: 'Mojito, Caipirinha or margarita?'

We can see that calling save on a Serializer with instance causes that instance to be updated. :Code:`Poll.objects.get(pk=5).question` verifies that the Poll was updated.


In the next chapter, we will use the serializers to write views.


================================================
FILE: docs/setup-models-admin.rst
================================================
Setup, Models and Admin
=============================

In this tutorial we will walk through a process of creating an API for a basic poll application. We will be using Python 3.6.x, Django 2.0.x and Django Rest Framework 3.7.x for creating API.

First things first, let's install the required modules within a virtual environment.

.. code-block:: python

    mkvirtualenv pollsapi
    pip install Django
    pip install djangorestframework

Creating a project
--------------------

Earliest in order, to create a project we should move to the directory where we would like to store our code. For this go to command line and use cd command. Then trigger the startproject command.

.. code-block:: python

    django-admin startproject pollsapi

This command gives us a 'pollsapi' directoy. The contents of this directory look like this::

    manage.py

    pollsapi/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Database setup
------------------

We will use SQlite database, which is already included with Python. The :code:`pollsapi/settings.py` file would already have the correct settings.

.. code-block:: python

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        }
    }

Now, use the migrate command which builds the needed database tables in regard to the :code:`django_pollsapi/settings.py` file.

.. code-block:: python

    python manage.py migrate


Creating models
---------------------

Before creating our database models, let us create our pollsapi App.

.. code-block:: python

    python manage.py startapp polls

The above command results in a 'polls' directory containing different files::

    admin.py
    apps.py
    models.py
    tests.py
    views.py

Step in to 'models.py' file and start writing the models. For creating the polls api we are going to create a :code:`Poll` model, a :code:`Choice` model and a :code:`Vote` model. Once we are done with designing our models, the :code:`models.py` file should look like this:

These models are the same as you would have seen in the Django introduction tutorial.

.. code-block:: python

    from django.db import models
    from django.contrib.auth.models import User


    class Poll(models.Model):
        question = models.CharField(max_length=100)
        created_by = models.ForeignKey(User, on_delete=models.CASCADE)
        pub_date = models.DateTimeField(auto_now=True)

        def __str__(self):
            return self.question


    class Choice(models.Model):
        poll = models.ForeignKey(Poll, related_name='choices', on_delete=models.CASCADE)
        choice_text = models.CharField(max_length=100)

        def __str__(self):
            return self.choice_text


    class Vote(models.Model):
        choice = models.ForeignKey(Choice, related_name='votes', on_delete=models.CASCADE)
        poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
        voted_by = models.ForeignKey(User, on_delete=models.CASCADE)

        class Meta:
            unique_together = ("poll", "voted_by")


The above models have been designed in such a way that, it would make our API bulding a smooth process.

Activating models
----------------------

With the simple lines of code in the 'models.py' Django can create a database schema and a Python database-access API which has the capability to access the objects of Poll, Choice, Vote. To create the database tables to our models, 'rest_framework' and 'polls' app needs to be added to the "INSTALLED_APPS" in the 'django_pollsapi/settings' file.

.. code-block:: python

    INSTALLED_APPS = (
    ...
    'rest_framework',
    'polls',
    )

Now, run the :code:`makemigrations` command which will notify Django that new models have been created and those changes needs to be applied to the migration. Run :code:`migrate` command to do the actual migration.

.. code-block:: bash

    $ python manage.py makemigrations polls

    $ python manage.py migrate



Create an empty :code:`urls.py` in your :code:`polls` app.

.. code-block:: python

    urlpatterns = [
    ]



Go to :code:`pollsapi/urls.py` and include the polls urls.

.. code-block:: python
    
    from django.urls import include, re_path
    
    urlpatterns = [
        re_path(r'^', include('polls.urls')),
    ]

Now you can runserver ::

    $ python manage.py runserver


Goto any browser of your choice and hit the url :code:`http://127.0.0.1:8000`

And we are in business, with a Django *Congratulations* page greeting us. (Though we haven't added any API endpoints yet.)

.. image:: congrats.png

We will be adding API endpoints for creating and viewing polls in the next chapter.

Setting up the admin
++++++++++++++++++++++

You should register :code:`Poll` and :code:`Choice` in the admin like this.

.. code-block:: python

    from django.contrib import admin

    from .models import Poll, Choice

    admin.site.register(Poll)
    admin.site.register(Choice)


================================================
FILE: docs/swagger.rst
================================================
Documenting APIs (with Swagger and more)
==========================================================

In this chapter we will see how to document our API.

As you build your API, you would need to document the API to collaborate with other people. In most companies and teams, the developer using the API is different from the one building them. API documentation and collaboration tools, become even more important in such an environment.

Swagger is a tool used to understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. In simple terms, with swagger you can see what all API end points are available for a web application. You can use swagger for testing the requests and responses of the API endpoints.

DRF comes with its own tool, coreapi, for documenting and interacting with the API.

We will use both coreapi and swagger to document our API.



Adding swagger documentation
-----------------------------

Install django-rest-swagger

.. code-block:: bash

    pip install django-rest-swagger

Update your :code:`settings.py`

.. code-block:: python

    INSTALLED_APPS = [
        # ...
        'polls',
        'rest_framework_swagger',
    ]

Add swagger to your urls.

.. code-block:: python

    from rest_framework_swagger.views import get_swagger_view

    schema_view = get_swagger_view(title='Polls API')

    # ...
    urlpatterns = [
        # ...
        path(r'swagger-docs/', schema_view),
    ]

Navigate to `/swagger-docs/`. And your swagger docs are ready in all their glory.

.. image:: swagger.png


Using coreapi for documentation
--------------------------------


Install coreapi

.. code-block:: bash

    pip install coreapi


Add coreapi urls to your urls.

.. code-block:: python

    from rest_framework.documentation import include_docs_urls
    # ...

    urlpatterns = [
        # ...
        path(r'docs/', include_docs_urls(title='Polls API')),
    ]

And your coreapi docs are ready in all their glory.

.. image:: coreapi.png


================================================
FILE: docs/testing-and-ci.rst
================================================
Testing and Continuous Integeration
==========================================


In this chapter we will add test to our API.

DRF provides a few important classes which makes testing APIs simpler. We will be using these classes later in the chapter in our tests.

- :code:`APIRequestFactory`: This is similar to Django's :code:`RequestFactory`. It allows you to create requests with any http method, which you can then pass on to any view method and compare responses.
- :code:`APIClient`: similar to Django's :code:`Client`. You can GET or POST a URL, and test responses.
- :code:`APITestCase`: similar to Django's :code:`TestCase`. Most of your tests will subclass this.

Now lets us write test cases to our polls application.

Creating Test Requests
------------------------
Django's 'Requestfactory' has the capability to create request instances which allow us in testing view functions individually. Django Rest Framework has a class called 'APIRequestFactory' which extends the standard Django's  'RequestFactory'. This class contains almost all the http verbs like .get(), .post(), .put(), .patch() et all.

Syntax for Post request:

.. code-block:: python

    factory = APIRequestFactory()
    request = factory.post(uri, post data)

Lets add a test for the polls list.

.. code-block:: python

    from rest_framework.test import APITestCase
    from rest_framework.test import APIRequestFactory

    from polls import apiviews


    class TestPoll(APITestCase):
        def setUp(self):
            self.factory = APIRequestFactory()
            self.view = apiviews.PollViewSet.as_view({'get': 'list'})
            self.uri = '/polls/'

        def test_list(self):
            request = self.factory.get(self.uri)
            response = self.view(request)
            self.assertEqual(response.status_code, 200,
                             'Expected Response Code 200, received {0} instead.'
                             .format(response.status_code))



In the above lines of code, we are trying to access the PollList view. We are asserting that the HTTP response code is 200.

Now run the test command.

.. code-block:: python

    python manage.py test

And it will display the below message.

.. code-block:: bash

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    F
    ======================================================================
    FAIL: test_list (polls.tests.TestPoll)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/Users/shabda/repos/building-api-django/pollsapi/polls/tests.py", line 19, in test_list
        .format(response.status_code))
    AssertionError: 401 != 200 : Expected Response Code 200, received 401 instead.

    ----------------------------------------------------------------------
    Ran 1 test in 0.002s

    FAILED (failures=1)
    Destroying test database for alias 'default'...

Ouch! Our test failed. This happened because the view is not accessible without authentication. So we need to create a user and test the view after getting authenticated.


Testing APIs with authentication
------------------------------------

To test apis with authentication, a test user needs to be created so that we can make requests in context of that user. Let's create a test user. Change your tests to

.. code-block:: python

    from django.contrib.auth import get_user_model
    from rest_framework.authtoken.models import Token
    # ...

    class TestPoll(APITestCase):
        def setUp(self):
            # ...
            self.user = self.setup_user()
            self.token = Token.objects.create(user=self.user)
            self.token.save()

        @staticmethod
        def setup_user():
            User = get_user_model()
            return User.objects.create_user(
                'test',
                email='testuser@test.com',
                password='test'
            )

        def test_list(self):
            request = self.factory.get(self.uri, 
                HTTP_AUTHORIZATION='Token {}'.format(self.token.key))
            request.user = self.user
            response = self.view(request)
            self.assertEqual(response.status_code, 200,
                             'Expected Response Code 200, received {0} instead.'
                             .format(response.status_code))


Now run the test command.

.. code-block:: python

    python manage.py test

You should get this response

.. code-block:: bash

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.119s

    OK
    Destroying test database for alias 'default'...

Using :code:`APIClient`
--------------------------

The same test can be written using :code:`APIClient`. It has :code:`get`, :code:`.post` and family. Unlike creating requests first, with :code:`APIClient` you can GET or POST to a url directly and get a response.

Add a test like this:

.. code-block:: python

    from rest_framework.test import APIClient

    # ...


    class TestPoll(APITestCase):
        def setUp(self):
            self.client = APIClient()
            # ...

        # ...
        def test_list2(self):
            response = self.client.get(self.uri)
            self.assertEqual(response.status_code, 200,
                             'Expected Response Code 200, received {0} instead.'
                             .format(response.status_code))

Let us test it now.

.. code-block:: bash

    python manage.py test polls.tests.TestPoll


    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    F
    ======================================================================
    FAIL: test_list2 (polls.tests.TestPoll)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/Users/shabda/repos/building-api-django/pollsapi/polls/tests.py", line 37, in test_list2
        .format(response.status_code))
    AssertionError: 401 != 200 : Expected Response Code 200, received 401 instead.

    ----------------------------------------------------------------------
    Ran 1 test in 0.136s

    FAILED (failures=1)
    Destroying test database for alias 'default'...

We are seeing the same failure we saw in the test with :code:`APIRequestFactory`. You can login a :code:`APIClient` by calling
:code:`APIClient.login`. Lets update the test.

.. code-block:: python

    class TestPoll(APITestCase):
        # ...

        def test_list2(self):
            self.client.login(username="test", password="test")
            response = self.client.get(self.uri)
            self.assertEqual(response.status_code, 200,
                             'Expected Response Code 200, received {0} instead.'
                             .format(response.status_code))

.. code-block:: bash

    python manage.py test polls.tests.TestPoll
    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.260s

    OK
    Destroying test database for alias 'default'...

Voilà! The test passed successfully.

:code:`.post` and create
--------------------------------------------------

We now know how to test our GET APIs. We can use the :code:`APIClient` with :code:`.post` method this time.

Let us try creating a new poll by sending the 'question', and 'created_by' parameters which are needs in the POST method. The test function looks as follows.

.. code-block:: python


    class TestPoll(APITestCase):

        # ...
        def test_create(self):
            self.client.login(username="test", password="test")
            params = {
                "question": "How are you?",
                "created_by": 1
                }
            response = self.client.post(self.uri, params)
            self.assertEqual(response.status_code, 201,
                             'Expected Response Code 201, received {0} instead.'
                             .format(response.status_code))


We are asserting that the the http code is 201 if the test passes succesfully. Lets run the tests.

.. code-block:: bash

    python manage.py test polls.tests.TestPoll.test_create

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.267s

    OK
    Destroying test database for alias 'default'...


Time to celebrate with the API :)


Continuous integration with CircleCI
---------------------------------------

We have the tests, but we also want it to run on every commit. If you are using Github, CircleCI provides a very well in integrated service to run your tests. We will use Circleci. v2

We can configure our application to use Circle CI  by adding a file named :code:`.circleci/config.yml` which is a YAML(a human-readable data serialization format) text file. It automatically detects when a commit has been made and pushed to a Github repository that is using CircleCI, and each time this happens, it will try to build the project and runs tests. The build failure or success is notified to the developer.

Setting up CircleCI
---------------------------------------

- Sign-in: To get started with Circle CI we can sign-in with our github account on circleci.com.
- Activate Github webhook: Once the Signup process gets completed we need to enable the service hook in the github profile page.
- Add .circle/config.yml: We should add the yml file to the project.

Writing circle configuration file
---------------------------------------

In order for circle CI to build our project we need to tell the system a little bit about it. we will be needed to add a file named :code:`.circleci/config.yml` to the root of our repository. We also need to create a :code:`pollsapi/requirements.txt` to define our dependencies.

Add this to your :code:`pollsapi/requirements.txt`

.. code-block:: text

    Django==2.0.3
    djangorestframework==3.7.7

And then add this to :code:`.circleci/config.yml`


.. code-block:: yaml

  version: 2
  jobs:
    build:
      docker:
        # specify the version you desire here
        - image: circleci/python:3.6.1


      working_directory: ~/repo

      steps:
        - checkout

        # Download and cache dependencies
        - restore_cache:
            keys:
            - v1-dependencies-{{ checksum "pollsapi/requirements.txt" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

        - run:
            name: install dependencies
            command: |
              python3 -m venv venv
              . venv/bin/activate
              pip install -r pollsapi/requirements.txt

        - save_cache:
            paths:
              - ./venv
            key: v1-dependencies-{{ checksum "requirements.txt" }}

        - run:
            name: run tests
            command: |
              . venv/bin/activate
              cd pollsapi
              python manage.py test

        - store_artifacts:
            path: test-reports
            destination: test-reports

Below are the important keywords that are used in writting circleci config.yml file.

- :code:`image`: Defines the base image including the language and version to use
- :code:`run`: It specifies a :code:`command` which will be run to setup environent and run tests. :code:`pip install -r pollsapi/requirements.txt` sets up the environment and :code:`pip install -r pollsapi/requirements.txt`

If everything passed successfully, you should see a green checkmark

.. image:: circleci.png

Congratulations, you have tests running in a CI environment.

From now onwards whenever we push our code to our repository a new build will be created for it and the tests will run.

We are at the end of the first part of our book. You can read the appendix, which tell about some documentation tools and api consumption tools. Go forward and build some amazing apps and apis.



================================================
FILE: docs/views-and-generic-views.rst
================================================
Views and Generic Views
============================

In this chapter, we will create views using :code:`APIVIew`, and :code:`generics.ListCreateAPIView` and family.

Creating Views with :code:`APIView`
-----------------------------------------


To start with, we will use the :code:`APIView` to build the polls list and poll detail API we built in the chapter, :doc:`apis-without-drf`.

Add this to a new file :code:`polls/apiviews.py`

.. code-block:: python

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from django.shortcuts import get_object_or_404

    from .models import Poll, Choice
    from  .serializers import PollSerializer

    class PollList(APIView):
        def get(self, request):
            polls = Poll.objects.all()[:20]
            data = PollSerializer(polls, many=True).data
            return Response(data)


    class PollDetail(APIView):
        def get(self, request, pk):
            poll = get_object_or_404(Poll, pk=pk)
            data = PollSerializer(poll).data
            return Response(data)


And change your :code:`urls.py` to

.. code-block:: python

    from django.urls import path

    from .apiviews import PollList, PollDetail

    urlpatterns = [
        path("polls/", PollList.as_view(), name="polls_list"),
        path("polls/<int:pk>/", PollDetail.as_view(), name="polls_detail")
    ]

DRF comes with a browsable api, so you can directly open :code:`http://localhost:8000/polls/` in the browser. It looks like this


.. image:: browsable-api-poll-details.png


You can now do an :code:`options` request to `/polls/`, which gives

.. code-block:: json


    {
        "name": "Poll List",
        "description": "",
        "renders": [
            "application/json",
            "text/html"
        ],
        "parses": [
            "application/json",
            "application/x-www-form-urlencoded",
            "multipart/form-data"
        ]
    }


This is how it looks like in postman.

.. image:: postman-poll-detail-options.png

Using DRF generic views to simplify code
-----------------------------------------


The :code:`PollList` and :code:`PollDetail` get the work done, but there are bunch of common operations, we can do it in abstract away.

The generic views of Django Rest Framework help us in code reusablity. They infer the response format and allowed methods from the serializer class and base class.

Change your :code:`apiviews.py` to the below code, and leave urls.py as is.

.. code-block:: python

    from rest_framework import generics

    from .models import Poll, Choice
    from .serializers import PollSerializer, ChoiceSerializer,\
        VoteSerializer


    class PollList(generics.ListCreateAPIView):
        queryset = Poll.objects.all()
        serializer_class = PollSerializer


    class PollDetail(generics.RetrieveDestroyAPIView):
        queryset = Poll.objects.all()
        serializer_class = PollSerializer

With this change, GET requests to :code:`/polls/` and :code:`/polls/<pk>/`, continue to work as was, but we have a more data available with OPTIONS.

Do an OPTIONs request to :code:`/polls/`, and you will get a response like this.

.. code-block:: javascript

    {
        "name": "Poll List",
        "description": "",
        "renders": [
            "application/json",
            "text/html"
        ],
        "parses": [
            "application/json",
            "application/x-www-form-urlencoded",
            "multipart/form-data"
        ],
        "actions": {
            "POST": {
                "id": {
                    "type": "integer",
                    "required": false,
                    "read_only": true,
                    "label": "ID"
                },
                // ...
                },
                "question": {
                    "type": "string",
                    "required": true,
                    "read_only": false,
                    "label": "Question",
                    "max_length": 100
                },
                "pub_date": {
                    "type": "datetime",
                    "required": false,
                    "read_only": true,
                    "label": "Pub date"
                },
                "created_by": {
                    "type": "field",
                    "required": true,
                    "read_only": false,
                    "label": "Created by"
                }
            }
        }
    }

This tells us

* Our API now accepts POST
* The required data fields
* The type of each data field.

Pretty nifty! This is what it looks like in Postman.


.. image:: postman-options-2.png

More generic views
------------------------


Let us add the view to create choices and for voting. We will look more closely at this code shortly.

.. code-block:: python

    from rest_framework import generics

    from .models import Poll, Choice
    from .serializers import PollSerializer, ChoiceSerializer, VoteSerializer


    class PollList(generics.ListCreateAPIView):
        queryset = Poll.objects.all()
        serializer_class = PollSerializer


    class PollDetail(generics.RetrieveDestroyAPIView):
        queryset = Poll.objects.all()
        serializer_class = PollSerializer


    class ChoiceList(generics.ListCreateAPIView):
        queryset = Choice.objects.all()
        serializer_class = ChoiceSerializer


    class CreateVote(generics.CreateAPIView):
        serializer_class = VoteSerializer


Connect the new apiviews to urls.py.

.. code-block:: python

    # ...
    from .apiviews import ChoiceList, CreateVote, # ...

    urlpatterns = [
        # ...
        path("choices/", ChoiceList.as_view(), name="choice_list"),
        path("vote/", CreateVote.as_view(), name="create_vote"),

    ]




There is a lot going on here, let us look at the attributes we need to override or set.

- :code:`queryset`: This determines the initial queryset. The queryset can be further filtered, sliced or ordered by the view.
- :code:`serializer_class`: This will be used for validating and deserializing the input and for serializing the output.

We have used three different classes from :code:`rest_framework.generic`. The names of the classes are representative of what they do, but lets quickly look at them.

- :code:`ListCreateAPIView`: Get a list of entities, or create them. Allows GET and POST.
- :code:`RetrieveDestroyAPIView`: Retrieve an individual entity details, or delete the entity. Allows GET and DELETE.
- :code:`CreateAPIView`: Allows creating entities, but not listing them. Allows POST.

Create some choices by POSTing to :code:`/choices/`.

.. code-block:: json

    {
        "choice_text": "Flask",
        "poll": 2
    }

The response looks like this

.. code-block:: json

    {
        "id": 4,
        "votes": [],
        "choice_text": "Flask",
        "poll": 2
    }

You can also retrieve the :code:`Poll` to by doing a :code:`GET` to :code:`/polls/<pk>/`. You should get something like this

.. code-block:: json

    {
        "id": 2,
        "choices": [
            {
                "id": 3,
                "votes": [],
                "choice_text": "Django",
                "poll": 2
            },
            {
                "id": 4,
                "votes": [],
                "choice_text": "Flask",
                "poll": 2
            }
        ],
        "question": "What do you prefer, Flask or Django?",
        "pub_date": "2018-03-12T10:15:55.949721Z",
        "created_by": 1
    }

If you make a mistake while POSTing, the API will warn you. POST a json with :code:`choice_text` missing to :code:`/choices/`.

.. code-block:: json

    {
        "poll": 2
    }

You will get a response like this

.. code-block:: json

    {
        "choice_text": [
            "This field is required."
        ]
    }

Check the status code is 400 Bad Request.

Next Steps
--------------

We have working API at this point, but we can simplify our API with a better URL design and remove some code duplication using viewsets. We will be doing that in the next chapter.


================================================
FILE: pollsapi/.gitignore
================================================
*.pyc
db.sqlite3
__pycache__


================================================
FILE: pollsapi/docs.raml
================================================
#%RAML 1.0
  ---
    title: Django polls API
    baseUri: http://api.example.com/{version}
    version: v1
    mediaType: application/json
    types:
        Poll:
            type: object
            properties:
                question:
                    required: true
                    type: string
                created_by:
                    required: true
                    type: User
                pub_date:
                    required: true
                    type: date
        Choice:
            type: object
            properties:
                poll:
                    required: true
                    type: Poll
                choice_text:
                    required: true
                    type: string
        Vote:
            type: object
            properties:
                choice:
                    required: true
                    type: Choice
                poll:
                    required: true
                    type: Poll
                voted_by:
                    required: true
                    type: User
    /polls:
        get:
            description: Get list of polls
            queryParameters:
                pollId:
                    description: Specify the poll id you want to retrieve
                    type: integer
            responses:
                200:
                    body:
                        application/json:
                        example:
                        {
                            "data":
                            {
                                "Id": 1,
                                "question": "Will A be the leader next time?",
                                "created_by": "user1",
                                "pub_date": "08:02:2014"
                            },
                            "success": true,
                            "status": 200
                        }
        post:
            description: Post a poll
            queryParameters:
                question:
                    description: Question you want to create for poll
                    type: string
                    required: true
                created_by:
                    description: Poll created by the user
                    type: User
                    required: true
                pub_date:
                    description: Poll created on
                    type: date
                    required: true
            responses:
                200:
                    body:
                        application/json:
                        example:
                        {
                            "success": true,
                            "status": 201
                        }
    /choices:
        get:
            description: Get list of choices
            queryParameters:
                choiceId:
                    description: Specify the choice id you want to retrieve
                    type: integer
            responses:
                200:
                    body:
                        application/json:
                        example:
                        {
                            "data":
                            {
                                "Id": 1,
                                "poll": "Will A be the leader next time?",
                                "choice_text": "user1"
                            },
                            "success": true,
                            "status": 200
                        }
        post:
            description: Post a choice
            queryParameters:
                poll:
                    description: Choice you want to create for poll
                    type: string
                    required: true
                choice_text:
                    description: Choice for the poll
                    type: string
                    required: true
            responses:
                200:
                    body:
                        application/json:
                        example:
                        {
                            "success": true,
                            "status": 201
                        }
    /votes:
        get:
            description: Get votes of polls
            queryParameters:
                pollId:
                    description: Specify the vote id you want to retrieve
                    type: integer
            responses:
                200:
                    body:
                        application/json:
                        example:
                        {
                            "data":
                            {
                                "Id": 1,
                                "poll": "Will A be the leader next time?",
                                "choice": "user1",
                                "voted_by": "user2"
                            },
                            "success": true,
                            "status": 200
                        }
        post:
            description: Post a vote
            queryParameters:
                choice:
                    description: Choice for the poll
                    type: Choice
                    required: true
                poll:
                    description: Poll to be voted
                    type: Poll
                    required: true
                voted_by:
                    description: Poll voted by
                    type: User
                    required: true
            responses:
                200:
                    body:
                        application/json:
                        example:
                        {
                            "success": true,
                            "status": 201
                        }


================================================
FILE: pollsapi/docs.swagger.json
================================================
{
    "swagger": "2.0",
    "info": {
        "description": "This is a sample server for polls api.",
        "version": "1.0.0",
        "title": "Polls API",
        "termsOfService": "http://example.com/terms/",
        "contact": {"email": "apiteam@example.com"},
        "license": {"name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html"}
    },
    "host": "polls.example.com",
    "basePath": "/v2",
    "tags": [
        {
            "name": "polls",
            "description": "Everything about your Polls",
            "externalDocs": {"description": "Find out more","url":"http://example.com"}
        },
        {
            "name": "choices",
            "description": "Access to choices for the polls"
        },
        {
            "name": "user",
            "description": "Operations about user",
            "externalDocs": {"description": "Find out more about our store","url":"http://example.com"}
        }
    ],
    "schemes": ["http"],
    "paths": {
        "/polls": {
            "get": {
                "tags": ["poll"],
                "summary": "Get all the polls",
                "description": "",
                "operationId": "pollList",
                "consumes": ["application/json","application/xml"],
                "produces": ["application/xml","application/json"],
                "parameters": [{
                    "in": "query",
                    "name": "body",
                    "description": "Get all the polls.",
                    "required": false,
                    "schema":{"$ref":"#/pollsapi/Poll"}
                }],
                "responses": {"200":{"description":"Successfull operation"}},
            },
            "post":{
                "tags": ["poll"],
                "summary": "Create a new poll",
                "description": "Creates a new poll.",
                "operationId": "createPoll",
                "consumes":["application/json","application/xml"],
                "produces":["application/xml","application/json"],
                "parameters":[{
                    "in":"query",
                    "name":"body",
                    "description": "Poll object that needs to be added.",
                    "required": true,
                    "schema": {"$ref":"#/pollsapi/Poll"}
                }],
                "responses": {
                    "200": {"description":"Poll created successfully"}
                }
            }
        },
        "/choices": {
            "get": {
                "tags": ["choice"],
                "summary": "Get all the choices",
                "description": "",
                "operationId": "choiceList",
                "consumes": ["application/json","application/xml"],
                "produces": ["application/xml","application/json"],
                "parameters": [{
                    "in": "query",
                    "name": "body",
                    "description": "Get all the choices.",
                    "required": false,
                    "schema":{"$ref":"#/pollsapi/Choice"}
                }],
                "responses": {"200":{"description":"Successfull operation"}},
            },
            "post":{
                "tags": ["choice"],
                "summary": "Create a new choice",
                "description": "Creates a new choice.",
                "operationId": "createChoice",
                "consumes":["application/json","application/xml"],
                "produces":["application/xml","application/json"],
                "parameters":[{
                    "in":"query",
                    "name":"body",
                    "description": "Choice object that needs to be added.",
                    "required": true,
                    "schema": {"$ref":"#/pollsapi/Poll"}
                }],
                "responses": {
                    "200": {"description":"Poll created successfully"}
                }
            }
        },
        "/user": {
            "get": {
                "tags": ["user"],
                "summary": "Get user details",
                "description": "",
                "operationId": "user",
                "consumes": ["application/json","application/xml"],
                "produces": ["application/xml","application/json"],
                "parameters": [{
                    "in": "query",
                    "name": "body",
                    "description": "Get the user.",
                    "required": false,
                    "schema":{"$ref":"#/pollsapi/User"}
                }],
                "responses": {"200":{"description":"Successfull operation"}},
            },
            "post":{
                "tags": ["user"],
                "summary": "Create a new user",
                "description": "Creates a new user.",
                "operationId": "createUser",
                "consumes":["application/json","application/xml"],
                "produces":["application/xml","application/json"],
                "parameters":[{
                    "in":"query",
                    "name":"body",
                    "description": "User object that needs to be added.",
                    "required": true,
                    "schema": {"$ref":"#/pollsapi/User"}
                }],
                "responses": {
                    "200": {"description":"User created successfully"}
                }
            }
        },
        "/vote": {
            "post":{
                "tags": ["vote"],
                "summary": "Create a new vote",
                "description": "Creates a new vote.",
                "operationId": "createVote",
                "consumes":["application/json","application/xml"],
                "produces":["application/xml","application/json"],
                "parameters":[{
                    "in":"query",
                    "name":"body",
                    "description": "Vote object that needs to be added.",
                    "required": true,
                    "schema": {"$ref":"#/pollsapi/Vote"}
                }],
                "responses": {
                    "200": {"description":"Vote created successfully"}
                }
            }
        }
    }
}


================================================
FILE: pollsapi/manage.py
================================================
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pollsapi.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


================================================
FILE: pollsapi/polls/__init__.py
================================================


================================================
FILE: pollsapi/polls/admin.py
================================================
from django.contrib import admin

from .models import Poll, Choice

admin.site.register(Poll)
admin.site.register(Choice)


================================================
FILE: pollsapi/polls/apiviews.py
================================================
from rest_framework import generics
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.response import Response
from rest_framework import viewsets
from rest_framework.exceptions import PermissionDenied

from django.contrib.auth import authenticate

from .models import Poll, Choice
from .serializers import PollSerializer, ChoiceSerializer, VoteSerializer, UserSerializer


class PollViewSet(viewsets.ModelViewSet):
    queryset = Poll.objects.all()
    serializer_class = PollSerializer

    def destroy(self, request, *args, **kwargs):
        poll = Poll.objects.get(pk=self.kwargs["pk"])
        if not request.user == poll.created_by:
            raise PermissionDenied("You can not delete this poll.")
        return super().destroy(request, *args, **kwargs)


class ChoiceList(generics.ListCreateAPIView):
    serializer_class = ChoiceSerializer

    def get_queryset(self):
        queryset = Choice.objects.filter(poll_id=self.kwargs["pk"])
        return queryset

    def post(self, request, *args, **kwargs):
        poll = Poll.objects.get(pk=self.kwargs["pk"])
        if not request.user == poll.created_by:
            raise PermissionDenied("You can not create choice for this poll.")
        return super().post(request, *args, **kwargs)



class CreateVote(APIView):

    def post(self, request, pk, choice_pk):
        voted_by = request.data.get("voted_by")
        data = {'choice': choice_pk, 'poll': pk, 'voted_by': voted_by}
        serializer = VoteSerializer(data=data)
        if serializer.is_valid():
            vote = serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response({"error": "Wrong Credentials"}, status=status.HTTP_400_BAD_REQUEST)


class UserCreate(generics.CreateAPIView):
    serializer_class = UserSerializer


class LoginView(APIView):
    def post(self, request,):
        username = request.data.get("username")
        password = request.data.get("password")
        user = authenticate(username=username, password=password)
        if user:
            return Response({"token": user.auth_token.key})
        else:
            return Response({"error": "Wrong Credentials"}, status=status.HTTP_400_BAD_REQUEST)



================================================
FILE: pollsapi/polls/apps.py
================================================
from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'


================================================
FILE: pollsapi/polls/migrations/0001_initial.py
================================================
# Generated by Django 2.0.3 on 2018-03-08 17:01

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='Choice',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('choice_text', models.CharField(max_length=100)),
            ],
        ),
        migrations.CreateModel(
            name='Poll',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('question', models.CharField(max_length=100)),
                ('pub_date', models.DateTimeField(auto_now=True)),
                ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
            ],
        ),
        migrations.CreateModel(
            name='Vote',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('choice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='polls.Choice')),
                ('poll', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Poll')),
                ('voted_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
            ],
        ),
        migrations.AddField(
            model_name='choice',
            name='poll',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='polls.Poll'),
        ),
        migrations.AlterUniqueTogether(
            name='vote',
            unique_together={('poll', 'voted_by')},
        ),
    ]


================================================
FILE: pollsapi/polls/migrations/__init__.py
================================================


================================================
FILE: pollsapi/polls/models.py
================================================
from django.db import models
from django.contrib.auth.models import User


class Poll(models.Model):
    question = models.CharField(max_length=100)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)
    pub_date = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.question


class Choice(models.Model):
    poll = models.ForeignKey(Poll, related_name='choices',on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=100)

    def __str__(self):
        return self.choice_text


class Vote(models.Model):
    choice = models.ForeignKey(Choice, related_name='votes', on_delete=models.CASCADE)
    poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
    voted_by = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        unique_together = ("poll", "voted_by")


================================================
FILE: pollsapi/polls/serializers.py
================================================
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token

from rest_framework import serializers

from .models import Poll, Choice, Vote


class VoteSerializer(serializers.ModelSerializer):
    class Meta:
        model = Vote
        fields = '__all__'


class ChoiceSerializer(serializers.ModelSerializer):
    votes = VoteSerializer(many=True, required=False)

    class Meta:
        model = Choice
        fields = '__all__'


class PollSerializer(serializers.ModelSerializer):
    choices = ChoiceSerializer(many=True, read_only=True, required=False)

    class Meta:
        model = Poll
        fields = '__all__'


class UserSerializer(serializers.ModelSerializer):

        class Meta:
            model = User
            fields = ('username', 'email', 'password')
            extra_kwargs = {'password': {'write_only': True}}

        def create(self, validated_data):
            user = User(
                email=validated_data['email'],
                username=validated_data['username']
            )
            user.set_password(validated_data['password'])
            user.save()
            Token.objects.create(user=user)
            return user




================================================
FILE: pollsapi/polls/tests.py
================================================
from django.contrib.auth import get_user_model


from rest_framework.test import APITestCase
from rest_framework.test import APIClient, APIRequestFactory

from polls import apiviews


class TestPoll(APITestCase):
    def setUp(self):
        self.factory = APIRequestFactory()
        self.client = APIClient()
        self.uri = '/polls/'
        self.user = self.setup_user()
        self.view = apiviews.PollViewSet.as_view({'post': 'list'})

    @staticmethod
    def setup_user():
        User = get_user_model()
        return User.objects.create_user(
            'test',
            email='testuser@test.com',
            password='test'
        )

    def _test_list(self):
        request = self.factory.get(self.uri)
        request.user = self.user
        response = self.view(request)
        self.assertEqual(response.status_code, 200,
                         'Expected Response Code 200, received {0} instead.'
                         .format(response.status_code))

    def test_list2(self):
        self.client.login(username="test", password="test")
        response = self.client.get(self.uri)
        self.assertEqual(response.status_code, 200,
                         'Expected Response Code 200, received {0} instead.'
                         .format(response.status_code))

    def test_create(self):
        self.client.login(username="test", password="test")
        params = {
            "question": "How are you?",
            "created_by": 1
            }
        response = self.client.post(self.uri, params)
        self.assertEqual(response.status_code, 201,
                         'Expected Response Code 201, received {0} instead.'
                         .format(response.status_code))



================================================
FILE: pollsapi/polls/urls.py
================================================
from django.urls import path

from .apiviews import PollViewSet, ChoiceList, CreateVote, UserCreate, LoginView

from rest_framework.routers import DefaultRouter

from rest_framework.documentation import include_docs_urls

from rest_framework_swagger.views import get_swagger_view

schema_view = get_swagger_view(title='Polls API')


router = DefaultRouter()
router.register('polls', PollViewSet, base_name='polls')


urlpatterns = [
    path("login/", LoginView.as_view(), name="login"),
    path("users/", UserCreate.as_view(), name="user_create"),
    path("polls/<int:pk>/choices/", ChoiceList.as_view(), name="polls_list"),
    path("polls/<int:pk>/choices/<int:choice_pk>/vote/", CreateVote.as_view(), name="polls_list"),
    path(r'docs/', include_docs_urls(title='Polls API')),
    path(r'swagger-docs/', schema_view),
]

urlpatterns += router.urls


================================================
FILE: pollsapi/polls/views.py
================================================
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse

from .models import Poll

def polls_list(request):
    MAX_OBJECTS = 20
    polls = Poll.objects.all()[:20]
    data = {"results": list(polls.values("pk", "question", "created_by__username", "pub_date"))}
    return JsonResponse(data)


def polls_detail(request, pk):
    poll = get_object_or_404(Poll, pk=pk)
    data = {"results": {
        "question": poll.question,
        "created_by": poll.created_by.username,
        "pub_date": poll.pub_date
    }}
    return JsonResponse(data)


================================================
FILE: pollsapi/pollsapi/__init__.py
================================================


================================================
FILE: pollsapi/pollsapi/settings.py
================================================
"""
Django settings for pollsapi project.

Generated by 'django-admin startproject' using Django 2.0.3.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'n*z@*&0ein2+poiu$rhkv2c0a@^2gbzg=g!_e%+dz#2ik5f$g2'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'polls',
    'rest_framework_swagger',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'pollsapi.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'pollsapi.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'


================================================
FILE: pollsapi/pollsapi/urls.py
================================================
"""pollsapi URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'', include('polls.urls')),
]


================================================
FILE: pollsapi/pollsapi/wsgi.py
================================================
"""
WSGI config for pollsapi project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pollsapi.settings")

application = get_wsgi_application()


================================================
FILE: pollsapi/requirements.txt
================================================
Django==2.2.13
djangorestframework==3.7.7
django-rest-swagger==2.1.2
coreapi==2.3.3


================================================
FILE: requirements.txt
================================================
Sphinx
Download .txt
gitextract_czqqekaq/

├── .circleci/
│   └── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs/
│   ├── Documenting-API-with-RAML.rst.draft
│   ├── Makefile
│   ├── access-control.rst
│   ├── apis-without-drf.rst
│   ├── conf.py
│   ├── index.rst
│   ├── introduction.rst
│   ├── make.bat
│   ├── more-views-and-viewsets.rst
│   ├── postman.rst
│   ├── serailizers.rst
│   ├── setup-models-admin.rst
│   ├── swagger.rst
│   ├── testing-and-ci.rst
│   └── views-and-generic-views.rst
├── pollsapi/
│   ├── .gitignore
│   ├── docs.raml
│   ├── docs.swagger.json
│   ├── manage.py
│   ├── polls/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apiviews.py
│   │   ├── apps.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── pollsapi/
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── requirements.txt
└── requirements.txt
Download .txt
SYMBOL INDEX (35 symbols across 7 files)

FILE: pollsapi/polls/apiviews.py
  class PollViewSet (line 14) | class PollViewSet(viewsets.ModelViewSet):
    method destroy (line 18) | def destroy(self, request, *args, **kwargs):
  class ChoiceList (line 25) | class ChoiceList(generics.ListCreateAPIView):
    method get_queryset (line 28) | def get_queryset(self):
    method post (line 32) | def post(self, request, *args, **kwargs):
  class CreateVote (line 40) | class CreateVote(APIView):
    method post (line 42) | def post(self, request, pk, choice_pk):
  class UserCreate (line 53) | class UserCreate(generics.CreateAPIView):
  class LoginView (line 57) | class LoginView(APIView):
    method post (line 58) | def post(self, request,):

FILE: pollsapi/polls/apps.py
  class PollsConfig (line 4) | class PollsConfig(AppConfig):

FILE: pollsapi/polls/migrations/0001_initial.py
  class Migration (line 8) | class Migration(migrations.Migration):

FILE: pollsapi/polls/models.py
  class Poll (line 5) | class Poll(models.Model):
    method __str__ (line 10) | def __str__(self):
  class Choice (line 14) | class Choice(models.Model):
    method __str__ (line 18) | def __str__(self):
  class Vote (line 22) | class Vote(models.Model):
    class Meta (line 27) | class Meta:

FILE: pollsapi/polls/serializers.py
  class VoteSerializer (line 9) | class VoteSerializer(serializers.ModelSerializer):
    class Meta (line 10) | class Meta:
  class ChoiceSerializer (line 15) | class ChoiceSerializer(serializers.ModelSerializer):
    class Meta (line 18) | class Meta:
  class PollSerializer (line 23) | class PollSerializer(serializers.ModelSerializer):
    class Meta (line 26) | class Meta:
  class UserSerializer (line 31) | class UserSerializer(serializers.ModelSerializer):
    class Meta (line 33) | class Meta:
    method create (line 38) | def create(self, validated_data):

FILE: pollsapi/polls/tests.py
  class TestPoll (line 10) | class TestPoll(APITestCase):
    method setUp (line 11) | def setUp(self):
    method setup_user (line 19) | def setup_user():
    method _test_list (line 27) | def _test_list(self):
    method test_list2 (line 35) | def test_list2(self):
    method test_create (line 42) | def test_create(self):

FILE: pollsapi/polls/views.py
  function polls_list (line 6) | def polls_list(request):
  function polls_detail (line 13) | def polls_detail(request, pk):
Condensed preview — 40 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (122K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 1200,
    "preview": "version: 2\njobs:\n  build:\n    docker:\n      # specify the version you desire here\n      - image: circleci/python:3.6.1\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 491,
    "preview": "# sphinx build folder\n_build\n\n# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Packages #\n#"
  },
  {
    "path": "LICENSE",
    "chars": 200,
    "preview": "This work is licensed under a CC-BY-SA 4.0 (Creative Commons Attribution-ShareAlike 4.0 International License) licence. "
  },
  {
    "path": "README.md",
    "chars": 1271,
    "preview": " Building APIs with Django and Django Rest Framework\n==================================================================\n"
  },
  {
    "path": "docs/Documenting-API-with-RAML.rst.draft",
    "chars": 4297,
    "preview": "Documenting API with RAML\n============================\n\nIn this chapter we will see how to use raml for all the views of"
  },
  {
    "path": "docs/Makefile",
    "chars": 7453,
    "preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD "
  },
  {
    "path": "docs/access-control.rst",
    "chars": 10578,
    "preview": "Access Control\n=================================\n\nIn this chapter, we will add access control to our APIs,\nand add APIs "
  },
  {
    "path": "docs/apis-without-drf.rst",
    "chars": 3616,
    "preview": "A simple API with pure Django\n========================================\n\nIn this chapter, we will build an API with pure "
  },
  {
    "path": "docs/conf.py",
    "chars": 9190,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Building API Django documentation build configuration file, created by\n# sphinx-quickstart o"
  },
  {
    "path": "docs/index.rst",
    "chars": 885,
    "preview": "Building APIs with Django and Django Rest Framework\n======================================================\n\nBuilding API"
  },
  {
    "path": "docs/introduction.rst",
    "chars": 1082,
    "preview": "Introductions\n=================\n\n*Building APIs with Django and Django Rest Framework* starts where the `Django \"Polls\" "
  },
  {
    "path": "docs/make.bat",
    "chars": 7266,
    "preview": "@ECHO OFF\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\n"
  },
  {
    "path": "docs/more-views-and-viewsets.rst",
    "chars": 6563,
    "preview": "More views and viewsets\n======================================\n\nA better URL structure\n-----------------------------\n\nWe"
  },
  {
    "path": "docs/postman.rst",
    "chars": 1832,
    "preview": "Testing and Using API with Postman\n==========================================\n\nIn this chapter, we'll learn how to use t"
  },
  {
    "path": "docs/serailizers.rst",
    "chars": 3991,
    "preview": "Serializing and Deserializing Data\n========================================\n\nDRF makes the process of building web API's"
  },
  {
    "path": "docs/setup-models-admin.rst",
    "chars": 5016,
    "preview": "Setup, Models and Admin\n=============================\n\nIn this tutorial we will walk through a process of creating an AP"
  },
  {
    "path": "docs/swagger.rst",
    "chars": 2039,
    "preview": "Documenting APIs (with Swagger and more)\n==========================================================\n\nIn this chapter we "
  },
  {
    "path": "docs/testing-and-ci.rst",
    "chars": 12281,
    "preview": "Testing and Continuous Integeration\n==========================================\n\n\nIn this chapter we will add test to our"
  },
  {
    "path": "docs/views-and-generic-views.rst",
    "chars": 8123,
    "preview": "Views and Generic Views\n============================\n\nIn this chapter, we will create views using :code:`APIVIew`, and :"
  },
  {
    "path": "pollsapi/.gitignore",
    "chars": 29,
    "preview": "*.pyc\ndb.sqlite3\n__pycache__\n"
  },
  {
    "path": "pollsapi/docs.raml",
    "chars": 5820,
    "preview": "#%RAML 1.0\n  ---\n    title: Django polls API\n    baseUri: http://api.example.com/{version}\n    version: v1\n    mediaType"
  },
  {
    "path": "pollsapi/docs.swagger.json",
    "chars": 6245,
    "preview": "{\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"description\": \"This is a sample server for polls api.\",\n        \"version\""
  },
  {
    "path": "pollsapi/manage.py",
    "chars": 540,
    "preview": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE"
  },
  {
    "path": "pollsapi/polls/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "pollsapi/polls/admin.py",
    "chars": 122,
    "preview": "from django.contrib import admin\n\nfrom .models import Poll, Choice\n\nadmin.site.register(Poll)\nadmin.site.register(Choice"
  },
  {
    "path": "pollsapi/polls/apiviews.py",
    "chars": 2290,
    "preview": "from rest_framework import generics\nfrom rest_framework.views import APIView\nfrom rest_framework import status\nfrom rest"
  },
  {
    "path": "pollsapi/polls/apps.py",
    "chars": 85,
    "preview": "from django.apps import AppConfig\n\n\nclass PollsConfig(AppConfig):\n    name = 'polls'\n"
  },
  {
    "path": "pollsapi/polls/migrations/0001_initial.py",
    "chars": 2032,
    "preview": "# Generated by Django 2.0.3 on 2018-03-08 17:01\n\nfrom django.conf import settings\nfrom django.db import migrations, mode"
  },
  {
    "path": "pollsapi/polls/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "pollsapi/polls/models.py",
    "chars": 846,
    "preview": "from django.db import models\nfrom django.contrib.auth.models import User\n\n\nclass Poll(models.Model):\n    question = mode"
  },
  {
    "path": "pollsapi/polls/serializers.py",
    "chars": 1208,
    "preview": "from django.contrib.auth.models import User\nfrom rest_framework.authtoken.models import Token\n\nfrom rest_framework impor"
  },
  {
    "path": "pollsapi/polls/tests.py",
    "chars": 1730,
    "preview": "from django.contrib.auth import get_user_model\n\n\nfrom rest_framework.test import APITestCase\nfrom rest_framework.test im"
  },
  {
    "path": "pollsapi/polls/urls.py",
    "chars": 856,
    "preview": "from django.urls import path\n\nfrom .apiviews import PollViewSet, ChoiceList, CreateVote, UserCreate, LoginView\n\nfrom res"
  },
  {
    "path": "pollsapi/polls/views.py",
    "chars": 582,
    "preview": "from django.shortcuts import render, get_object_or_404\nfrom django.http import JsonResponse\n\nfrom .models import Poll\n\nd"
  },
  {
    "path": "pollsapi/pollsapi/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "pollsapi/pollsapi/settings.py",
    "chars": 3478,
    "preview": "\"\"\"\nDjango settings for pollsapi project.\n\nGenerated by 'django-admin startproject' using Django 2.0.3.\n\nFor more inform"
  },
  {
    "path": "pollsapi/pollsapi/urls.py",
    "chars": 797,
    "preview": "\"\"\"pollsapi URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https:/"
  },
  {
    "path": "pollsapi/pollsapi/wsgi.py",
    "chars": 393,
    "preview": "\"\"\"\nWSGI config for pollsapi project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nF"
  },
  {
    "path": "pollsapi/requirements.txt",
    "chars": 84,
    "preview": "Django==2.2.13\ndjangorestframework==3.7.7\ndjango-rest-swagger==2.1.2\ncoreapi==2.3.3\n"
  },
  {
    "path": "requirements.txt",
    "chars": 7,
    "preview": "Sphinx\n"
  }
]

About this extraction

This page contains the full source code of the agiliq/building-api-django GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 40 files (111.8 KB), approximately 27.6k tokens, and a symbol index with 35 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!