class UserProfile(AbstractUser):
# 继承Django原先的用户基类
'''
Username and password are required. Other fields are optional.
'''
gender_choice = [
('male', '男'),
('female', '女'),
]
username = models.CharField('用户名', max_length=30, unique=True)
mobile = models.CharField('电话', max_length=20, null=False, blank=False)
email = models.EmailField('邮箱', null=True, blank=True)
gender = models.CharField('性别', null=False, choices=gender_choice)
def __str__(self):
return self.username
class Meta:
db_table = 'user'
verbose_name = '用户信息'
verbose_name_plural = verbose_name
mobile = models.CharField('电话', max_length=20, null=False, blank=False)
1.null 是针对数据库而言的,False表示不能为空
2.blank 是针对表单而言,False 表示表单的该字段不能为空
gender = models.CharField('性别', null=False, choices=gender_choice)
如果提供了选择,则模型验证将强制执行这些选择,默认表单窗口小部件将是带有这些选择的选择框,而不是标准文本字段。
每个元组中的第一个元素是要在模型上设置的实际值,第二个元素是人类可读的名称。
# xadmin
$ pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2
# ckeditor
$ pip install django-ckeditor
将第三方插件要注册到应用中去
商品的数据库模型
class Category(models.Model):
# 类别的多种级别
types = [
(1, '一级类别'),
(2, '二级类别'),
(3, '三级列别')
]
# …………
category_type = models.IntegerField('类别级别', choices=types)
# 类别的多级分类
# 一级类别:二级类别 1:n
parent_category = models.ForeignKey('self', verbose_name='父级分类'
, related_name='sub_category', on_delete=models.CASCADE,null=True, blank=True)
# 是否添加导航
is_tab = models.BooleanField('是否导航', default=False)
add_time = models.DateTimeField('添加时间', default=datetime.now)
parent_category = models.ForeignKey('self', verbose_name='父级分类'
, related_name='sub_category', on_delete=models.CASCADE,null=True, blank=True)
# 父级分类和子分类 为 1:n关系
on_delete参数:
To create a recursive relationship – an object that has a many-to-one relationship with itself – use models.ForeignKey('self', on_delete=models.CASCADE).
级联删除。 Django会在DELETE CASCADE上模拟SQL约束的行为,并删除包含ForeignKey的对象。
class GoodsAdmin(object):
# …………
# 在添加商品的时候可以添加商品图片
class GoodsImagesInline(object):
model = GoodsImage
# 不显示的字段名称
exclude = ["add_time"]
# 控制初始表单数量,默认为3
extra = 3
style = 'tab'
# 内部嵌套
inlines = [GoodsImagesInline]
# 加载Django配置和Django APP的注册。
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shopAPI.settings")
django.setup()
# 然后导入数据,引入之前定义的数据库模型,将数据进行填充
注意要在加载Django配置之后在导入数据库模型
# settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# urls.py
urlpatterns = [
# …………
# 配置静态文件的路由
path('media/' , serve, {'document_root': MEDIA_ROOT})
]
视图函数的两种形式
FBV 视图函数接受request请求,函数体中直接是视图函数
CBV 该方法的视图继承了视图类views,然后类中可以定义各种http请求方式,
不同的请求方式可以对应不同的视图函数,即将对图函数和HTTP请求方式绑定,
编写路由的时候可以使用as_views()来使类生成视图函数
FBV适合小型项目,综合、大型项目可以使用CBV
Django API
什么是序列化?
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,例如json或者xml格式
序列化的作用
安装Django Rest Frameword的依赖包
$ pip install djangorestframework
# 自动生成API文档
$ pip install coreapi
# 文档的markdown展示
$ pip install markdown
# django-guardian是为Django提供额外的基于对象权限的身份验证后端
$ pip install django-guardian
# 过滤支持
$ pip install django-filter
# 最后在setting.py中添加第三方插件
1.编写序列化类,规定需要进行序列化传输的数据
2.编写视图类,其中使用序列化
3.配置路由
# app/goods/serializers.py
from rest_framework import serializers
# 编写序列化类,其中的字段需要和数据库模型中的字段名对应
class GoodsSerializers(serializers.Serializer):
name = serializers.CharField(required=True, max_length=20)
shop_price = serializers.FloatField(default='0.0')
goods_front_image = serializers.ImageField()
# app/goods/views.py
from app.goods.models import Goods
from app.goods.serializers import GoodsSerializers
class GoodsListViews(APIView):
def get(self, request):
goods = Goods.objects.all()
# 对数据进行序列化
goods_serializers = GoodsSerializers(goods, many=True)
# 返回序列化后的数据
return Response(goods_serializers.data)
# APIView是对Django的原有类View的进一步封装,对请求和相应做了进一步的处理,在一次封装了分发函数,增加了 一些功能:
# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
# 但是继承APIView的类都禁用了csrf_token
这里GoodsSerializers需要指定参数many=True,否则会出现如下报错
many = True表示一对多关系
AttributeError: Got AttributeError when attempting to get a value for field `name` on serializer `GoodsSerializers`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'name'.
'AutoSchema' object has no attribute 'get_link'
参考如下配置
# settings.py
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'
}
需要自己手动指定需要序列化的字段名称,当字段名比较多的时候工作量会很大
因此下面提供ModelSerializers类:
1.ModelSerializer能够自动序列化部分或者所有的字段
2.能够对联合唯一约束进行验证
3.能够自动完成创建、更新等操作
class GoodsSerializers(serializers.ModelSerializer):
class Meta:
model = Goods
# fields = '__all__'
exclude = ['goods_desc']
当你想看数据中外键的详细信息时,可以对外键的数据库模型进行序列化,然后嵌套进主序列化类中
class CategorySerializers(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class GoodsSerializers(serializers.ModelSerializer):
# 实现外键信息的嵌套
category = CategorySerializers()
class Meta:
model = Goods
# fields = '__all__'
exclude = ['goods_desc']
它在APIView的基础上,增加了筛选、分页等操作,并且可以和Mixin扩展类结合实现list、create、update等操作
class GoodsListViews(GenericAPIView, mixins.ListModelMixin):
# ListAPIView(mixins.ListModelMixin,GenericAPIView):
# …………
# def get(self, request, *args, **kwargs):
# return self.list(request, *args, **kwargs)
# 指定查询集
queryset = Goods.objects.all()
# 指定序列化类
serializer_class = GoodsSerializers
# 该方法继承了mixins.ListModelMixin的list方法
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
GenericAPIView和 mixins扩展类结合的进一步封装
CreateAPIView(mixins.CreateModelMixin,GenericAPIView)
# Concrete view for creating a model instance.
# 创建模型实例
ListAPIView(mixins.ListModelMixin,GenericAPIView)
# Concrete view for listing a queryset.
# 列出查询集
RetrieveAPIView(mixins.RetrieveModelMixin,GenericAPIView)
# Concrete view for retrieving a model instance.
# 检索该实例的详细信息
DestroyAPIView(mixins.DestroyModelMixin,GenericAPIView)
# Concrete view for deleting a model instance.
# 删除该模型实例
UpdateAPIView(mixins.UpdateModelMixin,GenericAPIView)
# Concrete view for updating a model instance.
# 更新该模型实例的信息
GenericAPIView, mixins.ListModelMixin方法生成视图类的弊端
可以从上述代码看到我们需要手动绑定http请求方法和对应扩展类的方法(get和list)
# 继承了两个类
GenericViewSet(ViewSetMixin, generics.GenericAPIView)
# ViewSetMixin作用
# Overrides `.as_view()` so that it takes an `actions` keyword that performs the binding of HTTP methods to actions on the Resource.
# 绑定了请求方法和资源请求方式
# view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
url绑定的两种方式
goodList = GoodsListViews.as_view({'get': 'list'})
urlpatterns = [
# …………
path('goods/', goodList, name='goods'),
# …………
]
# 实例化路由对象
router = DefaultRouter()
# 第一个参数为路由地址,第二个为视图类,第三个为别名
router.register('goods', GoodsListViews, basename='goods')
# 记得将该路由地址加入总的路由模式中去
urlpatterns += router.urls
官网上其实还有很多绑定的方式,例如使用Django的include函数,主要看自己
官网上简单路由的对应
URL Style | HTTP Method | Action | URL Name |
---|---|---|---|
{prefix}/ | GET | list | {basename}-list |
POST | create | ||
{prefix}/{url_path}/ | GET, or as specified by methods argument |
@action(detail=False) decorated method |
{basename}-{url_name} |
{prefix}/{lookup}/ | GET | retrieve | {basename}-detail |
PUT | update | ||
PATCH | partial_update | ||
DELETE | destroy | ||
{prefix}/{lookup}/{url_path}/ | GET, or as specified by methods argument |
@action(detail=True) decorated method |
{basename}-{url_name} |
基于GenericAPIView的分页功能实现
# 使用全局的分页配置
REST_FRAMEWORK = {
# …………
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 5
}
# 自定义分页配置
class LargeResultsSetPagination(PageNumberPagination):
page_size = 1000
page_size_query_param = 'page_size'
max_page_size = 10000
class BillingRecordsView(generics.ListAPIView):
# …………
pagination_class = LargeResultsSetPagination
过滤功能
django-filter文档
class UserFilter(django_filters.FilterSet):
max = django_filters.NumberFilter(field_name='shop_price', lookup_expr='gte')
min = django_filters.NumberFilter(field_name='shop_price', lookup_expr='lte')
class Meta:
model = User
fields = ['shop_price', 'last_login']
搜索功能
# 注意导包 from rest_framework import filters
class GoodsListViews(GenericViewSet, ListModelMixin, RetrieveModelMixin):
# …………
# 设置过滤器后端
filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.SearchFilter]
# …………
search_fields = ['name', 'goods_brief']
排序功能
商品类别API的层级显示
# 首先显示一级分类,在显示一级分类下的子分类
# serializers.py
class CategorySerializers3(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class CategorySerializers2(serializers.ModelSerializer):
sub_category = CategorySerializers3(many=True)
class Meta:
model = Category
fields = '__all__'
class CategorySerializers1(serializers.ModelSerializer):
# 实现外键信息的嵌套
# sub_category是当前Category的子分类
sub_category = CategorySerializers2(many=True)
# many = True表示子分类有多个
def get_categoty_goods(self, queryset, name, value):
return queryset.filter(Q(category_id=value) |
Q(category__parent_category_id=value) | Q(category__parent_category__parent_category_id=value))
# 根据分类进行筛选
category = django_filters.NumberFilter(field_name='category', method='get_categoty_goods')
# 当输入一级分类的时候,找出一级分类和2、3级分类
# 当输入二级分类的时候,找出二级分类和三级分类
# 三级分类直接筛选
# session适用于前后端都出于处于一台服务器上的场景
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
# token使用于前后端分离处于不同服务器的场景
1 # To use the TokenAuthentication scheme you'll need to configure the authentication classes to include TokenAuthentication, and additionally include rest_framework.authtoken in your INSTALLED_APPS setting
2 INSTALLED_APPS = [
...
'rest_framework.authtoken'
]
什么是token?
token方案的设置
INSTALLED_APPS = [
...
'rest_framework.authtoken'
]
REST_FRAMEWORK = {
# …………
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
]
}
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]
使用post访问,服务端返回token信息
$ curl -X POST -d 'username=XXXX&password=XXXXX' http://127.0.0.1:8000/api-token-auth/
DRF自带的Token的缺陷
使用djangorestframework-jwt
$ pip install djangorestframework-jwt
# 并在配置文件中注册
# 使用jwt认证
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.BasicAuthentication',
# 'rest_framework.authentication.TokenAuthentication',
"rest_framework_jwt.authentication.JSONWebTokenAuthentication",
]
# JWT的相关配置
JWT_AUTH = {
# token的失效时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
# token前缀
'JWT_AUTH_HEADER_PREFIX': 'JWT',
}
urlpatterns = [
# …………
# 设置登录的路由
url(r'^login/', obtain_jwt_token),
]
Django自带的用户认证功能只能验证用户名和密码登录
重写后台认证的API,使得可以通过电话号码和密码登录
# 在用户app定义视图类
User = get_user_model()
class CustomBackend(ModelBackend):
# 该方法重写了ModelBackend中的认证方法
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username) |
Q(mobile=username))
if user.check_password(password) and self.user_can_authenticate(user):
return user
except Exception as e:
return None
User = get_user_model()
# 当你自定义用户模块的项目中
# 你应该使用django.contrib.auth.get_user_model() 来引用用户模型,而不要直接引用 User。 这个方法将返回当前正在使用的用户模型 —— 指定的自定义用户模型或者User。
# 当前使用的模型在配置文件中已经设置
AUTH_USER_MODEL = 'user.UserProfile'
# 编写自定义的后台验证,添加到配置文件中
AUTHENTICATION_BACKENDS = {
'app.user.views.CustomBackend'
}
# sms.py
import random
import string
from shopAPI.settings import APIKEY
import requests
class YunPian():
def __init__(self, phone):
# 单条发送短信
self.url = 'https://sms.yunpian.com/v2/sms/single_send.json'
self.apikey = APIKEY
self.phone = phone
@staticmethod
def generate_code(count):
return ''.join(random.sample(string.digits, count))
def send_message(self, code):
response = requests.post(self.url, data={
'apikey': self.apikey,
'text': '【XXtest】您的验证码是code'.replace('code', str(code)),
'mobile': self.phone
})
return response
if __name__ == '__main__':
yunpian = YunPian(XXXX)
code = YunPian.generate_code(count=4)
response = yunpian.send_message(code)
print(response.json())
# serializers.py
User = get_user_model()
class smsCodeSerializers(serializers.Serializer):
# 注册时候发送验证码
mobile = serializers.CharField(max_length=20)
# 在序列化的时候进行数据验证(validate+验证字段名)
def validate_mobile(self, mobile):
# 如果该手机号已经被注册,则抛异常
if User.objects.filter(mobile=mobile).count():
raise serializers.ValidationError('该手机已被注册')
# 判断该手机的形式是否合法
if not re.match(REG_PATTERN, mobile):
raise serializers.ValidationError('手机号码不合法')
# 判断发送时间是否超过60s
before_minute_time = datetime.now() - timedelta(minutes=1)
# 拿出该手机号码的所有验证码信息,拿出最近的一条信息
# codes = VerifyCode.objects.all().order_by('add_time')
# recent_code_time = codes[0].add_time
if VerifyCode.objects.filter(add_time__gt=before_minute_time, mobile=mobile):
raise serializers.ValidationError('距离上一次发送短信未超过60s')
return mobile
class Meta:
# 指定序列化的数据库模型
model = VerifyCode
# views.py
class SmsView(GenericViewSet, CreateModelMixin):
serializer_class = smsCodeSerializers
# 需要重写create方法
# 若不重写,在创建验证码的时候不会使用云片发送验证码,也不会将验证码存储到数据库中
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 从验证的信息中获取电话
mobile = serializer.validated_data['mobile']
yunpian = YunPian(mobile)
code = YunPian.generate_code(count=6)
response_status = yunpian.send_message(code).json()
if response_status['code'] != 0:
# 短信发送失败
return Response(data={
'mobile': mobile,
'msg': response_status['msg']
}, status=HTTP_400_BAD_REQUEST)
else:
# 如果发送成功,实例化验证码对象存入数据库中
vcode = VerifyCode(mobile=mobile, code=code)
vcode.save()
return Response(serializer.data, status=HTTP_201_CREATED)
# urls.py
router.register('code', SmsView, basename='code')
class registerSerializers(serializers.Serializer):
# 注册信息的序列化
code = serializers.CharField(required=True, allow_blank=False,
max_length=6, min_length=6,
label='验证码',
error_messages={
'required': '请输入验证码',
'allow_blank': '请输入验证码',
'max_length': '验证码格式错误',
'min_length': '验证码格式错误',
})
# UniqueValidator 用户名唯一验证
username = serializers.CharField(label='用户名', required=True, allow_blank=False,validators= [UniqueValidator(queryset=User.objects.all(),message='用户已存在')])
mobile = serializers.CharField(label='电话', required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(),message='电话已注册')])
# style={'input_type': 'password'} 表示密码不明文显示
password = serializers.CharField(label='密码', required=True, allow_blank=False, min_length=6,error_messages={'min_length': '密码不能少于六位数'}, style={'input_type': 'password'})
# 检验验证码是否过期
def validate_code(self, code):
codes = VerifyCode.objects.filter(mobile=self.initial_data['mobile']).order_by('-add_time')
if codes:
# 拿出最近的一个验证码
recent_code = codes[0]
ten_minutes_age = datetime.now() - timedelta(minutes=10)
if ten_minutes_age > recent_code.add_time:
raise serializers.ValidationError('验证码过期')
if code != recent_code.code:
raise serializers.ValidationError('验证码错误')
else:
raise serializers.ValidationError('请发送验证码')
return code
def validate(self, attrs):
# 所有的字段信息存储在attrs字典中
# 验证完成之后删除code属性,因为数据库表中没有code
del attrs['code']
return attrs
class Meta:
model = User
field = ['username', 'mobile', 'code', 'password']
class RegisterView(GenericViewSet, CreateModelMixin):
serializer_class = registerSerializers
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
username = serializer.validated_data['username']
mobile = serializer.validated_data['mobile']
password = serializer.validated_data['password']
user = User(username=username, mobile=mobile, password=password)
# 对密码进行加密
user.set_password(password)
user.save()
return Response(data={
'msg': '创建用户成功',
}, status=status.HTTP_201_CREATED)
# urls.py
router.register('register', RegisterView, basename='register')
用户收藏API
class UserFavSerializers(serializers.ModelSerializer):
# HiddenField当前用户,不显示
# serializers.CurrentUserDefault()表示获取当前用户
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = UserFav
fields = ['user', 'id', 'goods']
# 对用户和收藏商品进行联合唯一验证
validators = [
UniqueTogetherValidator(
queryset=UserFav.objects.all(),
fields=('user', 'goods'),
message='该商品已经收藏'
)
]
class UserFavDetailSerializers(serializers.ModelSerializer):
goods = GoodsSerializers()
# 外键覆盖,不只显示goods的id,还显示goods的详细信息
class Meta:
model = UserFav
fields = '__all__'
class UserFavView(GenericViewSet, CreateModelMixin,
ListModelMixin, RetrieveModelMixin, DestroyModelMixin):
# queryset是返回所有的查询结果
# queryset = UserFav.objects.all()
# serializer_class = UserFavSerializers
# 采用动态序列化,list时候展示收藏商品的详细信息
def get_serializer_class(self):
if self.action == 'list':
return UserFavDetailSerializers
else:
return UserFavSerializers
# 获取当前用户的收藏商品
def get_queryset(self):
# 获取当前用户
user = self.request.user
# 如果当前用户存在,拿出他的收藏商品,否则返回空
if user:
return UserFav.objects.filter(user_id=self.request.user.pk)
else:
return None
Gitee连接:
项目地址
https://gitee.com/hugeblackdog/shopAPI