天天生鲜Django项目

天天生鲜

  • 一、Web项目开发流程
  • 二、需求分析
    • 2.1 用户模板
    • 2.2 商品相关
    • 2.3 购物车相关
    • 2.4 订单提交
  • 三、项目架构
    • 3.1 页面图
    • 3.2 功能图
    • 3.3 部署图
    • 3.4 项目架构
    • 3.5 数据库设计
  • 四、初始化
    • 项目配置
    • 富文本
      • 在Admin中使用
  • 五、项目开始
    • 项目框架搭建
  • 六、用户模块开发
    • 6.1 用户注册
    • 6.2 用户登录
      • Redis存储Session
      • 记住用户名
      • 父模板页抽象
    • 6.3 用户中心三页面
    • 6.4 登录后页面跳转
    • 6.5 登录后欢迎信息
    • 6.6 用户退出登录
    • 6.7 用户中心_地址页
    • 6.8 模型管理器类方法封装
    • 6.8 用户中心_个人信息页
    • 6.9 历史浏览记录
  • 七、分布式存储系统
    • 7.1 文件上传流程
    • 7.2 文件下载流程
    • 7.3 FastDFS的安装和配置
      • 7.3.1 安装FastDFS
      • 7.4 配置跟踪服务器tracker
      • 7.5 配置存储服务器storage
      • 7.6 启动tracker和storage
      • 7.7 安装nginx及fastdfs-nginx-module
    • 测试
    • 如何修改django的上传行为
  • 八、首页页面静态化
    • 1.celery定义任务函数,生成静态页面
    • 2.nginx配置提供静态页面
    • 3.管理员修改首页所使用表中的数据数,重新生成index静态页面
    • 4.静态页面index页面和IndexView的调度
    • 5.页面数据的缓存
    • 6.页面静态化小结
  • 九、商品模型模型类
    • 1.首页内容获取和展示
    • 2.添加购物车
    • 3.商品详情信息的获取和显示
  • 十、部署服务器
    • 1.uwsgi作为web服务器
    • 2.Nginx配置转发请求给uwsgi
    • 3.Nginx配置处理静态文件
    • 4.Nginx转发请求给另外地址
    • 5.Nginx配置upstream实现负载均衡
  • 十一、项目总结
  • 十二、商品列表页
  • 十三、商品搜索

天天生鲜Django项目_第1张图片

一、Web项目开发流程

天天生鲜Django项目_第2张图片

二、需求分析

2.1 用户模板

1)注册页

  • 注册时校验用户名是否已被注册。
  • 完成用户信息的注册。
  • 给用户的注册邮箱发送邮件。用户点击邮件中的激活链接完成用户账户的激活。

2)登录页

  • 实现用户的登录功能。

3)用户中心

  • 用户中心信息页:显示登录用户的信息,包括用户名、电话和地址,同时页面下方显示出用户最近浏览的商品信息。
  • 用户中心地址页:显示登录用户的默认收件地址,页面下方的表单可以新增用户的收货地址。
  • 用户中心订单页:显示登录用户的订单信息。

4)其他

  • 如果用户已经登录,页面顶部显示登录用户的信息。

2.2 商品相关

1)首页

  • 动态指定首页轮播商品信息。
  • 动态指定首页活动信息。
  • 动态获取商品的种类信息并显示。
  • 动态指定首页显示的每个种类的商品(包括图片商品和文字商品)。
  • 点击某一个商品时跳转到商品的详情页面。

2)商品详情页

  • 显示出某个商品的详情信息。
  • 页面的左下方显示出该种类商品的2个新品信息。

3)商品列表页

  • 显示出某一个种类商品的列表数据,分页显示并支持按照默认、价格、和人气进行排序。
  • 页面的左下方显示出该种类商品的2个新品信息。

4)其他

  • 通过页面搜索框搜索商品信息。

2.3 购物车相关

  • 列表页和详情页将商品添加到购物车。
  • 用户登录后,首页,详情页,列表页显示登录用户购物车中商品的数目。
  • 购物车页面:对用户购物车中商品的操作。如选择某件商品,增加或减少购物车中商品的数目。

2.4 订单提交

  • 提交订单页面:显示用户准备购买的商品信息。
  • 点击提交订单完成订单的创建。
  • 用户中心订单页显示用户的订单信息。
  • 点击支付完成订单的支付。

三、项目架构

3.1 页面图

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

3.2 功能图

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

3.3 部署图

天天生鲜Django项目_第5张图片

3.4 项目架构

天天生鲜Django项目_第6张图片

3.5 数据库设计

天天生鲜Django项目_第7张图片
SKU和SPU概念
SPU = Standard Product Unit (标准产品单位)
SPU 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述 了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个 SPU。
例如:iphone7 就是一个 SPU,与商家,与颜色、款式、套餐都无关。

SKU=stock keeping unit(库存量单位)
SKU 即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU 是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。 在服装、鞋类商品中使用最多最普遍。
例如:纺织品中一个 SKU 通常表示:规格、颜色、款式。

天天生鲜Django项目_第8张图片

四、初始化

项目配置

django-admin startproject dailyfresh
修改DATABASES配置

__init__.py
import pymysql
pymysql.install_as_MySQLdb()
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': "fruit",
        'USER':'root',
        'PASSWORD':'123',
        'HOST':'localhost',
        'PORT':3306,
    }
}
mysql> create database fruit

创建应用:执行< python manage.py startapp goods> 执行manage.py这个文件 startapp 表示创建应用 goods表示应用名

激活应用:在settings.py文件中,将goods应用加入 到INSTALLED_APPS选项中

模板:在目录下创建templates文件夹,在setting.py进行路径设置

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')],
    ...
    }
    ...
]

LANGUAGE_CODE = 'en-zh-hans'
TIME_ZONE = 'Asia/Shanghai'

两个url
project的urls.py

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    url('', include('goods.urls')),
]

goods的urls.py


在数据库中生成数据表:
生成迁移文件

python manage.py makemigrations

执行迁移文件

python manage.py migrate

富文本

1)安装

pip install django-tinymce==2.6.0

2)在test/settings.py为INSTALLED_APPS添加编辑器应用。

INSTALLED_APPS = (
	...
	'tinymce'
)

3)富文本编辑器配置在test/settings.py

TINYMCE_DEFAULT_CONFIG = {
    'theme':'advanced',
    'width':600,
    'height':400,
}

4)在test/urls.py中配置编辑器url

urlpatterns = [
    url('index/', views.index),
    url(r'^tinymce/', include('tinymce.urls')),  # 富文本编辑器
    url(r'^', include('goods.urls')),
]

在Admin中使用

1)在goods/models.py中,定义模型的属性为HTMLField()类型。

from django.db import models
from tinymce.models import HTMLField

class GoodsInfo(models.Model):
	gcontent=HTMLField()

2)在goods/admin.py

from django.contrib import admin
from goods.models import GoodsInfo

# Register your models here.
admin.site.register(GoodsInfo)

3)创建超级管理员用户

C:\Users\acer\Desktop\Fruit>python manage.py createsuperuser

Username (leave blank to use 'acer'): admin
Email address: admin@qq.com
Password:
Password (again):
Superuser created successfully.

4)运行

C:\Users\acer\Desktop\Fruit>python manage.py runserver

url访问:127.0.0.1:8000/admin/

五、项目开始

项目框架搭建

settings.py 指定相对路径

import sys
sys.path.insert(0,os.path.join(BASE_DIR,'apps'))

创建

django-admin startproject dailyfresh
python manage.py startapp user
python manage.py startapp goods
python manage.py startapp cart
python manage.py startapp order

dailyfresh/settings.py,这里不用apps.user,因为指定了path

INSTALLED_APPS = (
    'user', # 用户模块
    'goods', # 商品模块
    'cart', # 购物车模块
    'order', # 订单模块
)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')],


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dailyfresh',
        'USER': 'root',
        'PASSWORD': '123',
        'HOST': 'localhost',
        'PORT': 3306,
    }
}

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

dailyfresh/__init__.py

import pymysql
pymysql.install_as_MySQLdb()

dailyfresh/urls.py

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^user/', include('user.urls', namespace='user')),   # 用户模块
    url(r'^cart/', include('cart.urls', namespace='cart')),   # 购物车模块
    url(r'^order/', include('order.urls', namespace='order')), # 订单模块
    url(r'^', include('goods.urls', namespace='goods')),  # 商品模块
]

namespace反向解析,动态生成路径
把这个urls.py拷贝到各个模块
from django.conf.urls import url
urlpatterns = [

]

建立db文件夹,base_model.py

from django.db import models

class BaseModel(models.Model):
    """模型抽象基类
        使所有模型类继承这些字段"""
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    is_delete = models.BooleanField(default=False, verbose_name='删除标记')

    class Meta:
        # 说明是一个抽象模型类
        abstract = True

dailyfresh/settings.py,富文本编辑器

INSTALLED_APPS = (
    'tinymce', # 富文本编辑器
)

# 富文本编辑器配置
TINYMCE_DEFAULT_CONFIG = {
    'theme': 'advanced',
    'width':600,
    'height':400,
}

dailyfresh/urls.py,富文本编辑器

