安装restframework
powershell
pip install djangorestframework
pip install markdown # Markdown support for the browsable API.
pip install django-filter # Filtering support
安装其他模块
powershell
pip install pillow
powershell
pip install django-cors-headers
建模和迁移数据
drf包含四个部分,models,views,urls,和serializers,其中serializers是比django多出的内容。
serializers实现了数据的序列化和反序列化,即数据orm转json,和json转orm。
user数据已经建好。
models还是原来的models
serializers.py
bash
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('id', 'url', 'username', 'email', )
urls指定访问api的路径
urls
bash
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework import routers
from quickstartapp import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
# router.register(r'groups', views.GroupViewSet)
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include(router.urls)),
]
views指定展示的形式,可以在这里定制
views
bash
from django.contrib.auth.models import User
from rest_framework import viewsets
from quickstartapp.serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
查看、编辑用户数据的API接口。
"""
queryset = User.objects.all().order_by('-date_joined') # 排序方法
serializer_class = UserSerializer
Filtering against query parameters(根据查询参数进行过滤)
过滤初始查询集的最后一个示例是基于url中的查询参数确定初始查询集。
我们可以通过重写.get_queryset()方法来处理像http://example.com/api/purchases?username=denvercoder9这样的网址,并且只有在URL中包含username参数时,才过滤queryset:
比如下例子:
访问链接如下。
bash
http://localhost:8000/blogapp/BlogPageCate/?cxcategory=3
数据库表如下
views中需要这样设置
bash
class BlogPageCatetView(generics.ListAPIView):
'''分类导航图标列表'''
serializer_class = BlogPageSerializer
permissin_classes = (permissions.AllowAny,)
pagination_class = LimitOffsetPagination # 分页 请求加 ?limit = xx
def get_queryset(self):
# user = self.request.user
queryset = BlogPage.objects.all()
# queryset = BlogPage.objects.all()
cxcategory = self.request.query_params.get('cxcategory', None)
if cxcategory is not None:
queryset = queryset.filter(cxcategory_id=cxcategory)
return queryset
setting
增加分组功能
serializers
views
urls
增加分页功能
setting
增加登陆功能
urls
限制权限
DRF示例-snippets
snippets\settings.py
bash
"""
Django settings for snippets project.
Generated by 'django-admin startproject' using Django 3.1.7.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'q3s1m3_sg6$p8yf9hmz%#tu)&tv(r@%mqbbfezkd1#993vysrw'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'snippetsapp.apps.SnippetsappConfig',
]
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 = 'snippets.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 = 'snippets.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'snippets',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '123',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/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/3.1/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/3.1/howto/static-files/
STATIC_URL = '/static/'
snippetsapp\models.py
bash
from django.db import models
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_all_lexers, get_lexer_by_name
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE, null= True , blank= True)
highlighted = models.TextField( null=True,blank=True)
class Meta:
ordering = ('created',)
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
高亮显示相关
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
snippetsapp\serializers.py
bash
from rest_framework import serializers
from snippetsapp.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style','owner')
#
# class SnippetSerializer(serializers.Serializer):
# id = serializers.IntegerField(read_only=True)
# title = serializers.CharField(required=False, allow_blank=True, max_length=100)
# code = serializers.CharField(style={'base_template': 'textarea.html'})
# linenos = serializers.BooleanField(required=False)
# language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
# style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
#
# def create(self, validated_data):
# """
# Create and return a new `Snippet` instance, given the validated data.
# """
# return Snippet.objects.create(**validated_data)
#
# def update(self, instance, validated_data):
# """
# Update and return an existing `Snippet` instance, given the validated data.
# """
# instance.title = validated_data.get('title', instance.title)
# instance.code = validated_data.get('code', instance.code)
# instance.linenos = validated_data.get('linenos', instance.linenos)
# instance.language = validated_data.get('language', instance.language)
# instance.style = validated_data.get('style', instance.style)
# instance.save()
# return instance
snippetsapp\views.py
bash
from snippetsapp.models import Snippet
from snippetsapp.serializers import SnippetSerializer
from rest_framework import generics
from rest_framework import permissions
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
# #######################################
# from snippetsapp.models import Snippet
# from snippetsapp.serializers import SnippetSerializer
# from rest_framework import mixins
# from rest_framework import generics
#
# class SnippetList(mixins.ListModelMixin,
# mixins.CreateModelMixin,
# generics.GenericAPIView):
# queryset = Snippet.objects.all()
# serializer_class = SnippetSerializer
#
# def get(self, request, *args, **kwargs):
# return self.list(request, *args, **kwargs)
#
# def post(self, request, *args, **kwargs):
# return self.create(request, *args, **kwargs)
#
#
# class SnippetDetail(mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin,
# mixins.DestroyModelMixin,
# generics.GenericAPIView):
# queryset = Snippet.objects.all()
# serializer_class = SnippetSerializer
#
# def get(self, request, *args, **kwargs):
# return self.retrieve(request, *args, **kwargs)
#
# def put(self, request, *args, **kwargs):
# return self.update(request, *args, **kwargs)
#
# def delete(self, request, *args, **kwargs):
# return self.destroy(request, *args, **kwargs)
####################################3
# from snippetsapp.models import Snippet
# from snippetsapp.serializers import SnippetSerializer
# from django.http import Http404
# from rest_framework.views import APIView
# from rest_framework.response import Response
# from rest_framework import status
#
# class SnippetList(APIView):
# """
# List all snippets, or create a new snippet.
# """
# def get(self, request, format=None):
# snippets = Snippet.objects.all()
# serializer = SnippetSerializer(snippets, many=True)
# return Response(serializer.data)
#
# def post(self, request, format=None):
# serializer = SnippetSerializer(data=request.data)
# if serializer.is_valid():
# serializer.save()
# return Response(serializer.data, status=status.HTTP_201_CREATED)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#
#
# class SnippetDetail(APIView):
# """
# Retrieve, update or delete a snippet instance.
# """
# def get_object(self, pk):
# try:
# return Snippet.objects.get(pk=pk)
# except Snippet.DoesNotExist:
# raise Http404
#
# def get(self, request, pk, format=None):
# snippet = self.get_object(pk)
# serializer = SnippetSerializer(snippet)
# return Response(serializer.data)
#
# def put(self, request, pk, format=None):
# snippet = self.get_object(pk)
# serializer = SnippetSerializer(snippet, data=request.data)
# if serializer.is_valid():
# serializer.save()
# return Response(serializer.data)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#
# def delete(self, request, pk, format=None):
# snippet = self.get_object(pk)
# snippet.delete()
# return Response(status=status.HTTP_204_NO_CONTENT)
###############################################
# from rest_framework import status
# from rest_framework.decorators import api_view
# from rest_framework.response import Response
# from snippetsapp.models import Snippet
# from snippetsapp.serializers import SnippetSerializer
#
#
# @api_view(['GET', 'POST'])
# def snippet_list(request,format=None):
# """
# List all code snippets, or create a new snippet.
# """
# if request.method == 'GET':
# snippets = Snippet.objects.all()
# serializer = SnippetSerializer(snippets, many=True)
# return Response(serializer.data)
#
# elif request.method == 'POST':
# serializer = SnippetSerializer(data=request.data)
# if serializer.is_valid():
# serializer.save()
# return Response(serializer.data, status=status.HTTP_201_CREATED)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#
# @api_view(['GET', 'PUT', 'DELETE'])
# def snippet_detail(request, pk):
# """
# Retrieve, update or delete a code snippet.
# """
# try:
# snippet = Snippet.objects.get(pk=pk)
# except Snippet.DoesNotExist:
# return Response(status=status.HTTP_404_NOT_FOUND)
#
# if request.method == 'GET':
# serializer = SnippetSerializer(snippet)
# return Response(serializer.data)
#
# elif request.method == 'PUT':
# serializer = SnippetSerializer(snippet, data=request.data)
# if serializer.is_valid():
# serializer.save()
# return Response(serializer.data)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#
# elif request.method == 'DELETE':
# snippet.delete()
# return Response(status=status.HTTP_204_NO_CONTENT)
###########################################################
#
#
# from django.http import HttpResponse, JsonResponse
# from django.views.decorators.csrf import csrf_exempt
# from rest_framework.renderers import JSONRenderer
# from rest_framework.parsers import JSONParser
# from snippetsapp.models import Snippet
# from snippetsapp.serializers import SnippetSerializer
#
#
# @csrf_exempt
# def snippet_list(request):
# """
# List all code snippets, or create a new snippet.
# """
# if request.method == 'GET':
# snippets = Snippet.objects.all()
# serializer = SnippetSerializer(snippets, many=True)
# return JsonResponse(serializer.data, safe=False)
#
# elif request.method == 'POST':
# data = JSONParser().parse(request)
# serializer = SnippetSerializer(data=data)
# if serializer.is_valid():
# serializer.save()
# return JsonResponse(serializer.data, status=201)
# return JsonResponse(serializer.errors, status=400)
#
#
# @csrf_exempt
# def snippet_detail(request, pk):
# """
# Retrieve, update or delete a code snippet.
# """
# try:
# snippet = Snippet.objects.get(pk=pk)
# except Snippet.DoesNotExist:
# return HttpResponse(status=404)
#
# if request.method == 'GET':
# serializer = SnippetSerializer(snippet)
# return JsonResponse(serializer.data)
#
# elif request.method == 'PUT':
# data = JSONParser().parse(request)
# serializer = SnippetSerializer(snippet, data=data)
# if serializer.is_valid():
# serializer.save()
# return JsonResponse(serializer.data)
# return JsonResponse(serializer.errors, status=400)
#
# elif request.method == 'DELETE':
# snippet.delete()
# return HttpResponse(status=204)
重新迁移数据库
When that's all done we'll need to update our database tables. Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.
当修改过models后,需要更新数据库表。通常是建立一个数据库迁移,在这个课程中,我们只是删除这个数据库并且重新开始。
bash
rm -f db.sqlite3
rm -r snippetsapp/migrations
python manage.py makemigrations snippetsapp
python manage.py migrate
实际中的操作
bash
C:\djproject\snippets>python manage.py makemigrations
Migrations for 'snippetsapp':
snippetsapp\migrations\0002_auto_20210402_0756.py
- Add field highlighted to snippet
- Add field owner to snippet
C:\djproject\snippets>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, snippetsapp
Running migrations:
Applying snippetsapp.0002_auto_20210402_0756... OK
DRF接口增加流程
setting配置
时区设置
项目setting文件
bash
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
资源文件路径
bash
STATIC_URL = '/static/'
MEDIA_URL='/media/'
MEDIA_ROOT=os.path.join(os.path.dirname(BASE_DIR), 'eshop/media')
restframework设置
bash
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 6, # 分页设置
'DEFAULT_AUTHENTICATION_CLASSES': ( # 认证设置
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
}
数据库设定
bash
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': 'eshop',
# 'HOST': '127.0.0.1',
# 'PORT': 3306,
# 'USER': 'root',
# 'PASSWORD': '123',
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
项目初始化文件mysql支持设定
项目根_init_.py
bash
import pymysql
pymysql.install_as_MySQLdb()
中间件设定
bash
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'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',
]
应用设定
bash
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # restframework支持
'rest_framework.authtoken', # 增加身份认证
# 'django_filters',
'computerapp.apps.ComputerappConfig', # 你的应用
'corsheaders', 跨站支持
]
创建数据模型
数据模型是整个应用的基础,应事先规划好,避免后期有大的改动
bash
from django.db import models
# Create your models here.
from django.db import models
# from django.utils.six import python_2_unicode_compatible
from django.conf import settings
# @python_2_unicode_compatible
class Category(models.Model):
"""
商品类别:笔记本、平板电脑、一体机、台式机、服务器
"""
name = models.CharField(max_length=200, verbose_name='名称')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = '商品类别'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
# @python_2_unicode_compatible
class Manufacturer(models.Model):
"""
生产厂商
"""
name = models.CharField(max_length=200)
description = models.TextField()
logo = models.ImageField(blank=True, null=True, max_length=200, upload_to='manufacturer/uploads/%Y/%m/%d/')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
# @python_2_unicode_compatible
class Product(models.Model):
"""
产品
"""
model = models.CharField(max_length=200,verbose_name='型号')
description = models.TextField()
image = models.ImageField(max_length=200, upload_to='product/uploads/%Y/%m/%d/')
price = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='价格')
sold = models.PositiveIntegerField(default=0)
category = models.ForeignKey(Category, related_name='product_in', on_delete=models.CASCADE, verbose_name='类别')
manufacturer = models.ForeignKey(Manufacturer, related_name='product_of', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = '产品'
verbose_name_plural = verbose_name
def __str__(self):
return self.model
# @python_2_unicode_compatible
class DeliveryAddress(models.Model):
"""
收货地址
"""
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='delivery_address_of', )
contact_person = models.CharField(max_length=200)
contact_mobile_phone = models.CharField(max_length=200)
delivery_address = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.delivery_address
# @python_2_unicode_compatible
class UserProfile(models.Model):
"""
用户档案
"""
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile_of', )
mobile_phone = models.CharField(blank=True, null=True, max_length=200)
nickname = models.CharField(blank=True, null=True, max_length=200)
description = models.TextField(blank=True, null=True)
icon = models.ImageField(blank=True, null=True, max_length=200, upload_to='user/uploads/%Y/%m/%d/')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
delivery_address = models.ForeignKey(DeliveryAddress, related_name='user_delivery_address',
on_delete=models.CASCADE, blank=True, null=True, )
# @python_2_unicode_compatible
class Order(models.Model):
"""
订单
"""
STATUS_CHOICES = (
('0', 'new'),
('1', 'not paid'),
('2', 'paid'),
('3', 'transport'),
('4', 'closed'),
)
status = models.CharField(choices=STATUS_CHOICES, default='0', max_length=2)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='order_of', )
remark = models.TextField(blank=True, null=True)
product = models.ForeignKey(Product, related_name='order_product', on_delete=models.CASCADE)
price = models.DecimalField(max_digits=12, decimal_places=2)
quantity = models.PositiveIntegerField(default=1)
address = models.ForeignKey(DeliveryAddress, related_name='order_address', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return 'order of %d' % (self.user.id)
创建序列化器
用Django开发RESTful风格的API存在着很多重复的步骤。过程往往都是,
(1)把到前端请求的过来的json字符串,然后通过json.loads转换为字典,字典在转换为对象,保存在数据库。
(2)返回的时候呢,都是先把对象查询出来,然后转换为字典,再通过JsonResponse转换为json字符串并且返回给前端。
接口的开发,基本就是在重复这两个动作,而且这两个动作语句特别多,序列化就是来解决这个问题。
- 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串
- 反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型
- 反序列化,完成数据校验功能
bash
from django.contrib.auth.models import User
from rest_framework import serializers
from computerapp.models import Product, Manufacturer, UserProfile, DeliveryAddress, Order
class ProductListSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('id', 'model', 'image', 'price', 'sold', 'category', 'manufacturer',)
class ManufacturerSerializer(serializers.ModelSerializer):
class Meta:
model = Manufacturer
fields = ('id', 'name',)
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Manufacturer
fields = ('id', 'name',)
class ProductRetrieveSerializer(serializers.ModelSerializer):
manufacturer = ManufacturerSerializer() # 获取厂商对象,替换下面的字段
category = CategorySerializer() # 获取类别对象,替换下面的字段
class Meta:
model = Product
fields = (
'id', 'model', 'image', 'price', 'sold', 'category', 'manufacturer', 'description', 'created', 'updated',)
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id', 'user', 'mobile_phone', 'nickname', 'description', 'icon', 'created', 'updated',)
read_only_fields = ('user',)
class UserInfoSerializer(serializers.ModelSerializer):
profile_of = UserProfileSerializer()
class Meta:
model = User
fields = ('id', 'username', 'email', 'first_name', 'last_name', 'date_joined', 'profile_of',)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name',)
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(**validated_data) # 接受前端传过来的用户名和密码,关键字参数
user.set_password(validated_data['password']) # 通过字典方式调用
user.save() # 保存到内存中
user_profile = UserProfile(user=user)
user_profile.save()
return user
class DeliveryAddressSerilizer(serializers.ModelSerializer):
'''收货地址'''
class Meta:
model = DeliveryAddress
fields = ('id', 'user', 'contact_person', 'contact_mobile_phone', 'delivery_address', 'created', 'updated',)
read_only_fields = ('user',) # 设置为只读类型,不能在前端修改
class OrderListSerializer(serializers.ModelSerializer):
product = ProductListSerializer()
address = DeliveryAddressSerilizer()
class Meta:
model = Order
fields = ('id', 'status', 'user', 'product', 'price', 'quantity', 'remark', 'address', 'created', 'updated',)
class OrderCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ('id', 'status', 'user', 'product', 'price', 'quantity', 'remark', 'address', 'created', 'updated',)
read_only_fields = ('user', 'price', 'address', 'status',)
class OrderRUDSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ('id',)
创建视图类
bash
from rest_framework import generics
from rest_framework import permissions
from rest_framework.exceptions import NotFound
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.response import Response
from rest_framework.views import APIView
from computerapp.models import Product, DeliveryAddress, UserProfile, Order
from computerapp.serializers import ProductListSerializer, ProductRetrieveSerializer, UserInfoSerializer, \
UserSerializer, DeliveryAddressSerilizer, UserProfileSerializer, OrderListSerializer, OrderCreateSerializer, \
OrderRUDSerializer
import logging
import datetime
import json
LOG_FILENAME = 'shop.log'
# logging.basicConfig(filename=LOG_FILENAME,level = logging.DEBUG)
logging.basicConfig(filename=LOG_FILENAME, level=logging.INFO)
# Create your views here.
class ProductListView(generics.ListAPIView):
'''产品列表'''
queryset = Product.objects.all()
serializer_class = ProductListSerializer
permissin_classes = (permissions.AllowAny,)
filter_backends = (OrderingFilter, SearchFilter) # 过滤选项
ordering_fields = ('category', 'manufacturer', 'created', 'sold',) # 排序字段
search_fields = ('description', 'model') # 搜索字段
ordering = ('id',)
pagination_class = LimitOffsetPagination # 分页 请求加 ?limit = xx
class ProductListByCategoryView(generics.ListAPIView):
'''产品类别列表'''
serializer_class = ProductListSerializer
permissin_classes = (permissions.AllowAny,)
filter_backends = (OrderingFilter, SearchFilter)
ordering_fields = ('category', 'manufacturer', 'created', 'sold', 'stock', 'price',)
search_fields = ('description',)
ordering = ('id',)
'''查询集根据条件获取'''
def get_queryset(self):
category = self.request.query_params.get('category', None)
if category is not None:
queryset = Product.objects.filter(category=category)
else:
queryset = Product.objects.all()
return queryset
class ProductListByCategoryManufacturerView(generics.ListAPIView):
'''产品按类别品牌列表'''
serializer_class = ProductListSerializer
permissin_classes = (permissions.AllowAny,)
filter_backends = (OrderingFilter, SearchFilter)
ordering_fields = ('category', 'manufacturer', 'created', 'sold', 'stock', 'price',)
search_fields = ('description',)
ordering = ('id',)
def get_queryset(self):
category = self.request.query_params.get('category', None)
manufacturer = self.request.query_params.get('manufacturer', None)
if category is not None:
queryset = Product.objects.filter(category=category, manufacturer=manufacturer)
else:
queryset = Product.objects.all()
return queryset
class ProductRetrieveView(generics.RetrieveAPIView):
queryset = Product.objects.all()
serializer_class = ProductRetrieveSerializer
permission_classes = (permissions.AllowAny,)
class UserInfoView(APIView):
'''用户基本信息'''
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, format=None):
user = self.request.user
serializer = UserInfoSerializer(user)
return Response(serializer.data)
class UserProfileRUView(generics.RetrieveUpdateAPIView):
'''用户其他信息'''
serializer_class = UserProfileSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
user = self.request.user
obj = UserProfile.objects.get(user=user)
return obj
class UserCreateView(generics.CreateAPIView):
'''用户创建'''
serializer_class = UserSerializer
class DeliveryAddressLCView(generics.ListCreateAPIView):
'''收货地址LC'''
serializer_class = DeliveryAddressSerilizer # 没有加LC,因为较简单
permission_classes = (permissions.IsAuthenticated,) # 需要登陆认证
def get_queryset(self):
user = self.request.user
queryset = DeliveryAddress.objects.filter(user=user) # 模型来的对象
return queryset
def perform_create(self, serializer):
user = self.request.user
s = serializer.save(user=user)
profile = user.profile_of # profile_of 反向关系,从models来
profile.delivery_address = s # 把新创建的收货地址设为默认地址存入用户扩展信息中
profile.save()
class DeliveryAddressRUDView(generics.RetrieveUpdateDestroyAPIView):
'''收货地址RUD'''
serializer_class = DeliveryAddressSerilizer
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
user = self.request.user
# obj =DeliveryAddress.objects.get(user=user)
try:
obj = DeliveryAddress.objects.get(id=self.kwargs['pk'], user=user) # id由前端传来,一个用户有多个id
except Exception as e:
raise NotFound('no found')
return obj
class CartListView(generics.ListAPIView):
'''购物车列表'''
serializer_class = OrderListSerializer # 可以和订单公用一个序列器
permissin_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
user = self.request.user
queryset = Order.objects.filter(user=user, status='0') # 我的状态为零的,表示在购物车
return queryset
class OrderListView(generics.ListAPIView):
'''订单列表'''
serializer_class = OrderListSerializer # 和cart公用一个序列器
permissin_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
user = self.request.user
queryset = Order.objects.filter(user=user, status__in=['1', '2', '3', '4']) # status__in 是django用法,表示值在。。范围
return queryset
class OrderCreateView(generics.CreateAPIView):
'''创建订单'''
queryset = Order.objects.all()
serializer_class = OrderCreateSerializer
permission_classes = (permissions.IsAuthenticated,)
def perform_create(self, serializer):
user = self.request.user
product = serializer.validated_data.get('product')
serializer.save(user=user, price=product.price, address=user.profile_of.delivery_address, )
logging.info('user %d cart changed,product %d related.Time is %s.', user.id, product.id,
str(datetime.datetime.now()))
class OrderRUDView(generics.RetrieveUpdateDestroyAPIView):
'''OrderRUD'''
serializer_class = OrderRUDSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
user = self.request.user
obj = Order.objects.get(user=user, id=self.kwargs['pk'])
return obj
def perform_update(self, serializer):
user = self.request.user
serializer.save(user=user, status='1')
过滤组件与分页组件的使用简介
bash
# views.py
# drf提供两种过滤组件类:全文搜索、排序
from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
# 通过安装第三方库django-filter,即可使用第三种过滤组件:分类
from django_filters.rest_framework import DjangoFilterBackend
# drf提供三种分页类
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
class CourseViewSet(GenericViewSet, ListModelMixin):
# 过滤组件
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
# 过滤:全文搜索
search_fields = ['name'] # 按数据库非外键字段,包含了目标参数即可匹配
# 过滤:分类搜索
fields = [] # 按数据库字段,包括外键字段,完全相等才匹配到结果
filter_class = CourseFilterSet # 按设置的分类规则进行分类,优先级大于按字段分类
# 过滤:排序
ordering_fields = [] # 按数据库字段,包括外键字段
# 分页组件
pagination_class = PageNumberPagination
# pagination_class = LimitOffsetPagination
# 分页参数配置:PageNumberPagination
page_size = 100 # 每页显示最多的数目
page_query_param = 'page' # 前端发送的分页的页数参数的关键字名,默认位"page"
page_size_query_param = 'page_size' # 前端发送的每页数目参数的关键字名,默认位None
max_page_size = None # 前端最多能设置的每页数量
# 分页参数配置:LimitOffsetPagination
'''
default_limit = 100 # 每页显示最多的数目,默认配置与page_size相同,api_settings.PAGE_SIZE
limit_query_param = 'limit'
offset_query_param = 'offset' # 偏移多少数目的参数的关键字名
max_limit = None
'''
过滤组件和分页组件的入口函数
过滤与分页均只对群查接口有意义,因此只在群查中使用,即list()方法中调用组件的入口函数
但是组件的入口函数,是定义在GenericAPIView中的
bash
class GenericAPIView(views.APIView):
def filter_queryset(self, queryset):
pass
def paginate_queryset(self, queryset):
pass
过滤组件:分类filter_class
bash
from . import models
from django_filters.filterset import FilterSet
from django_filters import filters
class CourseFilterSet(FilterSet):
# 通过NumberFilter类自定义分类字段
# 如下定义两个区间分类字段:teacher_id大于min_price和teacher_id小于max_price
min_price = filters.NumberFilter(field_name='teacher_id', lookup_expr='gte')
max_price = filters.NumberFilter(field_name='teacher_id', lookup_expr='lte')
class Meta:
model = models.Course
fields = ['参与分类的数据库字段'] # 自定义的min_price、max_price字段不需要写在fields里面
创建路由
bash
"""eshop URL Configuration
项目urls
"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views
urlpatterns = [
path('admin/', admin.site.urls),
path('computer/', include('computerapp.urls')),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # 增加api认证
path('api-token-auth/', views.obtain_auth_token), # 获得token,利用rest框架自身功能实现
]
# 如果开启debug模式,使用media文件需要加入以下设置
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
应用urls
bash
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from computerapp import views
urlpatterns = [
path('user_info/', views.UserInfoView.as_view()),
path('user_profile_ru/<int:pk>/', views.UserProfileRUView.as_view()),
path('product_list/', views.ProductListView.as_view()),
path('product_list_by_category/', views.ProductListByCategoryView.as_view()),
path('product_list_by_category_manufacturer/', views.ProductListByCategoryManufacturerView.as_view()),
path('product_retrieve/<int:pk>/', views.ProductRetrieveView.as_view()),
path('user_create/', views.UserCreateView.as_view()),
path('delivery_address_lc/', views.DeliveryAddressLCView.as_view()),
path('delivery_address_rud/<int:pk>/', views.DeliveryAddressRUDView.as_view()),
path('cart_list/', views.CartListView.as_view()),
path('order_list/', views.OrderListView.as_view()),
path('order_create/', views.OrderCreateView.as_view()),
path('order_rud/<int:pk>/', views.OrderRUDView.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
增加用户信息
model使用时系统自带的用户user
views增加用户基本信息
serializers增加
urls增加
models增加用户扩展信息
增加认证功能
认证方法
流程
前端通过用户名密码访问token获取接口,获取token,存储在本地,以后再访问需要认证的资源,可以带着这个token访问接口,后端即认为是此用户名密码用户访问。
目前常用的为客户端webstorage,服务端token;cookie和session方法不再常用
使用token方法
要使用 TokenAuthentication 方案 ,需要在setting包含rest_framework.authtoken应用项;并且配置认证类包含TokenAuthentication。
bash
INSTALLED_APPS = [
...
'rest_framework.authtoken'
]
增加认证类
当使用 TokenAuthentication, 你可能需要通过提供用户名和密码而获得token这样一个机制。REST framework提供了一个内置的 view 来实现这个功能。只需要添加 obtain_auth_token view 到你的 URLconf就可以使用它。
from rest_framework.authtoken import views
urlpatterns += [
path('api-token-auth/', views.obtain_auth_token)
]
用户前端访问这个接口,只需要这一行代码即完成token的生成并返回。
Note that the URL part of the pattern can be whatever you want to use.
The obtain_auth_token view will return a JSON response when valid username and password fields are POSTed to the view using form data or JSON:
{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
配置完成后的数据库迁移
bash
manage.py makemigrations
manage.py migrate
及
rest_framework.authtoken
应用提供了数据库迁移。
bash
C:\djproject\eshop>python manage.py makemigrations
Migrations for 'computerapp':
computerapp\migrations\0002_auto_20210404_1048.py
- Change Meta options on category
- Change Meta options on product
- Alter field name on category
- Alter field category on product
- Alter field model on product
- Alter field price on product
bash
C:\djproject\eshop>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, authtoken, computerapp, contenttypes, sessions
Running migrations:
Applying computerapp.0002_auto_20210404_1048... OK
以上第一条记录auth, authtoken,数据迁移。
以下是先做,报了错误,修改后然后做的上面步骤。
bash
C:\djproject\eshop>python manage.py migrate
System check identified some issues:
WARNINGS:
?: (rest_framework.W001) You have specified a default PAGE_SIZE pagination rest_framework setting, without specifying also a DEFAULT_PAGINATION_CLASS.
HINT: The default for DEFAULT_PAGINATION_CLASS is None. In previous versions this was PageNumberPagination. If you wish to define PAGE_SIZE globally whilst defining pagination_
class on a per-view basis you may silence this check.
Operations to perform:
Apply all migrations: admin, auth, authtoken, computerapp, contenttypes, sessions
Running migrations:
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying authtoken.0003_tokenproxy... OK
在根urls增加
第一行为引入views
第二行为增加认证功能
第三行为获得token功能,利用了rest自带功能