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 } }