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恶意攻击(很短时间内让成千上万台电脑访问网站造成瘫痪);