Python3 Django的补充
本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.youkuaiyun.com/Rozol/article/details/79526637
以下代码以Python3.6.1为例
Less is more!
Python 3.6.1
Django 2.0.2
django-tinymce==2.7.0
django-redis-cache==1.7.1
django-haystack==2.8.0
whoosh==2.7.4
jieba==0.39
celery==4.1.0
celery-with-redis==3.0
django-celery==3.2.2
uwsgi==2.0.17
项目名: Django_templates 应用名: booktest
静态文件
- 静态文件: js / css / 图片
配置
settings:py
:STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
路径选择: (
STATIC_URL = '/static/'
)- 选择一: 项目下 √
- STATICFILES_DIRS中路径配置为
os.path.join(BASE_DIR, 'static'),
- 使用:
- 全路径: /static/booktest/1.jpg
- 标签生成路径: booktest/1.jpg
- STATICFILES_DIRS中路径配置为
- 选择二: 应用下
- STATICFILES_DIRS中路径配置为
os.path.join(BASE_DIR, 'booktest.static'),
- 使用:
- 全路径: /static/1.jpg
- 标签生成路径: 1.jpg
- STATICFILES_DIRS中路径配置为
- 选择一: 项目下 √
- 创建文件夹:
- 项目下创建
static
文件夹 - static下创建应用名booktest文件夹
- 项目下创建
- STATIC_URL: 逻辑路径
- 当
STATIC_URL = '/static/'
时, 路径为<img src="/static/booktest/1.jpg">
- 当
STATIC_URL = '/img/'
时, 路径为<img src="/img/booktest/1.jpg">
- 当
- STATICFILES_DIRS: 物理路径
- 文件夹名可以与STATIC_URL同名, 也可不同名
- 文件夹路径添加到列表
- 路径
- 方式一: 全路径(静态)
<img src="/img/booktest/1.jpg">
- 方式二: 标签(动态生成) √
- 写第一行:
{% load static from staticfiles %}
<img src="{% static 'booktest/1.jpg' %}" />
- 写第一行:
- 方式一: 全路径(静态)
上传图片
- 配置路径:
MEDIA_ROOT = os.path.join(BASE_DIR,"static/media")
- 创建文件夹:
- 在static文件夹下创建
media
文件夹
- 在static文件夹下创建
上传图片
方式一: 通过表单上传
图片上传表单:
<!-- 方式一: 通过表单上传文件 --> <form method="post" action="upload" enctype="multipart/form-data"> {% csrf_token %} <input type="text" name="title"><br> <input type="file" name="pic"/><br> <input type="submit" value="上传"> </form>
图片接收并保存:
from django.conf import settings # 接收图片 def upload(request): if request.method == "POST": # 接收图片 file = request.FILES['pic'] # 保存图片 file_name = os.path.join(settings.MEDIA_ROOT, file.name) # 图片路径 with open(file_name, 'wb') as pic: for b in file.chunks(): pic.write(b) return HttpResponse('<img src="/static/media/{}"/>'.format(file.name)) else: return HttpResponse('图片接收错误')
方式二: 移动端上传
以Android系统, okhttp3为例
// --- Android的代码 --- // 混合类型(Multipart) // python django2 可用 // java spring 可用 // RequestBody构建方式一 RequestBody mbody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("media/type"), file)) .addFormDataPart("key1", "value1") .addFormDataPart("key2", "value2") .build(); // RequestBody构建方式二 RequestBody mbody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addPart( Headers.of("Content-Disposition", "form-data; name=\"key1\""), RequestBody.create(null, "value1")) .addPart( Headers.of("Content-Disposition", "form-data; name=\"key2\""), RequestBody.create(null, "value2")) .addPart( Headers.of("Content-Disposition", "form-data; name=\"file\"; filename =\"" + file.getName() + "\""), RequestBody.create(MediaType.parse("media/type"), file)) .build(); Request request = new Request.Builder().url(url).post(mbody).build(); new OkHttpClient().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { } });
# --- django2的代码 --- # 从移动端接收图片, 移动端使用okhttp库post请求 def uoload_mobile(request): http = HttpResponse() if request.method == "POST": # 接收图片 file = request.FILES['file'] # 保存图片 file_name = os.path.join(settings.MEDIA_ROOT, file.name) # 图片路径 with open(file_name, 'wb') as pic: for b in file.chunks(): pic.write(b) print('key1: {}'.format(request.POST['key1'])) print('key2: {}'.format(request.POST['key2'])) http.status_code = 200 http.write('ok') else: http.status_code = 400 http.write('err') return http
分页
# 分页
def pagination(request, index_page):
books = BookInfo.objects.all()
'''
Paginator
属性:
count: item总条数
num_pages: 总页数
page_range: 页码, range对象
方法:
page(num): 指定页, 页码不存在抛InvalidPage异常, 页码非整数抛PageNotAnInteger异常, 页码无页面抛EmptyPage异常
'''
# 分页(一次取几页)
list = Paginator(books, 3)
'''
Page: 由Paginator.page()返回的Page对象, 可迭代对象
属性:
object_list: 当前页所有对象的列表
number: 当前第几页 (非item序号)
paginator: 当前page对象的Paginator对象
方法:
has_next(): 是否有下一页
has_previous(): 是否有上一页
has_other_pages(): 是否还有其他页
next_page_number(): 下一页的页码, 不存在抛InvalidPage异常
previous_page_number(): 上一页的页码, 不存在抛InvalidPage异常
len(): 当前页面item个数
'''
# 单页(一页有多个条目)
pages = list.page(int(index_page))
context = {
'books': pages, 'page_sum': list.num_pages, 'page': pages.number,
}
return render(request, 'pagination.html', context)
Ajax
- 数据通过’get’请求获取, 而非直接让Django生成在模板里, 这能降低耦合
省市区三级联动案例:
# view层处理 # 省市区选择测试 def areatest(request): return render(request, 'areatest.html') # 省 def pro(request): prolist = AreaInfo.objects.filter(area_parent__isnull=True) list = [] for item in prolist: list.append([item.id, item.area_title]) # 采用列表 [[111, 'xxx'],[112, 'xxx']] return JsonResponse({'data': list}) # 市 def city(request, id): citylist = AreaInfo.objects.filter(area_parent_id=id) list = [] for item in citylist: list.append({'id': item.id, 'title': item.area_title}) # 采用字典 [{'id'= 111, 'title'= 'xxx'},{'id'= 112, 'title'= 'xxx'}] return JsonResponse({'data': list})
# html页ajax编写 <head> <title>Title</title> <script src="/static/jquery-1.12.4.min.js"></script> <script> $(function () { // 执行方法 // 使用ajax异步加载省信息 $.get('/booktest/pro', function (list) { // get请求, 并回调 pro = $('#pro') // 列表, {data: [[111, 'xxx'][112, 'xxx']]} $.each(list.data, function(index, item) { // <option value="111">xxx</option> pro.append('<option value="' + item[0] + '">' + item[1] + '</option>') }); }) // 查询市的信息 $('#pro').change(function () { // 绑定pro改变事件 $.get('/booktest/city' + $(this).val(), function (list) { // this是pro的option city = $('#city'); // 重置 city.empty().append('<option value="0">请选择市</option>') $('#dis').empty().append('<option value="0">请选择区县</option>') // 字典, {data: [{'id'= 111, 'title'= 'xxx'},{'id'= 112, 'title'= 'xxx'}]} $.each(list.data, function(index, item) { // <option value="111">xxx</option> city.append('<option value="' + item.id + '">' + item.title + '</option>'); }); }); }); //查询区县的信息 $('#city').change(function() { $.get('/booktest/city' + $(this).val(), function(list) { // 重置 dis = $('#dis').empty().append('<option value="0">请选择区县</option>'); // {data: [{'id'= 111, 'title'= 'xxx'},{'id'= 112, 'title'= 'xxx'}]} $.each(list.data, function(index, item) { // <option value="111">xxx</option> dis.append('<option value="' + item.id + '">' + item.title + '</option>'); }); }); }); }); </script> </head> <body> <select id="pro"> <option value="0">请选择省</option> </select> <select id="city"> <option value="0">请选择市</option> </select> <select id="dis"> <option value="0">请选择区县</option> </select> </body>
富文本编辑 (安装包: django-tinymce==2.7.0)
- 使用`django-tinymce==2.7.0为例
setting.py
配置- INSTALLED_APPS列表添加
'tinymce',
添加 (展示于admin)
TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', 'width': 600, 'height': 400, }
项目下
urls.py
添加path('tinymce/', include('tinymce.urls')),
- INSTALLED_APPS列表添加
models.py
模型(可在admin界面使用)from django.db import models from tinymce.models import HTMLField class BookInfo(models.Model): book_name = models.CharField(max_length=10) book_content = HTMLField() def __str__(self): return self.book_name
html页面配置:
<head> <meta charset="UTF-8"> <title>Title</title> <!-- 富文本编辑器js脚本 --> <script type="text/javascript" src='/static/tiny_mce/tiny_mce.js'></script> <script type="text/javascript"> tinyMCE.init({ 'mode':'textareas', 'theme':'advanced', 'width':400, 'height':100 }); </script> </head> <body> <form method="post" action="content"> {% csrf_token %} <input type="text" name="book_name"> <br> <!-- textarea 将被替换成富文本编辑器 --> <textarea name='book_content'>内容多行文本框</textarea> <br> <input type="submit" value="提交"> </form> </body>
通过post传递数据:
def html_content(request): name = request.POST['book_name'] content = request.POST['book_content'] book = BookInfo() book.book_name = name book.book_content = content book.save() return render(request, 'html_content.html', {'book_content': book.book_content})
缓存 (选装包: django-redis-cache==1.7.1)
- Django具有不同级别的缓存粒度
- 缓存被用于缓存
- 需要计算的程序
- 不变的数据和模板
settings.py
配置:CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'TIMEOUT': 60, # 默认300s, 永久None, 立即失效0 } }
可以缓存到django-redis-cache (默认库1)
CACHES = { "default": { "BACKEND": "redis_cache.cache.RedisCache", "LOCATION": "localhost:6379", 'TIMEOUT': 60, }, }
- redis命令(cd到redis安装目录):
- 启动:
redis-server redis.windows.conf
- 连接: 运行
redis-cli
- 切换数据库:
select 1
- 查看键:
keys *
- 查看值:
get [键]
- 删除数据:
del [键/*]
- 启动:
- redis命令(cd到redis安装目录):
缓存指定数据:
指定views视图:
from django.views.decorators.cache import cache_page @cache_page(60) # 缓存指定视图 def index(request): return render(request, 'index.html')
指定模板片段:
<!-- 写第一行--> {% load cache %} <!-- 缓存指定位置的模板碎片(可缓存DTL/HTML) --> {% cache 60 cache_name %} <img src="{% static '1.jpg' %}" /> {% endcache %}
指定值:
from django.core.cache import cache # 缓存指定值 def cachetest(request): # 设置缓存 cache.set('key', 'value', 600) # # 获取缓存 cache_content = cache.get('key') # 删除缓存 cache.delete('key') # 清空缓存 cache.clear() return HttpResponse('缓存: {}'.format(cache_content))
中文分词全文检索 (安装包: django-haystack==2.8.0 / whoosh==2.7.4 / jieba==0.39)
- 比 字段模糊查询 效率高, 并能对中文进行分词
- django-haystack: Django的包, 通过whoosh, solr, Xapian, Elasticsearc四种全文检索引擎 检索model里的内容
- whoosh: 全文检索引擎, 全python写的, 性能一般, 用于中小型网站
- jieba: 中文分词包, 效果一般, 免费
settings.py
配置- 列表中添加
INSTALLED_APPS'haystack',
添加
# 搜索引擎 HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), } }
添加
# 自动更新 whoosh_index 索引 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
- 列表中添加
项目
urls.py
中添加path('search', include('haystack.urls')),
应用下创建
search_indexes.py
文件from haystack import indexes from .models import BookInfo class BookInfoIndex(indexes.SearchIndex, indexes.Indexable): # 将来将对这种字段进行检索 text = indexes.CharField(document=True, use_template=True) # 获取模型对象 def get_model(self): return BookInfo # 对哪些数据进行检索 def index_queryset(self, using=None): return self.get_model().objects.all() # 检索所有数据
目录
templates/search/indexes/booktest[应用名称]/
下创建BookInfo[模型类名]_text.txt
文件# 列出要对哪些字段进行检索 {{ object.book_name }} {{ object.book_content }}
在目录
templates/search/
下创建search.html
文件{% if query %} <h3>搜索结果如下: </h3> {% for result in page.object_list %} {{ result.object.id }} {{ result.object.book_name }} {{ result.object.book_content }} <br/> {% empty %} <p>没有检索到内容</p> {% endfor %} <!-- 分页 --> {% if page.has_previous or page.has_next %} <div> {% if page.has_previous %}<a href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« 上一页{% if page.has_previous %}</a>{% endif %} | {% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}下一页 »{% if page.has_next %}</a>{% endif %} </div> {% endif %} {% endif %}
在
haystack安装
目录python\Lib\site-packages\haystack\backends\
下创建ChineseAnalyzer.py
文件import jieba from whoosh.analysis import Tokenizer, Token class ChineseTokenizer(Tokenizer): def __call__(self, value, positions=False, chars=False, keeporiginal=False, removestops=True, start_pos=0, start_char=0, mode='', **kwargs): t = Token(positions, chars, removestops=removestops, mode=mode, **kwargs) seglist = jieba.cut(value, cut_all=True) for w in seglist: t.original = t.text = w t.boost = 1.0 if positions: t.pos = start_pos + value.find(w) if chars: t.startchar = start_char + value.find(w) t.endchar = start_char + value.find(w) + len(w) yield t def ChineseAnalyzer(): return ChineseTokenizer()
在
haystack安装
目录复制whoosh_backend.py
文件, 改名为whoosh_cn_backend.py
- 导入
from .ChineseAnalyzer import ChineseAnalyzer
- 查找
analyzer=StemmingAnalyzer()
替换为analyzer=ChineseAnalyzer()
- 导入
生成索引
python manage.py rebuild_index
- 向数据库中添加数据会自动生成索引, 不用执行命令
模板中创建搜索栏
<!-- 搜索栏 --> {% comment %} action="/search/" // 自动匹配到localhost/search/ {% endcomment %} <form method='get' action="/search/" target="_blank"> {% csrf_token %} <input type="text" name="q"> <input type="submit" value="查询"> </form>
Celery 分布式任务队列 (安装: celery==4.1.0 / celery-with-redis==3.0 / django-celery==3.2.2)
- 用于解决耗时操作 和 定时任务
- 名词解析:
- task: 任务, 用于封装耗时操作
- queue: 队列, 存放任务
- worker: 工人, 在新进程中, 执行队列中的任务
- broker: 代理人, 把任务加入队列中
配置
settings.py
INSTALLED_APPS
列表中添加'djcelery',
添加
import djcelery djcelery.setup_loader() BROKER_URL = 'redis://127.0.0.1:6379/0' CELERY_IMPORTS = ('booktest[应用名].task') # 将耗时任务定义到该task文件里
在应用下创建
task.py
文件封装任务: 用
@task
语法糖注解import time from celery import task @task def tasktest(par): print('hello') time.sleep(10) print('world')
迁移数据
python manage.py migrate
- 启动redis
- 启动worker
python manage.py celery worker --loglevel=info
调用:
def celery(request): # celery调用耗时任务 tasktest.delay('参数') return HttpResponse('hello!!!')
布署到服务器
- 收集虚拟环境所有安装的python包:
- cd到项目根目录下, 执行命令
pip freeze > plist.txt
- cd到项目根目录下, 执行命令
- 将项目传到服务器
- 安装虚拟环境
- 安装需要的python包:
- cd到指定目录下, 执行
pip install -r plist.txt
- cd到指定目录下, 执行
更改
settings.py
配置:DEBUG = False ALLOW_HOSTS=['*',]表示可以访问服务器的ip
启动服务器 (注: 使用
runserver
运行的服务器, 静态文件在生产环境是无法访问的)
服务器 (安装: uwsgi==2.0.17)
uWSGI
- 开发环境:
python manage.py runserver
= 生成环境:WSGI
(协议) - 实现WSGI的服务器
- 安装:
pip install uwsgi==2.0.17
配置:
- 项目下创建
usgi.ini
文件 编写(使用要去掉注释)
[uwsgi] #socket = 127.0.0.1:8000 # 外网ip:端口 (使用nginx连接时, 使用socket) http = 127.0.0.1:8000 # 外网ip:端口 (直接做web服务器, 使用http) chdir = C:\Code\Python_Vir\Django_other # 项目根目录 wsgi-file = Django_other\wsgi.py # 项目中wsgi.py文件的目录, 相对于项目根目录 processes = 2 # 运行进程数 threads = 2 # 每个进程的线程数 master = True pidfile = uwsgi.pid daemonize = uwsgi.log
- 项目下创建
命令
- 启动:
uwsgi --ini uwsgi.ini
- 查看是否启动
ps ajx|grep uwsgi
- 查看是否启动
- 停止:
uwsgi --stop uwsgi.pid
- 重启:
uwsgi --reload uwsgi.pid
- 启动:
- 开发环境:
nginx
- 作用:
- 负载均衡: 多态服务器轮流处理请求
- 反射代理: 隐藏真实服务器
- 实现: IE <-> nginx <-> uwsgi <-> django (静态文件: IE <-> nginx)
- 安装:
sudo apt-get install nginx
配置(版本是不同版本的nginx配置不同, 其他配置操作一样):
- 查看路径:
- 安装目录:
ps -ef | grep nginx
- 配置文件目录:
nginx -t
√
- 安装目录:
版本一(默认安装在
/usr/local/niginx
目录下):编辑
vim conf/nginx.conf
文件, http的server下listen 80; # 监听端口 server_name luzhuo.me; # 服务名(域名) location / { include uwsgi_params; # 将所有的参数转到uwsgi下 uwsgi_pass 127.0.0.1:8000; # uwsgi的ip与端口 } location /static { alias /var/www/django[项目名]/static/; }
版本二(默认安装在
/etc/nginx
目录下)编辑
vim /etc/nginx/sites-enabled/default
文件, server下# 注释掉, 另外一个listen... listen 外网ip:80 default_server # 端口 服务名 location / { # 注释掉, try_files ... include uwsgi_params; # 将所有的参数转到uwsgi下 uwsgi_pass 127.0.0.1:8000; # uwsgi的ip与端口 } location /static { alias /var/www/django[项目名]/static/; }
cd /var
下创建目录/var/www/django/static
- 修改权限
sudo chmod 777 static
- 所有静态文件都放到static文件夹中
- 修改权限
- 修改
uwsgi.ini
文件
- 使用socket
- 禁用http
修改
settings.py
文件STATIC_URL='/static/' STATIC_ROOT = '/var/www/django[项目名]/static/' # 用于nginx采集静态文件
收集项目所有静态文件, 并移到STATIC_ROOT指定目录:
python manage.py collectstatic
- 查看路径:
重启nginx, uwsgi (注意顺序)
- 命令:
- 版本一(cd到目录/usr/local/niginx目录):
- 版本:
sudo sbin/nginx -v
- 启动:
sudo sbin/nginx
- 停止:
sudo sbin/nginx -s stop
- 重启:
sudo sbin/nginx -s reload
- 版本:
- 版本二:
- 版本:
sudo nginx -v
- 启动:
sudo nginx
- 停止:
sudo nginx -s stop
- 重启:
sudo nginx -s reload
- 版本:
- 版本一(cd到目录/usr/local/niginx目录):
- 注意: listen端口为80, location 和 uwsgi.ini 的端口为8000, ip地址均为外网ip
- 作用:
项目实战
- 应用
- 商品
- 用户
- models:
- UserInfo
- models:
- …
共用页面
- 页头
- 页尾
- …
目录选择
- 模板文件: 应用下templates
- 静态文件: 项目下static
常用命令
- 生成迁移:
python manage.py makemigrations
- 执行迁移:
python manage.py migrate
- 生成迁移:
问题解答
如何 服务器/虚拟机里的服务器 里的网站?
- runserver访问:
python manage.py runserver 外网ip:端口
- 访问:
http://外网ip:端口
- 访问:
- runserver访问:
安装uWSGI时报错
- 先安装
sudo apt-get install python3.6-dev
(注意版本号, 直接在虚拟环境安装就可以了) - 再安装uWSGI(也安装到虚拟环境)
- 先安装