文章目录

  • 1.需求分析
    • 1.1 用户模块
    • 1.2 商品模块
    • 1.3 购物车模块
    • 1.4 订单相关
  • 2. 项目架构概览
    • 2.1 页面图
    • 2.2 功能图
  • 3. 数据库设计
    • 生成迁移文件
  • 3. 用户注册
    • 3.1 web页面搭建
      • 加载static 文件
    • 3.2 显示注册页面和注册处理使用同一个url地址
    • 3.3 生成激活用户token
    • 3.4 celery异步处理
      • celery工作流程
      • Linux、Windows开启worker
      • 视图views.py调用celery异步发送邮件
  • 4. 用户登录
    • django中session存储方式
    • 配置redis作为Django缓存和session存储后端
    • 登录后台逻辑
  • 5. 用户中心
    • 登录装饰器 login_required与LoginRequiredMixin
    • 06_登录后欢迎信息
    • 用户退出
    • 收货地址
    • 个人信息页
  • 06-分布式FastDFS文件系统


天天生鲜Django项目②

1.需求分析

1.1 用户模块

  • 注册页
  1. 注册时校验用户名是否已被注册。
  2. 完成用户信息的注册
  3. 给用户的注册邮箱发送邮件,用户点击邮件中的激活链接完成用户账户的激活。
  • 登陆页
    1 实现用户的登录功能

  • 用户中心

  1. 用户中心信息页,显示登录用户的信息,包括用户名、电话和地址,同时页面下方显示出用户最近浏览的商品信息。

  2. 用户中心地址页:显示登陆用户的默认收件地址,页面下方的表单可以新增用户的收货地址。

  3. 用户中心订单页:显示登录用户的订单信息。

  • 其他
  1. 如果用户已经登陆,页面顶部显示用户的订单信息。

1.2 商品模块

  • 首页

    1.动态指定首页轮播商品信息。

    2.动态指定首页活动信息。

    3.动态获取商品的种类信息并显示。

    4.动态指定首页显示的每个种类的商品(包括图片商品的文字商品)。

    5.点击某一个商品时跳转到商品的详情页面。

  • 商品详情页

    1.显示出某个商品的详细信息。

    2.页面下方显示出该商品的两个新品信息。

  • 3.商品列表页

    显示出某一个种类的商品的列表数据,分页显示并支持按照默认、价格和人气进行排序。

    页面下方显示出该商品的两个新品信息。

  • 4.其他

    通过搜索框搜索商品信息。

1.3 购物车模块

  • 列表页和详情页将商品添加到购物车。

  • 用户登录后,首页,详情页,列表页显示用户购物车中的商品数目。

  • 购物车页面:对用户购物车中的商品操作。如选择某件商品,增加或减少购物车中的商品数目。

1.4 订单相关

  • 提交订单页面:显示用户准备购买的商品信息。

  • 点击提交订单完成订单的创建。

  • 用户中心订单页显示用户的订单信息。

  • 点击支付完成订单的支付。

  • 点击评价完成订单的评价。

2. 项目架构概览

2.1 页面图

天天生鲜Django项目①-编程之家
天天生鲜Django项目①-编程之家

2.2 功能图

天天生鲜Django项目①-编程之家

3. 数据库设计

天天生鲜Django项目①-编程之家

生成迁移文件

遇到的问题集合

  • 当使用makemigrations时报错No changes detected

    在修改了models.py后,有些用户会喜欢用python manage.py makemigrations生成对应的py代码。

    但有时执行python manage.py makemigrations命令(也可能人比较皮,把migrations文件夹给删了),会提示"No changes detected." 可能有用的解决方式如下:

    先 python manage.py makemigrations –empty yourappname 生成一个空的initial.py

    再 python manage.py makemigrations 生成原先的model对应的migration file
    天天生鲜Django项目①-编程之家
    逐行迁移文件
    https://stackoverflow.com/questions/41398949/django-related-model-users-userprofile-cannot-be-resolved
    天天生鲜Django项目①-编程之家

天天生鲜Django项目①-编程之家

3. 用户注册

3.1 web页面搭建

as_view()最终干的事情就是根据request请求方式来执行视图类的不同请求方法

