天天生鲜Django项目(三)

32、项目中上传和使用图片

Django默认在admin页面上传文件后,保存到MEDIA_ROOT指定的目录下,由FileSystemStorage类的save()方法实现;

我们要让文件保存在FastDFS,不需要改他源代码,Django已经准备好了扩展的方式:自定义一个文件存储类,继承Storage类(FileSystomStorage的父类),重写相应的方法;

utils目录下新建fdfs目录:

client.conf和配置FastDFS时用的客户端配置文件一样,python里创建FastDFS客户端也要用客户端配置文件

# utils/fdfs/storage.py

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


class FDFSStorage(Storage):
    '''FastDFS文件存储类'''
    def _open(self, name, mode='rb'):
        '''打开文件'''
        # 我们用不到打开,但是自定义文件存储类必须实现_open()_save(),所以pass
        pass
    
    def _save(self, name, content):
        '''保存文件'''
        # name:要上传的文件的名字
        # content:包含上传文件内容的File对象
        # 创建一个FastDFS客户端
        client = Fdfs_client('./utils/fdfs/client.conf')
        # 上传后返回一个字典,有Status(上传状态),Remote file_id(文件id)等属性
        res = client.upload_by_buffer(content.read())

        if res.get('Status') != 'Upload successed.':
            # 上传失败
            raise Exception('上传文件到FastDFS失败')
        filename = res.get('Remote file_id')
        # _save()返回什么,表df_goods_type.image就保存什么,我们要保存文件id
        return filename
    
    def exists(self, name):
        '''判断Django内置的文件存储系统中是否已存在这个文件名,存在就不可用,而我们根本没用他的系统,当然不会存在'''
        return False
    
    def url(self, name):
        '''返回'''
        # url()返回什么,goods.image.url就返回什么
        return 'http:127.0.0.1:8888/' + name
# settings.py追加

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

from django.contrib import admin

from apps.goods.models import GoodsType

admin.site.register(GoodsType)

创建admin用户,用户名密码都是admin:

进入admin页面:

点商品种类后面的增加:

填好,选择文件,点保存:

保存成功,点击刚添加的猪牛羊跳到修改页,前提是FDFSStorage类重写了url()方法:

static目录下创建test.html用于测试上传图片成功否:

<!--static/test.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--src是http://127.0.0.1:8888/ + 图片存到FastDFS返回的文件id-->
<img src="http://127.0.0.1:8888/group1/M00/00/00/wKgrLV7d5f-AVIH_AA6-ItK1iQI3713508" alt="">
</body>
</html>

访问test.html,能访问到图片,说明上传成功了:

 

32.5、注意:/etc/fdfs/下4个配置文件tracker.conf、storage.conf、client.conf、mod_fastdfs.conf以及上文的utils/fdfs/client.conf里的IP地址都要一致(不支持localhost),因为换了网络之后IP地址会变,所以要记得改。

 

33、改进自定义的文件存储类:

_save()中client.conf的路径写死了,url()中nginx服务器的IP:端口也写死了,我们应该动态地指定:

# utils/fdfs/storage.py

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

from dailyfresh import settings


class FDFSStorage(Storage):
    '''FastDFS文件存储类'''
    def __init__(self, client_conf=settings.FDFS_CLIENT_CONF, base_url=settings.FDFS_URL):
        self.client_conf = client_conf
        self.base_url = base_url

    def _open(self, name, mode='rb'):
        '''打开文件'''
        # 我们用不到打开,但是自定义文件存储类必须实现_open()_save(),所以pass
        pass

    def _save(self, name, content):
        '''保存文件'''
        # name:要上传的文件的名字
        # content:包含上传文件内容的File对象
        # 创建一个FastDFS客户端
        client = Fdfs_client(self.client_conf)
        # 上传后返回一个字典,有Status(上传状态),Remote file_id(文件id)等属性
        res = client.upload_by_buffer(content.read())

        if res.get('Status') != 'Upload successed.':
            # 上传失败
            raise Exception('上传文件到FastDFS失败')
        filename = res.get('Remote file_id')
        # _save()返回什么,表df_goods_type.image就保存什么,我们要保存文件id
        return filename

    def exists(self, name):
        '''判断Django内置的文件存储系统中是否已存在这个文件名,存在就不可用,而我们根本没用他的系统,当然不会存在'''
        return False

    def url(self, name):
        '''返回'''
        # url()返回什么,goods.image.url就返回什么
        return self.base_url + namefrom django.core.files.storage import Storage
