Django 项目搭建
Django项目搭建
本项目是一个典型的电商项目,采用
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中打开
在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异步校验的方式验证用户名是否存在
在子路由中添加ajax
的路由:
url(r'^usernames/(?P[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异步校验的方式验证手机号是否存在
在子路由中添加ajax
的路由:
url('^mobiles/(?P1[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[\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[\w-]+)/$', views.ImagecodeViews.as_view()),
url('^sms_codes/(?P1[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
QQ授权登录
在虚拟环境中安装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'
的注释打开,其余均注释
QQ账号信息与本网站绑定
新建视图类于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 = '美多商城'
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',