加载static 文件

settings.py 设置静态文件路径

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))STATIC_URL = '/static/'STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
{% load staticfiles %}

3.2 显示注册页面和注册处理使用同一个url地址

类视图的使用

from django.views.generic import Viewclass RegisterView(View):"""注册"""def get(self, request):"""显示注册页面"""return render(request, 'register.html')def post(self, request):"""进行注册处理"""return register_handle(request)

3.3 生成激活用户token

urls.py

re_path(r'^register$', RegisterView.as_view(), name='register'),
re_path(r'^active/(?P<token>.*)$', ActiveView.as_view(), name='active'),

视图views.py
生成token

def register_handle(request):"""进行注册的处理"""# 接收数据username = request.POST.get('user_name')password = request.POST.get('pwd')email = request.POST.get('email')allow = request.POST.get('allow')# 进行数据校验if not all([username, password, email]):# 数据有误return render(request, 'register.html', {'errmsg': '数据不完整'})# 校验邮箱if not re.match(r'^[a-z0-9][w.-]*@[a-z0-9-]+(.[a-z]{2,5}){1,2}$', email):# 数据有误return render(request, 'register.html', {'errmsg': '邮箱格式不对'})if allow != 'on':return render(request, 'register.html', {'errmsg': '请同意协议条款再进行注册'})# 校验用户是否重复try:user = User.objects.get(username=username)except User.DoesNotExist:user = Noneif user:return render(request, 'register.html', {'errmsg': '用户名已存在'})# 进行业务处理:进行用户注册user = User.objects.create_user(username, email, password)user.is_active = 0 #不激活user.save()# 发送激活邮件,包含激活链接# 激活链接中需要包含用户的身份信息,并且需要将身份信息进行加密处理# 加密用户身份信息,生成激活Token。过期时间的单位为秒serializer = Serializer(settings.SECRET_KEY, 3600*72)info = {'confirm': user.id}token = serializer.dumps(info)  # 此处返回是数据类型是 bytes,将字节流转换成字符串需要对其进行解码。token = token.decode() # 解码变成字符串# 发邮件send_register_active_email.delay(email, username, token)# 返回应答 反向解析,跳转到首页return redirect(reverse('goods:index'))

激活处理

class ActiveView(View):"""用户激活"""def get(self, request, token):"""进行用户激活"""# 进行解密,获取要激活的用户信息serializer = Serializer(settings.SECRET_KEY, 3600*72)try:info = serializer.loads(token)# 获取待激活用户的iduser_id = info['confirm']# 根据id获取用户信息user = User.objects.get(id=user_id)user.is_active = 1user.save()# 跳转到登录界面return redirect(reverse('user:login'))except SignatureExpired as e: #激活异常return HttpResponse('激活链接已过期')

3.4 celery异步处理

Celery分为3个部分

(1)worker部分负责任务的处理,即工作进程(我的理解工作进程就是你写的python代码,当然还包括python调用系统工具功能)(2)broker部分负责任务消息的分发以及任务结果的存储,这部分任务主要由中间数据存储系统完成,比如消息队列服务器RabbitMQ、redis、

Amazon SQS、MongoDB、IronMQ等或者关系型数据库,使用关系型数据库依赖sqlalchemy或者django的ORM

(3)Celery主类,进行任务最开始的指派与执行控制,他可以是单独的python脚本,也可以和其他程序结合,应用到django或者flask等web框架里面以及你能想到的任何应用

celery工作流程

天天生鲜Django项目①-编程之家
启动celery任务时,需要添加django初始环境

# 在任务处理者一端加这几句
# import os
# import django
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
# django.setup()# 创建一个Celery类的实例对象
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8')

Linux、Windows开启worker

tips:与异步文件夹处于同一目录下

  • linux:

    celery -A celery_tasks.tasks worker -l info
    

    天天生鲜Django项目①-编程之家
    接收消息
    天天生鲜Django项目①-编程之家

  • windows:

    celery -A celery_tasks.tasks worker --loglevel=info --pool=solo
    

    天天生鲜Django项目①-编程之家

