django rest framework api構建

Introductions

what is django rest framework

Django REST框架是一個功能強大且靈活的工具包,用於構建Web API。

An example for basedata

1. settings

INSTALLED_APPS = [
    'suit',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # local
    'basedata',
    'news',
    'reports',
    'analysis',
    'common',

    #Thrid Part

    'rest_framework',
    'rest_framework_jwt',
    'ckeditor',
    'ckeditor_uploader',
    'rest_framework.authtoken',
    'rest_auth'


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

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 'oauth2_provider.ext.rest_framework.OAuth2Authentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'PAGE_SIZE': 10,
    'DEFAULT_PAGINATION_CLASS': 'common.paginations.DefaultPagination',
    'URL_FIELD_NAME': 'api_url',
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter'
    ),
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

以上爲django 在 rest framework 的配置項

2. model

此次以user的擴展屬性介紹api實現流程,代碼如下:

import os
import uuid

from PIL import Image
from ckeditor.fields import RichTextField
from django.contrib.auth.models import User
from django.db import models

# Create your models here.
from django.utils.six import text_type

from tdc import settings

class UserProfile(models.Model):
    NATURE = (
        ('國企', '國企'),
        ('私企', '私企'),
        ('外資', '外資'),
        ('其他', '其他'),)

    user = models.OneToOneField(User, verbose_name='用戶', on_delete=models.CASCADE, )
    company_name = models.CharField(verbose_name='企業名稱', blank=True, null=True, max_length=settings.DB_CHAR_NAME_40)
    company_code = models.CharField(verbose_name='企業代碼', blank=True, null=True, max_length=settings.DB_CHAR_NAME_40)
    credit_code = models.CharField(verbose_name='社會信用代碼', blank=True, null=True, max_length=settings.DB_CHAR_NAME_40)
    company_nature = models.CharField(verbose_name='企業性質', choices=NATURE, blank=True,
                                      max_length=settings.DB_CHAR_NAME_20)
    registration_type = models.CharField(verbose_name='登記註冊類型', blank=True, null=True,
                                         max_length=settings.DB_CHAR_NAME_40)
    carbon_trade = models.BooleanField(verbose_name='是否碳交易企業', blank=True, default=True)
    industry = models.ForeignKey(Industry, verbose_name='所屬行業', on_delete=models.CASCADE, related_name='user_industry')
    link_person = models.CharField(verbose_name='聯繫人姓名', blank=True, null=True, max_length=settings.DB_CHAR_NAME_20)
    moble = models.CharField(verbose_name='聯繫人電話', max_length=settings.DB_CHAR_NAME_20, blank=True, null=True)
    email = models.EmailField(verbose_name='郵箱', blank=True, null=True, max_length=settings.DB_CHAR_NAME_20)
    business_address = models.CharField(verbose_name='經營地址', blank=True, default='',
                                        max_length=settings.DB_CHAR_NAME_100)
    settingsruction_area = models.FloatField(verbose_name='建築面積', blank=True, null=True,
                                             max_length=settings.DB_CHAR_NAME_40)
    product = RichTextField(null=True)

    class Meta:
        verbose_name = '企業信息'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.user.username if (self.company_name is None) else self.company_name

    @property
    def industry_name(self):
        return self.industry.industry_type

    @property
    def group_name(self):
        try:
            return self.user.groups.all()[0].name
        except:
            return ''

    @property
    def carbon_trade_name(self):
        if self.carbon_trade:
            return '%s' % ('是')
        else:
            return '%s' % ('否')
def scramble_uploaded_filename(instance, filename):
    """
    Scramble / uglify the filename of the uploaded file, but keep the files extension (e.g., .jpg or .png)
    :param instance:
    :param filename:
    :return:
    """
    extension = filename.split(".")[-1]
    return "{}.{}".format(uuid.uuid4(), extension)

def create_thumbnail(input_image, thumbnail_size=(500, 300)):
    """
    Create a thumbnail of an existing image
    :param input_image:
    :param thumbnail_size:
    :return:
    """
    if not input_image or input_image == "":
        return
    try:
        image = Image.open(input_image)
    except IOError:
        print (' in  IOError')
        return

    mode = image.mode
    if mode not in ('L', 'RGB'):
        if mode == 'RGBA':
            # 透明圖片需要加白色底
            image.load()
            alpha = image.split()[3]
            bgmask = alpha.point(lambda x: 255 - x)
            image = image.convert('RGB')
            # paste(color, box, mask)
            image.paste((255, 255, 255), None, bgmask)
        else:
            image = image.convert('RGB')
    # make sure an image has been set

    #
    # # open image
    # image = Image.open(input_image)

    # use PILs thumbnail method; use anti aliasing to make the scaled picture look good
    image.thumbnail(thumbnail_size, Image.ANTIALIAS)

    # parse the filename and scramble it
    filename = scramble_uploaded_filename(None, os.path.basename(input_image.name))
    arrdata = filename.split(".")
    # extension is in the last element, pop it
    extension = arrdata.pop()
    basename = "".join(arrdata)
    # add _thumb to the filename
    new_filename = basename + "_thumb." + extension

    # save the image in MEDIA_ROOT and return the filename
    image.save(os.path.join(settings.MEDIA_ROOT, new_filename))

    return new_filename
