完成了模型层,需要映射到数据库中,创建相应的表。在项目的 settings.py 文件中配置数据库,Django 有数据读写分离的配置方式。
创建新的项目,Django 会默认使用 sqlite 数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
再执行 python manage.py makemigrations 命令之前,需要在 settings.py 文件中重载新建的 User 模型,然后再执行 python manage.py migrate 命令,将迁移文件,映射到数据库中,创建相应的表。
# django认证系统使用的用户模型
AUTH_USER_MODEL = "users.User"
配置读写分离路由器
DATABASE_ROUTERS = ["utils.db_router.MasterSlaveDBRouter"
在项目路径下创建 utils 文件夹,新建 db_router.py 文件,再创建 MasterSlaveDBRouter 类。
class MasterSlaveDBRouter(object):
"""读写分离路由"""
def db_for_read(self, model, **hints):
"""读"""
return "slave"
def db_for_write(self, model, **hints):
"""写"""
return "default"
def allow_relation(self, obj1, obj2, **hints):
"""允许关联查询"""
return True
一、发送邮件
使用 python 的 celery (分布式任务队列) 模块,实现用户注册邮箱激活功能。
需要在项目路径下,新建 celery_tasks 的 python 包,再创建 tasks.py 文件
from celery import Celery
import os
os.environ["DJANGO_SETTINGS_MODULE"] = "ihome.settings"
# 放到celery服务器上时将注释打开
# import django
# django.setup()
from django.core.mail import send_mail
from django.conf import settings
from goods.models import GoodsCategory, IndexGoodsBanner, IndexPromotionBanner
from goods.models import IndexCategoryGoodsBanner
from django.template import loader
# celery -A celery_tasks.tasks worker -l info
# 创建celery应用对象
app = Celery("celery_tasks.tasks", broker="redis://192.168.108.57/4")
@app.task
def send_active_email(to_email, user_name, token):
"""发送激活邮件"""
subject = "生鲜用户激活" # 标题
body = "" # 文本邮件体
sender = settings.EMAIL_FROM # 发件人
receiver = [to_email] # 接收人
html_body = '<h1>尊敬的用户 %s, 感谢您注册生鲜!</h1>' \
'<br/><p>请点击此链接激活您的帐号<a href="http://127.0.0.1:8000/users/active/%s">' \
'http://127.0.0.1:8000/users/active/%s</a></p>' % (user_name, token, token)
send_mail(subject, body, sender, receiver, html_message=html_body)
在 settings.py 文件中的配置
# Email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_FROM = ''
二、用户注册
class RegisterView(View):
"""用户注册"""
def get(self, request):
"""处理get请求, 提供注册页面"""
return render(request, "register.html")
def post(self, request):
"""处理post请求,处理注册数据"""
# 获取前端发送的数据/参数
user_name = request.POST.get("user_name")
password = request.POST.get("pwd")
email = request.POST.get("email")
allow = request.POST.get("allow")
# 参数校验
if not all([user_name, password, email]):
# 如果参数不完整,从定向到注册页面
return redirect(reverse("users:register"))
if not re.match(r"^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$", email):
# 如果email格式不正确
return render(request, "register.html", {"errmsg": "邮箱格式不正确!"})
if allow != "on":
# 如果用户没有勾选协议
return render(request, "register.html", {"errmsg": "请同意协议!"})
# 进行业务逻辑处理
# 将密码加密
# 将用户数据保存到数据库中
# user = User()
# user.save()
# 使用django的认证系统创建用户
try:
user = User.objects.create_user(user_name, email, password)
except db.IntegrityError:
# 如果抛出此异常,表示用户已经注册
return render(request, "register.html", {"errmsg": "用户已注册!"})
# 将用户的激活状态设置为假
user.is_active = False
user.save()
# 生成激活token
token = user.generate_active_token()
# 使用celery发送邮件
send_active_email.delay(email, user_name, token)
# 返回给前端结果
return redirect(reverse("goods:index"))
三、用户激活
class ActiveView(View):
"""激活"""
def get(self, request, token):
# 根据token 解析,获取用户的id
# 创建转换工具(序列化器)
s = Serializer(settings.SECRET_KEY, 3600)
# 解析
try:
ret = s.loads(token)
except SignatureExpired:
# 如果出现异常,表示token过期,返回信息给用户
return HttpResponse("激活链接已过期")
# 更新用户在数据库中的激活状态
user_id = ret.get("confirm")
# 查询数据库
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
# 用户不存在
return HttpResponse("用户不存在")
user.is_active = True
user.save()
# 返回信息给用户
return redirect(reverse("users:login"))
四、用户登录
class LoginView(View):
"""登录"""
def get(self, request):
"""提供登录页面"""
return render(request, "login.html")
def post(self, request):
"""处理登录请求"""
user_name = request.POST.get("username")
password = request.POST.get("pwd")
remembered = request.POST.get("remembered")
if not all([user_name, password]):
return redirect(reverse("users:login"))
#
# password = sha256(password)
# User.objects.get(username=user_name, password=password)
# 使用django的认证系统
user = authenticate(username=user_name, password=password)
if user is None:
# 表示用户的认证失败
return render(request, "login.html", {"errmsg": "用户名或密码错误"})
# 表示用户认证成功
# 判断用户的激活状态
if user.is_active is False:
# 表示用户未激活
return render(request, "login.html", {"errmsg": "用户名尚未激活"})
# 在session中保存用户的登录状态信息
login(request, user)
# 处理记住用户名的逻辑
if remembered != "on":
# 不需要记住用户状态
# 使用set_expiry设置 session 有效期
request.session.set_expiry(0)
else:
# 需要记住用户状态
request.session.set_expiry(None)
# 将cookie中的购物车数据与redis中的购物车数据合并
# 从cookie中获取购物车数据
cart_json = request.COOKIES.get("cart")
if cart_json is not None:
cart_cookie = json.loads(cart_json)
else:
cart_cookie = {}
# 从redis中获取购物车数据
redis_conn = get_redis_connection("default")
cart_redis = redis_conn.hgetall("cart_%s" % user.id)
# 进行合并
# cart_redis.update(cart_cookie)
for sku_id, count in cart_cookie.items():
# 在redis中的键与值都是bytes类型, 在cookie中的sku_id是str类型
sku_id = sku_id.encode() # 将str类型的sku_id转为bytes类型
if sku_id in cart_redis: # {b'1': b'3'}
# cookie中有的商品,在redis中也有,进行数量求和,再设置到redis对应的购物车中
origin_count = cart_redis[sku_id]
count += int(origin_count)
cart_redis[sku_id] = count
# 将合并的购物车数据保存到redis中
if cart_redis:
redis_conn.hmset("cart_%s" % user.id, cart_redis)
# 清除cookie中的购物车数据
# 登录成功,根据next参数跳转页面
next = request.GET.get("next")
if next is None:
# 如果没有next参数,跳转到主页
response = redirect(reverse("goods:index"))
else:
# 如果next存在,跳转到next路径
response = redirect(next)
response.delete_cookie("cart")
return response