Django电商平台
Django电商平台
数据库设计
- 用户数据库模型设计
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.usernameclass 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
# ckeditor
$ pip install django-ckeditor
-
将第三方插件要注册到应用中去
-
商品的数据库模型
class Category(models.Model):# 类别的多种级别types = [(1, '一级类别'),(2, '二级类别'),(3, '三级列别')]# …………category_type = models.IntegerField('类别级别', choices=types)# 类别的多级分类# 一级类别:二级类别 1:nparent_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"]# 控制初始表单数量,默认为3extra = 3style = 'tab'# 内部嵌套inlines = [GoodsImagesInline]
- 进行数据填充
# 加载Django配置和Django APP的注册。
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shopAPI.settings")
django.setup()# 然后导入数据,引入之前定义的数据库模型,将数据进行填充
注意要在加载Django配置之后在导入数据库模型
- 配置多媒体文件存储路径
# settings.pyMEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
- 配置多媒体路由
# urls.py
urlpatterns = [# …………# 配置静态文件的路由path('media/<path:path>', serve, {'document_root': MEDIA_ROOT})
]
视图函数
-
视图函数的两种形式
-
FBV 视图函数接受request请求,函数体中直接是视图函数
-
CBV 该方法的视图继承了视图类views,然后类中可以定义各种http请求方式,
不同的请求方式可以对应不同的视图函数,即将对图函数和HTTP请求方式绑定,
编写路由的时候可以使用as_views()来使类生成视图函数
FBV适合小型项目,综合、大型项目可以使用CBV
-
-
Django API
- Django中可以通过原生的数据用json封装在返回response,但是图片、时间等字段不同正确转成json类型
- Django中提供的序列化类,可以实现弥补上述缺点,但是图片的存储为相对路径,不能将静态资源的绝对路径自动补全,例如MEDIA_ROOT、MEDIA_URL无法自动添加
- 使用DRF(Django Rest Framework)
-
什么是序列化?
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,例如json或者xml格式
-
序列化的作用
- 可以将查询集或者模型实例对象进行序列化为json或者xml格式,然后传输给用户
- 可以对post或者patch/put请求进行数据处理,并进行验证。
-
安装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 GoodsSerializersclass 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 permittedself.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'.
- 访问API文档时候的异常
'AutoSchema' object has no attribute 'get_link'
参考如下配置
# settings.py
REST_FRAMEWORK = {'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'
}
- 上述Serializer的弊端
需要自己手动指定需要序列化的字段名称,当字段名比较多的时候工作量会很大
因此下面提供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 = Categoryfields = '__all__'class GoodsSerializers(serializers.ModelSerializer):# 实现外键信息的嵌套category = CategorySerializers()class Meta:model = Goods# fields = '__all__'exclude = ['goods_desc']
- APIView的进一步封装GenericAPIView
它在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
# 继承了两个类
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 = 1000page_size_query_param = 'page_size'max_page_size = 10000class 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 = Userfields = ['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 = Categoryfields = '__all__'
class CategorySerializers2(serializers.ModelSerializer):sub_category = CategorySerializers3(many=True)class Meta:model = Categoryfields = '__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级分类# 当输入二级分类的时候,找出二级分类和三级分类# 三级分类直接筛选
DRF认证
- 使用缓存的设置方案
# session适用于前后端都出于处于一台服务器上的场景
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.BasicAuthentication','rest_framework.authentication.SessionAuthentication',]
}
- 使用Token的设置方案
# 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 setting2 INSTALLED_APPS = [...'rest_framework.authtoken'
]
-
什么是token?
- Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
- Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
- 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的缺陷
- token信息表格存在数据库中,不方便分布式管理
- 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 userexcept 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 requestsclass YunPian():def __init__(self, phone):# 单条发送短信self.url = '.json'self.apikey = APIKEYself.phone = phone@staticmethoddef 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 responseif __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('手机号码不合法')# 判断发送时间是否超过60sbefore_minute_time = datetime.now() - timedelta(minutes=1)# 拿出该手机号码的所有验证码信息,拿出最近的一条信息# codes = VerifyCode.objects.all().order_by('add_time')# recent_code_time = codes[0].add_timeif VerifyCode.objects.filter(add_time__gt=before_minute_time, mobile=mobile):raise serializers.ValidationError('距离上一次发送短信未超过60s')return mobileclass 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')
用户注册API
- 序列化类
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 codedef validate(self, attrs):# 所有的字段信息存储在attrs字典中# 验证完成之后删除code属性,因为数据库表中没有codedel attrs['code']return attrsclass Meta:model = Userfield = ['username', 'mobile', 'code', 'password']
- 视图类的编写
class RegisterView(GenericViewSet, CreateModelMixin):serializer_class = registerSerializersdef 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 = UserFavfields = ['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 = UserFavfields = '__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 UserFavDetailSerializerselse: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连接:
项目地址
Django电商平台
Django电商平台
数据库设计
- 用户数据库模型设计
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.usernameclass 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
# ckeditor
$ pip install django-ckeditor
-
将第三方插件要注册到应用中去
-
商品的数据库模型
class Category(models.Model):# 类别的多种级别types = [(1, '一级类别'),(2, '二级类别'),(3, '三级列别')]# …………category_type = models.IntegerField('类别级别', choices=types)# 类别的多级分类# 一级类别:二级类别 1:nparent_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"]# 控制初始表单数量,默认为3extra = 3style = 'tab'# 内部嵌套inlines = [GoodsImagesInline]
- 进行数据填充
# 加载Django配置和Django APP的注册。
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shopAPI.settings")
django.setup()# 然后导入数据,引入之前定义的数据库模型,将数据进行填充
注意要在加载Django配置之后在导入数据库模型
- 配置多媒体文件存储路径
# settings.pyMEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
- 配置多媒体路由
# urls.py
urlpatterns = [# …………# 配置静态文件的路由path('media/<path:path>', serve, {'document_root': MEDIA_ROOT})
]
视图函数
-
视图函数的两种形式
-
FBV 视图函数接受request请求,函数体中直接是视图函数
-
CBV 该方法的视图继承了视图类views,然后类中可以定义各种http请求方式,
不同的请求方式可以对应不同的视图函数,即将对图函数和HTTP请求方式绑定,
编写路由的时候可以使用as_views()来使类生成视图函数
FBV适合小型项目,综合、大型项目可以使用CBV
-
-
Django API
- Django中可以通过原生的数据用json封装在返回response,但是图片、时间等字段不同正确转成json类型
- Django中提供的序列化类,可以实现弥补上述缺点,但是图片的存储为相对路径,不能将静态资源的绝对路径自动补全,例如MEDIA_ROOT、MEDIA_URL无法自动添加
- 使用DRF(Django Rest Framework)
-
什么是序列化?
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,例如json或者xml格式
-
序列化的作用
- 可以将查询集或者模型实例对象进行序列化为json或者xml格式,然后传输给用户
- 可以对post或者patch/put请求进行数据处理,并进行验证。
-
安装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 GoodsSerializersclass 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 permittedself.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'.
- 访问API文档时候的异常
'AutoSchema' object has no attribute 'get_link'
参考如下配置
# settings.py
REST_FRAMEWORK = {'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'
}
- 上述Serializer的弊端
需要自己手动指定需要序列化的字段名称,当字段名比较多的时候工作量会很大
因此下面提供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 = Categoryfields = '__all__'class GoodsSerializers(serializers.ModelSerializer):# 实现外键信息的嵌套category = CategorySerializers()class Meta:model = Goods# fields = '__all__'exclude = ['goods_desc']
- APIView的进一步封装GenericAPIView
它在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
# 继承了两个类
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 = 1000page_size_query_param = 'page_size'max_page_size = 10000class 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 = Userfields = ['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 = Categoryfields = '__all__'
class CategorySerializers2(serializers.ModelSerializer):sub_category = CategorySerializers3(many=True)class Meta:model = Categoryfields = '__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级分类# 当输入二级分类的时候,找出二级分类和三级分类# 三级分类直接筛选
DRF认证
- 使用缓存的设置方案
# session适用于前后端都出于处于一台服务器上的场景
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.BasicAuthentication','rest_framework.authentication.SessionAuthentication',]
}
- 使用Token的设置方案
# 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 setting2 INSTALLED_APPS = [...'rest_framework.authtoken'
]
-
什么是token?
- Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
- Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
- 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的缺陷
- token信息表格存在数据库中,不方便分布式管理
- 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 userexcept 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 requestsclass YunPian():def __init__(self, phone):# 单条发送短信self.url = '.json'self.apikey = APIKEYself.phone = phone@staticmethoddef 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 responseif __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('手机号码不合法')# 判断发送时间是否超过60sbefore_minute_time = datetime.now() - timedelta(minutes=1)# 拿出该手机号码的所有验证码信息,拿出最近的一条信息# codes = VerifyCode.objects.all().order_by('add_time')# recent_code_time = codes[0].add_timeif VerifyCode.objects.filter(add_time__gt=before_minute_time, mobile=mobile):raise serializers.ValidationError('距离上一次发送短信未超过60s')return mobileclass 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')
用户注册API
- 序列化类
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 codedef validate(self, attrs):# 所有的字段信息存储在attrs字典中# 验证完成之后删除code属性,因为数据库表中没有codedel attrs['code']return attrsclass Meta:model = Userfield = ['username', 'mobile', 'code', 'password']
- 视图类的编写
class RegisterView(GenericViewSet, CreateModelMixin):serializer_class = registerSerializersdef 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 = UserFavfields = ['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 = UserFavfields = '__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 UserFavDetailSerializerselse: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连接:
项目地址