学习日记之《Django3 Web应用开发实战》——第十一章——常用的web应用程序
第十一章——常用的web应用程序
会话的配置与操作
# Django 提供了5种session的保存方式
# 数据库方式保存 默认是这种方式 无需设置
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# 以文件的方式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
SESSION_FILE_PATH = '/文件路径'
# 以缓存的形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 设置缓存名,默认是内存缓存的方式,与缓存机制的设置有关
SESSION_CACHE_ALIAS = 'default'
# 数据库加缓存的形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# Cookie形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
使用会话实现商品抢购
from django.shortcuts import render, redirect
from .models import Product
from django.contrib.auth.decorators import login_required
# Create your views here.
@login_required(login_url='/admin/login')
def index(request):
id = request.GET.get('id', '')
if id:
idList = request.session.get('idList', []) # 向session 中添加键值对
if not id in idList:
idList.append(id)
request.session['idList'] = idList
return redirect('purchase:index')
products = Product.objects.all()
return render(request, 'index.html', locals())
def orderView(request):
idList = request.session.get('idList', []) # 获取session中的键值对
del_id = request.GET.get('id', '')
if del_id in idList:
idList.remove(del_id)
request.session['idList'] = idList
products = Product.objects.filter(id__in=idList)
return render(request, 'order.html', locals())
缓存机制
为解决短时间内访问量过大的问题,可以实行缓存策略,若在某个时间内再次收到同一个请求,不再执行响应过程,而是从内存或者高速缓存系统中获取该请求的响应内容。
Django 提供5种缓存机制
1、Memcached 缓存
高性能的分布式内存对象缓存系统,需要安装Memcached系统服务器,DJango 通过python-memcached或者pylibme模块来调用Memcached系统服务器,适合超大型网站使用
# Memcached配置
# BACKEND用于配置缓存引擎,LOCATION是Memcached服务器的IP地址
# django.core.cache.backends.memcached.MemcachedCache
# python-memcached模块连接Memcached
# or
# django.core.cache.backends.memcached.PyLibMCCache
# 使用pylibme连接Memcached
CACHE = {
'default':{
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
# or
# 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': [
'172.19.26.240.11211',
'172.19.26.240.11212',
]
}
}
2、数据库缓存,适合大中型网站
# 数据库缓存配置
# BACKEND用于配置缓存引擎,LOCATION是数据表命名
CACHE = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
3、文件系统缓存,适合中小型网站
# 文件系统缓存
# BACKEND用于配置缓存引擎,LOCATION是文件保存的绝对路径
CACHE = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': 'D:/django_cache',
}
}
4、本地内存缓存, 适合开发测试
# 本地内存缓存
# BACKEND用于配置缓存引擎,LOCATION用于存储器命名,识别单个存储器
CACHE = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique_snow',
}
}
5、虚拟缓存,适合开发测试
# 虚拟缓存缓存
# BACKEND用于配置缓存引擎
CACHE = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
以数据库缓存方式举例
# 数据库缓存配置
# BACKEND用于配置缓存引擎,LOCATION是数据表命名
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
# TIMEOUT 设置缓存的生命周期,秒为单位,若为None,则为永不过期
'TIMEOUT': 60,
'OPTION': {
# MAX_ENTRIES 代表最大缓存记录的数量
'MAX_ENTRIES': 100,
# CULL_FREQUENCY 当缓存达到最大量的时候,剔除缓存的数量
'CULL_FREQUENCY': 3,
}
},
# 可设置多个缓存数据表
'mydjango': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'mydjango_cache_table',
# TIMEOUT 设置缓存的生命周期,秒为单位,若为None,则为永不过期
'TIMEOUT': 60,
'OPTION': {
# MAX_ENTRIES 代表最大缓存记录的数量
'MAX_ENTRIES': 100,
# CULL_FREQUENCY 当缓存达到最大量的时候,剔除缓存的数量
'CULL_FREQUENCY': 3,
}
},
}
# 创建缓存数据表,依赖于DATABASES数据库的配置,如果配置了多库的,缓存数据表默认在default数据库内
python manage.py createcachetable
全站缓存
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', #
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 配置全站缓存
'django.middleware.cache.FetchFromCacheMiddleware',
]
# 设置缓存生命周期
CACHE_MIDDLEWARE_SECONDS = 15
# 设置缓存存放的数据表
# 来自 setting中CACHES的配置
CACHE_MIDDLEWARE_ALIAS= 'default'
# 设置缓存字段表cache_key的值
# 同一个django项目 多个站点之间 共享缓存
CACHE_MIDDLEWARE_KEY_PREFIX= 'myview'
视图缓存
# timeout 缓存的生命周期
# cache setting中缓存存放的数据表,没有设定默认保存在 default中
# key_prefix 同一个django项目 多个站点之间 共享缓存
from django.views.decorators.cache import cache_page
@cache_page(timeout=10, cache='mydjango', key_prefix='myview')
def cacheview(request):
return render(request, 'index2.html')
路由缓存
from django.urls import path
from . import views
from django.views.decorators.cache import cache_page
app_name = 'purchase'
urlpatterns = [
path('index2/', cache_page(timeout=10, cache='mydjango', key_prefix='myurl')(views.cacheview), name='index2')
]
模板缓存
模板缓存使用缓存标签实现
<html>
<body>
<div>
{# 设置模版缓存 #}
{% load cache %}
{# 10代表生命周期 #}
{# MyTemp代表缓存数据的cache_key字段 #}
{# using="MyDjango"代表缓存数据表 #}
{% cache 10 MyTemp using="MyDjango" %}
<div>Hello Django</div>
{# 缓存结束 #}
{% endcache %}
</div>
</body>
</html>
消息框架
消息框架主要在视图和模板中使用。
# settings.py
# 设置消息框架功能引擎
# MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
# MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
# 默认,无需设置
MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'
# views.py
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
# 视图中使用消息框架
def messageView(request):
# 帅选并保留比message.WARNING级别更高的消息提示
# set_level 必须在添加消息提示前使用,否则无法筛选
# messages.set_level(request, messages.WARNING)
# 消息添加方法一
# messages.debug(request, 'DEBUG 类型')
# messages.info(request, 'INFO 类型')
# messages.success(request, 'SUCCESS 类型')
# messages.warning(request, 'WARNING 类型')
# messages.error(request, 'ERROR 类型')
# 消息添加方法二
messages.add_message(request, messages.INFO, 'INFO 类型2')
# 自定义消息类型
# request 代表参数request
# 66 代表参数level
# 自定义类型代表message
# MyDefile 代表参数extra_tags
# messages.add_message(request, 66, '自定义类型', 'MyDefine')
# 获取所有消息提示的最低级别
current_level = messages.get_level(request)
print(current_level)
# 获取当前用户的消息提示对象
mg = messages.get_messages(request)
print(mg)
return render(request, 'messageview.html', locals())
# 视图类使用消息框架
def success(request):
return render(request, 'messageview.html', locals())
# 消息框架定义的SuccessMessageMixin 只能用于数据操作视图,只能与视图类 FormVIew、CreateView、UpdateView和DeleteView结合使用
class IClass(SuccessMessageMixin, CreateView):
model = Product
fields = ['name', 'slogan', 'sell', 'price', 'photo']
template_name = 'indexClass.html'
success_url = '/purchase/success'
success_message = 'created successfully'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>消息提示</title>
</head>
<body>
{% if messages %}
<ul>
{% for m in messages %}
<script>alert('消息内容:');</script> # 以弹窗的形式出现
<li>消息内容:{{ m.message }}</li>
<div>消息类型:{{ m.level }}</div>
<div>消息级别:{{ m.level_tag }}</div>
<div>参数extra_tags的值:{{ m.extra_tags }}</div>
<div>extra_tags和level_tag组合值:{{ m.tags }}</div>
{% endfor %}
</ul>
{% else %}
<script>alert('暂无消息');</script>
{% endif %}
</body>
</html>
分页功能
# views.py
from django.shortcuts import render
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger
from .models import Product
def index5(request, page):
# 获取模型PersonInfo的全部数据
person = Product.objects.all().order_by('-name')
# 设置每一页的数据量为2
p = Paginator(person, 2, 1)
try:
pages = p.get_page(page)
except PageNotAnInteger:
# 如果参数page的数据类型不是整型,就返回第一页数据
pages = p.get_page(1)
except EmptyPage:
# 若用户访问的页数大于实际页数,则返回最后一页的数据
pages = p.get_page(p.num_pages)
return render(request, 'index5.html', locals())
<!DOCTYPE html>
<html lang="zh-hans">
<head>
{% load static %}
<title>分页功能</title>
<link rel="stylesheet" href="{% static 'css/base.css' %}"/>
<link rel="stylesheet" href="{% static 'css/lists.css' %}">
</head>
<body class="app-route model-hkrouteinfo change-list">
<div id="container">
<div id="content" class="flex">
<h1>分页功能</h1>
<div id="content-main">
<div class="module filtered" id="changelist">
<form id="changelist-form" method="post">
<div class="results">
<table id="result_list">
<thead>
<tr>
<th class="action-checkbox-column">
<div class="text">
<span><input type="checkbox"/></span>
</div>
</th>
<th><div class="text">姓名</div></th>
<th><div class="text">年龄</div></th>
</tr>
</thead>
<tbody>
{% for p in pages %}
<tr>
<td class="action-checkbox">
<input type="checkbox" class="action-select">
</td>
<td>{{ p.name }}</td>
<td>{{ p.age }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<p class="paginator">
{# 上一页的路由地址 #}
{% if pages.has_previous %}
<a href="{% url 'purchase:index5' pages.previous_page_number %}">上一页</a>
{% endif %}
{# 列出所有的路由地址 #}
{% for n in pages.paginator.page_range %}
{% if n == pages.number %}
<span class="this-page">{{ pages.number }}</span>
{% else %}
<a href="{% url 'purchase:index5' n %}">{{ n }}</a>
{% endif %}
{% endfor %}
{# 下一页的路由地址 #}
{% if pages.has_next %}
<a href="{% url 'purchase:index5' pages.next_page_number %}">下一页</a>
{% endif %}
</p>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
国际化和本地化
(未成功,待再尝)
centos 系统下安装语言工具
yum install gettext gettext-devel
settings.py
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# 配置国际化和本地化
# 国际化翻译文件路径
LOCALE_PATHS = [os.path.join(BASE_DIR, 'language')]
# 设置支持的语言
LANGUAGES = [
('en', 'English'),
('zh', 'Simplified Chinese'),
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', #
# 使用国际化和本地化功能
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 配置全站缓存
'django.middleware.cache.FetchFromCacheMiddleware',
]
# views.py
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy
def index6(request):
print('qq', request.LANGUAGE_CODE)
if request.LANGUAGE_CODE == 'zh':
language = gettext('Chinese')
# language = gettext_lazy('Chinese')
else:
language = gettext('English')
# language = gettext_lazy('English')
print('ll', language)
return render(request, 'index6.html', locals())
<!DOCTYPE html>
<html>
<head>
{% load i18n %}
<title>{% trans "Choice language" %}</title>
</head>
<body>
<div>
{#将配置属性LANGUAGES进行列举,显示网站支持的语言#}
{% get_available_languages as languages %}
{% trans "Choice language" %}
{% for lang_code, lang_name in languages %}
{% language lang_code %}
<a href="{% url 'purchase:index6'%}">{{ lang_name }}</a>
{% endlanguage %}
{% endfor %}
</div>
<br>
<div>
{% blocktrans %}
The language is {{ language }}
{% endblocktrans %}
</div>
</body>
</html>
以管理员身份执行
python manage.py makemessages -l zh
python manage.py compilemessages
单元测试
from django.test import TestCase
from .models import PersonInfo
from django.contrib.auth.models import User
from django.test import Client
from .models import MyUser
# Create your tests here.
class PersonInfoTest(TestCase):
# 添加数据
def setUp(self):
PersonInfo.objects.create(name='cooper', age=30)
PersonInfo.objects.create(name='bob', age=18)
# 编写测试用例
def test_personinfo_age(self):
# 编写用例
name1 = PersonInfo.objects.get(name='cooper')
name2 = PersonInfo.objects.get(name='bob')
# 判断测试用例的执行结果
self.assertEqual(name1.age, 30)
self.assertEqual(name2.age, 18)
# 编写测试用例
def test_api(self):
c = Client()
response = c.get('/api/')
self.assertIsInstance(response.json(), dict)
def test_html(self):
c = Client()
response = c.get('/index7/')
name = response.context['person'].name
self.assertEqual(name, 'cooper')
self.assertTemplateUsed(response, 'index7.html')
class UserTest(TestCase):
# 添加数据
@classmethod
def setUpTestData(cls):
MyUser.objects.create_user(username='test', password='test', email='testqq@.com',
qq='123', wechat='123', mobile='123')
def test_user(self):
r = MyUser.objects.get(username='test')
self.assertEquals(r.email, 'testqq@.com')
self.assertTrue(r.password)
def test_login(self):
c = Client()
r = c.login(username='test', password='test')
self.assertTrue(r)
python manage.py test # 执行整个项目的测试类
python manage.py test index # 执行index 应用的测试类
python manage.py test index.tests.somemodle # 执行应用下某个测试模型的测试类
python manage.py test --pattern="tests.py" # 执行整个项目带有tests开头的.py的文件
自定义中间件
# 创建自定义中间件
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
"""运行Django将会自动执行"""
self.get_response = get_response
# super().__init__()
print("this is __init__")
def process_request(self, request):
'''完成请求对象的创建,但用户访问的网址尚未与网站路由地址匹配'''
print("this is process_request")
def process_view(self, request, func, *args, **kwargs):
"""完成用户访问的网址,与路由地址匹配,但尚未执行视图函数"""
print("this is process_view")
def process_exception(self, request, exception):
"""在执行视图函数的时候发生异常"""
print("this is process_exception")
def process_response(self, request, response):
"""完成视图函数的执行,但尚未将响应内容返回给浏览器"""
print("this is process_response")
print('rr', response)
print('r1', type(response))
return response
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', #
# 使用国际化和本地化功能
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 配置全站缓存
'django.middleware.cache.FetchFromCacheMiddleware',
'web1.myMiddleware.MyMiddleware', # 自定义中间件
]
异步编程
在Django中启用ASGI(Asynchronous Server Gateway Interface),你需要做以下几步:
确保你的Django版本是3.1或更高。
在你的Django项目的settings.py文件中,找到ASGI_APPLICATION设置,并确保它指向你的Django应用的ASGI应用实例。
如果你的Django项目名为myproject,可能会看起来像这样
# settings.py
# ...
ASGI_APPLICATION = 'myproject.asgi.application'
# ...
创建一个asgi.py文件在你的Django项目根目录下。这个文件会导入并定义ASGI应用实例
# asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_asgi_application()
配置你的服务器来使用ASGI。这通常涉及到选择一个ASGI服务器或者中间件,比如Uvicorn或Daphne。
例如,使用Uvicorn启动ASGI服务器的命令如下:
pip3 install uvicorn
在Django项目中使用uvicorn作为异步的ASGI服务器,你可以通过以下命令来指定IP和端口启动项目:
uvicorn your_project_name.asgi:application --host 0.0.0.0 --port 8000
uvicorn your_project_name.asgi:application --reload --host 0.0.0.0 --port 8000
--reload 参数会在每次代码更改后自动重启服务器,这对开发环境很有用,但不推荐在生产环境使用,因为它可能会影响性能。
同步任务和异步任务
应用下创建 tasks.py
import time
import asyncio
from asgiref.sync import sync_to_async
from .models import TaskInfo
# 异步任务
async def asyns():
startTime = time.time()
for num in range(1, 6):
await asyncio.sleep(1)
print('异步任务', num)
await sync_to_async(TaskInfo.objects.create, thread_sensitive=True)(task='异步任务')
print('异步任务完成 用时:', time.time() - startTime )
# 同步任务
def syns():
startTime = time.time()
for num in range(1, 6):
time.sleep(1)
print('同步任务', num)
TaskInfo.objects.create(task='同步任务')
print('同步任务完成,用时:', time.time() - startTime)
views.py
import asyncio
from .tasks import syns, asyns
from django.http import HttpResponse
# 同步视图调用同步任务
def synView(request):
syns()
return HttpResponse('Hello this is syns')
# 异步视图调用异步任务
async def asynView(request):
loop = asyncio.get_event_loop()
loop.create_task(asyns())
return HttpResponse('Hello this is asyns!')
信号机制
创建一个信号文件 如signals.py
from django.core.signals import request_started # HTTP 请求之间触发
import time
# 方法一
# 设置内置信号request_started 的回调函数signal_request
def signal_request(sender, **kwargs):
time.sleep(10)
print('request is coming.')
print('sender', sender)
print('kwargs', kwargs)
# 将内置信号request_started 和回调函数 signal_request绑定
request_started.connect(signal_request)
# 方法二
from django.dispatch import receiver
# 使用内置函数receiver作为回调函数的装饰器
# 将内置信号request_started 和回调函数signal_request_2绑定
@receiver(request_started)
def signal_request_2(sender, **kwargs):
time.sleep(10)
print('request_2 is coming.')
print('sender_2', sender)
print('kwargs_2', kwargs)
在视图中导入即可
from web1.signalsw import signal_request, signal_request_2
自定义信号
signals.py
from app1.models import TaskInfo
from django.dispatch import Signal
my_signals = Signal()
# 注册信号的回调函数
def mySignal(sender, **kwargs):
print('sender is ', sender)
print('kwargs is ', kwargs)
print('TaskInfos ', TaskInfo.objects.all())
# 将自定义信号my_signals 和回调函数mySignal进行绑定
my_signals.connect(mySignal)
# 如果一个信号有多个回调函数,使用connect 和 disconnect进行切换
# my_signals.disconnect(mySignal)
views.py
# 同步视图调用同步任务
from web1.signalsw import my_signals
def synView(request):
my_signals.send(sender='MYSignal', name='cooper', age=18) # 同步函数中使用
syns()
return HttpResponse('Hello this is syns')
举例 ——订单创建和取消
from django.db.models.signals import post_save
from app1.models import ProductInfo, OrderInfo
def signal_orders(sender, **kwargs):
print('pre_save is coming.')
print('kwargs', kwargs)
# instance 代表当前修改或者新增的对象
instance = kwargs.get('instance')
# created 代表当前操作是否在模型中新增数据, 如新增则为True
created = kwargs.get('created')
# using 代表当前使用的数据库
# 如果连接了多个数据库,则显示当前修改的数据表所在的数据库名称
using = kwargs.get('using')
# update_fields 控制需要更新的字段 默认是None
update_fields = kwargs.get('update_fields')
if instance.state == 0: # 说明订单被取消,商品数量+1
p = ProductInfo.objects.get(id=instance.product_id)
p.number += 1
p.save()
elif instance.state == 1:
p = ProductInfo.objects.get(id=instance.product_id)
p.number -= 1
p.save()
print('all product', ProductInfo.objects.all())
post_save.connect(signal_orders, sender=OrderInfo)
def createView(request):
product_id = request.GET.get('product_id', '')
buyer = request.GET.get('buyer', '')
if product_id and buyer:
o = OrderInfo(product_id=product_id, buyer=buyer)
o.save()
return HttpResponse('订单被创建')
return HttpResponse('参数无效')
def cancelView(request):
id = request.GET.get('id', '')
if id:
o = OrderInfo.objects.get(id=int(id))
if o.state == 1:
o.state = 0
o.save()
return HttpResponse('订单被取消')
return HttpResponse('订单未被取消')
return HttpResponse('参数无效')