Data interface development of Django

Nowadays, more and more web projects use front-end and back-end separation, which realizes the decoupling of data and interface. They interact through network API.
  API - Application Programming Interface

REST introduction

  REST has nothing to do with technology and represents only one architecture, one style. His full name is REpresentational State Transfer, which means "expressive information transfer".
  network API: it mainly emphasizes to request a URL to obtain (json) data through HTTP(s) request.
It is generally agreed that designing a RESTful API for network interface is the best choice
  according to the author of REST, REST is the most suitable architecture for Internet applications. How can REST authors have the courage to think that REST is the most suitable architecture for Internet applications? First, HTTP is stateless and connectionless. REST architecture emphasizes two words stateless Idempotency . It proposes that the design of URL s must use nouns instead of verbs. We need to use HTTP verbs to indicate what we need to do, because HTTP verbs are enough for us to do all kinds of operations.

Idempotent emphasizes horizontal expansion

Internet applications will face high concurrency, so we will generally expand the system horizontally: single machine structure to multi machine structure (distributed cluster)

Verbs for   HTTP:

GET get resources from the server
 POST adds resources to the server (idempotent is not required)
DELETE delete resource from server
 PUT updates the server resources (the client provides the changed complete resources)
PATCH updates server resources (client provides changed properties)

State code

200 OK - [GET]: the server successfully returns the data requested by the user. The operation is Idempotent.
201 CREATED - [POST/PUT/PATCH]: user creates or modifies data successfully.
202 Accepted - [*]: indicates that a request has entered the background queue (asynchronous task)
204 NO CONTENT - [DELETE]: user successfully deleted data.

400 INVALID REQUEST - [POST/PUT/PATCH]: there is an error in the user's request, and the server has not performed the operation of creating or modifying data, which is idempotent.
401 Unauthorized - [*]: indicates that the user does not have permission (wrong token, user name, password).
403 Forbidden - [*] indicates that the user is authorized (as opposed to a 401 error), but access is forbidden.
404 NOT FOUND - [*]: the user's request is for a nonexistent record. The server does not operate. The operation is idempotent.
406 Not Acceptable - [GET]: the format of user request is not available (for example, user requests JSON format, but only XML format).
410 Gone -[GET]: the resource requested by the user is permanently deleted and will not be obtained again.
422 unprocessable entity - [post / put / patch] a validation error occurred while creating an object.

500 INTERNAL SERVER ERROR - [*]: if the server has an error, the user will not be able to determine whether the request is successful.

Interface design and development

  • When we do data interface, we should design around entity rather than business.

For data interface, we need to install two tripartite Libraries

Pip install djangorestframework
Pip install drf-extensions

We need to register our

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
 	# Register djangorest framework
    'rest_framework',
]

Function based view (FBV)
1)Views

# Accept only get requests
@api_view(('GET', ))    
def get_peovinces(request):
    """Access to provincial administrative areas"""

    # Get the data whose database field is empty (specify get distid, name) field
    queryset = District.objects.filter(pid__isnull=True).only('distid', 'name')
    # Serialize the data (if you add many=True, it will be a list, otherwise it will be a dictionary)
    serializer = DistrictSimpleSerializer(queryset, many=True)
    return Response(serializer.data)

2) Define serializer

  • Create a new serializer.py file under the current apps to define our serializer class
  • Create a DistrictSimpleSerializer class
class DistrictSimpleSerializer(serializers.ModelSerializer):
    """Create serializer"""
    class Meta:
    	# Specify which model (table name) to serialize
        model = District
        # Fields: fields to keep
        fields = ('distid', 'name')

3) mapping URL

urlpatterns = [
    path('districts/', get_peovinces),  # Province inspection
]

Class based view (CBV)
When writing an interface class, we need to inherit the parent class. Here are a few parent classes

ListAPIView query multiple
 RetrieveAPIView query single
 CreateAPIView new data
 DestroyAPIView delete data
 RetrieveUpdateDestroyAPIView get single / delete / update

Query interface: fields to be queried for customization
1)Views

class EstateView(ListAPIView):
	"""Access to property information"""

    queryset = District.objects.all().only('district', 'agents')
    # Which class is used to serialize the real estate
    serializer_class = EstateSerializer

2) Define serializer

  • Create a new serializer.py file under the current apps to define our serializer class
  • Define an EstateSerializer class
class EstateSerializer(serializers.ModelSerializer):

    class Meta:
        model = Estate
        exclude = ('district', 'agents')

3) mapping URL

urlpatterns = [
    path('estates/', EstateView.as_view()),  # Province inspection
]

