天天生鲜Django项目①

文章目录

  • 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项目①_第1张图片
天天生鲜Django项目①_第2张图片

2.2 功能图

天天生鲜Django项目①_第3张图片

3. 数据库设计

天天生鲜Django项目①_第4张图片

生成迁移文件

遇到的问题集合

  • 当使用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项目①_第5张图片
    逐行迁移文件
    https://stackoverflow.com/questions/41398949/django-related-model-users-userprofile-cannot-be-resolved
    天天生鲜Django项目①_第6张图片

天天生鲜Django项目①_第7张图片

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 View

class 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.*)$', 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 = None

    if 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)
            # 获取待激活用户的id
            user_id = info['confirm']
            # 根据id获取用户信息
            user = User.objects.get(id=user_id)
            user.is_active = 1
            user.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项目①_第8张图片
启动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项目①_第9张图片

  • windows:

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

    天天生鲜Django项目①_第10张图片

# 使用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_FROM
    receiver = [to_email]
    html_message = '

%s, 欢迎您成为天天生鲜注册会员

请点击下面链接激活您的账户
http://127.0.0.1:8000/user/active/%s'
% (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异步发送邮件成功
在这里插入图片描述

4. 用户登录

django中session存储方式

天天生鲜Django项目①_第11张图片

配置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')) # #HttpResponseRedirect

                    next_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')

                    # 返回response
                    return response

                else:
                    return render(request, 'login.html', {'errmsg': '请激活你的账户'})
            else:
                # 返回应答
                return render(request, 'login.html', {'errmsg': '用户名或密码错误'})
        except:
            return render(request, 'login.html', {'errmsg': '无此用户'})

redis缓存信息
天天生鲜Django项目①_第12张图片

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):
    @classmethod
    def 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项目①_第13张图片
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项目①_第14张图片
模板中可以直接使用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项目①_第15张图片

用户退出

# /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.Manager
        except self.model.DoesNotExist:
            # 不存在默认收货地址
            address = None

        return 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 = None

        address = Address.objects.get_default_address(user)

        if address:
            is_default = False
        else:
            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项目①_第16张图片
个人信息与浏览商品历史记录

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.user
        address = 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个商品的id
        sku_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项目①_第17张图片

<!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项目②

你可能感兴趣的:(Django)