# 使用celery
from django.core.mail import send_mail
from django.conf import settings
from celery import Celery
import time# 在任务处理者worker一端加这几句
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mydailyfresh.settings")
django.setup()# 创建一个Celery类的实例对象
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8')# 定义任务函数
@app.task
def send_register_active_email(to_email, username, token):'''发送激活邮件'''# 组织邮件信息subject = '天天生鲜欢迎信息'message = ''sender = settings.EMAIL_FROMreceiver = [to_email]html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token)send_mail(subject, message, sender, receiver, html_message=html_message)time.sleep(5)

视图views.py调用celery异步发送邮件

send_register_active_email.delay(email, username, token)

注册后,使用celery异步发送邮件成功
天天生鲜Django项目①-编程之家

4. 用户登录

django中session存储方式

天天生鲜Django项目①-编程之家

配置redis作为Django缓存和session存储后端

# Django的缓存配置
CACHES = {"default": {"BACKEND": "django_redis.cache.RedisCache","LOCATION": "redis://127.0.0.1:6379/9","OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient",}}
}# 配置session存储
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

登录后台逻辑

Django 2.1 用户认证系统authenticate()一直返回None

class LoginView(View):def get(self, request):"""显示登录页面"""# 判断是否记住了用户名if 'username' in request.COOKIES:username = request.COOKIES.get('username')checked = 'checked'else:username = ''checked = ''# 使用模板return render(request, 'login.html', {'username':username, 'checked':checked})def post(self, request):"""登录校验"""# 接受数据username = request.POST.get('username')password = request.POST.get('pwd')# 校验数据if not all([username, password]):return render(request, 'login.html', {'errmsg': '数据不完整'})# 业务处理:登录校验try:user=User.objects.get(username=username)pwd=user.password# user = authenticate(username=username, password=password)if check_password(password,pwd) and user is not None:# if user is not None:if user.is_active:# 记录用户登录状态login(request, user)# 判断是否需要记住用户名remember = request.POST.get('remember')# 获取登录后所要跳转的地址# 获取get上请求的路径,默认跳转到首页# response = redirect(reverse('goods:index')) # #HttpResponseRedirectnext_url = request.GET.get('next', reverse('goods:index'))response = redirect(next_url)if remember == 'on':# 记住用户名response.set_cookie('username', username, max_age=7*24*3600)else:response.delete_cookie('username')# 返回responsereturn responseelse:return render(request, 'login.html', {'errmsg': '请激活你的账户'})else:# 返回应答return render(request, 'login.html', {'errmsg': '用户名或密码错误'})except:return render(request, 'login.html', {'errmsg': '无此用户'})

redis缓存信息
天天生鲜Django项目①-编程之家

5. 用户中心

用户中心页面显示
urls.py