Full set of interface 1: Method rewriting custom data
1) Define serializer

  • Create a new serializer.py file under the current apps to define our serializer class
  • Define an EstateSerializer class for viewing (because we exclude regions when viewing)
  • Define an EstatePostSerializer class for adding and modifying
class EstateSerializer(serializers.ModelSerializer):

    class Meta:
        model = Estate
        exclude = ('district', 'agents')


class EstatePostSerializer(serializers.ModelSerializer):

    class Meta:
        model = Estate
        exclude = ('agents', )

2) mapping URL

urlpatterns = [
    path('estates/', EstateView.as_view()),
    # Configure to pass in a value when a single request is made (pk: pk is a convention, and the name passed in must be pk)
    path('estates/<int:pk>', EstateView.as_view()),
]

3)Views

class EstateView(ListAPIView, RetrieveAPIView, RetrieveUpdateDestroyAPIView):
	"""Access to property information"""

    queryset = District.objects.all().only('district', 'agents')
    # Which class is used to serialize the real estate
    serializer_class = EstateSerializer
	
	# The override method is used to determine what request is going (because we don't need to look at the region when we go to get here, but we need the region when we add it; so we wrote another serializer)
    def get_serializer_class(self):
        if self.request.method in ('POST', 'PUT', 'PATCH'):
            return EstatePostSerializer
        else:
        	# get and delete go down here
            return EstateSerializer    

    # Override the get method to distinguish which parent's get is inherited (multiple inheritance; the pk value passed in is in the request)
    def get(self, request, *args, **kwargs):
    	# If there is pk in the request, a single pk will be returned, otherwise multiple pk will be returned
        if 'pk' in kwargs:
            cls = RetrieveAPIView
        else:
            cls = ListAPIView

        return cls.get(self, request, *args, **kwargs)

Full set of interface 2: rapid development of full set of interface
1) Define serializer

  • Create a new serializer.py file under the current apps to define our serializer class
  • fields = all means all data
class HouseTypeSerializer(serializers.ModelSerializer):

    class Meta:
        model = HouseType
        fields = '__all__'

2)Views

class HouseTypeViewSet(ModelViewSet):
    # ReadOnlyModelViewSet read only interface
    # ModelViewSet read only full interface
    queryset = HouseType.objects.all()
    serializer_class = HouseTypeSerializer

3) mapping URL

urlpatterns = [
    ...
]

# Configure router
router = SimpleRouter()
# The slash will be filled by itself. Do not write the slash by yourself (register view set)
router.register('housetypers', HouseTypeViewSet)
# Merge urls into urlpatterns
urlpatterns += router.urls

The functions mentioned below are only for CBV

Data interface paging

  after we request the data, we will paginate the data, which is the basic function. Then paging can be divided into cursor paging and pager paging. Pager can configure global paging, and cursor paging has to be added in a single page by itself.

Single page paging
  - Take cursor paging as an example

Custom paging class in views view

class EstatePagination(CursorPagination):
    # Query parameter of page size (max. cannot be greater than max. page size)
    page_size_query_description = 'size'
    # The default data displayed on the page is 30
    max_page_size = 30
    # Page by established
    ordering = 'estateid'

We can add the following sentence to the views view class of the page to be customized:

# Specify paging with custom paging class rules
pagination_class = EstatePagination

Configure global paging
  - Take pager paging as an example

Let's write a global configuration to * * rest framework '*

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

"""
PageNumberPagination    Page by page
LimitOffsetPagination   How many pages to skip
"""

REST_FRAMEWORK = {
    # Default paging class
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    # How many pieces of data are displayed per page
    'PAGE_SIZE': 5,
}

After we add the global paging function, the default pager we configured will be used for every page. However, some pages do not need paging. We can add the following in the views view class of the current page:

# Specifies that the current page is not paged
pagination_class = None

Data interface data cache

Configure our caching service in settings.py

# Configure redis cache
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache', # Using xxx as cache
        # Connect to Redis database (server address)
        # One master with multiple slaves (you can configure a Redis, write the first, read other machines)
        'LOCATION': [
            'redis://47.98.174.108:6379/0',
        ],
        'KEY_PREFIX': 'apidemo',   # Use your project name as a file prefix
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient', # Connection options (default, do not change)
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 512,    # Connections to the pool (maximum connections)
            },
            'PASSWORD': 'password', # Your Redis password
        }
    },
}

Method 1: implement our cache with a mixed class

  • Mixed classes cannot cache cursor pages, so when we cache mixed classes, our paging function must be pager pages used

1) The DRF framework provides caching services for us, but we need to install a tripartite library * * DRF extensions**

pip install drf-extensions 