urlpatterns = [
    url(r'^tinymce/',include('tinymce.urls')),  # 富文本编辑器

apps/goods/models.py

from django.db import models
from db.base_model import BaseModel
from tinymce.models import HTMLField
# Create your models here.


class GoodsType(BaseModel):
    '''商品类型模型类'''
    name = models.CharField(max_length=20, verbose_name='种类名称')
    logo = models.CharField(max_length=20, verbose_name='标识')
    image = models.ImageField(upload_to='type', verbose_name='商品类型图片')

    class Meta:
        db_table = 'df_goods_type'
        verbose_name = '商品种类'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class GoodsSKU(BaseModel):
    '''商品SKU模型类'''
    status_choices = (
        (0, '下线'),
        (1, '上线'),
    )
    type = models.ForeignKey('GoodsType', verbose_name='商品种类')
    goods = models.ForeignKey('Goods', verbose_name='商品SPU')
    name = models.CharField(max_length=20, verbose_name='商品名称')
    desc = models.CharField(max_length=256, verbose_name='商品简介')
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格')
    unite = models.CharField(max_length=20, verbose_name='商品单位')
    image = models.ImageField(upload_to='goods', verbose_name='商品图片')
    stock = models.IntegerField(default=1, verbose_name='商品库存')
    sales = models.IntegerField(default=0, verbose_name='商品销量')
    status = models.SmallIntegerField(default=1, choices=status_choices, verbose_name='商品状态')

    class Meta:
        db_table = 'df_goods_sku'
        verbose_name = '商品'
        verbose_name_plural = verbose_name


class Goods(BaseModel):
    '''商品SPU模型类'''
    name = models.CharField(max_length=20, verbose_name='商品SPU名称')
    # 富文本类型:带有格式的文本
    detail = HTMLField(blank=True, verbose_name='商品详情')

    class Meta:
        db_table = 'df_goods'
        verbose_name = '商品SPU'
        verbose_name_plural = verbose_name


class GoodsImage(BaseModel):
    '''商品图片模型类'''
    sku = models.ForeignKey('GoodsSKU', verbose_name='商品')
    image = models.ImageField(upload_to='goods', verbose_name='图片路径')

    class Meta:
        db_table = 'df_goods_image'
        verbose_name = '商品图片'
        verbose_name_plural = verbose_name


class IndexGoodsBanner(BaseModel):
    '''首页轮播商品展示模型类'''
    sku = models.ForeignKey('GoodsSKU', verbose_name='商品')
    image = models.ImageField(upload_to='banner', verbose_name='图片')
    index = models.SmallIntegerField(default=0, verbose_name='展示顺序')

    class Meta:
        db_table = 'df_index_banner'
        verbose_name = '首页轮播商品'
        verbose_name_plural = verbose_name


class IndexTypeGoodsBanner(BaseModel):
    '''首页分类商品展示模型类'''
    DISPLAY_TYPE_CHOICES = (
        (0, "标题"),
        (1, "图片")
    )

    type = models.ForeignKey('GoodsType', verbose_name='商品类型')
    sku = models.ForeignKey('GoodsSKU', verbose_name='商品SKU')
    display_type = models.SmallIntegerField(default=1, choices=DISPLAY_TYPE_CHOICES, verbose_name='展示类型')
    index = models.SmallIntegerField(default=0, verbose_name='展示顺序')

    class Meta:
        db_table = 'df_index_type_goods'
        verbose_name = "主页分类展示商品"
        verbose_name_plural = verbose_name


class IndexPromotionBanner(BaseModel):
    '''首页促销活动模型类'''
    name = models.CharField(max_length=20, verbose_name='活动名称')
    url = models.URLField(verbose_name='活动链接')
    image = models.ImageField(upload_to='banner', verbose_name='活动图片')
    index = models.SmallIntegerField(default=0, verbose_name='展示顺序')

    class Meta:
        db_table = 'df_index_promotion'
        verbose_name = "主页促销活动"
        verbose_name_plural = verbose_name

dailyfresh/settings.py

# django认证系统使用的模型类,替换默认的auth_user。
AUTH_USER_MODEL = 'user.User'

后面如果进行 python manage.py createsuperuser 则创建在user.User

创建数据库
create database dailyfresh;

生成迁移文件
python manage.py makemigrations
天天生鲜Django项目_第9张图片

执行迁移
python manage.py migrate
天天生鲜Django项目_第10张图片
天天生鲜Django项目_第11张图片
auth_user已经没有了,变成了df_user

启动
python rmanage.py runserver

 

六、用户模块开发

6.1 用户注册

把register.html复制到templates目录下,然后将该目录下的路径改为{% load staticfiles %}
user/views.py

# /user/register
def register(request):
    '''显示注册页面'''
    return render(request, 'register.html')

user/urls.py

from django.conf.urls import url
from user import views

urlpatterns = [
    url(r'^register$', views.register, name='register'), # 注册
]

天天生鲜Django项目_第12张图片
register.html 点击注册跳转到/user/register_handl

<form method="post" action="/user/register_handle">
	{% csrf_token %}

user/urls.py

urlpatterns = [
    url(r'^register$', views.register, name='register'), # 注册
    url(r'^register_handle$', views.register_handle, name='register_handle'), # 注册处理
]

user/views.py

from django.shortcuts import render,redirect
from django.core.urlresolvers import reverse
import re
from user.models import User
# Create your views here.

# /user/register
def register(request):
    '''显示注册页面'''
    return render(request, 'register.html')

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: # get抛出的异常
        # 用户名不存在
        user = None

    if user:
        # 用户名已存在
        return render(request,'register.html',{'errmsg':'用户名已存在'})


    # 进行业务处理:进行用户注册,保存到数据库
    user = User()
    # user.username = username
    # user.password = password
    # user.save()
    # 不用上面这么繁杂,可以使用Django的认证系统,语法
    user = User.objects.create_user(username, email, password)
    user.is_active = 0  # 用户未激活
    user.save()

    # 返回应答,跳转到首页
    # reverse是反向解析函数
    return redirect(reverse('goods:index'))  # /goods/urls.py的name=index

goods/urls.py

from django.conf.urls import url
from goods import views

urlpatterns = [
    url(r'^$', views.index, name='index'), # 首页
]

goods/views.py

from django.shortcuts import render

# Create your views here.
def index(request):
    '''首页'''
    return render(request,'index.html')

如何让显示注册页面和注册处理使用同一个url地址又区分?
普通点击是get请求
表单提交是post请求
通过判断请求方式
templates/register.html

<div class="reg_form clearfix">
	<form method="post" action="/user/register">
		{% csrf_token %}

user/views.py

from django.shortcuts import render,redirect
from django.core.urlresolvers import reverse
import re
from user.models import User
# Create your views here.

# /user/register
def register(request):
    '''注册'''
    if request.method == 'GET':
        # 显示注册页面
        return render(request, 'register.html')

    else:
        # 进行注册处理
        # 接收数据
        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:  # get抛出的异常
            # 用户名不存在
            user = None

        if user:
            # 用户名已存在
            return render(request, 'register.html', {'errmsg': '用户名已存在'})

        # 进行业务处理:进行用户注册,保存到数据库
        user = User()
        # user.username = username
        # user.password = password
        # user.save()
        # 不用上面这么繁杂,可以使用Django的认证系统,语法
        user = User.objects.create_user(username, email, password)
        user.is_active = 0  # 用户未激活
        user.save()

        # 返回应答,跳转到首页
        # reverse是反向解析函数
        return redirect(reverse('goods:index'))  # /goods/urls.py的name=index

类视图 class
可以有效的提高代码复用,因为类是可以被继承的,假如我们已经写好了一个基于类的通用视图,要对其拓展功能,只需继承原本这个类视图即可。基于类的视图使用Python对象实现视图,它提供除函数视图之外的另一种方式。它们不替换基于函数的视图,但与基于函数的视图相比具有一定的区别和优势
天天生鲜Django项目_第13张图片
天天生鲜Django项目_第14张图片

user/views.py

from django.views.generic import View
class RegisterView(View):
    '''注册'''
    def get(self, request):
        '''显示注册页面'''
        return render(request,'register.html')
    def post(self, request):
        '''进行注册处理'''
        # 进行注册处理
        # 接收数据
        username = request.POST.get('user_name')
    	....

user/urls.py

from user.views import RegisterView
urlpatterns = [
    url(r'^register$',RegisterView.as_view(), name='register')
]

生成激活用户token,给注册邮箱发邮件,用户点击链接激活

pip install itsdangerous

user/views.py

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings
from django.core.mail import send_mail
SECRET_KEY 是 settings.py中django内部的,直接借用这个。 也可以自己设置的
        # 发送激活邮件,包含激活链接:http://127.0.0.1:8000/user/active/user.id
        # 激活链接中需要包含用户的身份信息,根据用户身份信息加密,id + token 加密
        # itsdangerous,基于Django签名模块,还可以设置过期时间
        # 加密用户的身份信息,生成激活token
        serializer = Serializer(settings.SECRET_KEY, 600)
        info = {'confirm':user.id}
        token = serializer.dumps(info)  # 返回的信息是Bytes字节流数据,要解码
        token = token.decode('utf8')

        # 发邮件,内置 django.core.mail
        subject = '天天生鲜欢迎━(*`∀´*)ノ亻!'
        message = ''
        sender = settings.EMAIL_FROM
        receiver = [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) # 第五个参数是html_message 已经指定了。 # 返回应答,跳转到首页 reverse是反向解析函数 return redirect(reverse('goods:index')) # /goods/urls.py的name=index

邮件在settings.py中的配置

# 发送邮件配置
# 发送邮件配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# 发邮件的smpt服务地址
EMAIL_HOST = 'smtp.qq.com'
EMAIL_PORT = 25
# 发送邮件的邮箱
EMAIL_HOST_USER = '[email protected]'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'ffffffffxxxasdasdasda'
# 收件人看到的发件人
EMAIL_FROM = '天天生鲜<[email protected]>'

用户激活注册
user/urls.py

from user.views import ActiveView
urlpatterns = [
    url(r'^active/(?P.*)$', ActiveView.as_view(), name='acitve'), # 用户激活
    # 用<>包裹视图函数中的参数
    url(r'^login$', LoginView.as_view(), name='login'), # 登录
]

user/views.py

from django.http import HttpResponse
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from itsdangerous import SignatureExpired

# 用户注册激活
class ActiveView(View):
    '''用户激活'''
    def get(self, request, token):
        '''进行用户激活'''
        # 进行解密,获取要激活的用户信息
        serializer = Serializer(settings.SECRET_KEY, 600)
        try:
            info = serializer.loads(token)
            # 获取待激活用户的id
            user_id = info['confirm']

            # 根据id获取用户信息
            user = User.objects.get(id=user_id)
            user.is_active = 1
            user.save()

            # 跳转到登录页面 login.html
            return redirect(reverse('user:login'))
        except SignatureExpired as e:
            # 激活链接已过期
            return HttpResponse('激活链接已过期,再次点击发送激活链接')

 
用户注册流程小结
用户点击注册——》调用RegisterView,接收数据,校验——》校验通过,写入到数据库——》发送用户激活邮件(用户id进行加密),加密后的数据是字节流,先进行解码再发邮件链接,如果邮件内容含有HTML参数的话就要放在send_mail()的第五个参数那传参——》urls.py中active/(?P)$获取token信息,调用ActiveView,修改数据库is_active=1代表激活 ——》激活成功跳转到登录页

send_mail是阻塞执行,会造成用户长时间等待
天天生鲜Django项目_第15张图片

 
优化:使用celery异步发邮件
天天生鲜Django项目_第16张图片
Python Celery介绍
Celery是一个功能完备即插即用的任务队列。它使得我们不需要考虑复杂的问题,使用非常简单。
celery看起来似乎很庞大,本章节我们先对其进行简单的了解,然后再去学习其他一些高级特性。
celery适用异步处理问题,当发送邮件、或者文件上传,图像处理等等一些比较耗时的操作,我们可将其异步执行,这样用户不需要等待很久,提高用户体验。celery的特点是:

  • 简单,易于使用和维护,有丰富的文档。
  • 高效,单个celery进程每分钟可以处理数百万个任务。
  • 灵活,celery中几乎每个部分都可以自定义扩展。

celery非常易于集成到一些web开发框架中。
天天生鲜Django项目_第17张图片
任务队列(Broker),使用RabbitMQ或Redis。
天天生鲜Django项目_第18张图片
Windows安装redis:https://www.runoob.com/redis/redis-install.html
天天生鲜Django项目_第19张图片

celery_task\task.py

# 使用celery
from celery import Celery
from django.conf import settings
from django.core.mail import send_mail
import time

# 创建一个Celery类的实例对象
app = Celery('celery_task.tasks', broker='redis://127.0.0.1:6379/8')

# 定义任务函数,使用task方法进行装饰
@app.task
def send_register_active_mail(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) # 第五个参数是html_message 已经指定了。

user\views.py
任务发出者

from celery_task.tasks import send_register_active_mail
class RegisterView(View):
	def post(self, request):
		......
        # 发邮件,经过celery.task装饰后,就可以直接使用delay函数把任务放到任务队列
        send_register_active_mail.delay(email, username, token)

任务队列 broker

app = Celery('celery_task.tasks', broker='redis://127.0.0.1:6379/8')

任务处理者 worker
\dailyfresh\celery_task\task.py

Windows 下 Pycharm django启动的话是不用加下面这几句的,因为已经在wsgi.py初始化了
针对的是放在Linux下,表示django环境初始化
# django环境的初始化
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
django.setup()

Windows终端界面在这个project下的celery_task开启celery的worker作为后台接受任务:

\dailyfresh\celery_task>celery -A tasks worker -l info   
单独启动celery,并没有启动django项目。加上面的初始化后就会启动了

Linux的话要把项目拉过去,然后pip install celery

——————————————————————————————————————
Windows下,启动worker,broker
borker
天天生鲜Django项目_第20张图片
worker
天天生鲜Django项目_第21张图片——————————————————————————————————————

celery4不再支持windows,所以在Windows环境下使用请选择celery==3.1.18
报keyerror错误,搞了半天都没弄出来,卸载重装
天天生鲜Django项目_第22张图片

重新安装pip install celery==3.1.18,启动项目,启动worker

from kombu.async.timer import Entry, Timer as Schedule, to_timestamp, logger
                   ^
SyntaxError: invalid syntax
报错。太难了

https://blog.csdn.net/lelemom/article/details/87810038

天天生鲜Django项目_第23张图片
降低版本到python3.5

celery使用流程
任务发出者发出任务——》任务放到任务队列——《任务处理器监听,收到任务,执行处理者也需要任务的代码
步骤:
celery_tasks文件夹下tasks.py

  • 导入from celery impor Celery
  • 创建一个Celery(名字,中间人)实例对象,名字最好写导包的路径
  • def 发邮件

user文件夹下views.py

  • 发邮件,经过celery.task装饰后,就可以直接使用delay函数把任务 加入到任务队列
    send_register_active_mail.delay(email, username, token)

执行任务初始化tasks.py

import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
django.setup()

部署在Linux上

  • 安装redis
    https://www.cnblogs.com/autohome7390/p/6433956.html

  • 安装python3
    python虚拟环境
    https://blog.csdn.net/bailiangcn/article/details/104644258 安装Python3后,默认python指2,python3指3
    简单介绍python虚拟环境 virtualenv的使用 | 《Linux就该这么学》
    https://www.linuxprobe.com/python-virtualenv-linux.html
    python指python3,python2指python2
    https://blog.csdn.net/haifeng10001/article/details/105170727

  • Linux启动

[root@aliyun dailyfresh]# celery -A celery_task.tasks worker -l info

https://blog.csdn.net/lpw_cn/article/details/103978909
彻底解决django 2.2与mysql兼容性问题(不用改源码)

————————————分割————————————————
继续往下走吧

 

6.2 用户登录

Redis存储Session

user\views.py

class LoginView(View):
    '''登录'''
    def get(self, request):
        '''显示登录页面'''
        return render(request, 'login.html')
    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':'数据不完整'})

        # 业务处理:登录校验
        # User.objects.get(username=username, password=password)
        # 自动密码加密对比
        user = authenticate(username=username, password=password)
        if user is not None:
            # 用户名已激活
            if user.is_active:
                # 用户已激活
                # 保存记录用户的登陆状态
                login(request, user) # login()使用Django的session框架来讲用户的ID保存在session中

①使用Redis存储Session
1)安装包

