提交 fdafa4bf 编写于 作者: C Corley

V1.4

上级 220a1eb0
......@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import datetime
import os
import sys
......@@ -19,7 +19,6 @@ sys.path.insert(0, BASE_DIR)
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
......@@ -33,7 +32,6 @@ ALLOWED_HOSTS = []
AUTH_USER_MODEL = 'users.UserProfile'
# Application definition
INSTALLED_APPS = [
......@@ -42,7 +40,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.users.apps.UsersConfig',
'users.apps.UsersConfig',
'goods.apps.GoodsConfig',
'trade.apps.TradeConfig',
'user_operation.apps.UserOperationConfig',
......@@ -53,6 +51,7 @@ INSTALLED_APPS = [
'rest_framework',
'django_filters',
'corsheaders',
'rest_framework.authtoken'
]
MIDDLEWARE = [
......@@ -90,7 +89,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'Fresh_Ecommerce.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
......@@ -100,12 +98,11 @@ DATABASES = {
'NAME': 'fresh_ec',
'USER': 'root',
'PASSWORD': 'root',
'HOST':'127.0.0.1',
'HOST': '127.0.0.1',
'OPTIONS': {'init_command': 'SET default_storage_engine=INNODB;'}
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
......@@ -124,7 +121,6 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
......@@ -138,7 +134,6 @@ USE_L10N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
......@@ -149,5 +144,29 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# DRF配置
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}
\ No newline at end of file
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
# 自定义用户认证配置
AUTHENTICATION_BACKENDS = [
'users.views.CustomBackend',
]
# JWT配置
JWT_AUTH = {
# 过期时间
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
# 请求头前缀
'JWT_AUTH_HEADER_PREFIX': 'JWT',
}
# 手机号码验证正则表达式
REGEX_MOBILE = '^1[35789]\d{9}$|^147\d{8}$'
# 云片网APIKEY
APIKEY = 'edf71361381f31b3957beda37f20xxxx' # 更换为自己的API_KEY
\ No newline at end of file
......@@ -18,10 +18,13 @@ from django.conf.urls import url, include
from django.views.static import serve
from rest_framework.documentation import include_docs_urls
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken import views
from rest_framework_jwt.views import obtain_jwt_token
import xadmin
from .settings import MEDIA_ROOT
from goods.views import GoodsListViewSet, CategoryViewSet
from users.views import SmsCodeViewSet, UserViewSet
# Create a router and register our viewsets with it.
......@@ -33,14 +36,26 @@ router.register(r'goods', GoodsListViewSet, basename='goods')
# 配置categories的路由
router.register(r'categorys', CategoryViewSet, basename='categorys')
# 配置短信验证码路由
router.register(r'codes', SmsCodeViewSet, basename='codes')
# 配置注册路由
router.register(r'users', UserViewSet, basename='users')
urlpatterns = [
url(r'^xadmin/', xadmin.site.urls),
url(r'^media/(?P<path>.*)$', serve, {'document_root':MEDIA_ROOT}),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# 商品列表页
url(r'^', include(router.urls)),
# 文档路由
url(r'docs/', include_docs_urls(title='生鲜电商'))
url(r'docs/', include_docs_urls(title='生鲜电商')),
# DRF自带认证路由
url(r'^api-token-auth/', views.obtain_auth_token, name='api_token_auth'),
# JWT认证路由
url(r'^login/', obtain_jwt_token),
]
\ No newline at end of file
......@@ -13,4 +13,7 @@
主要实现DRF(Django Restful Framework),先通过普通方式实现商品详情页,再通过DRF的各种View实现,最后实现过滤(包括字段过滤、搜索和筛选)。
#### V1.3
先通过嵌套方式实现商品类别数据接口,再通过Vue展示商品分类,最后实现展示商品列表页数据和搜索功能。
\ No newline at end of file
先通过嵌套方式实现商品类别数据接口,再通过Vue展示商品分类,最后实现展示商品列表页数据和搜索功能。
#### V1.4
先实现在DRF中使用token,再使用JSON Web Token登录,再实现短信发送和注册功能,最后实现前后端结合、完成注册功能。
\ No newline at end of file
......@@ -17,7 +17,7 @@ class GoodsPagination(PageNumberPagination):
class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
'''商品列表页,并实现分页、搜索、过滤、排序'''
queryset = Goods.objects.filter(is_delete=False)
queryset = Goods.objects.filter(is_delete=False).order_by('id')
serializer_class = GoodsSerializer
pagination_class = GoodsPagination
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
......
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'apps.users'
name = 'users'
verbose_name = '用户管理'
def ready(self):
import users.signals
# Generated by Django 3.0.8 on 2020-07-28 21:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20200725_0954'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='mobile',
field=models.CharField(blank=True, max_length=11, null=True, verbose_name='电话'),
),
]
......@@ -12,7 +12,7 @@ class UserProfile(AbstractUser):
birthday = models.DateField(null=True, blank=True, verbose_name='出生日期')
gender = models.CharField(max_length=6, choices=(('male', u'男'), ('female', u'女')), default='female',
verbose_name='性别')
mobile = models.CharField(max_length=11, verbose_name='电话')
mobile = models.CharField(max_length=11, null=True, blank=True, verbose_name='电话')
email = models.CharField(max_length=50, null=True, blank=True, verbose_name='邮箱')
is_delete = models.BooleanField(default=False, verbose_name='是否删除')
......@@ -26,6 +26,7 @@ class UserProfile(AbstractUser):
class VerifyCode(models.Model):
'''短信验证码'''
code = models.CharField(max_length=10, verbose_name='验证码')
mobile = models.CharField(max_length=11, verbose_name='电话')
......
import re
from datetime import datetime, timedelta
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from django.contrib.auth import get_user_model
from Fresh_Ecommerce.settings import REGEX_MOBILE
from .models import VerifyCode
User = get_user_model()
class SmsSerializer(serializers.Serializer):
'''短信发送序列化'''
mobile = serializers.CharField(max_length=11)
def validate_mobile(self, mobile):
'''验证手机号码'''
# 验证手机号码是否合法
if not re.match(REGEX_MOBILE, mobile):
raise serializers.ValidationError('手机号格式有误,请重新输入')
# 验证手机是否注册
if User.objects.filter(mobile=mobile).count():
raise serializers.ValidationError('手机号已经被注册过,请更换手机号重新注册或直接使用该手机号登录')
# 验证短信发送频率
one_minute_ago = datetime.now() - timedelta(minutes=1)
if VerifyCode.objects.filter(add_time__gt=one_minute_ago, mobile=mobile).count():
raise serializers.ValidationError('验证码发送频率过快,请稍后再试')
return mobile
class UserRegSerializer(serializers.ModelSerializer):
'''用户序列化'''
code = serializers.CharField(max_length=4, min_length=4, label='验证码', write_only=True,
help_text='验证码',
error_messages={
'required': '请输入验证码',
'blank': '请输入验证码',
'max_length': '请输入4位验证码',
'min_length': '请输入4位验证码'
})
username = serializers.CharField(required=True, allow_blank=False, label='用户名', validators=[UniqueValidator(queryset=User.objects.filter(is_delete=False), message='用户已经存在')])
password = serializers.CharField(label='密码', write_only=True, style={'input_type': 'password'})
def validate_code(self, code):
verify_records = VerifyCode.objects.filter(mobile=self.initial_data['username']).order_by('-add_time')
# 验证验证码是否存在
if verify_records:
last_record = verify_records[0]
five_minute_ago = datetime.now() - timedelta(minutes=5)
# 验证验证码是否过期
if five_minute_ago > last_record.add_time:
raise serializers.ValidationError('验证码已过期,请重新验证')
# 验证验证码是否正确
if last_record.code != code:
raise serializers.ValidationError('验证码错误')
else:
raise serializers.ValidationError('数据有误,请重新验证')
def validate(self, attrs):
attrs['mobile'] = attrs['username']
del attrs['code']
return attrs
class Meta:
model = User
fields = ['username', 'code', 'mobile', 'password']
\ No newline at end of file
from django.db.models.signals import post_save
from django.contrib.auth import get_user_model
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
User = get_user_model()
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
password = instance.password
instance.set_password(password)
instance.save()
\ No newline at end of file
from django.shortcuts import render
from random import choice
from django.db.models import Q
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from rest_framework import viewsets, status
from rest_framework.mixins import CreateModelMixin
from rest_framework.response import Response
from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler
from .models import VerifyCode
from .serializers import SmsSerializer, UserRegSerializer
from utils.yunpian import yunpian
User = get_user_model()
# Create your views here.
class CustomBackend(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 user.is_delete != True:
return user
except Exception as e:
return None
class SmsCodeViewSet(CreateModelMixin, viewsets.GenericViewSet):
'''发送短信验证码'''
serializer_class = SmsSerializer
def generate_code(self):
'''生成4位数验证码'''
seeds = '1234567890'
random_str = []
for i in range(4):
random_str.append(choice(seeds))
return ''.join(random_str)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
mobile = serializer.validated_data['mobile']
print(mobile)
code = self.generate_code()
sms_status = yunpian.send_sms(code, mobile)
print(sms_status)
if sms_status['code'] != 0:
return Response({
'mobile': sms_status['msg']
}, status=status.HTTP_400_BAD_REQUEST)
else:
code_record = VerifyCode(code=code, mobile=mobile)
code_record.save()
return Response({
'mobile': mobile,
'code': code
}, status=status.HTTP_201_CREATED)
class UserViewSet(CreateModelMixin, viewsets.GenericViewSet):
'''用户'''
serializer_class = UserRegSerializer
queryset = User.objects.filter(is_delete=False)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
re_dict = serializer.data
payload = jwt_payload_handler(user)
re_dict['token'] = jwt_encode_handler(payload)
re_dict['name'] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
\ No newline at end of file
import requests
import json
from Fresh_Ecommerce.settings import APIKEY
class YunPian(object):
def __init__(self):
self.api_key = APIKEY
self.single_send_url = 'https://sms.yunpian.com/v2/sms/single_send.json'
def send_sms(self, code, mobile):
params = {
'apikey': self.api_key,
'mobile': mobile,
'text': '【Python进化讲堂】欢迎您注册Fresh_Ecommerce ,验证码:{}(5分钟内有效,如非本人操作,请忽略)'.format(code)
}
response = requests.post(self.single_send_url, data=params)
re_dict = json.loads(response.text)
return re_dict
yunpian = YunPian()
if __name__ == '__main__':
yunpian.send_sms('1234', '13312345678') # 改为你自己的手机号
\ No newline at end of file
......@@ -55,12 +55,12 @@ export const getFav = goodsId => { return axios.get(`${host}/userfavs/`+goodsId+
//登录
export const login = params => {
return axios.post(`${host}/login/`, params)
return axios.post(`${local_host}/login/`, params)
}
//注册
export const register = parmas => { return axios.post(`${host}/users/`, parmas) }
export const register = parmas => { return axios.post(`${local_host}/users/`, parmas) }
//短信
export const getMessage = parmas => { return axios.post(`${host}/code/`, parmas) }
......
......@@ -14,6 +14,7 @@ django-guardian==2.3.0
django-import-export==2.3.0
django-reversion==3.0.7
djangorestframework==3.11.0
djangorestframework-jwt==1.11.0
et-xmlfile==1.0.1
future==0.18.2
httplib2==0.18.0
......@@ -28,6 +29,7 @@ mysqlclient==1.4.6
odfpy==1.4.1
openpyxl==3.0.4
Pillow==7.2.0
PyJWT==1.7.1
pytz==2020.1
PyYAML==5.3.1
requests==2.24.0
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册