标签:nat contex 对象 过滤 state 输入 命令 编写 首页
本项目是一个典型的电商项目,采用
Python
语言编写,Django
框架搭建。
github
中创建远程仓库在github
上创建仓库meiduo
,填写描述,选择.gitignore
文件的语言为python
,软件许可协议为MIT
。
修改.gitignore
的内容为:.idea/
,*.pyc
,*.log
。
新建分支dev
。
git clone https://github.com/junsircoding/meiduo.git # 克隆项目
cd meiduo # 进入项目目录
git branch # 查看当前分支
git branch dev origin/dev # 克隆远程仓库中的dev分支
git checkout dev # 切换到dev分支
workon django_py3_1.11 # 进入虚拟环境
django-admin startproject shop # 创建项目,项目名称为shop
cd shop # 进入项目目录
pwd # 查看当前地址并拷贝,在pycharm中打开
settings
文件路径开发环境和上线环境用不同的配置文件比较容易部署和维护,故而最好重新设置
settings
的路径。在
django
自动创建的项目中,根目录下有一个同名目录,在此做一个约定:根目录的shop
为一级shop
,根目录下的同名目录为二级shop
。
新建名为settings
的python package
于二级shop中,将原二级shop中的settings.py
更名为dev.py
,并将其移动到新建的settings
目录中。
修改一级shop下的manage.py
中的环境变量:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings") # 原
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings.dev") # 现
jinja2
模板在dev.py
(位于二级shop)中配置jinja2
的模板:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.jinja2.Jinja2', # jinja2模板引擎
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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',
],
# 补充Jinja2模板引擎环境
'environment': 'meiduo_mall.utils.jinja2_env.jinja2_environment',
},
},
]
新建名为utils
的python package
于二级shop
中。
新建名为jinja2_env.py
的python file
于utils
目录中,并在其中写如下代码:
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from jinja2 import Environment
def jinja2_environment(**options):
env = Environment(**options)
env.globals.update({
'static': staticfiles_storage.url,
'url': reverse,
})
return env
"""
确保可以使用Django模板引擎中的{% url('') %} {% static('') %}这类的语句
"""
mysql
数据库新建mysql数据库
mysql -uroot -p111
create database shop charset=utf8; # 创建名为shop的数据库,指定字符编码为utf8
create user shoproot identified by '111'; # 创建名为shoproot的mysql新用户,授权其访问shop数据库
grant all on shop.* to 'shop'@'%'; # %表示匹配用户名为junsir的所有ip地址
flush privileges; # 刷新刚才所做的设置
配置mysql
数据库于dev.py
中
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'shoproot', # 数据库用户名
'PASSWORD': '111', # 数据库用户密码
'NAME': 'shop' # 数据库名字
},
}
__init__.py
(二级shop目录),配置pymysql
连接from pymysql import install_as_MySQLdb
install_as_MySQLdb()
Redis
中的缓存
及Session
在dev.py
(二级shop/settings)中添加如下内容:
CACHES = {
"default": { # 默认
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"session": { # session
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
新建名为logs
的python package
于一级shop
中
新建名为shop.log
的file
于目录logs
中
在dev.py
(二级shop/settings)中添加日志的配置信息:
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 是否禁用已经存在的日志器
'formatters': { # 日志信息显示的格式
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': { # 对日志进行过滤
'require_debug_true': { # django在debug模式下才输出日志
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 日志处理方法
'console': { # 向终端中输出日志
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': { # 向文件中输出日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(os.path.dirname(BASE_DIR), 'logs/shop.log'), # 日志文件的位置
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': { # 日志器
'django': { # 定义了一个名为django的日志器
'handlers': ['console', 'file'], # 可以同时向终端与文件中输出日志
'propagate': True, # 是否继续传递日志信息
'level': 'INFO', # 日志器接收的最低日志级别
},
}
}
将静态页面文件拷贝到二级shop目录下
在dev.py
中配置静态文件路径
STATIC_URL = '/static/'
# 配置静态文件加载路径
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
新建名为templates
的python package
于二级shop
中,将其标记为Template Folder
在浏览器中输入地址:127.0.0.1:8000/static/index.html
新建名为apps
的python package
于二级shop
中
进入apps
目录,用Django
命令创建子应用:
cd apps
python ../../manage.py startapp users
在dev.py
中注册app
,在二级shop/apps/users/apps.py
中,右击app名称sConfig
,选择Copy Reference
,拷贝引用,在dev.py
中粘贴
‘shop.apps.users.apps.UsersConfig’,
在dev.py
中追加导包路径
import sys
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
改写注册内容:
‘users.apps.UsersConfig’,
新建名为urls.py
的python file
于二级shop/apps/users
中,在其中填写如下内容:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^register/$', views.RegisterView.as_view()), # 匹配注册功能的路由
]
将此子路由
添加至总路由
(二级shop/urls.py):
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls), # 匹配admin站点的路由
url(r'^', include('users.urls')), # 匹配用户模块的路由
]
编写视图函数RegisterView
在二级shop/apps/users/views.py
中:
from django.shortcuts import render
from django.views import View
class RegisterView(View):
def get(self, request):
return render(request, 'register.html')
Django
自带了用户模型类,如要添加别的字段,只需继承Django自带的模型类,再添加自己的特有字段即可
在二级shop/apps/users/models.py
中添加如下内容:
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class User(AbstractUser):
"""自定义用户模型类"""
mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
class Meta:
db_table = 'tb_users'
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
在dev.py
中指定用户模型类:
AUTH_USER_MODEL = 'users.User' # 应用名.模型类名
创建迁移文件
python manage.py makemigrations
执行迁移文件
python manage.py migrate
填写完注册页面表单后,后台要处理
POST
请求
在二级shop/apps/users/views.py/RegisterView
中添加如下代码:
def post(self, request):
# 1.接收
user_name = request.POST.get('user_name')
pwd = request.POST.get('pwd')
cpwd = request.POST.get('cpwd')
phone = request.POST.get('phone')
allow = request.POST.get('allow')
# 2.验证
# 2.1验证非空
if not all([user_name, pwd, cpwd, phone, allow, sms_code_request]):
return http.HttpResponseBadRequest('参数不完整') # from django import http
# 2.2用户名拼写是否符合规范,是否已存在该用户名
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
return http.HttpResponseBadRequest('请输入5-20个字符') # import re
if User.object.filter(username=user_name).count() > 0: # from .models import User
return http.HttpResponseBadRequest('用户名已存在')
# 2.3密码拼写是否符合规范,两次密码输入是否一致
if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
return http.HttpResponseBadRequest('请输入8-20的密码')
if pwd != cpwd:
return http.HttpResponseBadRequest('两次密码输入不一致')
# 2.4手机号拼写是否符合规范,手机号是否已经被使用
if not re.match(r'^1[345789]\d{9}', phone):
return http.HttpResponseBadRequest('手机号格式不正确')
if User.objects.filter(mobile=phone).count() > 0:
return http.HttpResponseBadRequest('手机号已存在')
# 3.创建用户对象并保存在列表中
user = User.objects.create_user(username=user_name, password=pwd, mobile=phone)
# 4.状态保持
login(request, user) # from django.contrib.auth import login
# 5.响应,返回首页
return redirect('/') # from django.shortcuts import redirect
在子路由中添加ajax
的路由:
url(r'^usernames/(?P<username>[a-zA-Z0-9_-]{5,20})/count/$', views.UsernameCountView.as_view()),
在二级shop/apps/users/views.py
中添加视图类:
class UsernameCountView(View):
def get(self, request, username):
# 接收
# 验证
# 处理
count = User.objects.filter(username=username).count()
# 响应
return http.JsonResponse({
'code':RETCODE.OK, # from shop.utils.response_code import RETCODE
'errmsg':'OK',
'count':count
})
在子路由中添加ajax
的路由:
url('^mobiles/(?P<mobile>1[3-9]\d{9})/count/$', views.MobileCountView.as_view()),
在二级shop/apps/users/views.py
中添加视图类:
class MobileCountView(View):
def get(self, request, mobile):
# 接收
# 验证
# 处理,查询统计
count = User.objects.filter(mobile=mobile).count()
# 响应
return http.JsonResponse({
'code':RETCODE.OK,
'errmsg':'OK',
'count':count
})
新建名为response_code
的python file
于utils
目录中,内容如下:
class RETCODE:
OK = "0"
IMAGECODEERR = "4001"
THROTTLINGERR = "4002"
NECESSARYPARAMERR = "4003"
USERERR = "4004"
PWDERR = "4005"
CPWDERR = "4006"
MOBILEERR = "4007"
SMSCODERR = "4008"
ALLOWERR = "4009"
SESSIONERR = "4101"
DBERR = "5000"
EMAILERR = "5001"
TELERR = "5002"
NODATAERR = "5003"
NEWPWDERR = "5004"
OPENIDERR = "5005"
PARAMERR = "5006"
STOCKERR = "5007"
安装pillow
pip install Pillow
新建名为libs
的python package
目录于二级shop
中,将第三方图片验证码工具captcha
拷贝至这里。
新建名为verifications
的app
于apps
中:
python ../../manage.py startapp verifications
注册app
于dev.py
中:
'verifications.apps.VerificationsConfig',
新建路由表urls
,在子路由中添加路由:
urlpatterns = [
url(r'^image_codes/(?P<uuid>[\w-]+)/$', views.ImagecodeView.as_view()), # from . import views
] # from django.conf.urls import url
在总路由中包含子路由:
urlpatterns = [
url(r'^', include('verifications.urls')),
]
编写视图函数:
class ImagecodeView(View):
def get(self, request, uuid):
# 接收
# 验证
# 处理
# 1.生成图形验证码数据:字符code、图片image
text, code, image = captcha.generate_captcha()
# from shop.libs.captcha.captcha import captcha
# 2.保存字符,用于后续验证
# 2.1连接redis,参数为caches中的键
redis_cli = get_redis_connection('verify_code') # from django_redis import get_redis_connection
# 2.2向redis中写数据
redis_cli.setex(uuid, constants.IMAGE_CODE_EXPIRES, code)
# from . import constants
# 响应,输出图片数据
return http.HttpResponse(image, content_type='image/png')
新建名为constants
的py
文件于verifications
目录下,编写其内容如下:
# 验证码过期时间,当前为5分钟
IMAGE_CODE_EXPIRES = 60 * 5 # 设置过期时间
# 短信验证码过期时间,当前为5分钟
SMS_CODE_EXPIRES = 60 * 5
# 过期时间of短信是否重复发送的标记,当前为60s
SMS_CODE_FLAG_EXPIRES = 60
验证码字符在redis
缓存中存储,在dev.py
中新建缓存字段verify_code
:
"verify_code":{
"BACKEND":"django_redis.cache.RedisCache",
"LOCATION":"redis://127.0.0.1:6379/2", # 用第二个数据库
"OPTIONS":{
"CLIENT_CLASS":"django_redis.client.DefaultClient",
}
},
注意:chrome有个大坑,它会缓存之前请求过的地址。验证码的请求地址是和host.js关联的。当更改了host.js时,重新访问发现地址并没有更改,这是chrome缓存的缘故,记得定期清理缓存。
拷贝工具代码yuntongxun(短信)
至子shop/libs
目录中
在verifications/url
中添加路由:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^image_codes/(?P<uuid>[\w-]+)/$', views.ImagecodeViews.as_view()),
url('^sms_codes/(?P<mobile>1[3-9]\d{9})/$', views.SmscodeView.as_view()),
]
在verifications/views.py
中添加名为SmscodeView
的视图函数:
class SmscodeView(View):
def get(self, request):
# 接收
image_code_request = request.GET.get('image_code') # 用户填写的图形验证码文本
uuid = request.GET.get('image_code_id') # 图形验证码的唯一编号
# 验证,图形验证码是否正确
redis_cli = get_redis_connection('verify_code')
image_code_redis = redis_cli.get(uuid)
if not image_code_redis:
return http.JsonResponse({
'code':RETCODE.PARAMERR,
'errmsg':'图形验证码已过期'
})
# 强制验证码只能使用一次
redis_cli.delete(uuid)
# 判断用户输入的值是否正确
# 注意:所有从redis总读取的数据都是bytes,需要转码
if image_code_redis.decode() != image_code_request.upper():
return http.JsonResponse({
'code':RETCODE.PARAMERR,
'errmsg':'图形验证码错误'
})
# 处理
# 1.生成六位随机数
sms_code = '%6d' % random.randint(0, 999999)
# 2.保存到redis
redis_cli.setex('sms_'+mobile, 300, sms_code)
# 发短信,from shop.lib.yuntongxun.sms import CCP
# ccp = CCP()
# ret = ccp.send_template_sms(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
print(sms_code)
# 响应
return http.JsonResponse({
'code':RETCODE.OK,
'errmsg':'OK'
})
在注册视图中验证短信验证码,改写二级shop/apps/users/views.py
:
def post(self, request):
# 1.接收
user_name = request.POST.get('user_name')
pwd = request.POST.get('pwd')
cpwd = request.POST.get('cpwd')
phone = request.POST.get('phone')
allow = request.POST.get('allow')
sms_code_request = request.POST.get('msg_code') # 新添加
# 2.验证
# 2.1验证非空
if not all([user_name, pwd, cpwd, phone, allow, sms_code_request]):
return http.HttpResponseBadRequest('参数不完整') # from django import http
# 2.2用户名拼写是否符合规范,是否已存在该用户名
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
return http.HttpResponseBadRequest('请输入5-20个字符') # import re
if User.objects.filter(username=user_name).count() > 0: # from .models import User
return http.HttpResponseBadRequest('用户名已存在')
# 2.3密码拼写是否符合规范,两次密码输入是否一致
if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
return http.HttpResponseBadRequest('请输入8-20的密码')
if pwd != cpwd:
return http.HttpResponseBadRequest('两次密码输入不一致')
# 2.4手机号拼写是否符合规范,手机号是否已经被使用
if not re.match(r'^1[345789]\d{9}', phone):
return http.HttpResponseBadRequest('手机号格式不正确')
if User.objects.filter(mobile=phone).count() > 0:
return http.HttpResponseBadRequest('手机号已存在')
# 2.5短信验证码,from django_redis import get_redis_connection
# 连接redis
redis_cli = get_redis_connection('verify_code')
# 读取短信验证码
sms_code_redis = redis_cli.get('sms_' + phone)
# 判断是否过期
if not sms_code_redis:
return http.HttpResponseBadRequest('短信验证已过期')
# 强制立即过期
redis_cli.delete('sms_' + phone)
# 判断短信验证码是否正确
if sms_code_redis != sms_code_request:
return http.HttpResponseBadRequest('短信验证码错误')
# 3.创建用户对象并保存在列表中
user = User.objects.create_user(username=user_name, password=pwd, mobile=phone)
# 4.状态保持
login(request, user) # from django.contrib.auth import login
# 5.响应,返回首页
return redirect('/') # from django.shortcuts import redirect
避免频繁发送短信,添加如下代码于verificatons/views.py
中:
# 判断用户输入的值是否正确
# 注意:所有从redis总读取的数据都是bytes,需要转码
if image_code_redis.decode() != image_code_request.upper():
return http.JsonRequest({
'code':RETCODE.PARAMERR,
'errmsg':'图形验证码错误'
})
# 是否已经向此手机号发送过短信,新加代码
if redis_cli.get('sms_flag_' + mobile):
return http.JsonResponse({
'code':RETCODE.PARAMERR,
'errmsg':'已经向次手机号发过短信,请查看手机'
})
# 处理
# 1.生成六位随机数
sms_code = '%6d' % random.randint(0, 999999)
# 2.保存到redis
redis_cli.setex('sms_'+mobile, constants.SMS_CODE_EXPIRES, sms_code)
# 存储60s发送的标记,新加代码
redis_cli.setex('sms_flag_' + mobile, constants.SMS_CODE_FLAG_EXPIRES, 1)
# 发短信,from shop.lib.yuntongxun.sms import CCP
# ccp = CCP()
# ret = ccp.send_template_sms(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
print(sms_code)
使用pipeline
优化与redis
交互,只与redis
服务器交互一次,执行多条命令
# 1.生成六位随机数
# sms_code = '%6d' % random.randint(0, 999999)
# 2.保存到redis
# redis_cli.setex('sms_'+mobile, constants.SMS_CODE_EXPIRES, sms_code)
# 存储60s发送的标记,新加代码
# redis_cli.setex('sms_flag_' + mobile, constants.SMS_CODE_FLAG_EXPIRES, 1)
# 改写成下面这样
redis_pl = redis_cli.pipeline()
redis_pl.setex('sms_'+mobile, constants.SMS_CODE_EXPIRES, sms_code)
redis_pl.setex('sms_flag_' + mobile, constants.SMS_CODE_FLAG_EXPIRES, 1)
redis_pl.execute()
celery
框架实现异步新建名为celery_tasks
的python package
于一级shop
中
新建名为main
的python file
于celery_tasks
中,内容如下:
from celery import Celery
import os
os.environ["DJANGO_SETTINGS_MODULE"] = "shop.settings.dev"
# 创建主对象
app = Celery('shop')
# 读取配置:指定消息队列,当前使用redis
app.config_from_object('celery_tasks.config')
# 自动识别
app.autodiscover_tasks([
'celery_tasks.sms',
])
新建名为config
的python file
于celery_tasks
中,内容如下:
broker_url = 'redis://127.0.0.1:6379/15'
新建名为sms
的python package
于celery_tasks
中
新建名为tasks
的python file
于sms
中,内容如下:
from shop.libs.yuntongxun.sms import CCP
from celery_tasks.main import app
@app.task(bind=True, name='send_sms', retry_backoff = 3)
def send_sms(self, to, datas, tempid):
try:
#ccp = CCP()
#ret = ccp.send_template_sms(to, datas, 1)
print(datas[0])
except Exception as e:
self.retry(exc = e, max_retries = 3)
在shell
中运行如下命令,开启celery
服务:
celery -A celery_tasks.main worker -l info
调用任务,于二级shop/apps/users/views.py
中:
from celery_tasks.sms.tasks import send_sms
# 发短信,from shop.lib.yuntongxun.sms import CCP
# ccp = CCP()
# ret = ccp.send_template_sms(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
# print(sms_code)
send_sms.delay(mobile, [sms_code, contansts.SMS_CODE_EXPIRES / 60], 1)
在二级shop/apps/users/urls.py
中添加登录的路由:
url('^login/$', views.LoginView.as_view()),
在二级shop/apps/users/views.py
中添加视图类:
class LoginView(View):
def get(self, request):
return render(request, 'login.html') # 之后拷贝模板
def post(self, request):
# 接收
username = request.POST.get('username')
pwd = request.POST.get('pwd')
# 验证
if not all([user_name, pwd]):
return http.HttpResponseBadRequest('参数不完整') # from django import http
# 2.2用户名拼写是否符合规范,是否已存在该用户名
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
return http.HttpResponseBadRequest('请输入5-20个字符') # import re
if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
return http.HttpResponseBadRequest('请输入8-20的密码')
# 处理
user = authenticate(username = username, pwd = pwd) # from django.contrib.auth import authenticate
if user is None:
# 用户名或密码错误
return render(request, 'login.html', {
'loginerror':'用户名或密码错误'
})
else:
# 登录成功,状态保持
login(request, user)
return redirect('/')
# 响应
新建名为shop_backends
的python file
于utils
中,内容如下:
from django.contrib.auth.backends import ModelBackend
class ShopModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
# username接收的数据,可能是手机号,也可能是用户名
try:
if re.match('^1[3-9]\d{9}$', username):
# 手机号
user = User.objects.get(mobile=username)
else:
# 用户名
user = User.objects.get(username=username)
except:
return None
else:
# 验证密码
if user.check_password(password):
return user
else:
return None
在dev.py
中添加自定义认证类型:
AUTHENTICATION_BACKENDS = ['shop.utils.auth_backends.ShopModelBackend']
新建应用,cd shop/shop/apps
,python ../../manage.py startapp contents
注册应用,新建urls.py
:
from django.conf.urls import url
from . import views
urlpatterns = [
url('^$', views.IndexView.as_view()),
]
在dev.py
中注册应用:
'contents.apps.ContentsConfig',
在总路由中添加子路由:
url('^', include('content.urls')),
添加视图类:
from django.shortcuts import render
from django.views import View
class IndexView(View):
def get(self, request):
return render(request, 'index.html')
更改users/views.py.LoginView
:
else:
# 登录成功,状态保持
login(request, user)
# 向cookie中输出用户名,用于在前端提示登录状态
response = redirect('/')
response.set_cookie('username', user.username, max_age=60*60*24*14)
return response
更改users/views.py.RegisterView
:
# 响应
response = redirect('/')
response.set_cookie('username', user.username, max_age=60*60*24*14)
return response
在users/views.py
中添加新的视图类:
class LogoutView(View):
def get(self, request):
# 删除session
logout(request)
# 删除cookie
response = redirect('/')
response.delete_cookie('usename')
return response
在users.urls.py
中添加路由:
url('^logout/$', views.LogoutView.as_view()),
添加路由在users/urls.py
中:
url('^info/$', views.InfoView.as_view()),
添加视图类在users/views.py
中:
class InfoView(View):
def get(self, request):
# 判断是否登录
if request.user.is_authenticated:
# 已登录
return render(request, 'user_center_info.html')
else:
# 未登录
return redirect('/login/')
拷贝页面至templates
中。
在dev.py
中指定登录页视图:
LOGIN_URL = '/login/'
改写views.py
中的代码:
from django.contrib.auth.mixins import LoginRequiredMixin
class InfoView(LoginRequiredMixin, View):
def get(self, request):
# 判断是否登录
# if request.user.is_authenticated:
# 已登录
# return render(request, 'user_center_info.html')
# else:
# 未登录
# return redirect('/login/')
return render(request, 'user_center_info.html')
完善登录视图代码:
class LoginView(View):
def get(self, request):
return render(request, 'login.html') # 之后拷贝模板
def post(self, request):
# 接收
username = request.POST.get('username')
pwd = request.POST.get('pwd')
# 接收重定向参数,新加代码
next_url = request.GET.get('next', '/')
# 验证
if not all([user_name, pwd]):
return http.HttpResponseBadRequest('参数不完整') # from django import http
# 2.2用户名拼写是否符合规范,是否已存在该用户名
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', user_name):
return http.HttpResponseBadRequest('请输入5-20个字符') # import re
if not re.match(r'[0-9A-Za-z]{8,20}$', pwd):
return http.HttpResponseBadRequest('请输入8-20的密码')
# 处理
user = authenticate(username = username, pwd = pwd) # from django.contrib.auth import authenticate
if user is None:
# 用户名或密码错误
return render(request, 'login.html', {
'loginerror':'用户名或密码错误'
})
else:
# 登录成功,状态保持
login(request, user)
# 向cookie中输出用户名,用于在前端提示登录状态
response = redirect(next_url)
response.set_cookie('username', user.username, max_age=60*60*24*14)
return response
在虚拟环境中安装QQLoginTool
pip install QQLoginTool
新建名为oauth
的应用,python ../../manage.py startapp oauth
新建urls.py
,内容如下:
from django.conf.urls import url
from . import views
urlpatterns = [
url('^qq/login/$', views.QQurlView.as_view()),
url('^oauth_callback$', views.QQopenidView.as_view()),
]
在总路由中添加子路由:
url('^', include('oauth.urls')),
在dev.py
中注册app
'oauth.apps.OauthConfig',
在views.py
中创建类视图:
from django.shortcuts import render
from django.views import View
from django import http
from QQLoginTool.QQtool import OAuthQQ
from django.conf import settings
class QQurlView(View):
def get(self, request):
# 生成QQ授权地址url
next_url = request.GET.get('next', '/')
# 创建工具对象,提供appid、appkey、回调地址
oauthqq_tool = OAuthQQ(
settings.QQ_CLIENT_ID,
settings.QQ_CLIENT_SECRET,
settings.QQ_CLIENT_URI,
next_url
)
login_url = oauthqq_tool.get_qq_url()
return http.JsonResponse({
'code':RETCODE.OK,
'errmsg':'OK',
'login_url':login_url
})
class QQopenidView(View):
def get(self, request):
# 获取授权用户的唯一标识openid
# 接收code
code = request.GET.get('code')
next_url = request.GET.get('state', '/')
# 创建工具对象,提供appid、appkey、回调地址
oauthqq_tool = OAuthQQ(
settings.QQ_CLIENT_ID,
settings.QQ_CLIENT_SECRET,
settings.QQ_CLIENT_URI,
next_url
)
try:
# 根据code获取token
token = oauthqq_tool.get_access_token(code)
# 根据token获取openid
openid = oauthqq_tool.get_openid(token)
except:
openid = '0'
return http.HttpResponse(openid)
在dev.py
中添加QQ授权信息:
QQ_CLIENT_ID = '101518219'
QQ_CLIENT_SECRET = '418d84ebdc7241efb79536886ae95224'
QQ_REDIRECT_URI = 'http://www.meiduo.site:8000/oauth_callback'
在/etc/hosts
中添加127.0.0.1 www.meiduo.site
在dev.py
中添加:ALLOWED_HOSTS = [‘www.meiduo.site‘,]
将host.js
中的var host = ‘http://www.meiduo.site:8000‘
的注释打开,其余均注释
新建视图类于oauth/model.py
中:
from users.models import User
from shop.utils.models import BaseModel
class OAuthQQUser(models.Model):
user = models.ForeignKey(User)
openid = models.CharField(max_length = 50)
class Meta:
db_table = 'tb_oauth_qq'
新建名为models.py
的python file
文件于utils
目录中,内容为:
from django.db import models
class BaseModel(models.Model):
create_time = models.DateField(auto_now_add = True)
update_time = models.DateField(auto_now = True)
class Meta:
# 当前模型类为抽象类,用于继承,不需要创建表
abstract = True
执行迁移:
python manage.py makemigrations
python manage.py migrate
改写oauth/views.py
文件:
from django.shortcuts import render
from django.views import View
from django import http
from QQLoginTool.QQtool import OAuthQQ
from django.conf import settings
from .models import OAuthQQUser
class QQurlView(View):
def get(self, request):
# 生成QQ授权地址url
next_url = request.GET.get('next', '/')
# 创建工具对象,提供appid、appkey、回调地址
oauthqq_tool = OAuthQQ(
settings.QQ_CLIENT_ID,
settings.QQ_CLIENT_SECRET,
settings.QQ_CLIENT_URI,
next_url
)
login_url = oauthqq_tool.get_qq_url()
return http.JsonResponse({
'code':RETCODE.OK,
'errmsg':'OK',
'login_url':login_url
})
class QQopenidView(View):
def get(self, request):
# 获取授权用户的唯一标识openid
# 接收code
code = request.GET.get('code')
next_url = request.GET.get('state', '/')
# 创建工具对象,提供appid、appkey、回调地址
oauthqq_tool = OAuthQQ(
settings.QQ_CLIENT_ID,
settings.QQ_CLIENT_SECRET,
settings.QQ_CLIENT_URI,
next_url
)
try:
# 根据code获取token
token = oauthqq_tool.get_access_token(code)
# 根据token获取openid
openid = oauthqq_tool.get_openid(token)
# 绑定
try:
# 1.查询是否已经绑定过
qquser = OAuthQQUser.objects.get(openid=openid)
except:
# 2.如果未绑定过,则提示绑定页面
return render(request, 'oauth_callback.html') # 将oauth_callback.html放到templates目录中
# 3.如果已绑定过,则状态保持
except:
openid = '0'
return http.HttpResponse(openid)
在views.py
中添加post方法:
class QQopenidView(View):
def get(self, request):
# 获取授权用户的唯一标识openid
# 接收code
code = request.GET.get('code')
next_url = request.GET.get('state', '/')
# 创建工具对象,提供appid、appkey、回调地址
oauthqq_tool = OAuthQQ(
settings.QQ_CLIENT_ID,
settings.QQ_CLIENT_SECRET,
settings.QQ_CLIENT_URI,
next_url
)
try:
# 根据code获取token
token = oauthqq_tool.get_access_token(code)
# 根据token获取openid
openid = oauthqq_tool.get_openid(token)
# 绑定
try:
# 1.查询是否已经绑定过
qquser = OAuthQQUser.objects.get(openid=openid)
except:
# 2.如果未绑定过,则提示绑定页面
context = {'token':openid}
return render(request, 'oauth_callback.html', context) # 将oauth_callback.html放到templates目录中
# 3.如果已绑定过,则状态保持
except:
openid = '0'
return http.HttpResponse(openid)
def post(self, request):
# openid与账号绑定
安装加密的包:itsdangerous
pip install itsdangerous
新建名为shop_signature.py
的python file
文件于utils.py
中,内容如下:
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings
def dumps(json, expires):
'''
:param json:字典
:param expires:加密数据的过期时间
:return:字符串
'''
# 将字典加密,返回字符串
# 1.创建工具对象
serializer = Serializer(settings.SECRET_KEY, expires)
# 2.加密
serializer.dumps(json)
# 3.转字符串,返回
return s1.decode()
def loadds(s1, expires):
'''
:param s1:字符串
:param expires:加密数据的过期时间
:return:字典
'''
# 将字符串解密,返回字典
# 1.创建工具对象
serializer = Serializer(settings.SECRET_KEY, expires)
# 2.解密
try:
json = serializer.loads(s1)
except:
return None
# 3.返回字典
return json
更改views.py
:
class QQopenidView(View):
def get(self, request):
# 获取授权用户的唯一标识openid
# 接收code
code = request.GET.get('code')
next_url = request.GET.get('state', '/')
# 创建工具对象,提供appid、appkey、回调地址
oauthqq_tool = OAuthQQ(
settings.QQ_CLIENT_ID,
settings.QQ_CLIENT_SECRET,
settings.QQ_CLIENT_URI,
next_url
)
try:
# 根据code获取token
token = oauthqq_tool.get_access_token(code)
# 根据token获取openid
openid = oauthqq_tool.get_openid(token)
# 绑定
try:
# 1.查询是否已经绑定过
qquser = OAuthQQUser.objects.get(openid=openid)
except:
# 2.如果未绑定过,则提示绑定页面
token = shop_signature.dumps({'openid':openid}, contants.OPENID_EXPIRES) # from shop.utils import shop_signature, from . import contants
context = {'token':openid}
return render(request, 'oauth_callback.html', context) # 将oauth_callback.html放到templates目录中
# 3.如果已绑定过,则状态保持
except:
openid = '0'
return http.HttpResponse(openid)
def post(self, request):
# openid与账号绑定
新建名为contants
的python file
文件于oauth
目录中,内容如下:
# openid的过期时间
OPENID_EXPIRES = 60 * 10
写post方法:
def post(self, request):
# openid与账号绑定
# 接收
mobile = request.POST.get('mobile')
pwd = request.POST.get('pwd')
sms_code_request = request.POST.get('sms_code')
access_token = request.POST.get('access_token')
next_url = request.GET.get('state')
# 验证:与注册逻辑相同,参考注册代码
json = shop_signature.loadds(access_token, contants.OPENID_EXPIRES)
if json is None:
return http.HttpResponseBadRequest('授权信息无效,请重新授权')
openid = json.get('openid')
# 处理
# 根据手机号查询注册对象
try:
user = User.objects.get(mobile = mobile)
except:
# 手机号不存在,则注册新用户
user = User.objects.create_user(username=mobile, password=pwd, mobile=mobile)
else:
# 手机号存在,则验证密码
if not user.check_password(pwd):
return http.HttpResponseBadRequest('密码错误')
# 绑定
OAuthQQUSer.objects.create(user=user, openid=openid) # from .models import OAuthQQUser
# 状态保持
login(request, user) # from django.contrib.auth import login
# 响应
response = redirect(next_url) # from django.shortcuts import redirect
response.set_cookie('username', user.username, max_age = 60 * 60 * 24 * 14)
return response
非初次授权的情况。补充get方法:
# 3.如果已绑定过,则状态保持
login(request, qquser.user)
response = redirect(next_url)
response.set_cookie('username', qquser.user.username, max_age = 60 * 60 * 24 * 14)
return response
改写users/views.py/InfoView
:
def InfoView(LoginRequiredMixin, View):
def get(self, request):
# 获取当前登录的用户
user = request.user
context = {
'username':user.username,
'mobile':user.mobile,
'email':user.email,
'email_active':user.email_active
}
在models.py/User
中添加邮箱激活属性:
class User(AbstractUser):
mobile = model.CharField(max_length=11)
# 邮箱是否被激活
email_active = models.BooleanField(default=False)
迁移:
python manage.py makemigratons
python manage.py migrate
在users/urls.py
中添加路由:
url('^emails$', views.EmailView.as_view()),
在users.views.py
中添加视类图:
class EmailView(LoginRequiredMixin, View):
def put(self, request):
# 接收,以put方式发送的请求
dict1 = json.loads(request.body.decode()) # import json
email = dict1.get('email')
# 验证
if not all([email]):
return http.JsonResponse({
'code':RETCODE.PARAMERR,
'errmsg':'没有邮箱参数'
})
if not re.match('^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return http.JsonResponse({
'code':RETCODE.PARAMERR,
'errmsg':'邮箱格式错误'
})
# 处理
# 修改属性
user = request.user
user.email = email
user.save()
# 发邮件
# 响应
return http.JsonResponse({
'code':RETCODE.OK,
'errmsg':'OK'
})
在dev.py
中添加邮件服务器配置:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'hmmeiduo@163.com'
EMAIL_HOST_PASSWORD = 'hmmeiduo123'
EMAIL_FROM = '美多商城<hmmeiduo@163.com>'
EMAIL_VERIFY_URL = 'http://www.meiduo.site:8000/emails/verification/'
新建名为mail
的python package
于celery_tasks
目录中,在其中新加文件tasks.py
在其中定义方法:
from django.core.mail import send_mail
from django.conf import settings
from celery_tasks.main import app
@app.task(name='send_user_email', bind=True)
def send_user_email(to_mail, verify_url):
html_message = '您的邮箱为:%s,激活链接为%s' % (to_mail, verify_url)
try:
send_mail('美多商城-邮箱激活','', settings.EMAIL_FROM, [to_mail], html_message=html_message)
except Exception as e:
self.retry(exc=e, max_retries=2)
在celery_tasks/sms/main.py
中添加任务:
app.autodiscover_tasks([
'celery_tasks.sms',
'celery_tasks.mail',
])
启动celery:celery -A celery tasks.main worker -l info
在views.py
中调用任务
from celery_tasks.mail.tasks import send_user_email
...
# 发邮件,from django.conf import settings, from shop.utils import shop_signature,from . import contants
token = shop_signature.dumps({'user_id':user_id}, contants.EMAIL_EXPIRES)
verify_url = settings.EMAIL_VERIFY_URL + '?token=%s' % token
send_user_email.delay(email, verify_url)
在users
,目录总新建contants.py
,内容为:
# 邮箱激活的有效时间
EMAIL_EXPIRES = 60 * 60 * 2
新建视图类:
class EmailVerifyView(View):
def get(self, request):
# 接收
token = request.GET.get('token')
# 验证
dict1 = meiduo_signature.loadds(token, contants.EMAIL_EXPIRES)
if dict1 is None:
return http.HttpResponseBadRequest('激活信息无效,请重新发邮件')
user_id = dict1.get('user_id')
# 处理
try:
user = User.objects.get(pk=user_id)
except:
return http.HttpResponseBadRequest('激活信息无效')
else:
user.email_active = True
user.save()
# 响应
return redirect('/info/')
添加路由:
url('^emails/verifyication$', views.EmailVerifyView.as_view()),
在users/urls.py
中添加路由:
url('^addresses$', views.AddressesView.as_view()),
在users/views.py
中定义视图类:
class AddressesView(LoginRequiredMixin, View):
def get(self, request):
return render(request, 'user_center_site.html')
新建应用
cd shop/shop/apps/
python ../../manage.py startapp areas
新建路由urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
]
在总路由中添加子路由
url('^', include('areas.urls')),
注册app
'areas.apps.AreasConfig',
标签:nat contex 对象 过滤 state 输入 命令 编写 首页
原文地址:https://www.cnblogs.com/lulujunsir/p/meiduo.html