pip install django-redis-sessions==0.5.6
pip install django-redis-sessions

2)修改settings文件

SESSION_ENGINE ='redis_sessions.session' #配置作为redis存储
SESSION_REDIS_HOST = 'localhost' #配置主机地址
SESSION_REDIS_PORT = 6379 # 端口
SESSION_REDIS_DB = 0 # 数据库
SESSION_REDIS_PASSWORD = '' # 密码 没有可以为空
SESSION_REDIS_PREFIX = 'session' #唯一的标识码

②django-redis,pip install django-redis
settings.py
使Django支持Redis cache/session 后端的全功能组件
django-redis 中文文档

为了使用 django-redis , 你应该将你的 django cache setting 改成这样:
# 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"

我用②方法,将session信息存到redis数据库中。配置Redis作为缓存和session存储。
天天生鲜Django项目_第24张图片

记住用户名

login.html

<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">
<div class="user_error">输入错误</div>
<input type="checkbox" name="remember" {{ checked }}>

views.py

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':'数据不完整'})

        # 业务处理:登录校验
        # User.objects.get(username=username, password=password)
        # 自动密码加密对比
        user = authenticate(username=username, password=password)
        if user is not None:
            # 用户名已激活
            if user.is_active:
                # 用户已激活
                # 保存记录用户的登陆状态
                login(request, user) # login()使用Django的session框架来讲用户的ID保存在session中

                # 获取登录后所要跳转到的地址
                # 默认跳转到首页
                next_url = request.GET.get('next', reverse('goods:index'))  # None

                # 跳转到首页
                response = redirect(next_url)

                # 判断是否需要记录用户名
                remember = request.POST.get('remember')
                if remember == 'on':
                    # 记住用户名
                    response.set_cookie('username', username, max_age=7 * 24 * 3600)
                else:
                    response.delete_cookie('username')

                return response
            else:
                # 用户未激活
                return render(request, 'login.html', {'errmsg': '账户未激活'})
        else:
            # 用户名或密码错误
            return render(request, 'login.html', {'errmsg': '用户名或密码错误'})
        # 返回应答

记住用户名和协议,点击登录后,重新加载user/login
天天生鲜Django项目_第25张图片

父模板页抽象

不同的地方预留个块 {% block xxx %}{% endblock xxx %},有些有,有些无的也预留一个块。
可以重写继承后的
base.html、base_detail_list.html、base_no_cart.html、base_user_center.html

base.html