class ProductImage(models.Model):
    user = models.ForeignKey(User, verbose_name='用戶', on_delete=models.CASCADE)
    url = models.ImageField(upload_to='images/%Y/%m/%d', null=False, blank=False, verbose_name='圖片url')
    thum_img = models.ImageField(null=True, blank=True, verbose_name='壓縮圖片')

    class Meta:
        verbose_name = '圖片'
        verbose_name_plural = verbose_name

    def save(self, *args, **kwargs):
        self.thum_img=create_thumbnail(self.url)
        super(ProductImage, self).save(*args, **kwargs)

上圖配置,進行了基本信息,圖片,副文本等模型創建。

3. serializers

因爲前後端分離放棄了view,templeta,jijia,所以精簡,只需將數據進行序列化,傳入前臺,方便處理數據。


class UserProfileSerializer(serializers.ModelSerializer):
  

    class Meta:
        model = UserProfile
        fields = (
            'id', 'company_name', 'company_code', 'credit_code', 'company_nature', 'registration_type', 'carbon_trade',
            'industry_name', 'link_person', 'moble', 'email', 'business_address', 'settingsruction_area',
            'product','group_name','carbon_trade_name')
        read_only_fields = ('id',)
class UpdateUserProfileSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = UserProfile
        fields = '__all__'

    def create(self, validated_data):
        return UserProfile.objects.create(**validated_data)
class ProductImageSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = ProductImage
        fields = ('user', 'url','id','thum_img')

serializer 跟django的form是非常相像。在字段中包含required , max_length和default 等驗證標誌,此處採用的是繼承model的類型,省略大部分代碼。故,不需要重複填寫驗證。同時,也可以在此處自定義驗證方式。字段標誌還可以控制在某些情況下應該如何顯示序列化程序,例如在呈現HTML時。 上面的{‘base_template’: ‘textarea.html’}標誌等同於在Django Form類上使用widget=widgets.Textarea。
對於數據展示類型,我們傾向於json,便於前後端交互。外鍵處理,以及更多內容需查看官方文檔serializer

4. use viewset prefer view

REST框架包含一個ViewSets 的抽象行爲,它允許開發人員能專注於API的狀態和相互作用進行建模,並基於常規約定自動構建URL。
ViewSet 類和 View 類幾乎是一樣的,區別是它提供了類似於read 或 update 這樣的操作, 而不處理像 get 或者 put 這樣的方法。
一個ViewSet 類當被實例化成一組視圖時, 通常會通過使用一個路由類(Router class)來幫你處理複雜的URL定義,最終綁定到一組處理方法。
處理viewset基本都是重構已有的類,也可自己重寫類。該示例的代碼如下:

class UserViewset(mixins.ListModelMixin, mixins.UpdateModelMixin, GenericViewSet):
    """
    用戶的基本信息的查看,編輯
    """
    queryset = UserProfile.objects.all()
    serializer_class = UserProfileSerializer
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)  # 訪問該視圖需要驗證身份信息,將使用這些類

    def get_queryset(self):

        return UserProfile.objects.filter(user=self.request.user)

    def get_serializer_class(self):
        if self.action == 'list':
            return UserProfileSerializer
        return UpdateUserProfileSerializer




class UplodaViewSet(viewsets.ModelViewSet):
    """創建圖片
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    queryset = ProductImage.objects.all()
    serializer_class = ProductImageSerializer

因爲基本信息是在採用django signal 機制自動生成,故api中不需要創建以及刪除的方法,沒有繼承modelViewSet,只有list和update,以及基本GenericViewSet,permission_classes是檢查用戶對model的權限,authentication_classes是網頁驗證的方法,都可以進行二次開發。其中IsOwnerOrReadOnly的代碼如下:

class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Object-level permission to only allow owners of an object to edit it.
    Assumes the model instance has an `owner` attribute.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if obj.user == request.user:
            return True
        # Instance must have an attribute name "owner"

        return obj.user == request.user

更多詳細內容參考項目,以及官方文檔Viewsetbase view

5. router

由於我們使用 ViewSet 類而不是View 類, 我們實際上不需要自己設計URL配置。 通過一個 Router 類會自動處理 視圖和 url 連接資源。 我們所需要做的就是通過路由註冊一個適當的視圖集。
現在我們重寫urls.py 文件:

rounter = DefaultRouter()
rounter.register(r'users', UserViewset, base_name='users')
rounter.register(r'reports', ReportViewSet, base_name='reports')
rounter.register(r'fossil', CCategoryOneFViewSet, base_name='fossil')
rounter.register(r'electricity', CElectricityCViewSet, base_name='electricity')
rounter.register(r'heat', CHeatCViewSet, base_name='heat')
rounter.register(r'news', NewViewSet, base_name='news')
rounter.register(r'pdf',PDFViewSet,base_name='pdf')
rounter.register(r'upload', UplodaViewSet, base_name='upload')

使用 viewsets 可以是一個非常有用的抽象。 它有助於確保您的API中的URL約定是一致的,最大限度地減少您編寫的代碼量,並允許您專注於API提供的交互和表示,而不是URL conf的具體內容。
這並不意味着這總是好的選擇,就像在權衡使用基於類的視圖還是是基於函數的視圖一樣。
運行起來的內容爲:
api
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章