re_path(r'^$', UserInfoView.as_view(), name='user'),  # 用户中心-信息页
re_path(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
re_path(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页

登录装饰器 login_required与LoginRequiredMixin

老版的Django使用装饰器login_required来限制用户登录

新版的Dajngo通过继承LoginRequiredMixin类来限制用户登录,必须是第一个继承,在继承列表最左侧位置
utils.mixin.py

from django.contrib.auth.decorators import login_required
# 只有在使用了内置的登录信息认证系统的时候,才能使用内置的装饰器函数login_required()class LoginRequiredMixin(object):@classmethoddef as_view(cls, **initkwargs):# 调用父类的as_view,以下两种方法都可调用super的方法# view = super().as_view(**initkwargs)view = super(LoginRequiredMixin, cls).as_view(**initkwargs)return login_required(view)
from utils.mixin import LoginRequiredMixin  # 自定义认证检查
class UserInfoView(LoginRequiredMixin, View):def get(self, request):

天天生鲜Django项目①-编程之家
urls.py

# 方法一,使用函数检查登录装饰器 login_required(UserInfoView.as_view()),
# re_path(r'^$', login_required(UserInfoView.as_view()), name='user'),  # 用户中心-信息页
# re_path(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页
# re_path(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页
# 方法二,继承LoginRequiredMixin对象,进行检查。
re_path(r'^$', UserInfoView.as_view(), name='user'),  # 用户中心-信息页
re_path(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
re_path(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页

如果用户还没有登录,默认会跳转到‘/accounts/login/’。这个值可以在settings文件中通过LOGIN_URL参数来设定。(后面还会自动加上你请求的url作为登录后跳转的地址,如: /accounts/login/?next=/polls/3/ 登录完成之后,会去请求/poll/3)

因此,settings.py设置默认值

# 配置登录url地址,当认证检查不通过时,会自动跳转到指定的界面
LOGIN_URL = '/user/login'  # 默认的地址是 /accounts/login,需要重新设置来修改默认值

未登录时自动跳转http://127.0.0.1:8000/user/login?next=/user/ 登录页

不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据,因此,登录逻辑改变,获取next的地址

 # 获取登录后所要跳转的地址
# 获取get上请求的路径,默认跳转到首页
# response = redirect(reverse('goods:index')) # #HttpResponseRedirect
next_url = request.GET.get('next', reverse('goods:index')) # 无next时返回默认值reverse('goods:index')
response = redirect(next_url)

06_登录后欢迎信息

登录后应该不显示登录和注册按钮
天天生鲜Django项目①-编程之家
模板中可以直接使用request对象的,比如request.user。
如果不能的话需要settings中进行配置TEMPLATES的OPTIONS.context_processors增加

django.template.context_processors.request:

request.user.is_authenticated()
除了你给模板文件传递的模板变量之外,Django框架会自动把request.user也传递给模板文件,这样就可以直接在模板文件中只用这个user对象。

如果用户未登录,那么request.user则是一个AnonymousUser(匿名用户)对象。is_authenticated()返回false。

如果用户已登录,那么request.user则是一个真实的User实例对象。is_authenticated()返回true。

<div class="header_con"><div class="header"><div class="welcome fl">欢迎来到天天生鲜!</div><div class="fr">{% if user.is_authenticated %}<div class="login_btn fl">欢迎您:<em>{{ user.username }}</em><span>|</span><a href="{% url 'user:logout' %}">退出</a></div>{% else %}<div class="login_btn fl"><a href="{% url 'user:login' %}">登录</a><span>|</span><a href="{% url 'user:register' %}">注册</a></div>{% endif %}

天天生鲜Django项目①-编程之家

用户退出

# /user/logout
class LogoutView(View):"""退出登录"""def get(self, request):"""退出登录"""# 清除用户的session信息logout(request)# 跳转到首页return redirect(reverse('goods:index'))

收货地址

model.py 添加 地址模型管理器类

class AddressManager(models.Manager):"""地址模型管理器类"""# 1.改变原有查询的结果集:all()# 2.封装方法:用户操作模型类对应的数据表(增删改查)def get_default_address(self, user):# print(self)  这里的self就是user.Address.objects"""获取用户默认收货地址"""# self.model:获取self对象所在的模型类try:address = self.get(user=user, is_default=True)  # models.Managerexcept self.model.DoesNotExist:# 不存在默认收货地址address = Nonereturn address
# /user/address
class AddressView(LoginRequiredMixin, View):"""用户中心-地址页"""def get(self, request):"""显示"""# 获取登录用户对应User对象user = request.user# 获取用户的默认收货地址address = Address.objects.get_default_address(user)# 使用模板return render(request, 'user_center_site.html', {'page': 'address', 'address': address})def post(self, request):"""地址的添加"""# 接收数据receiver = request.POST.get('receiver')addr = request.POST.get('addr')zip_code = request.POST.get('zip_code')phone = request.POST.get('phone')# 校验数据if not all([receiver, addr, phone]):return render(request, 'user_center_site.html', {'errmsg': '数据不完整'})# 校验手机号if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone):return render(request, 'user_center_site.html', {'errmsg': '手机格式不正确'})# 业务处理:地址添加# 如果用户已存在默认收货地址,添加的地址不作为默认收货地址,否则作为默认收货地址# 获取登录用户对应User对象user = request.user# try:#     address = Address.objects.get(user=user, is_default=True)# except Address.DoesNotExist:#     # 不存在默认收货地址#     address = Noneaddress = Address.objects.get_default_address(user)if address:is_default = Falseelse:is_default = True# 添加地址Address.objects.create(user=user,receiver=receiver,addr=addr,zip_code=zip_code,phone=phone,is_default=is_default)# 返回应答,刷新地址页面return redirect(reverse('user:address'))  # get请求方式
{% extends 'base_user_center.html' %}
{% block right_content %}<div class="right_content clearfix"><h3 class="common_title2">收货地址</h3><div class="site_con"><dl><dt>当前地址:</dt>{% if address %}<dd>{{ address.addr }} ({{ address.receiver }} 收) {{ address.phone }}</dd>{% else %}<dd>无默认地址</dd>{% endif %}</dl></div><h3 class="common_title2">编辑地址</h3><div class="site_con"><form method="post">{% csrf_token %}<div class="form_group"><label>收件人:</label><input type="text" name="receiver"></div><div class="form_group form_group2"><label>详细地址:</label><textarea class="site_area" name="addr"></textarea></div><div class="form_group"><label>邮编:</label><input type="text" name="zip_code"></div><div class="form_group"><label>手机:</label><input type="text" name="phone"></div><input type="submit" value="提交" class="info_submit"></form></div></div>
{% endblock right_content %}

个人信息页

12_历史记录存储格式设计
天天生鲜Django项目①-编程之家
个人信息与浏览商品历史记录

class UserInfoView(LoginRequiredMixin, View):def get(self, request):# request.user.is_authenticated()# 除了你给模板文件传递的模板变量之外,Django框架会自动把request.user也传递给模板文件,这样就可以直接在模板文件中只用这个user对象。# 如果用户未登录,那么request.user则是一个AnonymousUser(匿名用户)对象。is_authenticated()返回false。# 如果用户已登录,那么request.user则是一个真实的User实例对象。is_authenticated()返回true。# 获取用户的个人信息user = request.useraddress = Address.objects.get_default_address(user)# 获取用户的历史浏览记录# from redis import StrictRedis# sr = StrictRedis(host='172.16.179.130', port='6379', db=9)con = get_redis_connection('default')history_key = 'history_%d' % user.id# 获取用户最新浏览的5个商品的idsku_ids = con.lrange(history_key, 0, 4)  # [2,3,1]# 从数据库中查询用户浏览的商品的具体信息# goods_li = GoodsSKU.objects.filter(id__in=sku_ids)## goods_res = []# for a_id in sku_ids:#     for goods in goods_li:#         if a_id == goods.id:#             goods_res.append(goods)# 遍历获取用户浏览的商品信息goods_li = []for id in sku_ids:goods = GoodsSKU.objects.get(id=id)goods_li.append(goods)# 组织上下文context = {'page': 'user','address': address,'goods_li': goods_li}return render(request, 'user_center_info.html', context)
{% extends 'base_user_center.html' %}
{% block right_content %}<div class="right_content clearfix"><div class="info_con clearfix"><h3 class="common_title2">基本信息</h3><ul class="user_info_list"><li><span>用户名:</span>{{ user.username }}</li>{% if address %}<li><span>联系方式:</span>{{ address.phone }}</li><li><span>联系地址:</span>{{ address.addr }}</li>{% else %}<li><span>联系方式:</span>无默认</li><li><span>联系地址:</span>无默认</li>{% endif %}</ul></div><h3 class="common_title2">最近浏览</h3><div class="has_view_list"><ul class="goods_type_list clearfix">{% for goods in goods_li %}<li><a href="detail.html"><img src="{{ goods.image.url }}"></a><h4><a href="detail.html">{{ goods.name }}</a></h4><div class="operate"><span class="prize">¥{{ goods.price }}</span><span class="unit">{{ goods.price }}/{{ goods.unite }}</span><a href="#" class="add_goods" title="加入购物车"></a></div></li>{% empty %}无历史浏览记录{% endfor %}</ul></div></div>
{% endblock right_content %}

06-分布式FastDFS文件系统

FastDFS安装与使用
天天生鲜Django项目①-编程之家

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<img src="http://47.98.126.56:8888/group1/M00/00/00/L2J-OF45VlqAR9y7AADGU6re_xM3605706">
</body>
</html>

天天生鲜Django项目②