{# 首页 注册 登录 #}

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
{% load staticfiles %}
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	{# 网页标题内容块 #}
	<title>{% block title %}{% endblock title %}title>
	<link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
	{# 网页顶部引入文件块 #}
	{% block topfiles %}{% endblock topfiles %}

head>
<body>
{# 网页顶部欢迎信息块 #}
{% block header_con %}
	<div class="header_con">
		<div class="header">
			<div class="welcome fl">欢迎来到天天生鲜!div>
			<div class="fr">
				<div class="login_info fl">
					欢迎您:<em>彬 Godem>
				div>
				<div class="login_btn fl">
					<a href="login.html">登录a>
					<span>|span>
					<a href="register.html">注册a>
				div>
				<div class="user_link fl">
					<span>|span>
					<a href="user_center_info.html">用户中心a>
					<span>|span>
					<a href="cart.html">我的购物车a>
					<span>|span>
					<a href="user_center_order.html">我的订单a>
				div>
			div>
		div>		
	div>
{% endblock header_con %}

{# 网页顶部搜索框块 #}
{% block search_bar %}
	<div class="search_bar clearfix">
		<a href="index.html" class="logo fl"><img src="{% static 'images/logo.png' %}">a>
		<div class="search_con fl">
			<input type="text" class="input_text fl" name="" placeholder="搜索商品">
			<input type="button" class="input_btn fr" name="" value="搜索">
		div>
		<div class="guest_cart fr">
			<a href="#" class="cart_name fl">我的购物车a>
			<div class="goods_count fl" id="show_count">1div>
		div>
	div>
{% endblock search_bar %}

{# 网站主题内容块 #}
{% block body %}{% endblock body %}

	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们a>
			<span>|span>
			<a href="#">联系我们a>
			<span>|span>
			<a href="#">招聘人才a>
			<span>|span>
			<a href="#">友情链接a>		
		div>
		<p>CopyRight © 2020 CN理工水果铺 All Rights Reservedp>
		<p>电话:1xxxxxxx   粤ICP备号-1p>
	div>

	{# 网页底部HTML元素块 #}
	{% block bottom %}{% endblock bottom %}
	
	{# 网页底部引入文件块 #}
	{% block bottomfiles %}{% endblock bottomfiles %}
body>
html>

login.html

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}天天生鲜-登录{% endblock title %}
{% block header_con %}{% endblock header_con %}
{% block search_bar %}{% endblock search_bar %}
{% block body %}
	<div class="login_top clearfix">
		<a href="index.html" class="login_logo"><img src="{% static 'images/logo02.png' %}">a>
	div>

	<div class="login_form_bg">
		<div class="login_form_wrap clearfix">
			<div class="login_banner fl">div>
			<div class="slogan fl">日夜兼程 · 急速送达div>
			<div class="login_form fr">
				<div class="login_title clearfix">
					<h1>用户登录h1>
					<a href="user/register">立即注册a>
				div>
				<div class="form_input">
					<form method="post">
						{% csrf_token %}
						<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">
						<div class="user_error">输入错误div>
						<input type="password" name="pwd" class="pass_input" placeholder="请输入密码">
						<div class="pwd_error">输入错误div>
						<div class="more_input clearfix">
							<input type="checkbox" name="remember" {{ checked }}>
							<label>记住用户名label>
							<a href="#">忘记密码a>
						div>
						<input type="submit" name="" value="登录" class="input_submit">
					form>
				div>
			div>
		div>
	div>
	{{ errmsg }}
{% endblock body %}

register.html

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}天天生鲜-注册{% endblock title %}
{% block topfiles %}
	<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}">script>
	<script type="text/javascript" src="{% static 'js/register.js' %}">script>
{% endblock topfiles %}


{% block header_con %}{% endblock header_con %}
{% block search_bar %}{% endblock search_bar %}
{% block body %}
	<div class="register_con">
		<div class="l_con fl">
			<a class="reg_logo"><img src="{% static 'images/logo02.png' %}">a>
			<div class="reg_slogan">足不出户  ·  新鲜每一天div>
			<div class="reg_banner">div>
		div>

		<div class="r_con fr">
			<div class="reg_title clearfix">
				<h1>用户注册h1>
				<a href="login">登录a>
			div>
			<div class="reg_form clearfix">
				<form method="post" action="/user/register">
					{% csrf_token %}
				<ul>
					<li>
						<label>用户名:label>
						<input type="text" name="user_name" id="user_name">
						<span class="error_tip">提示信息span>
					li>					
					<li>
						<label>密码:label>
						<input type="password" name="pwd" id="pwd">
						<span class="error_tip">提示信息span>
					li>
					<li>
						<label>确认密码:label>
						<input type="password" name="cpwd" id="cpwd">
						<span class="error_tip">提示信息span>
					li>
					<li>
						<label>邮箱:label>
						<input type="text" name="email" id="email">
						<span class="error_tip">提示信息span>
					li>
					<li class="agreement">
						<input type="checkbox" name="allow" id="allow" checked="checked">
						<label>同意”天天生鲜用户使用协议“label>
						<span class="error_tip2">提示信息span>
					li>
					<li class="reg_sub">
						<input type="submit" value="注 册" name="">
					li>
				ul>				
				form>
			div>
		div>
	div>
{% endblock body %}

6.3 用户中心三页面

template\base_user_center.html

反向解析,动态生成
先写dailyfress\urls.py的namespace
再冒号
再写对应配置项user\urls.py的name
如:user:user

{# 用户中心3页面 #}
{% extends 'base_no_cart' %}
{% block title %}天天生鲜-用户中心{% endblock title %}
{% block page_title %}用户中心{% endblock page_title %}
{% block body %}
	<div class="main_con clearfix">
		<div class="left_menu_con clearfix">
			<h3>用户中心h3>
			<ul>
				<li><a href="{% url 'user:user' %}" class="active">· 个人信息a>li>
				<li><a href="{% url 'user:order' %}">· 全部订单a>li>
				<li><a href="{% url 'user:address' %}">· 收货地址a>li>
			ul>
		div>
        {# 用户中心右侧内容块 #}
        {% block right_content %}{% endblock right_content %}
    div>>
{% endblock body %}

template\user_center_info.html

{% extends 'base_user_center.html' %}
{% block right_content %}
		
{% endblock right_content %}

user\views.py

# /user
class UserInfoView(View):
    '''用户中心-信息页'''
    def get(self, request):
        '''显示'''
        return render(request, 'user_center_info.html')

# /user/order
class UserOrderView(View):...

# /user/address
class AddressView(View):...

user\urls.py

from user.views import UserInfoView, UserOrderView, AddressView
urlpatterns = [
	url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
    url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
    url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
]

小细节:用户信息页点击变黄,传参
天天生鲜Django项目_第26张图片

user\views.py

class UserInfoView(View):
    '''用户中心-信息页'''
    def get(self, request):
        '''显示'''
        # page='user'
        return render(request, 'user_center_info.html', {'page':'user'})

base_user_center.html

		<div class="left_menu_con clearfix">
			<h3>用户中心h3>
			<ul>
				<li>· 个人信息a>li>
				<li>· 全部订单a>li>
				<li>· 收货地址a>li>
			ul>
		div>

6.4 登录后页面跳转

登录装饰器和登录后页面跳转
用户登录后才能访问用户中心,先进行登录判断
天天生鲜Django项目_第27张图片
天天生鲜Django项目_第28张图片
user\urls.py

from django.contrib.auth.decorators import login_required
urlpatterns = [
 url(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页
]

login_required(UserInfoView.as_view())
调用login_required的返回值
如果用户没有登录是不会调用这个函数,会直接重定向到settings.LOGIN_URL
如果用户登录,则会调用这个函数,执行视图
浏览器访问测试:127.0.0.1:8000/user
天天生鲜Django项目_第29张图片但默认跳转的页面accounts/login不符合预期,需要自定义项目地址
settings.py。

# 配置登录url地址
LOGIN_URL = '/user/login'

还有后面的?next=/user/,因为login.html


没有设置表单action,提交表单时,会向浏览器地址栏中的地址提交数据
views.py

class LoginView(View):
# 获取登录后所要跳转到的地址, ?问号后面的是get请求参数
# 默认跳转到首页
# 如果有next值会进行返回,如果获取不到值,则返回reverse('goods:index')
	next_url = request.GET.get('next', reverse('goods:index')) # None
	response = redirect(next_url)

 
LoginRequiredMixin类

天天生鲜Django项目_第30张图片
天天生鲜Django项目_第31张图片
 
utils/mixin.py

from django.contrib.auth.decorators import login_required

class LoginRequireMixin(object):
    @classmethod
    def as_view(cls, **initkwargs):
        # 调用父类的as_view
        view = super(LoginReuireMixin, cls).as_view(**initkwargs)
        return login_required(view)

原本:user/views.py

# /user
class UserInfoView(View):
    '''用户中心-信息页'''
    def get(self, request):
        '''显示'''
        # page='user'
        return render(request, 'user_center_info.html', {'page':'user'})

修改后:

from utils.mixin import LoginReuireMixin
# /user
class UserInfoView(LoginRequireMixin, View):
    '''用户中心-信息页'''
    def get(self, request):
        '''显示'''
        # page='user'
        return render(request, 'user_center_info.html', {'page':'user'})

# /user/order
class UserOrderView(LoginRequireMixin, View):
    '''用户中心-订单页'''
    def get(self, request):
        '''显示'''
        # page='order'
        return render(request, 'user_center_order.html', {'page':'order'})

# /user/address
class AddressView(LoginRequireMixin, View):
    '''用户中心-地址页'''
    def get(self, request):
        '''显示'''
        # page='address'
        return render(request, 'user_center_site.html', {'page':'address'})

user/urls.py

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

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

Mixin原理:输入127.0.0.1:8000/user/,匹配url(r’^$’, UserInfoView.as_view(), name=‘user’), 调用UserInfoView的父类,但现在继承了两个父类class UserInfoView(LoginReuireMixin, View):,从前面开始找,相当于调用LoginReuireMixin的as_view,as_view函数又调用了里面的as_view。 实际就是调用View里面的as_view,然后封装。

这样写的好处:没必要login_required,还有继承

6.5 登录后欢迎信息

右上角:有登录显示欢迎您,没有的话就显示登录和注册
利用Django的认证系统
天天生鲜Django项目_第32张图片
天天生鲜Django项目_第33张图片

如果是User实例的话,返回的是True,代表登录。如果是AnoymounsUser,返回的是False,代表没登录。

user/views.py

# /user
class UserInfoView(LoginReuireMixin, View):
    '''用户中心-信息页'''
    def get(self, request):
        '''显示'''
        # page='user'
        # request.user
        # 如果用户未登录——》AnonymousUser类的一个实例
        # 如果用户登录——》User类的一个实例
        # .is_authenticated()
        # 除了你给模板文件传递的模板变量之外,django框架会把request.user也传给模板文件
        # 因此在HTML页面可以直接使用 user,调用user的方法
        return render(request, 'user_center_info.html', {'page':'user'}

base.html

<body>
{# 网页顶部欢迎信息块 #}
{% block header_con %}
	<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>
				div>
				{% else %}
				<div class="login_btn fl">
					<a href="login.html">登录a>
					<span>|span>
					<a href="register.html">注册a>
				div>
				{% endif %}

在这里插入图片描述

6.6 用户退出登录

天天生鲜Django项目_第34张图片

base.html

反向解析,动态生成
先写dailyfress\urls.py的namespace
再冒号
再写对应配置项user\urls.py的name
如:user:user
<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 %}

views.py
退出视图

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

user/urls.py

url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销登录

在这里插入图片描述
登录判断__欢迎信息__退出登录小结:
使用内置的登录认证模块login_require 进行判断。
记录状态是login(request,user)
通过login_require,如果登陆的话会执行相应的视图。如果没登录的话就会跳转到登录页面,会直接重定向到settings.LOGIN_URL
跳转的页面默认是在settings.py,默认是/accounts/login,已经改为了/user/login

还有后面的?next=/user/,因为login.html


没有设置表单action,提交表单时,会向浏览器地址栏中的地址提交数据

认证登录,判断激活状态,记录登录状态

然后优化了urls.py中的login_require,进行了封装。然后在需要用户登录之后才能访问的视图里就要继承,顺序要一致
class UserInfoView(LoginRequireMixin, View)。
先调用LoginRequireMixin的as_view,然而在as_view里又调用了as_view,那么就会去找View里面的as_view方法,返回包装后的,因此直接在urls.py不用写login_require
天天生鲜Django项目_第35张图片

# request.user
# 如果用户未登录——》AnonymousUser类的一个实例
# 如果用户登录——》User类的一个实例
# .is_authenticated()
# 除了你给模板文件传递的模板变量之外,django框架会把request.user也传给模板文件
# 因此在HTML模板页面可以直接使用 user,调用user的方法is_authenticated

# 退出登录直接使用内置的logout

6.7 用户中心_地址页

/user/views.py

class AddressView(LoginRequireMixin, View):
    '''用户中心-地址页'''
    def get(self, request):
        '''显示'''
        # 获取登录用户对应的User对象
        user = request.user
        # 获取用户的默认收货地址
        try:
            address = Address.objects.get(user=user, is_default=True)  # 在数据库中默认是False。如果获取到user,那么is_default为True
        except Address.DoseNotExist:
            # 不存在默认收货地址
            address = None
        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]): # zip_code在数据库中已经默认设置null
            return render(request, 'user_center_site.html', {'errmsg':'数据不完整'})
        # 校验手机号
        if not re.match(r'^1[3|4|5|7|9][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)  # 在数据库中默认是False。如果获取到user,那么is_default为True
        except Address.DoseNotExist:
            # 不存在默认收货地址
            address = None

        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请求方式。回到上面的def get

templates/user_center_site.html
action不写,直接就往当前地址进行提交

<div class="site_con">
	<dl>
		<dt>当前地址:dt>
		{% if address %}
			<dd>{{ address.addr }} ({{ address.receiver }} 收) {{ address.phone }}dd>
		{% else %}
		<dd>无默认地址dd>dd>
		{% endif %}

<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>

6.8 模型管理器类方法封装

不使用默认的object(models.Manager)
自定义管理器——封装,因为6.7的代码中重复定义了两个获取用户默认收货地址

/user/models.py

class AddressManager(models.Manager):
    """地址模型管理器类"""
    # 1. 改变原有查询的结果集:all()
    # 2. 封装方法:用户操作模型类对应的数据表(增删改查)
    def get_default_address(self, user):
        """获取用户的默认收货地址"""
        # self.model:获取self对象所在的模型类
        try:
            address = self.get(user=user, is_default=True)
            # address = self.model.objects.get(user=user, is_default=True)
        except self.model.DoesNotExist:
            # 不存在默认收获地址
            address = None

        return address

# 上面的try:address = self.model.objects.get(user=user, is_default=True)
# 调用:Address.objects.get_default_address()
# self就是objects对象,self.model获取self所在模型类,self.model相当于objects
# self.model.objects。就是重复了objects,因为self本身就是objects,self是AddressManager的对象

class Address(BaseModel):
    # 自定义一个模型管理器对象
    objects = AddressManager()

/user/views.py

# /user/address
class AddressView(LoginRequireMixin, View):
    '''用户中心-地址页'''
    def get(self, request):
        '''显示'''
        # 获取登录用户对应的User对象
        user = request.user
        # 获取用户的默认收货地址
        # try:
        #     address = Address.objects.get(user=user, is_default=True)  # 在数据库中默认是False。如果获取到user,那么is_default为True
        # except Address.DoesNotExist:
        #     # 不存在默认收货地址
        #     address = None
        address = Address.objects.get_default_address(user)
    def post(self, request):
        '''地址的添加'''
        address = Address.objects.get_default_address(user)

6.8 用户中心_个人信息页

封装后的作用:

user/views.py

class UserInfoView(LoginRequireMixin, View):
    '''用户中心-信息页'''
    def get(self, request):
        '''显示'''
        # page='user'
        # request.user
        # 如果用户未登录——》AnonymousUser类的一个实例
        # 如果用户登录——》User类的一个实例
        # .is_authenticated()

        # 获取用户的个人信息
        user = request.user
        address = Address.objects.get_default_address(user)
        # 获取用户的历史浏览记录

        # 除了你给模板文件传递的模板变量之外,django框架会把request.user也传给模板文件
        # 因此在HTML页面可以直接使用 user,调用user的方法
        return render(request, 'user_center_info.html', {'page':'user', 'address':address})

user_center_info.html

<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>

6.9 历史浏览记录

  1. 什么时候需要添加历史浏览记录
    访问商品的详情页面的时候(在商品详情对应的视图中),需要添加历史浏览记录。

  2. 什么时候需要获取历史浏览记录?
    访问用户中心个人信息页的时候获取历史浏览记录

  3. 历史浏览记录需要存储在哪里?
    redis数据库——》内存型的数据库
    使用redis数据库存储历史浏览记录。

  4. redis中存储历史浏览记录的格式?
    string
    hash
    list
    set
    zset
    存储用户的历史浏览记录时,所有用户的历史浏览记录用一条数据保存,还是每个用户的历史浏览记录用一条数据保存?

所有用户的历史浏览记录用一条数据保存 hash
hash:history:user_用户id:‘1,2,3’ , 找到用户id后还要分割

每个用户的历史浏览记录用一条数据保存 list
history_用户id:[3,2,1],第一次浏览就是1,3是最新浏览
添加历史浏览记录时,用户最新浏览的商品的id从列表左侧插入。
天天生鲜Django项目_第36张图片

  1. 获取用户历史浏览记录

Redis

/user/views.py

from django_redis import get_redis_connection
from goods.models import GoodsSKU
class UserInfoView(LoginRequireMixin, View):
    '''用户中心-信息页'''
    def get(self, request):
        '''显示'''
        # page='user'
        # request.user
        # 如果用户未登录——》AnonymousUser类的一个实例
        # 如果用户登录——》User类的一个实例
        # .is_authenticated()

        # 获取用户的个人信息
        user = request.user
        address = Address.objects.get_default_address(user)

        # 获取用户的历史浏览记录
        # Redis写法:
        # from redis import StrictRedis
        # sr = StrictRedis(host='127.0.0.1', port='6379', db=9)
        con = get_redis_connection('default') # 这default是settings.py的

        history_key = 'history_%d' % user.id
        # 获取用户最新浏览的5个商品的id,返回列表的形式
        sku_ids = con.lrange(history_key, 0, 4)

        # 从数据库中查询用户浏览的商品的具体信息
        # id__in 表示在列表内的都符合
        # mysql直接查数据是按ID查的,跟实际历史浏览情况不一样,咋办

        # 遍历获取用户浏览的商品信息
        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
        }

        # 除了你给模板文件传递的模板变量之外,django框架会把request.user也传给模板文件
        # 因此在HTML页面可以直接使用 user,调用user的方法
        return render(request, 'user_center_info.html', context)

settings.py

# 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"

user_center_info.html

				<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>

首先通过 get_redis_connection 连接到redis数据库,拼接出key,传key,取出前5个,0到4

 

七、分布式存储系统

分布式图片服务器FastDFS
1.什么是FastDFS
FastDFS是用。语言编写的一款开源手布式文件系统。FastDFS为互联网量身定制。充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS 架构包括Tracker server 和Storage server。客户端请求Tracker server 进行文件上传、下载,通过Tracker server 调度最终由Storage sever完成文件上传和下载。
Tracker server 作用是负载均衡和调度,通过Tracker servcr 在文件上伸时可以根据一费策略找到Storage swrver 提供文件上传服务。可以将tracker 称为追踪服务器或调度服务器。
Storage sever 作用是文件存储。客户端上传的文件最终存储在Storage服务器上,Su您asssG堂投有实现自己的文件系统而是利用操作系统的文件系统来管理文件,可以将storage 称为存储服务器
天天生鲜Django项目_第37张图片
默认情况下,会存储在Django服务器上。
MEDIA_ROOT=os.path.join(BASE_DIR,'static/media')

服务器资源有限,因此应该单独保存在一个文件存储服务器
对上传的文件内容进行hash(唯一:文件的指纹)保存到Storage

服务端两个角色:
Tracker:tracker 也可以实现集群。每个tracker节点地位平等。收集Storage集群的状态。”
Storage:实际保存文件Storage分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。

FastDFS的意义:海量存储,存储容量扩展方便,只需要加Storage:server,解决文件内容重复问题(相同内容的只保存一份)

7.1 文件上传流程

天天生鲜Django项目_第38张图片

客户端上传文件后存储服务器将文件ID返回给客户端,此文件ID用于以后访问该文件的索引信息。文件索引信息包括组名,虚拟磁盘路径,数据两级目录,文件名。

组名:文件上传后所在的storage组名称,在文件上传成功后有storage 服务器返网。常要客户端自行保存。

虚拟磁盘路径storage配置的虚拟路径,与磁盘选项storc_path*对应。如果配置了store_patho则是M00,如果配置了storc_pathl则是M01,以此类推。

数据两级目录storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

7.2 文件下载流程

天天生鲜Django项目_第39张图片

  1. client询问tracker下载文件的storage,参数为文件标识(卷名和文件名);
  2. tracker返回一台可用的storage;
  3. client直接和storage通讯完成文件下载。

7.3 FastDFS的安装和配置

7.3.1 安装FastDFS

解压缩
先找到这两个包
安装fastdfs依赖包
1.解压缩libfastcommon-master.zip
2.进入到libfastcommon-master的目录中
3.执行 ./make.sh
4.执行 ./make.sh install

安装fastdfs
1.解压缩fastdfs-master.zip
2.进入到fastdfs-master目录中
3.执行 ./make.sh
4.执行 ./make.sh install

error: #error port me
天天生鲜Django项目_第40张图片

7.4 配置跟踪服务器tracker

1.cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
2.在/home/python目录中创建目录 fastdfs/tracker
mkdir -p /home/python/fastdfs/tracker
3.编辑/etc/fdfs/tracker.conf配置文件 vim /etc/fdfs/tracker.conf
修改base_path=/home/python/fastdfs/tracker

7.5 配置存储服务器storage

1.cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
2.在/home/python/fastdfs/ 目录中创建storage
mkdir -p /home/python/fastdfs/storage
3.编辑/etc/fdfs/storage.conf配置文件 vim /etc/fdfs/storage.conf
修改base_path=/home/python/fastdfs/storage
store_path0=/home/python/fastdfs/storage
tracker_server=172.17.0.2:22122

7.6 启动tracker和storage

启动tracker和storage
systemctl fdfs_trackerd start
systemctl fdfs_storaged start

测试是否安装成功
1.cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
2.编辑/etc/fdfs/client.conf配置文件
base_path=/home/python/fastdfs/tracker
tracker_server=172.17.0.2:22122
3.上传文件测试
fdfs_upload_file /etc/fdfs/client.conf test.jpg
如果返回类似group1xxxxx.jpg的文件id则说明文件上传成功

7.7 安装nginx及fastdfs-nginx-module

用户量大,怎么获取里面的文件。
通过nginx配合fastdfs,提供静态文件效率高。
1.解压缩

如果想要配合fastdfs,需要安装nginx的时候多安装一个附加模块

1.解压fastdfs-nginx-module-master
cp ./fastdfs-nginx-module-master.zip /usr/local
unzip fastdfs-nginx-module-master.zip

2.解压:tar -zxvf nginx-1.18.0.tar.gz
3.进入到解压目录:cd nginx-1.18.0
4.预配置:
./configure --prefix=/usr/local/nginx\
--sbin-path=/usr/sbin/nginx\
--conf-path=/etc/nginx/nginx.conf\
--error-log-path=/var/log/nginx/error.log\
--http-log-path=/var/log/nginx/access.log\
--with-http_dav_module\
--withhttp_stub_status_module\
--with-http_addition_module\
--with-http_sub_module\
--with-http_flv_module\
--with-http_mp4_module\
--with-http_stub_status_module\
--with-http_ssl_module\
--with-http_gzip_static_module\
--add-module=../fastdfs-nginx-module-master/src

5.编译
make
6.安装
make install

7.cp fastdfs-nginx-module-master解压后的目录中src下的mod_fastdfs.conf /etc/fdfs/mod_fastdfs.conf
#cp /usr/local/app/fastdfs-nginx-module-master/src/mod_fastdfs.conf  /etc/fdfs/mod_fastdfs.conf



8.vim /etc/fdfs/mod_fastdfs.conf
修改内容:
connect_timeout=10
tracker_server=172.17.0.2:22122
url_have_group_name=true
store_path0=/home/python/fastdfs/storage

9.cp解压缩的fastdfs-master目录中的http.conf   /etc/fdfs/http.conf
cp /usr/local/app/FastDFS/conf/http.conf  /etc/fdfs/http.conf

10.cp解压缩的fastdfs-master目录中的mime.types  /etc/fdfs/mime.types
cp /usr/local/app/FastDFS/conf/mime.types  /etc/fdfs/mime.types


————————

测试

❤测试Nginx与FastDFS交互,需要的操作
1.修改如下的配置文件(在/etc/fdfs目录中)
client.conf、mod_fastdfs.conf、storage.conf
tracker_server=自己的ip地址:22122
2.启动tracker、storage、nginx服务
service fdfs_trackerd start
service fdfs_storaged start
/usr/local/nginx/sbin/nginx

3.执行如下命令测试是否成功
fdfs_upload_file /etc/fdfs/client.conf 要上传的图片文件
fdfs_upload_file /etc/fdfs/client.conf /root/timg.jpg
如果返回类似group1/M00/00/00/rBLLjV9zEqaAH7qQAAFqehPdejQ988.jpg
的文件id则说明文件上传成功
在浏览器中可以用:127.0.0.1:8888/返回文件id访问图片
IP地址/group1/M00/00/00/rBLLjV9zEqaAH7qQAAFqehPdejQ988.jpg

测试Python客户端与FastDFS,需要的操作
https://blog.z0ukun.com/?p=606
天天生鲜Django项目_第41张图片
安装遇到
error: Microsoft Visual C++ 14.0 is required. Get it with “Build Tools for Visual Studio”: https://visualstudio.microsoft.com/downloads/
直接安装VS2014,因为Python3需要VS14才能编译
天天生鲜Django项目_第42张图片
天天生鲜Django项目_第43张图片

优势:

  • 海量存储,存储容量扩展方便,文件内容重复,速度提升

django上传文件,默认保存在MEDIA_ROOT下。现在要求保存到文件存储服务器

项目中上传和使用图片的流程
天天生鲜Django项目_第44张图片
https://blog.z0ukun.com/?p=606

如何修改django的上传行为

Django二次开发对接FastDFS,编写自定义存储系统
https://yiyibooks.cn/xx/django_182/index.html
默认上传文件是使用 Django自带的FileSystemStorage类
天天生鲜Django项目_第45张图片Storage是FileSystemStorage的父类,只需要继承Storage类,然后重写里面的方法

编写自定义存储系统
https://yiyibooks.cn/xx/django_182/index.html
天天生鲜Django项目_第46张图片
把client.conf复制到 /urls/fdfs/client.conf

Django调用_save方法前会去调用exists
exists(name)[source]
如果提供的名称所引用的文件在文件系统中存在,则返回True,s则如果这个名称可用于新文件,返回False。

storage.py

from django.core.files.storage import Storage
from fdfs_client.client import Fdfs_client

class FDFSStorage(Storage):
    '''fast dfs文件存储类'''

    def  _open(name, mode='rb'):
        '''打开文件时使用'''
        pass

    def _save(self, name, content):
        '''保存文件时使用'''
        # name:你选择上传文件的名字
        # content:包含你上传文件内容的File对象

        # 创建一个Fdfs_client对象
        # 路径是相对于dailyfresh的路径
        client = Fdfs_client('./utils/fdfs/client.conf')

        # 上传文件到fast dfs系统中,写文件内容
        res = client.upload_by_buffer(content.read())

        # dict {
        #     'Group name'      : group_name,
        #     'Remote file_id'  : remote_file_id,
        #     'Status'          : 'Upload successed.',
        #     'Local file name' : '',
        #     'Uploaded size'   : upload_size,
        #     'Storage IP'      : storage_ip
        # }
        if res.get('Status') != 'Upload successed.':
            # 上传失败,抛出异常
            raise Exception('上传文件到fast dfs失败')

        # 获取返回的文件ID
        filename = res.get('Remote file_id')

        return filename

    def exists(self, name):
        '''Django判断文件名是否可用'''
        # 因为文件内容没有保存在Django服务器上,而是保存在FastDFS,因此文件名字不涉及到不可用,永远是可用的
        return False
        
    def url(self, name):
        '''返回访问文件的url路径'''
        return 'IP地址+端口/' + name

如何让django上传文件的时候调用这个FDFSStorage类?需要修改配置项
天天生鲜Django项目_第47张图片在settings.py指定storage.py

# 设置Django的文件存储类
DEFAULT_FILE_STORAGE='utils.fdfs.storage.FDFSStorage'

 
admin页面测试文件存储类
apps/goods/admin.py

from django.contrib import admin
from goods.models import GoodsType
# Register your models here.

admin.site.register(GoodsType)

访问127.0.0.1:8000/admin,点击保存。Django就会去调用上面settings所设置的DFSStorage类的save方法
天天生鲜Django项目_第48张图片
RuntimeError at /admin/goods/goodstype/add/
因为版本的问题,python用的3.7的,django用的1.8.2的最后python3.7虽然看似能用一些功能,但是还是对于大多数功能来讲不能用,最后改用python3.5的环境就好了

在Django添加或者修改类属性的时候会报错 :RuntimeError at /admin/booktest/bookinfo/add/ generator raised StopIteration
原因是python使用版本3.7 Django使用的是1.8 版本太低)
解决办法python降到3.5 或者Django升到2.2

这里我就测试不了。懒得搞了。直接开干

 

改进自定义文件存储类
(1)动态传递配置文件创建Fdfs对象
(2)url动态
https://www.bilibili.com/video/BV1vt41147K8?p=47

settings.py

# 设置fdfs使用的client.conf文件路径
FDFS_CLIENT_CONF='./utils/fdfs/client.conf'

# 设置fdfs存储服务器上nginx的IP和端口号
FDFS_URL = 'http://120.76.63.10/'
storage.py

from django.core.files.storage import Storage
from django.conf import settings
from fdfs_client.client import Fdfs_client

class FDFSStorage(Storage):
    '''fast dfs文件存储类'''
    def __init__(self, client_conf=None, base_url=None):
        '''初始化'''
        if client_conf is None:
            client_conf = settings.FDFS_CLIENT_CONF
        self.client_conf = client_conf

        if base_url is None:
            base_url = settings.FDFS_URL
        self.base_url = base_url


    def  _open(name, mode='rb'):
        '''打开文件时使用'''
        pass

    def _save(self, name, content):
        '''保存文件时使用'''
        # name:你选择上传文件的名字
        # content:包含你上传文件内容的File对象

        # 创建一个Fdfs_client对象
        # 路径是相对于dailyfresh的路径
        client = Fdfs_client(self.client_conf)

        # 上传文件到fast dfs系统中,写文件内容
        res = client.upload_by_buffer(content.read())

        # dict {
        #     'Group name'      : group_name,
        #     'Remote file_id'  : remote_file_id,
        #     'Status'          : 'Upload successed.',
        #     'Local file name' : '',
        #     'Uploaded size'   : upload_size,
        #     'Storage IP'      : storage_ip
        # }
        if res.get('Status') != 'Upload successed.':
            # 上传失败,抛出异常
            raise Exception('上传文件到fast dfs失败')

        # 获取返回的文件ID
        filename = res.get('Remote file_id')

        return filename

    def exists(self, name):
        '''Django判断文件名是否可用'''
        # 因为文件内容没有保存在Django服务器上,而是保存在FastDFS,因此文件名字不涉及到不可用,永远是可用的
        return False

    def url(self, name):
        '''返回访问文件的url路径'''
        return self.base_url + name

FastDFS上传图片小结

  • 海量存储,存储容量扩展方便。文件内容重复
  • 结合nginx提高网站提供图片的效率。

八、首页页面静态化

当大量用户访问的时候,首页应该优化设置为静态页面。而不是调用数据库得到结果。

首页页面静态化:celery定义任务函数,生成静态页面

1.celery定义任务函数,生成静态页面

什么时候首页的静态页面需要重新生成?
当管理员后台修改首页信息对应的表格中的数据的时候,需要重新生成首页静态页。

步骤:
1.在base.html基础上再创建static_base.html
不需要用户登录认证,保留需要登录和注册
天天生鲜Django项目_第49张图片
index.html基础上创建static_index.html,继承static_base.html
{% extends ‘static_base.html’ %}

启动celery worker

task.py

@app.task
def send_register_active_mail(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) # 第五个参数是html_message 已经指定了。 @app.task def generate_static_index_html(): '''产生首页静态页面''' # 获取商品的种类信息 types = GoodsType.objects.all() # 获取首页轮播商品信息 goods_banners = IndexGoodsBanner.objects.all().order_by('index') # 获取首页促销活动信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 获取首页分类商品展示信息 for type in types: # GoodsType # 获取type种类首页分类商品的图片展示信息 image_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1).order_by('index') # 获取type种类首页分类商品的文字展示信息 title_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0).order_by('index') # 动态给type增加属性,分别保存首页分类商品的图片展示信息和文字展示信息 type.image_banners = image_banners type.title_banners = title_banners context = {'types': types, 'goods_banners': goods_banners, 'promotion_banners': promotion_banners} # 设置缓存 # key value timeout cache.set('index_page_data', context, 3600) # 组织模板上下文 context.update(cart_count=cart_count) # return render(request, 'index.html', context) # render返回的是HttpReponse对象 # 使用模板 # 1.加载模板文件,返回模板对象 loader.get_template('static_index.html') # 2.模板渲染,得到渲染后的内容 static_index_html = temp.render(context) # 生成首页对应静态文件 save_path = os.path.join(settings.BASE_DIR, 'static/index.html') with open(save_path,'w') as f: f.write(static_index_html)

worker启动后,django终端发出任务

from celery_tasks.tasks import generate_static_index_html
generate_static_index.html.delay()

django终端发出任务
天天生鲜Django项目_第50张图片

worker启动,接收任务情况
在这里插入图片描述

2.nginx配置提供静态页面

天天生鲜Django项目_第51张图片

server{
	location / {
		root /home/python/Desktop/daliFresh/static/;
		index index.html
	}
}

nginx帮助提供静态页面
天天生鲜Django项目_第52张图片

3.管理员修改首页所使用表中的数据数,重新生成index静态页面

当管理员后台修改首页信息对应的表格中的数据的时候,需要重新生成首页静态页。

Admin——》ModelAdmin
admin管理更新首页数据表数据时重新生成index静态页面
admin.py,以IndexPromotionBanner为例。

from django.contrib import admin
from goods.models import GoodsType,IndexPromotionBanner
# Register your models here.

class IndexPromotionBannerAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        '''新增或更新表中的数据时调用'''
        super().save_model(request, obj, form, change) # 调用父类

        # 发出任务,让celery worker重新生成首页静态页  异步
        from celery_task.tasks import generate_static_index_html
        generate_static_index_html.delay()

    def delete_model(self, request, obj):
        '''删除表中的数据时调用'''
        super().delete_model(request, obj)
        # 发出任务,让celery worker重新生成首页静态页  异步
        from celery_task.tasks import generate_static_index_html
        generate_static_index_html.delay()


admin.site.register(GoodsType)
admin.site.register(IndexPromotionBanner,IndexPromotionBannerAdmin)
# 管理员修改表里面数据,就会调用Admin方法里的model.savemodel,重新生成页面

然而首页有这么多的models关联,所以写一个父类
admin.py

from django.contrib import admin
from goods.models import GoodsType,IndexPromotionBanner
# Register your models here.

class BaseModelAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        '''新增或更新表中的数据时调用'''
        super().save_model(request, obj, form, change) # 调用父类

        # 发出任务,让celery worker重新生成首页静态页  异步
        from celery_task.tasks import generate_static_index_html
        generate_static_index_html.delay()

    def delete_model(self, request, obj):
        '''删除表中的数据时调用'''
        super().delete_model(request, obj)
        # 发出任务,让celery worker重新生成首页静态页  异步
        from celery_task.tasks import generate_static_index_html
        generate_static_index_html.delay()

class GoodsTypeAdmin(BaseModelAdmin):
    pass

class IndexPromotionBannerAdmin(BaseModelAdmin):
    pass

class IndexTypeGoodsBannerAdmin(BaseModelAdmin):
    pass

class IndexGoodsBannerAdmin(BaseModelAdmin):
    pass


admin.site.register(GoodsType, GoodsTypeAdmin)
admin.site.register(IndexTypeGoodsBanner, IndexTypeGoodsBannerAdmin)
admin.site.register(IndexGoodsBannerAdmin, IndexGoodsBannerAdmin)
admin.site.register(IndexPromotionBanner,IndexPromotionBannerAdmin)
# 管理员修改表里面数据,就会调用Admin方法里的ModelAdmin.savemodel,重新生成页面

天天生鲜Django项目_第53张图片

4.静态页面index页面和IndexView的调度

后面做完网站,再考虑这个部署
天天生鲜Django项目_第54张图片

5.页面数据的缓存

页面数据的缓存:把页面使用的数据存放在缓存中,当再次使用这些数据时,先从缓存中获取,如果获取不到,再去查询数据库,减少数据库查询的次数。

https://yiyibooks.cn/xx/django_182/topics/cache.html
设置缓存
缓存配置是通过setting 文件的CACHES 配置来实现的。 这里有CACHES所有可配置的变量值。

底层的缓存API
django.core.cache.get_cache(backend,**kwargs)
最基本的接口是set(key,value,timeout)和get(key):

goods.views.py

from django.core.cache import cache
class IndexView(View):
    '''首页'''
    def get(self, request):
        '''显示首页'''
        # 尝试从缓存中获取数据
        context = cache.get('index_page_data')
        if context is None:
            print('设置缓存')
            # 缓存中没有数据
            # 获取商品的种类信息
            types = GoodsType.objects.all()

            # 获取首页轮播商品信息
            goods_banners = IndexGoodsBanner.objects.all().order_by('index')

            # 获取首页促销活动信息
            promotion_banners = IndexPromotionBanner.objects.all().order_by('index')

            # 获取首页分类商品展示信息
            for type in types: # GoodsType
                # 获取type种类首页分类商品的图片展示信息
                image_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1).order_by('index')
                # 获取type种类首页分类商品的文字展示信息
                title_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0).order_by('index')

                # 动态给type增加属性,分别保存首页分类商品的图片展示信息和文字展示信息
                type.image_banners = image_banners
                type.title_banners = title_banners

            context = {'types': types,
                       'goods_banners': goods_banners,
                       'promotion_banners': promotion_banners}
            # 设置缓存
            # key  value timeout
            cache.set('index_page_data', context, 3600)
            
        # 获取用户购物车中商品的数目
        user = request.user
        cart_count = 0
        if user.is_authenticated():
            # 用户已登录
            conn = get_redis_connection('default')
            cart_key = 'cart_%d'%user.id
            cart_count = conn.hlen(cart_key)

        # 组织模板上下文
        context.update(cart_count=cart_count)

        # 使用模板
        return render(request, 'index.html', context)

天天生鲜Django项目_第55张图片
缓存更新:
admin.py

class BaseModelAdmin(admin.ModelAdmin):
        # 清除首页的缓存数据
        cache.delete('index_page_data')

6.页面静态化小结

网站本身性能的优化,减少数据库查询的次数

优化网站:

  • 页面静态化
  • 页面数据的缓存

天天生鲜Django项目_第56张图片

 

九、商品模型模型类

1.首页内容获取和展示

  • 获取商品的种类信息
  • 获取首页轮播商品信息
  • 获取首页促销活动信息
  • 获取首页分类商品展示信息(遍历获取每一个对象)
  • 获取用户购物车中商品的数目

需要数据库models.py里有数据

views.py

from goods.models import GoodsType, GoodsSKU, IndexGoodsBanner,IndexPromotionBanner,IndexTypeGoodsBanner
class IndexView(View):
    '''首页'''
    def get(self, request):
    获取商品的种类信息
	types = GoodsType.objects.all()
	
    获取首页轮播商品信息
	goods_banners = IndexGoodsBanner.objects.all().order_by('index')

    获取首页促销活动信息
	promotion_banners = IndexPromotionBanner.objects.all().order_by('index')

    # 获取首页分类商品展示信息 (all的话后面要改)
	# type_goods_banners = IndexTypeGoodsBanners.objects.all()
    # 获取首页分类商品展示信息()
     for type in types: # GoodsType
         # 获取type种类首页分类商品的图片展示信息
         image_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1).order_by('index')
         # 获取type种类首页分类商品的文字展示信息
         title_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0).order_by('index')

         # 动态给type增加属性,分别保存首页分类商品的图片展示信息和文字展示信息
         type.image_banners = image_banners
         type.title_banners = title_banners
                
                
    获取用户购物车中商品的数目
    cart_count =0

	# 组织模板上下文
	context = {'types':types,
			   'goods_banners':goods_banners,
			   'promotion_banners':promotion_banners,
			  }


index.html(需要数据库models.py里有数据)

	{% for type in types %}
	<div class="list_model">
		<div class="list_title clearfix">
			<h3 class="fl" id="model{{ forloop.counter }}">{{ type.name }}h3>
			<div class="subtitle fl">
				<span>|span>
				{% for banner in type.title_banners %}
					<a href="#">{{ banner.sku.name }}a>
				{% endfor %}
			div>
			<a href="#" class="goods_more fr" id="fruit_more">查看更多 >a>
		div>

		<div class="goods_con clearfix">
			<div class="goods_banner fl"><img src="{{ type.image.url }}">div>
			<ul class="goods_list fl">
				{% for banner in type.image_banners %}
				<li>
					<h4><a href="#">{{ banner.sku.name }}a>h4>
					<a href="#"><img src="{{ banner.sku.image.url }}">a>
					<div class="prize">¥ {{ banner.sku.price }}div>
				li>
				{% endfor %}
			ul>
		div>
	div>

2.添加购物车

1.什么时候添加购物车记录?
当用户点击加入购物车时需要添加购物车记录。

2.什么时候需要获取购物车记录?
使用到购物车中数据和访问购物车页面的时候需要获取购物车记录。

3.使用什么存储购物车记录?
redis存储购物车记录

4.分析存储购物车记录的格式?
一个用户的购物车记录用户一条数据保存。
string、list、hash(属性 值)、set、zset

list方案:‘cart_用户id’:[‘1,5’,‘2,3’] 第一个id1商品5件
hash方案:‘cart_用户id’:[‘sku_id’:商品数目,‘sku_id2’:商品数目]

获取用户购物车中商品的条目数
redis的hlen命令,返回哈希表key中域的数量

goods.views.py

from django_redis import get_redis_connection
class IndexView(View):
    '''首页'''
    def get(self, request):
    	....xxxx....
        # 获取用户购物车中商品的数目
        user = request.user
        cart_count = 0
        if user.is_authenticated():
            # 用户已登录
            conn = get_redis_connection('default') # 获取链接
            cart_key = 'cart_%d'%user.id  #拼接得到key
            cart_count = conn.hlen(cart_key) #返回个数

        # 组织模板上下文
        context.update(cart_count=cart_count) #个数发给模板文件
        # 使用模板展示
        return render(request, 'index.html', context) 

base.html

<div class="goods_count fl" id="show_count">{{ cart_count }}div>

redis模拟添加购物车
天天生鲜Django项目_第57张图片

3.商品详情信息的获取和显示

detail.html、goods.views.DetailView

goods/urls.py

urlpatterns = [
    url(r'^index$', IndexView.as_view(), name='index'), # 首页
    # ?P<> 捕获。 在对应的视图,get传goods.id
    # 捕获参数,如果是按顺序来传的话,就可不写名字。如果不按顺序就必须写名字
    url(r'^goods/(?P(\d+))$', DetailView.as_view(), name='detail'),  # 详情页

views.py

# /goods/商品id
class DetailView(View):
    '''详情页'''
    def get(self, request, goods_id):
        '''显示详情页'''
        try:
            sku = GoodsSKU.objects.get(id=goods_id)
        except GoodsSKU.DoesNotExist:
            # 商品不存在
            return redirect(reverse('goods:index'))

        # 获取商品的分类信息
        types = GoodsType.objects.all()

        # 获取商品的评论信息
        sku_orders = OrderGoods.objects.filter(sku=sku).exclude(comment='')

        # 获取新品信息,默认是升序,因此要减号
        new_skus = GoodsSKU.objects.filter(type=sku.type).order_by('-create_time')[:2]

        # 获取同一个SPU的其他规格商品
        same_spu_skus = GoodsSKU.objects.filter(goods=sku.goods).exclude(id=goods_id)

        # 获取用户购物车中商品的数目
        user = request.user
        cart_count = 0
        if user.is_authenticated():
            # 用户已登录
            conn = get_redis_connection('default')
            cart_key = 'cart_%d' % user.id
            cart_count = conn.hlen(cart_key)

            # 添加用户的历史记录
            conn = get_redis_connection('default')
            history_key = 'history_%d'%user.id
            # 移除列表中的goods_id
            conn.lrem(history_key, 0, goods_id)
            # 把goods_id插入到列表的左侧
            conn.lpush(history_key, goods_id)
            # 只保存用户最新浏览的5条信息
            conn.ltrim(history_key, 0, 4)

        # 组织模板上下文
        context = {'sku':sku, 'types':types,
                   'sku_orders':sku_orders,
                   'new_skus':new_skus,
                   'same_spu_skus':same_spu_skus,
                   'cart_count':cart_count}

        # 使用模板
        return render(request, 'detail.html', context)

62看完,61、63、65、64

十、部署服务器

P99~P105

基本部署架构:
天天生鲜Django项目_第58张图片

1.uwsgi作为web服务器

1、uwsgi的安装

pip install uwsgi

【Python】Windows系统安装uWSGI报错AttributeError: module ‘os’ has no attribute 'uname’解决办法

Python+Django+Nginx+Uwsgi 详细部署过程(爬坑过程)
https://www.cnblogs.com/fanbi/p/12375023.html
https://blog.csdn.net/hellokandy/article/details/95077960
https://blog.csdn.net/weixin_34409703/article/details/89863283

no python application found, check your startup logs for errors
Centos7使pip3 install 的uwsgi直接启动也会调用python2,uwsgi自动调用python2环境,指定uwsgi调用3版本
https://www.cnblogs.com/zoujl/p/11011041.html

天天生鲜Django项目_第59张图片
路径不对,确认下路径
failed to open python file dailyfresh/wsgi.py
unable to load app 0 (mountpoint=’’) (callable not found or import error)
*** no app loaded. going in full dynamic mode ***

 
uwsgi找不到django模块
ModuleNotFoundError: No module named 'django.urls’
uwsgi.ini中声明pythonpath:

 
宝塔一键式部署Django:https://www.bilibili.com/video/BV1hA41187sV?from=search&seid=14383759237733920207

 

2、uwsgi的配置
项目部署时,需要把settings.py文件夹下的:

DEBUG = False
ALLOWED_HOSTS = ['*']

创建uwsgi的配置文件

[uwsgi]
#使用nginx连接时使用
#socket=127.0.0.1:8080  ,由Nginx去转发

#直接做web服务器使用,相当于python manage.py runserver ip:port
http=127.0.0.1:8000
#项目目录
chdir=C:\\Users\\acer\\Desktop\\当下\\Python Django\\dailyfresh
#项目中wsgi.py文件的目录,相对于项目目录
wsgi-file=dailyfresh/wsgi.py
#指定启动的工作进程数
processes=4
#指定工作进程中的线程数
threads=2
#表示有主进程
master=True
#保存启动之后主进程的pid
pidfile=uwsgi.pid
#设置uwsgi后台运行,保存日志信息
daemonize=uwsgi.log
#设置虚拟环境的路径

3、uwsgi的启动和停止
启动:uwsgi --ini 配置文件路径 例如:uwsgi --ini uwsgi.ini
停止:uwsgi --stop uwsgi.pid路径 例如:uwsgi --stop uwsgi.pid

2.Nginx配置转发请求给uwsgi

http{
	server {
		listen		80;
		server_name		localhost;
		localtion / {
			# 包含uwsgi的请求参数
			include uwsgi_params;
			# 转交请求给uwsgi
			uwsgi_pass 127.0.0.1:8080;	
		}
	}
}

3.Nginx配置处理静态文件

django settings.py中配置收集静态文件路径:

STATIC_ROOT=收集的静态文件路径,例如:/var/www/dailyfresh/static;

django收集静态文件的命令:

python manage.py collectstatic

执行上面的命令会把项目中所使用的静态文件收集到STATIC_ROOT指定的目录下。

收集完静态文件之后,让nginx提供静态文件,需要在nginx配置文件中增加如下配置:

http{
	server {
		listen		80;
		server_name		localhost;
		localtion / {
			# 包含uwsgi的请求参数
			include uwsgi_params;
			# 转交请求给uwsgi
			uwsgi_pass 127.0.0.1:8080;	
		}
		
		location /static {
			# 指定静态文件存放的目录
			alias /var/www/dailyfresh/static/;
		}
	}
}

4.Nginx转发请求给另外地址

在location对应的配置项中增加proxy_pass转发的服务器地址
如当用户访问127.0.0.1时,在nginx中配置把这个请求转发给172.16.179.131:80(nginx)服务器,让这台服务器提供静态首页。

天天生鲜Django项目_第60张图片

静态页面index页面和IndexView的调度(对应之前的)

http{
	server {
		listen		80;
		server_name		localhost;
		localtion / { # 匹配/, /index等等
			# 包含uwsgi的请求参数
			include uwsgi_params;
			# 转交请求给uwsgi
			uwsgi_pass 127.0.0.1:8080;	
		}
		
		location /static {
			# 指定静态文件存放的目录
			alias /var/www/dailyfresh/static/;
		}
		location = / { # 只能严格是/
			# 传递请求给静态文件服务器的nginx
			proxy_pass http://172.16.179.131;
		}
	}
}

5.Nginx配置upstream实现负载均衡

天天生鲜Django项目_第61张图片

nginx配置负载均衡时,在server配置的前面增加upstream配置项。

启动了两个uwsgi情况下,有两个uwsgi进程

http {
	upstream dailyfresh{
		server 127.0.0.1:8080;
		server 127.0.0.1:8001;
	}
	server {
		listen		80;
		server_name		localhost;
		localtion / { # 匹配/, /index等等
			# 包含uwsgi的请求参数
			include uwsgi_params;
			# 转交请求给uwsgi
			# uwsgi_pass 127.0.0.1:8080;	
			uwsgi_pass dailyfresh; # 通过上面负载
		}
	}
}

十一、项目总结

https://www.bilibili.com/video/BV1vt41147K8?p=106

  1. 生鲜类产品B2CPC电脑端网页。
  2. 功能模块:用户模块商品模块(首页、搜索、商品)购物车模块订单模块(下单、支付)
  3. 用户模块:注册、登录、激活、退出、个人中心、地址
  4. 商品模块:音页、详情、列表、搜索(haystack + whoosh)
  5. 购物车:增加、删除、修改、查询
  6. 订单模块:确认订单页面、订单创建(下单)、请求支付、查询支付结果、评论
  7. django 默认的认证系统 AbstractUser
  8. itsdangerous 生成签名的token(序列化工具dumps loads)
  9. 邮件(django 提供邮件支持配置参数send_mail)
  10. celery(重点整体认识异步任务队列)
  11. 页面静态化(缓解压力 celery nginx)
  12. 缓存(缓解压力,保存的位置、有效期、与数据库的一致性问题)
  13. FastDFS(分布式的图片存储服务,修改了django的默认文件存储系统。解决了海量存储,存储容量扩展方便。防止文件内容重复)
  14. 搜索(whoosh索引分词)
  15. 购物车redis 哈希 历史记录 redis list
  16. ajax前端用ajax请求后端接口
  17. 事务mysql
  18. 高并发的库存问题(悲观锁、乐观锁)
  19. 支付的使用流程·
  20. nginx(负载均衡提供静态文件)

auth认证

方法名 备注
create_user 创建用户
authenticate 登录验证
login 记录登录状态
logout 退出用户登录
is_authenticated 判断用户是否登录
login_required 装饰器 进行登录判断

天天生鲜Django项目_第62张图片

 
celery任务队列
天天生鲜Django项目_第63张图片

天天生鲜Django项目_第64张图片

 
redis缓存

 

十二、商品列表页

P65、P64

十三、商品搜索

P66~P72
搜索
select * from df_goods_sku where name like ‘%草莓%’ or desc like ‘%草莓%’

搜索引擎:
1)可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据。

你可能感兴趣的:(#,Python,python,django)