from fdfs_client.client import Fdfs_client

from dailyfresh import settings


class FDFSStorage(Storage):
    '''FastDFS文件存储类'''
    def __init__(self, client_conf=settings.FDFS_CLIENT_CONF, base_url=settings.FDFS_URL):
        self.client_conf = client_conf
        self.base_url = base_url

    def _open(self, name, mode='rb'):
        '''打开文件'''
        # 我们用不到打开,但是自定义文件存储类必须实现_open()_save(),所以pass
        pass

    def _save(self, name, content):
        '''保存文件'''
        # name:要上传的文件的名字
        # content:包含上传文件内容的File对象
        # 创建一个FastDFS客户端
        client = Fdfs_client(self.client_conf)
        # 上传后返回一个字典,有Status(上传状态),Remote file_id(文件id)等属性
        res = client.upload_by_buffer(content.read())

        if res.get('Status') != 'Upload successed.':
            # 上传失败
            raise Exception('上传文件到FastDFS失败')
        filename = res.get('Remote file_id')
        # _save()返回什么,表df_goods_type.image就保存什么,我们要保存文件id
        return filename

    def exists(self, name):
        '''判断Django内置的文件存储系统中是否已存在这个文件名,存在就不可用,而我们根本没用他的系统,当然不会存在'''
        return False

    def url(self, name):
        '''返回'''
        # url()返回什么,goods.image.url就返回什么
        return self.base_url + name
# settings.py追加

# 设置fdfs使用的客户端配置文件(自定义变量)
FDFS_CLIENT_CONF = 'utils/fdfs/client.conf'
# 设置nginx服务器地址(自定义变量)
FDFS_URL = 'http://127.0.0.1:8888/'

至此,项目的文件存储系统已经配置完了;

测试前的准备:

1)Django运行runserver;

2)开启redis服务(缓存、session):

sudo redis-server /etc/redis/redis.conf

开启fdfs_trackerd、fdfs_storaged服务;

sudo fdfs_trackerd /etc/fdfs/tracker.conf
sudo fdfs_storaged /etc/fdfs/storage.conf

开启Nginx服务:

sudo /usr/local/nginx/sbin/nginx

 

34、首页页面静态化

由于一个网站访问量最多的就是未登录的首页,每个人访问都返回一模一样的东西,不需要每次都从数据库查,生成静态页面比较省开销;

怎么生成静态页面:用celery定义任务函数;

什么时候重新生成:操作数据库,改动首页有关数据的时候;

未登录的首页没有欢迎信息,因此重新为首页创建一个父模板,就是base.html删掉欢迎信息:

<!--templates/static_base.html-->

