提交 fdafa4bf 编写于 作者: C Corley

V1.4

上级 220a1eb0
...@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.0/topics/settings/ ...@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/ https://docs.djangoproject.com/en/3.0/ref/settings/
""" """
import datetime
import os import os
import sys import sys
...@@ -19,7 +19,6 @@ sys.path.insert(0, BASE_DIR) ...@@ -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, 'apps'))
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps')) sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
...@@ -33,7 +32,6 @@ ALLOWED_HOSTS = [] ...@@ -33,7 +32,6 @@ ALLOWED_HOSTS = []
AUTH_USER_MODEL = 'users.UserProfile' AUTH_USER_MODEL = 'users.UserProfile'
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
...@@ -42,7 +40,7 @@ INSTALLED_APPS = [ ...@@ -42,7 +40,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'apps.users.apps.UsersConfig', 'users.apps.UsersConfig',
'goods.apps.GoodsConfig', 'goods.apps.GoodsConfig',
'trade.apps.TradeConfig', 'trade.apps.TradeConfig',
'user_operation.apps.UserOperationConfig', 'user_operation.apps.UserOperationConfig',
...@@ -53,6 +51,7 @@ INSTALLED_APPS = [ ...@@ -53,6 +51,7 @@ INSTALLED_APPS = [
'rest_framework', 'rest_framework',
'django_filters', 'django_filters',
'corsheaders', 'corsheaders',
'rest_framework.authtoken'
] ]
MIDDLEWARE = [ MIDDLEWARE = [
...@@ -90,7 +89,6 @@ TEMPLATES = [ ...@@ -90,7 +89,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'Fresh_Ecommerce.wsgi.application' WSGI_APPLICATION = 'Fresh_Ecommerce.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
...@@ -100,12 +98,11 @@ DATABASES = { ...@@ -100,12 +98,11 @@ DATABASES = {
'NAME': 'fresh_ec', 'NAME': 'fresh_ec',
'USER': 'root', 'USER': 'root',
'PASSWORD': 'root', 'PASSWORD': 'root',
'HOST':'127.0.0.1', 'HOST': '127.0.0.1',
'OPTIONS': {'init_command': 'SET default_storage_engine=INNODB;'} 'OPTIONS': {'init_command': 'SET default_storage_engine=INNODB;'}
} }
} }
# Password validation # Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
...@@ -124,7 +121,6 @@ AUTH_PASSWORD_VALIDATORS = [ ...@@ -124,7 +121,6 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/ # https://docs.djangoproject.com/en/3.0/topics/i18n/
...@@ -138,7 +134,6 @@ USE_L10N = True ...@@ -138,7 +134,6 @@ USE_L10N = True
USE_TZ = False USE_TZ = False
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # https://docs.djangoproject.com/en/3.0/howto/static-files/
...@@ -149,5 +144,29 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media') ...@@ -149,5 +144,29 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# DRF配置 # DRF配置
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] '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 ...@@ -18,10 +18,13 @@ from django.conf.urls import url, include
from django.views.static import serve from django.views.static import serve
from rest_framework.documentation import include_docs_urls from rest_framework.documentation import include_docs_urls
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from rest_framework.authtoken import views
from rest_framework_jwt.views import obtain_jwt_token
import xadmin import xadmin
from .settings import MEDIA_ROOT from .settings import MEDIA_ROOT
from goods.views import GoodsListViewSet, CategoryViewSet from goods.views import GoodsListViewSet, CategoryViewSet
from users.views import SmsCodeViewSet, UserViewSet
# Create a router and register our viewsets with it. # Create a router and register our viewsets with it.
...@@ -33,14 +36,26 @@ router.register(r'goods', GoodsListViewSet, basename='goods') ...@@ -33,14 +36,26 @@ router.register(r'goods', GoodsListViewSet, basename='goods')
# 配置categories的路由 # 配置categories的路由
router.register(r'categorys', CategoryViewSet, basename='categorys') router.register(r'categorys', CategoryViewSet, basename='categorys')
# 配置短信验证码路由
router.register(r'codes', SmsCodeViewSet, basename='codes')
# 配置注册路由
router.register(r'users', UserViewSet, basename='users')
urlpatterns = [ urlpatterns = [
url(r'^xadmin/', xadmin.site.urls), url(r'^xadmin/', xadmin.site.urls),
url(r'^media/(?P<path>.*)$', serve, {'document_root':MEDIA_ROOT}), 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'^', 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
...@@ -14,3 +14,6 @@ ...@@ -14,3 +14,6 @@
#### V1.3 #### V1.3
先通过嵌套方式实现商品类别数据接口,再通过Vue展示商品分类,最后实现展示商品列表页数据和搜索功能。 先通过嵌套方式实现商品类别数据接口,再通过Vue展示商品分类,最后实现展示商品列表页数据和搜索功能。
#### V1.4
先实现在DRF中使用token,再使用JSON Web Token登录,再实现短信发送和注册功能,最后实现前后端结合、完成注册功能。
\ No newline at end of file
...@@ -17,7 +17,7 @@ class GoodsPagination(PageNumberPagination): ...@@ -17,7 +17,7 @@ class GoodsPagination(PageNumberPagination):
class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): 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 serializer_class = GoodsSerializer
pagination_class = GoodsPagination pagination_class = GoodsPagination
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
......
from django.apps import AppConfig from django.apps import AppConfig
class UsersConfig(AppConfig): class UsersConfig(AppConfig):
name = 'apps.users' name = 'users'
verbose_name = '用户管理' 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): ...@@ -12,7 +12,7 @@ class UserProfile(AbstractUser):
birthday = models.DateField(null=True, blank=True, verbose_name='出生日期') birthday = models.DateField(null=True, blank=True, verbose_name='出生日期')
gender = models.CharField(max_length=6, choices=(('male', u'男'), ('female', u'女')), default='female', gender = models.CharField(max_length=6, choices=(('male', u'男'), ('female', u'女')), default='female',
verbose_name='性别') 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='邮箱') email = models.CharField(max_length=50, null=True, blank=True, verbose_name='邮箱')
is_delete = models.BooleanField(default=False, verbose_name='是否删除') is_delete = models.BooleanField(default=False, verbose_name='是否删除')
...@@ -26,6 +26,7 @@ class UserProfile(AbstractUser): ...@@ -26,6 +26,7 @@ class UserProfile(AbstractUser):
class VerifyCode(models.Model): class VerifyCode(models.Model):
'''短信验证码'''
code = models.CharField(max_length=10, verbose_name='验证码') code = models.CharField(max_length=10, verbose_name='验证码')
mobile = models.CharField(max_length=11, 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. # 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+ ...@@ -55,12 +55,12 @@ export const getFav = goodsId => { return axios.get(`${host}/userfavs/`+goodsId+
//登录 //登录
export const login = params => { 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) } export const getMessage = parmas => { return axios.post(`${host}/code/`, parmas) }
......
...@@ -14,6 +14,7 @@ django-guardian==2.3.0 ...@@ -14,6 +14,7 @@ django-guardian==2.3.0
django-import-export==2.3.0 django-import-export==2.3.0
django-reversion==3.0.7 django-reversion==3.0.7
djangorestframework==3.11.0 djangorestframework==3.11.0
djangorestframework-jwt==1.11.0
et-xmlfile==1.0.1 et-xmlfile==1.0.1
future==0.18.2 future==0.18.2
httplib2==0.18.0 httplib2==0.18.0
...@@ -28,6 +29,7 @@ mysqlclient==1.4.6 ...@@ -28,6 +29,7 @@ mysqlclient==1.4.6
odfpy==1.4.1 odfpy==1.4.1
openpyxl==3.0.4 openpyxl==3.0.4
Pillow==7.2.0 Pillow==7.2.0
PyJWT==1.7.1
pytz==2020.1 pytz==2020.1
PyYAML==5.3.1 PyYAML==5.3.1
requests==2.24.0 requests==2.24.0
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册