Skip to content

REST API

Torgeir Slette edited this page May 14, 2018 · 27 revisions

Overview

API root is located at /apps/api/

Will return the API Root with links to the other endpoints of the api.

How to use

As of right now everything is in development

Production data endpoint

Located at /api/productiondata/ (Could be changed to /api/prod-data/)

Accepts lists on this form:

[
    {
        "time": "YYYY-MM-DDThh:mm:ss",
        "startlat": ddd.ddddddddddddd,
        "startlong": ddd.ddddddddddddd,
        "endlat": ddd.ddddddddddddd,
        "endlong": ddd.ddddddddddddd,

        # The fields below are optional
        "dry_spreader_active": <True/False>,
        "plow_active": <True/False>,
        "wet_spreader_active": <True/False>,
        "brush_active": <True/False>
        "material_type_code": <Integer>
    },
    ...
]

Exclude "dry_spreader_active", "plow_active", "wet_spreader_active", "brush_active" or "material_type_code" fields to set them to null.

For "material_type_code" the values correspond to these materials:

  • 1: Tørrsand
  • 2: Saltblandet sand
  • 3: Fastsan
  • 4: Tørt salt (NaCl
  • 5: Befuktet Salt (NaCl)
  • 6: Saltslurry (NaCl)
  • 7: Saltløsning (NaCl)
  • 11: Befuktet salt og saltslurry

The response will echo the inserted data.

Usage

Python script:

import requests

r = requests.post('url', json=list_of_data, auth=('username', 'password'))
print(r.status_code, r.text)

How to

Make test for db model

from django.test import TestCase
from api.models import ProductionData


class ProductionDataTest(TestCase):
    """
    Test module for ProductionData.
    """

    def setUp(self):
        ProductionData.objects.create(
            time=timezone.now(), startlat=60.7758584, startlong=20.756444,
            endlat=60.45454, endlong=20.57575, plow_active=True
        )

    def test_prod_data(self):
        """
        Test input of one ProductionData object
        """
        prod_data = ProductionData.objects.all()
        # Check that there is one and only one item in prod-data table
        self.assertEqual(len(prod_data), 1)

Put api root at /api/

In backend/urls.py include your api app's urls.py file.

urlpatterns = [
    url(r'^', include('api.urls')),
    ...
]

This should be near the top of urlpatterns, because it is prioritized by order.

In your api/urls.py file, the urlpatterns router include should be set to api/

urlpatterns = [
    url(r'api/', include(router.urls))
]

Make an endpoint accept both list and single dict in POST request

Override the create method to set the many flag in the serializer to True

Example:

from rest_framework import viewsets, status
from rest_framework.response import Response
from api.models import RoadSegment
from api.serializers import RoadSegmentSerializer


class RoadSegmentViewSet(viewsets.ModelViewSet):
    queryset = RoadSegment.objects.all()
    serializer_class = RoadSegmentSerializer

    def create(self, request, *args, **kwargs):
        many = False

        # Check if the incoming data is a list
        # If it is a list set the many flag to True
        if isinstance(request.data, list):
            many = True

        # Instantiates the serializer
        serializer = self.get_serializer(data=request.data, many=many)

        # Checks if the serializer is valid and takes the necessary actions
        if serializer.is_valid():
            serializer.save()
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

        # If not valid return error
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Example of POST request:

import requests
r = requests.post(url, json=roads)
print(r.text)

Use the json field to pass the data (for lists).

Short explanations (and possibly some tips and tricks)

See the Django and Django Rest Framework documentation for more informantion

Urls

api/urls.py is the file where the api is routed. In the file there is a section where the router is declared and the different viewsets are registered and a urlpatterns variable that just places the api router on /api/. In the routing section, to add a new endpoint, just add new line with router.register(r'<endpoint>', <viewset>)

If is replaced with users, /api/users will be the endpoint for the viewset.

It is fairly easy to change the name of an endpoint.

Views (viewsets)

Using a ModelViewSet list, create, retrieve, update and destroy actions are provided and do need to be declared, but can be overridden. There are others like ReadOnlyModelViewSet (pretty self explanatory what it does).

Queryset

The queryset variable is what set of data from the database to use with the serializer. In case of a basic viewset for a database model set it to queryset = <modelname>.objects.all().

We may have to use other, more complex queries for some of the functionality.

Serializer class

Selects which serializer to use for serializing and deserializing the data from/to the database. See the Serializer section for more information.

Permission classes

Some viewsets, like ReadOnlyModelViewSet have inbuilt permissions.

You can set your own permissions by doing this:

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

permission_classes takes a tuple. IsAuthenticatedOrReadOnly is a built in permission class and there are a few others, but you can make your own permission classes. (See below)

Custom permissions

Make an api/permissions.py file.

Example:

from rest_framework import permissions


class IsAdminOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow admins to create and edit.
    Read only for normal users and unauthorized users.
    """
    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        else:
            return request.user.is_staff

Description of class in docstring.

Checks if user is flagged as is_staff.

The permissions.SAFE_METHODS is a tuple containing the request methods GET, OPTIONS and HEAD.

Serializers

A serializer allow complex data such as model instances to be converted to native Python data types that can be rendered into html or json. It also provide deserialization, allowing data to be converted back to complex data.

HyperlinkedModelSerializer

A HyperlinkedModelSerializer is a ModelSerializer that generates a url for each row in a database table. Say you are making a serializer for the built in User model. If you use a HyperlinkedModelSerializer add a 'url' field to the fields metaoption and do a GET request to the api user endpoint. It will list all the users and also give a url to each specific user that can used for another GET request.

The Meta class

The Meta class has many different options that can be set.

Model

The model option specifies which model this serializer uses (database model). model = User

Fields

The fields option selects which fields from the model the serializer uses when serializing/deserializing. fields = ('id', 'username')

Models

Meta class

Similar to serializers the models supports metadata.

Abstract

The abstract option sets the model to be abstract. This can be used to make a template model with base fields if you want several of your models to have some of the same fields.

Example:

class BaseModel(models.Model):
    id = models.AutoField(primary_key=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

Usage:

class ProductionData(BaseModel):

This will include the three fields of BaseModel in ProductionData. The id field is the primary key and will auto increment. It will (should) also make it so the created field is set to the date at which the row was created and the updated field should be set to the current datetime every time the row is updated. Fields with auto_now_add=True and auto_now=True also have editable=False by default

Clone this wiki locally