{# 首页,注册,登录 #}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
{% load static %}
<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 head_con %}
	<div class="header_con">
		<div class="header">
			<div class="welcome fl">欢迎来到天天生鲜!</div>
			<div class="fr">
				<div class="login_btn fl">
					<a href="{% url 'user:login' %}">登录</a>
					<span>|</span>
					<a href="{% url 'user:register' %}">注册</a>
				</div>
				<div class="user_link fl">
					<span>|</span>
					<a href="{% url 'user:info' %}">用户中心</a>
					<span>|</span>
					<a href="cart.html">我的购物车</a>
					<span>|</span>
					<a href="{% url 'user:order' %}">我的订单</a>
				</div>
			</div>
		</div>
	</div>
{% endblock head_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">1</div>
		</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 © 2016 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>

{# 底部html元素块 #}
{% block bottom %}{% endblock bottom %}

{# 底部引入文件块 #}
{% block bottomfiles %}{% endblock bottomfiles %}

</body>
</html>

首页模板:

<!--templates/static_index.html-->

{% extends 'static_base.html' %}
{% load static %}
{% 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/jquery-ui.min.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/slide.js' %}"></script>
{% endblock topfiles %}
{% block body %}
	<div class="navbar_con">
		<div class="navbar">
			<h1 class="fl">全部商品分类</h1>
			<ul class="navlist fl">
				<li><a href="">首页</a></li>
				<li class="interval">|</li>
				<li><a href="">手机生鲜</a></li>
				<li class="interval">|</li>
				<li><a href="">抽奖</a></li>
			</ul>
		</div>
	</div>

	<div class="center_con clearfix">
		<ul class="subnav fl">
            {% for type in types %}
			    <li><a href="#model0{{ forloop.counter }}" class="{{ type.logo }}">{{ type.name }}</a></li>
            {% endfor %}
		</ul>
		<div class="slide fl">
			<ul class="slide_pics">
                {% for banner in goods_banners  %}
				    <li><a href="{% url 'goods:detail' banner.sku.id %}"><img src="{{ banner.image.url }}" alt="幻灯片"></a></li>
                {% endfor %}
			</ul>
			<div class="prev"></div>
			<div class="next"></div>
			<ul class="points"></ul>
		</div>
		<div class="adv fl">
            {% for banner in promotion_banners %}
			    <a href="{{ banner.url }}"><img src="{{ banner.image.url }}"></a>
            {% endfor %}
		</div>
	</div>

    {% for type in types %}
	<div class="list_model">
		<div class="list_title clearfix">
			<h3 class="fl" id="model0{{ forloop.counter }}">{{ type.name }}</h3>
			<div class="subtitle fl">
				<span>|</span>
                {% for banner in type.title_banners %}
				    <a href="{% url 'goods:detail' banner.sku.id  %}">{{ 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="{% url 'goods:detail' banner.sku.id  %}">{{ banner.sku.name }}</a></h4>
					<a href="{% url 'goods:detail' banner.sku.id  %}"><img src="{{ banner.sku.image.url }}"></a>
					<div class="prize">¥ {{ banner.sku.price }}</div>
				</li>
				{% endfor %}
			</ul>
		</div>
	</div>
    {% endfor %}
{% endblock body %}

{% block bottomfiles %}
<script type="text/javascript" src="js/slideshow.js"></script>
<script type="text/javascript">
    BCSlideshow('focuspic');
    var oFruit = document.getElementById('fruit_more');
    var oShownum = document.getElementById('show_count');

    var hasorder = localStorage.getItem('order_finish');

    if(hasorder)
    {
        oShownum.innerHTML = '2';
    }

    oFruit.onclick = function(){
        window.location.href = 'list.html';
    }
</script>
{% endblock bottomfiles %}

定义任务函数:

# celery_tasks/tasks.py

from celery import Celery
from django.core.mail import send_mail
from django.template import loader
from dailyfresh import settings

# 初始化Django环境变量,任务执行者要用到
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
# django.setup()  # 如果报错就把它注释掉

# 导入模型类要在初始化Django之后,否则不认识
from apps.goods.models import GoodsType, IndexGoodsBanner, IndexPromotionBanner, IndexTypeGoodsBanner

# 创建一个celery实例对象,参数1随便写,参数2是中间人,本机redis,8号数据库
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8')

# 定义任务函数,一个任务就是一个函数
@app.task
def send_active_email(to_email, username, token):
    subject = '天天生鲜用户激活'
    message = '足不出户,新鲜每一天'
    sender = settings.EMAIL_FROM
    receiver_list = [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_list, 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}

    # 加载模板文件,返回模板对象
    temp = loader.get_template('static_index.html')
    # 模板渲染
    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)

(日常启动redis、fdfs_trackerd、fdfs_storaged、nginx)

启动celery:(如果celery和项目不在同一主机,则复制完整项目目录到该主机)

简单测试任务:

这时候在celery所在机器的dailyfresh/static/目录下就会出现一个index.html文件;

配置nginx让用户访问到这个页面:

# /usr/local/nginx/conf/nginx.conf添加一个server{}

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
	    location /static {
	        alias /home/deepon/PycharmProjects/dailyfresh/static/;
	    }
        location / {
            # root   html;
            root /home/deepon/PycharmProjects/dailyfresh/static/;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

重启nginx:

sudo /usr/local/nginx/sbin/nginx -s reload

自定义商品种类模型管理类,使得添加数据到这个表中就会自动发出任务,产生index.html:

# goods/admin.py

from django.contrib import admin

from apps.goods.models import GoodsType
from celery_tasks.tasks import generate_static_index_html

class GoodsTypeAdmin(admin.ModelAdmin):
    # 重写save_model()方法
    def save_model(self, request, obj, form, change):
        '''添加或新增数据时调用'''
        super().save_model(request, obj, form, change)
        generate_static_index_html.delay()

    # 重写delete_model()方法
    def delete_model(self, request, obj):
        '''删除数据时调用'''
        super().delete_model(request, obj)
        generate_static_index_html.delay()

admin.site.register(GoodsType, GoodsTypeAdmin)

测试:访问127.0.0.1打开生成的静态index.html,访问127.0.0.1:8000/admin登录admin页面,添加商品种类,刷新index会发现静态页面随之改变了。

下一步就是在goods/admin.py为所有模型类自定义模型管理类,但是显然不可能一个个手写,所以抽离出一个父类,让其他模型管理类都继承这个父类:

# goods/admin.py

from django.contrib import admin

from apps.goods.models import GoodsType, IndexTypeGoodsBanner, IndexPromotionBanner, IndexGoodsBanner
from celery_tasks.tasks import generate_static_index_html

class BaseModelAdmin(admin.ModelAdmin):
    # 重写save_model()方法
    def save_model(self, request, obj, form, change):
        '''添加或新增数据时调用'''
        super().save_model(request, obj, form, change)
        generate_static_index_html.delay()

    # 重写delete_model()方法
    def delete_model(self, request, obj):
        '''删除数据时调用'''
        super().delete_model(request, obj)
        generate_static_index_html.delay()

class GoodsTypeAdmin(BaseModelAdmin):
    pass
class IndexTypeGoodsBannerAdmin(BaseModelAdmin):
    pass
class IndexPromotionBannerAdmin(BaseModelAdmin):
    pass
class IndexGoodsBannerAdmin(BaseModelAdmin):
    pass

admin.site.register(GoodsType, GoodsTypeAdmin)
admin.site.register(IndexTypeGoodsBanner, IndexTypeGoodsBannerAdmin)
admin.site.register(IndexPromotionBanner, IndexPromotionBannerAdmin)
admin.site.register(IndexGoodsBanner, IndexGoodsBannerAdmin)

 下一步就是在admin页面添加所有东西;

这其实把整个网站分成了请求nginx还是请求Django,而实际应该另有一台nginx服务器负责调度,只对外暴露这个nginx服务器的IP地址,根据url决定是访问Django还是访问nginx:

35、注意:

报错:

解决:

不要让Django和celery共用代码,把项目目录复制一份给celery用,Django不用初始化Django,celery初始化Django;

报错:

解决:

# 引入任务函数时在每次用到任务函数时引用,不再文件首引用,不知道为什么

class BaseModelAdmin(admin.ModelAdmin):
    # 重写save_model()方法
    def save_model(self, request, obj, form, change):
        '''添加或新增数据时调用'''
        super().save_model(request, obj, form, change)
        from celery_tasks.tasks import generate_static_index_html
        generate_static_index_html.delay()

    # 重写delete_model()方法
    def delete_model(self, request, obj):
        '''删除数据时调用'''
        super().delete_model(request, obj)
        from celery_tasks.tasks import generate_static_index_html
        generate_static_index_html.delay()

 

36、设置首页缓存(用于访问Django返回的首页,不是访问nginx返回的静态页面)

由于一个网站访问量最多的就是未登录的首页,每个人访问都返回一模一样的东西,不需要每次都从数据库查,保存把数据存到缓存比较节省开销

怎么存储缓存:用redis(前面已经配置过),不是存整个网站或一个页面,而是存模板上下文(通用部分);

什么时候更新缓存:操作数据库,改动首页有关数据的时候;

# goods/views.py

from django.shortcuts import render
from django.views.generic import View
from django.core.cache import cache
from apps.goods.models import GoodsType, IndexGoodsBanner,IndexPromotionBanner,IndexTypeGoodsBanner
from django_redis import get_redis_connection

# http://127.0.0.1:8000
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)

# 先获取缓存,如果没有缓存就查数据库
# 缓存只存通用部分,涉及用户的东西不存,因为访问首页不能出现某位用户的信息
# goods/admin.py添加删除缓存的代码(删除后下次用户访问会生成缓存,达到了更新的效果)

class BaseModelAdmin(admin.ModelAdmin):
    # 重写save_model()方法
    def save_model(self, request, obj, form, change):
        '''添加或新增数据时调用'''
        super().save_model(request, obj, form, change)
        from celery_tasks.tasks import generate_static_index_html
        generate_static_index_html.delay()
        # 清除缓存
        cache.delete('index_page_data')

    # 重写delete_model()方法
    def delete_model(self, request, obj):
        '''删除数据时调用'''
        super().delete_model(request, obj)
        from celery_tasks.tasks import generate_static_index_html
        generate_static_index_html.delay()
        # 清除缓存
        cache.delete('index_page_data')

 

37、首页静态化和首页缓存都属于网站优化,还能防止DDOS恶意攻击(很短时间内让成千上万台电脑访问网站造成瘫痪);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值