2) Add a mixed class to our cache. To do this, we need to add a configuration in settings.py:

# Configure the cache framework
REST_FRAMEWORK_EXTENSIONS = {
	# Cache time
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 120,
    # Which group to cache with
    'DEFAULT_USE_CACHE': 'default',
    # Single object cache goes through this group
    'DEFAULT_OBJECT_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_object_cache_key_func',
    # Multiple objects cache this group
    'DEFAULT_LIST_CACHE_KEY_FUNC': 'rest_framework_extensions.utils.default_list_cache_key_func',
}

3)views view we want to add our cacheresponsemixin to the front

class EstateView(CacheResponseMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')

    # Data filtering
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    filterset_class = EstateFilterSet   # Customize a class to define query conditions
    ordering_fields = ('hot', 'estateid')   # Sort by


    def get_serializer_class(self):
        if self.request.method in ('POST', 'PUT', 'PATCH'):
            return EstatePostSerializer
        else:
            return EstateSerializer

    def get(self, request, *args, **kwargs):
        if 'pk' in kwargs:
            cls = RetrieveAPIView
        else:
            cls = ListAPIView

        return cls.get(self, request, *args, **kwargs)

Method 2: implement our caching using decorators

@Method ABCD decorator can make the decorator of the original decoration function into a decorator of decoration class

# Add the cache [u page] to the list method (list is in the ModelViewSet, we don't need to write)
@method_decorator(decorator=cache_page(timeout=600), name='list')
@method_decorator(decorator=cache_page(timeout=600), name='retrieve')
class HouseTypeViewSet(ModelViewSet):
    queryset = HouseType.objects.all()
    serializer_class = HouseTypeSerializer
    pagination_class = None

Data interface filter sort operation

Method 1: in views view, filter and sort by overriding the get ﹣ queryset method

class EstateView(CacheResponseMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')
    
    def get_queryset(self):
        distid = self.request.GET.get('district')
        # Implementation screening
        if distid:
            self.queryset = self.queryset.filter(district=distid)
        ordering = self.request.GET.get('ordering')
        # Implementation sequencing
        if ordering:
            self.queryset = self.queryset.order_by(ordering)
        return self.queryset

Method 2: using Django filter to implement filtering and sorting
1) Install the tripartite Library (Django filter)

pip install django-filter

2) Register our tripartite Library in settings.py's apps

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Register Django filter (you will see a filter on the page)
    'django_filters',
]

3) In views view

  - 3.1 primary filtering (only precise query is supported)

class EstateView(ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')

    # Djangofilterbackend, ordering filter
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    # Filter by which column (precise filter)
    filter_fields = ('district', 'hot')
    # Sort by which column
    ordering_fields = ('hot', 'estateid')   

  - 3.2 advanced filtering (our query class needs to be customized)

class EstateView(ListCreateAPIView, RetrieveUpdateDestroyAPIView):

    queryset = Estate.objects.all().defer('agents')

    # Djangofilterbackend, ordering filter
    filter_backends = (DjangoFilterBackend, OrderingFilter)
    # Customize a filter class to define our filter rules
	filterset_class = EstateFilterSet
    # Sort by which column
    ordering_fields = ('hot', 'estateid')   

  we define our query class in utils.py

class EstateFilterSet(django_filters.FilterSet):
    """custom Filterset Filtering rule"""
    # Lookup? Expr lookup rules
    # Which field [u name] corresponds to (by default, find its name [eg: intro])
    minhot = django_filters.NumberFilter(field_name = 'hot', lookup_expr='gte')
    maxhot = django_filters.NumberFilter(field_name = 'hot',lookup_expr='lte')

	# Search in a single field
	# intro = django_filters.CharFilter(lookup_expr='contains')
	
    # Search in multiple fields (custom method)
    # method is bound to the following methods
    keyword = django_filters.CharFilter(method='filter_by_keyword')

    @staticmethod
    def filter_by_keyword(queryset, key, value):
        queryset = queryset.filter(Q(name__contains=value) | Q(intro__startswith=value))
        return queryset

Data interface current limiting

Current limiting can limit the frequency of interface access to reduce the pressure on the server.
It relies on redis for current restriction (so we need to configure our redis): redis will remember your IP address

Global configuration in settings.py

REST_FRAMEWORK = {
    # Default current limiting class (use rate to limit current)
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
    ),
    # Current limiting rate
    'DEFAULT_THROTTLE_RATES': {
        'anon': '5/min', # 5 times per minute
    }
}
Published 2 original articles, praised 0, visited 20
Private letter follow

Keywords: Django REST Redis pip

Added by pazzy on Sun, 12 Jan 2020 09:34:32 +0200