笔记15:Django提高篇

django提高php

(1)打包应用程序:可重用性html

  • 打包 Python 程序须要工具:setuptools 。打包时候建议使用django-appname
  • 1 在你的 Django 项目目录外建立一个名为 django-polls 的文件夹,用于盛放 polls
  • 2 将 polls 目录移入 django-polls 目录。
  • 3 建立一个名为 django-polls/README.rst 的文件,包含如下内容:
=====
Polls
=====

Polls is a simple Django app to conduct Web-based polls. For each
question, visitors can choose between a fixed number of answers.

Detailed documentation is in the "docs" directory.

Quick start
-----------

1. Add "polls" to your INSTALLED_APPS setting like this::

    INSTALLED_APPS = [
        ...
        'polls',
    ]

2. Include the polls URLconf in your project urls.py like this::

    path('polls/', include('polls.urls')),

3. Run `python manage.py migrate` to create the polls models.

4. Start the development server and visit http://127.0.0.1:8000/admin/
   to create a poll (you'll need the Admin app enabled).

5. Visit http://127.0.0.1:8000/polls/ to participate in the poll.
  • 4 建立一个 django-polls/LICENSE 文件。选择一个非本教程使用的受权协议,可是要足以说明发布代码没有受权证书是 不可能的 。Django 和不少兼容 Django 的应用是以 BSD 受权协议发布的;不过,你能够本身选择一个受权协议。只要肯定你选择的协议可以限制将来会使用你的代码的人。
  • 下一步咱们将建立 setup.py 用于说明如何构建和安装应用的细节。关于此文件的完整介绍超出了此教程的范围,可是 setuptools docs 有详细的介绍。建立文件 django-polls/setup.py 包含如下内容:
import os
from setuptools import find_packages, setup

with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
    README = readme.read()

# allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))

setup(
    name='django-polls',
    version='0.1',
    packages=find_packages(),
    include_package_data=True,
    license='BSD License',  # example license
    description='A simple Django app to conduct Web-based polls.',
    long_description=README,
    url='https://www.example.com/',
    author='Your Name',
    author_email='yourname@example.com',
    classifiers=[
        'Environment :: Web Environment',
        'Framework :: Django',
        'Framework :: Django :: X.Y',  # replace "X.Y" as appropriate
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',  # example license
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Topic :: Internet :: WWW/HTTP',
        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
    ],
)
  • 6 默认包中只包含 Python 模块和包。为了包含额外文件,咱们须要建立一个名为 MANIFEST.in 的文件。上一步中关于 setuptools 的文档详细介绍了这个文件。为了包含模板、README.rst 和咱们的 LICENSE 文件,建立文件 django-polls/MANIFEST.in 包含如下内容:
include LICENSE
include README.rst
recursive-include polls/static *
recursive-include polls/templates *
  • 7 在应用中包含详细文档是可选的,但咱们推荐你这样作。建立一个空目录 django-polls/docs 用于将来编写文档。额外添加一行至 django-polls/MANIFEST.in
recursive-include docs *
  • 8 试着构建你本身的应用包经过 ptyhon setup.py sdist (在 django-polls``目录内)。这将建立一个名为 ``dist 的目录并构建你本身的应用包, django-polls-0.1.tar.gz
C:\Users\Administrator\Desktop\django-polls> python setup.py sdist

(2)使用本身的包名python

因为咱们把 polls 目录移出了项目,因此它没法工做了。咱们如今要经过安装咱们的新 django-polls 应用来修复这个问题。mysql

C:\Users\Administrator\Desktop\django-polls\dist>pip install --user django-polls-0.1.tar.gz

1560310208431

(3)发布应用,能够邮件,github等等方式上传git

(4)模型github

(5)查询web

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

>>> all_entries = Entry.objects.all()

Entry.objects.filter(pub_date__year=2006)
Entry.objects.all().filter(pub_date__year=2006)

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

>>> one_entry = Entry.objects.get(pk=1)

>>> Entry.objects.all()[5:10] # OFFSET 5 LIMIT 5

>>> Entry.objects.order_by('headline')[0]

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

Entry.objects.get(headline__exact="Cat bites dog")

>>> Blog.objects.get(name__iexact="beatles blog")

Entry.objects.get(headline__contains='Lennon')

SELECT ... WHERE headline LIKE '%Lennon%';

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

>>> print([e.headline for e in Entry.objects.all()])

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

>>> e.delete()

>>> Entry.objects.filter(pub_date__year=2005).delete()

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
e.entrydetail = ed

(5)聚合sql

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()

class Publisher(models.Model):
    name = models.CharField(max_length=300)

class Book(models.Model):
    name = models.CharField(max_length=300)
    pages = models.IntegerField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    rating = models.FloatField()
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    pubdate = models.DateField()

class Store(models.Model):
    name = models.CharField(max_length=300)
    books = models.ManyToManyField(Book)
# Total number of books.
>>> Book.objects.count()

>>> Book.objects.filter(publisher__name='BaloneyPress').count()

# Average price across all books.
>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))

# Each publisher, each with a count of books as a "num_books" attribute.
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))

# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books

>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

(6)搜索数据库

>>> Entry.objects.filter(body_text__search='cheese')

>>> Entry.objects.annotate(
...     search=SearchVector('blog__tagline', 'body_text'),
... ).filter(search='cheese')

(7)Managerapache

from django.db import models

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        with connection.cursor() as cursor:
            cursor.execute("""
                SELECT p.id, p.question, p.poll_date, COUNT(*)
                FROM polls_opinionpoll p, polls_response r
                WHERE p.id = r.poll_id
                GROUP BY p.id, p.question, p.poll_date
                ORDER BY p.poll_date DESC""")
            result_list = []
            for row in cursor.fetchall():
                p = self.model(id=row[0], question=row[1], poll_date=row[2])
                p.num_responses = row[3]
                result_list.append(p)
        return result_list

class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    poll_date = models.DateField()
    objects = PollManager()

class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    person_name = models.CharField(max_length=50)
    response = models.TextField()
class ExtraManager(models.Model):
    extra_manager = OtherManager()

    class Meta:
        abstract = True

class ChildC(AbstractBase, ExtraManager):
    # ...
    # Default manager is CustomManager, but OtherManager is
    # also available via the "extra_manager" attribute.
    pass

(8)raw SQL queries

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)

>>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')

>>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
>>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]

>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
>>> for p in people:
...     print("%s is %s." % (p.first_name, p.age))

>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

from django.db import connection

def my_custom_sql(self):
    with connection.cursor() as cursor:
        cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
        cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
        row = cursor.fetchone()
    return row

cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])

>>> cursor.fetchall()

with connection.cursor() as c:
    c.execute(...)

with connection.cursor() as cursor:
    cursor.callproc('test_procedure', [1, 'test'])

(9) 数据事务

a.save() # Succeeds, but may be undone by transaction rollback
try:
    b.save() # Could throw exception
except IntegrityError:
    transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone

(10)多数据库

DATABASES = {
    'default': {
        'NAME': 'app_data',
        'ENGINE': 'django.db.backends.postgresql',
        'USER': 'postgres_user',
        'PASSWORD': 's3krit'
    },
    'users': {
        'NAME': 'user_data',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'priv4te'
    }
}
$ ./manage.py migrate --database=users
$ ./manage.py migrate --database=users
$ ./manage.py migrate --database=customers
from django.contrib import admin

# Specialize the multi-db admin objects for use with specific models.
class BookInline(MultiDBTabularInline):
    model = Book

class PublisherAdmin(MultiDBModelAdmin):
    inlines = [BookInline]

admin.site.register(Author, MultiDBModelAdmin)
admin.site.register(Publisher, PublisherAdmin)

othersite = admin.AdminSite('othersite')
othersite.register(Publisher, MultiDBModelAdmin)
from django.db import connections
with connections['my_db_alias'].cursor() as cursor:
    ...

(11) URL调度器

from django.urls import path
from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<int:year>/', views.year_archive),
    path('articles/<int:year>/<int:month>/', views.month_archive),
    path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
]
from django.urls import re_path

urlpatterns = [
    re_path(r'^blog/(page-(\d+)/)?$', blog_articles),                  # bad
    re_path(r'^comments/(?:page-(?P<page_number>\d+)/)?$', comments),  # good
]
from django.urls import include, path

urlpatterns = [
    # ... snip ...
    path('community/', include('aggregator.urls')),
    path('contact/', include('contact.urls')),
    # ... snip ...
]
# In settings/urls/main.py
from django.urls import include, path

urlpatterns = [
    path('<username>/blog/', include('foo.urls.blog')),
]

# In foo/urls/blog.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.blog.index),
    path('archive/', views.blog.archive),
]

示例

from django.urls import path
from . import views

urlpatterns = [
    #...
    path('articles/<int:year>/', views.year_archive, name='news-year-archive'),
    #...
]
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
{# Or with the year in a template context variable: #}
<ul>
{% for yearvar in year_list %}
<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
{% endfor %}
</ul>
from django.http import HttpResponseRedirect
from django.urls import reverse

def redirect_to_year(request):
    # ...
    year = 2006
    # ...
    return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))

(12)文件上传

forms.py

from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file = forms.FileField()

views.py

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField

def upload_file(request):
    if request.method == 'POST':
        form = ModelFormWithFileField(request.POST, request.FILES)
        if form.is_valid():
            # file is saved
            form.save()
            return HttpResponseRedirect('/success/url/')
    else:
        form = ModelFormWithFileField()
    return render(request, 'upload.html', {'form': form})
def handle_uploaded_file(f):
    with open('some/file/name.txt', 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)

使用 UploadedFile.chunks() 而不是 read() 是为了确保即便是大文件又不会将咱们系统的内存占满。

(13)快捷键函数

  • render(request, template_name, context=None, content_type=None, status=None, using=None)
from django.shortcuts import render

def my_view(request):
    # View code here...
    return render(request, 'myapp/index.html', {
        'foo': 'bar',
    }, content_type='application/xhtml+xml')
  • redirect(to, args, permanent=False, kwargs)
from django.shortcuts import redirect

def my_view(request):
    ...
    obj = MyModel.objects.get(...)
    return redirect(obj)

(14)中间件

中间件是 Django 请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变 Django 的输入或输出。

中间件工厂是一个可调用的程序,它接受 get_response 可调用并返回中间件。中间件是可调用的,它接受请求并返回响应,就像视图同样。

def simple_middleware(get_response):
    # One-time configuration and initialization.

    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

    return middleware

或者它能够写成一个类,它的实例是可调用的,以下:

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

中间件工厂必须接受 get_response 参数。还能够初始化中间件的一些全局状态。记住两个注意事项:

  • Django仅用 get_response 参数初始化您的中间件,所以不能定义 __init__(),由于须要其余参数。
  • 与每次请求调用 __call__() 方法不一样,当 Web 服务器启动时,__init__() 只被称为一次

激活中间件

若要激活中间件组件,请将其添加到 Django 设置中的 MIDDLEWARE 列表中。在 MIDDLEWARE 中,每一个中间件组件由字符串表示:指向中间件工厂的类或函数名的完整 Python 路径。例如,这里建立的默认值是 django-admin startproject

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',
]

MIDDLEWARE 的顺序很重要,由于中间件会依赖其余中间件。例如:类 AuthenticationMiddleware 在会话中存储通过身份验证的用户;所以,它必须在 SessionMiddleware 后面运行 。中间件。Session中间件。请参阅 Middleware ordering ,用于一些关于 Django 中间件类排序的常见提示。

(15) 会话

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

This simplistic view logs in a "member" of the site:

def login(request):
    m = Member.objects.get(username=request.POST['username'])
    if m.password == request.POST['password']:
        request.session['member_id'] = m.id
        return HttpResponse("You're logged in.")
    else:
        return HttpResponse("Your username and password didn't match.")

...And this one logs a member out, according to login() above:

def logout(request):
    try:
        del request.session['member_id']
    except KeyError:
        pass
    return HttpResponse("You're logged out.")

(16)表单

16.1 Get与POST

处理表单时只会用到 GETPOST 两种HTTP方法。Django的登陆表单使用 POST 方法传输数据,在这个方法中浏览器会封装表单数据,为了传输会进行编码,而后发送到服务端并接收它的响应。相比之下,GET 方法将提交的数据捆绑到一个字符串中,并用它来组成一个URL。该URL包含了数据要发送的地址以及一些键值对应的数据。若是您在Django文档中进行一次搜索,就会看到这点,它会生成一个形似 https://docs.djangoproject.com/search/?q=forms&release=1 的URL。

GETPOST 一般用于不一样的目的。

任何可能用于更改系统状态的请求应该使用 POST —— 好比一个更改数据库的请求。GET 应该只被用于不会影响系统状态的请求。

GET 方法也不适合密码表单,由于密码会出如今URL中,因而也会出如今浏览器的历史记录以及服务器的日志中,并且都是以纯文本的形式。它也不适合处理大量的数据或者二进制数据,好比一张图片。在WEB应用的管理表单中使用 GET 请求具备安全隐患:攻击者很容易经过模拟请求来访问系统的敏感数据。 POST 方法经过与其余像Django的 CSRF protection 这样的保护措施配合使用,能对访问提供更多的控制。另外一方面, GET 方法适用于诸如网页搜索表单这样的内容,由于这类呈现为一个 GET请求的URL很容易被存为书签、分享或从新提交。

<form action="/your-name/" method="post">
    <label for="your_name">Your name: </label>
    <input id="your_name" type="text" name="your_name" value="{{ current_name }}">
    <input type="submit" value="OK">
</form>

forms.py

from django import forms

class NameForm(forms.Form):
    your_name = forms.CharField(label='Your name', max_length=100)

views.py

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import NameForm

def get_name(request):
    # if this is a POST request we need to process the form data
    if request.method == 'POST':
        # create a form instance and populate it with data from the request:
        form = NameForm(request.POST)
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            # ...
            # redirect to a new URL:
            return HttpResponseRedirect('/thanks/')

    # if a GET (or any other method) we'll create a blank form
    else:
        form = NameForm()

    return render(request, 'name.html', {'form': form})

视图将再次建立一个表单实例并使用请求中的数据填充它: form = NameForm(request.POST) 这叫“绑定数据到表单” (如今它是一张 绑定的 表单)。调用表单的 is_valid() 方法;若是不为 True ,咱们带着表单返回到模板。此次表单再也不为空( 未绑定 ),因此HTML表单将用以前提交的数据进行填充,放到能够根据须要进行编辑和修正的位置。若是 is_valid()True ,咱们就能在其 cleaned_data 属性中找到全部经过验证的表单数据。咱们能够在发送一个HTTP重定向告诉浏览器下一步去向以前用这些数据更新数据库或者作其余处理。

name.html

<form action="/your-name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit">
</form>

16.2 详解Django Form类

forms.py

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField(widget=forms.Textarea)
    sender = forms.EmailField()
    cc_myself = forms.BooleanField(required=False)

views.py

from django.core.mail import send_mail

if form.is_valid():
    subject = form.cleaned_data['subject']
    message = form.cleaned_data['message']
    sender = form.cleaned_data['sender']
    cc_myself = form.cleaned_data['cc_myself']

    recipients = ['info@example.com']
    if cc_myself:
        recipients.append(sender)

    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect('/thanks/')
<p><label for="id_subject">Subject:</label>
    <input id="id_subject" type="text" name="subject" maxlength="100" required></p>
<p><label for="id_message">Message:</label>
    <textarea name="message" id="id_message" required></textarea></p>
<p><label for="id_sender">Sender:</label>
    <input type="email" name="sender" id="id_sender" required></p>
<p><label for="id_cc_myself">Cc myself:</label>
    <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>

(17)用户认证

Django 自带一个用户验证系统。它负责处理用户帐号、组、权限和基于cookie的用户会话。文档的这部分解释了默认的实现如何开箱即用,以及如何扩展和自定义以知足你的项目需求。

(18)验证系统

建立用户

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

# At this point, user is a User object that has already been saved
# to the database. You can continue to change its attributes
# if you want to change other fields.
>>> user.last_name = 'Lennon'
>>> user.save()

建立超级用户

$ python manage.py createsuperuser --username=joe --email=joe@example.com

更改密码

>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username='john')
>>> u.set_password('new password')
>>> u.save()

验证用户

from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    # A backend authenticated the credentials
else:
    # No backend authenticated the credentials

登出

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

限制对未登陆用户的访问:装饰器

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

(19)Django缓存框架

动态网站存在一个基本权衡是——它们是动态的。每次用户请求一个页面,web 服务器须要提供各类各样的计算——从数据库查询到模板渲染再到业务逻辑——最后创建页面呈现给用户。从处理开销的角度来看,这比标准读取文件系统服务安排的开销要高得多。下面是一些伪代码解释了动态网站生成页面的时候,缓存是怎么工做的:

given a URL, try finding that page in the cache
if the page is in the cache:
    return the cached page
else:
    generate the page
    save the generated page in the cache (for next time)
    return the generated page

19.1 Memcached内存缓存

Memcached 是一个彻底基于内存的缓存服务器,是 Django 原生支持的最快、最高效的缓存类型,最初被开发出来用于处理 LiveJournal.com 的高负载,随后由 Danga Interactive 开源。Facebook 和 Wikipedia 等网站使用它来减小数据库访问并显著提升网站性能。

Memcached 以一个守护进程的形式运行,而且被分配了指定数量的 RAM。它所作的就是提供一个快速接口用于在缓存中添加,检索和删除数据。全部数据都直接存储在内存中,所以不会产生数据库或文件系统使用的开销。

在安装 Memcached 自己后,你还须要安装一个 Memcached 绑定。有许多可用的 Python Memcached 绑定,最多见的两个是 python-memcached 和pylibmc

在 Django 中使用 Memcached :

  • 将 BACKEND 设置为 django.core.cache.backends.memcached.MemcachedCache 或者 django.core.cache.backends.memcached.PyLibMCCache (取决于你所选择的 memcached 绑定)
  • 将 LOCATION 设置为 ip:port 值,其中 ip 是 Memcached 守护进程的 IP 地址,port 是运行 Memcached 的端口;或者设置为一个 unix:path 值,其中 path 是 Memcached Unix 套接字文件的路径。

在这个示例中,Memcached 使用 python-memcached 绑定,在 localhost (127.0.0.1) 端口 11211 上运行:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

在这个示例中, Memcached 可经过本地 Unix 套接字文件 /tmp/memcached.sock 使用 python-memcached 绑定获得:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }
}

当使用 pylibmc 绑定时,不要包含 unix:/ 前缀:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '/tmp/memcached.sock',
    }
}

Memcached 的一个出色功能是它可以在多个服务器上共享缓存。这意味着您能够在多台计算机上运行 Memcached 守护程序,程序会视这组计算机为单个缓存,而无需在每台机器上复制缓存值。要使用此功能,须要在 LOCATION 中包含全部服务器的地址,能够是分号或者逗号分隔的字符串,也能够是一个列表。

在这个示例中,缓存经过端口 11211 的 IP 地址 172.19.26.240 、 172.19.26.242 运行的 Memcached 实例共享:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

在如下示例中,缓存经过在 IP 地址 172.19.26.240(端口号 11211),172.19.26.242(端口号 11212)和 172.19.26.244(端口号 11213)上运行的 Memcached 实例共享:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11212',
            '172.19.26.244:11213',
        ]
    }
}

关于 Memcached 的最后一点是,基于内存的缓存有一个缺点:由于缓存的数据存储在内存中,若是服务器崩溃,那么数据将会丢失。显然,内存不适用于持久数据存储,所以不要依赖基于内存的缓存做为你惟一的数据存储。毫无疑问,没有任何 Django 缓存后端应该被用于持久存储——它们都是适用于缓存的解决方案,而不是存储——咱们在这里指出这一点是由于基于内存的缓存是格外临时的。

19.2 数据库缓存

Django 能够在数据库中存储缓存数据。若是你有一个快速、索引正常的数据库服务器,这种缓存效果最好。

用数据库表做为你的缓存后端:

  • BACKEND 设置为 django.core.cache.backends.db.DatabaseCache
  • LOCATION 设置为 数据库表的表名。这个表名能够是没有使用过的任何符合要求的名称。

在这个例子中,缓存表的名称是 my_cache_table

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }
}
  • 建立缓存表

使用数据库缓存以前,必须经过下面的命令建立缓存表:

python manage.py createcachetable

这将在数据库中建立一个表,该表的格式与 Django 数据库缓存系统指望的一致。该表的表名取自 LOCATION

若是你正在使用多数据库缓存, createcachetable 会对每一个缓存建立一个表。

若是你正在使用多数据库, createcachetable 将遵循数据库路由的 allow_migrate() 方法。

migrate 同样, createcachetable 不会影响已经存在的表,它只建立缺失的表。

要打印即将运行的 SQL,而不是运行它,请使用 createcachetable --dry-run 选项。

  • 多数据库

若是在多数据库中使用缓存,你也须要设置数据库缓存表的路由指令。由于路由的缘由,数据库缓存表在 django_cache 应用程序中显示为 CacheEntry 的模型名。这个模型不会出如今模型缓存中,但模型详情可用于路由目的。

好比,下面的路由能够将全部缓存读取操做指向 cache_replica ,而且全部的写操做指向 cache_primary。缓存表将会只同步到 cache_primary

class CacheRouter:
    """A router to control all database cache operations"""

    def db_for_read(self, model, **hints):
        "All cache read operations go to the replica"
        if model._meta.app_label == 'django_cache':
            return 'cache_replica'
        return None

    def db_for_write(self, model, **hints):
        "All cache write operations go to primary"
        if model._meta.app_label == 'django_cache':
            return 'cache_primary'
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        "Only install the cache model on primary"
        if app_label == 'django_cache':
            return db == 'cache_primary'
        return None

若是你没有指定路由指向数据库缓存模型,缓存后端将使用 默认 的数据库。固然,若是没使用数据库缓存后端,则无需担忧为数据库缓存模型提供路由指令。

19.3 文件系统缓存

基于文件的后端序列化并保存每一个缓存值做为单独的文件。要使用此后端,可将 BACKEND 设置为 "django.core.cache.backends.filebased.FileBasedCache" 并将 LOCATION 设置为一个合适的路径。好比,在 /var/tmp/django_cache 存储缓存数据,使用如下配置:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
    }
}

若是使用 Windows 系统,将驱动器号放在路径开头,以下:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/foo/bar',
    }
}

目录路径应该是绝对路径——所以,它应该以文件系统根目录开始。无需担忧是否须要以斜杠结尾。

确保这个配置指向的目录存在,而且可由运行 Web 服务器的系统用户读写。继续上面的例子,若是服务器被用户 apache 运行,确保目录 /var/tmp/django_cache 存在而且可被用户 apache 读写。

19.4 本地内存缓存

若是在配置文件中没有指定缓存,那么将默认使用本地内存缓存。若是你想要内存缓存的速度优点,但又没有条件使用 Memcached,那么能够考虑本地内存缓存后端。

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

LOCATION 被用于标识各个内存存储。若是只有一个 locmem 缓存,你能够忽略 LOCATION 。可是若是你有多个本地内存缓存,那么你至少要为其中一个起个名字,以便将它们区分开。这种缓存使用最近最少使用(LRU)的淘汰策略。

注意,每一个进程将有它们本身的私有缓存实例,这意味着不存在跨进程的缓存。这也一样意味着本地内存缓存不是特别节省内存,所以它或许不是生成环境的好选择,不过它在开发环境中表现很好。

19.5 虚拟缓存(用于开发模式)

Django 带有一个实际上不是缓存的 "虚拟" 缓存,它只是实现缓存接口,并不作其余操做。若是你有一个正式网站在不一样地方使用了重型缓存,但你不想在开发环境使用缓存,并且不想为这个特殊场景而修改代码的时候,这将很是有用。要激活虚拟缓存,像这样设置 BACKEND

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

19.6 使用自定义的缓存后台

虽然 Django 自带一些缓存后端,但有时你也想使用自定义的缓存后端。当使用第三方缓存后端时,使用 Python 导入路径做为 Cache 设置的后端,像这样:

CACHES = {
    'default': {
        'BACKEND': 'path.to.backend',
    }
}

若是你正在建立本身的后端,你可使用标准缓存做为参考实现。你在 Django 源代码的 django/core/cache/backends/ 目录找到代码。

注意:除非是使人信服的理由,诸如服务器不支持缓存,不然你应该使用 Django 附带的缓存后端。他们通过了良好的测试并易于使用。

19.7 缓存参数

每一个缓存后端能够经过额外的参数来控制缓存行为。这些参数在 CACHES 设置中做为附加键提供。有效参数以下:

  • 缓存:setting:TIMEOUT :用于缓存的默认超时时间(以秒为单位)。这个参数默认为 300 秒(5分钟)。你能够设置 TIMEOUTNone,所以,默认状况下缓存键永不过期。值为 0 会致使键马上过时(实际上就是不缓存)。

  • OPTIONS :任何选项应该传递到缓存后端。有效选项列表将随着每一个后端变化,而且由第三方库缓存支持的后端直接传递它们的选项到底层缓存库。

    实现自有的淘汰策略的缓存后端(好比 locmem, filesystemdatabase 后端)将遵循如下选项:

    • MAX_ENTRIES :删除旧值以前容许缓存的最大条目。默认是 300

    • CULL_FREQUENCY :当达到 MAX_ENTRIES 时被淘汰的部分条目。实际比率为 1 / CULL_FREQUENCY ,当达到 MAX_ENTRIES 时,设置为2就会淘汰一半的条目。这个参数应该是一个整数,默认为3。

      CULL_FREQUENCY 的值为 0 意味着当达到 MAX_ENTRIES 缓存时,整个缓存都会被清空。在一些后端(尤为是 database ),这会使以更多的缓存未命中为代价来更快的进行淘汰。

    Memcached 后端传递 OPTIONS 的内容做为键参数到客户端构造函数,从而容许对客户端行为进行更高级的控制。参见下文:

  • KEY_PREFIX :将自动包含(默认预先添加)到Django 服务器使用的全部缓存键的字符串。

    查看 cache documentation 获取更多信息。

  • VERSION :经过 Django 服务器生成的缓存键的默认版本号。

    查看 cache documentation 获取更多信息。

  • KEY_FUNCTION :一个包含指向函数的路径的字符串,该函数定义将如何前缀、版本和键组成最终的缓存键。

    查看 cache documentation 获取更多信息。

在这个例子中,文件系统后端正被设置成60秒超时时间,而且最大容量是1000条。

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
        'TIMEOUT': 60,
        'OPTIONS': {
            'MAX_ENTRIES': 1000
        }
    }
}

这个的例子是基于 python-memcached 后端的设置,对象大小限制在 2MB :

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'server_max_value_length': 1024 * 1024 * 2,
        }
    }
}

这个例子是基于 pylibmc 后端的设置,改设置支持二进制协议、SASL 验证和 ketama行为模式:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'binary': True,
            'username': 'user',
            'password': 'pass',
            'behaviors': {
                'ketama': True,
            }
        }
    }
}

更多缓存知识访问>> https://docs.djangoproject.com/zh-hans/2.2/topics/cache/

(20)发送邮件

20.1 快速上手,仅需两行代码:

from django.core.mail import send_mail

send_mail(
    'Subject here',
    'Here is the message.',
    'from@example.com',
    ['to@example.com'],
    fail_silently=False,
)

邮件是经过 SMTP 主机和端口发送的,由配置项 EMAIL_HOSTEMAIL_PORT 指定。若是配置了 EMAIL_HOST_USEREMAIL_HOST_PASSWORD ,那么它们将被用来验证 SMTP 服务器。配置项 EMAIL_USE_TLSEMAIL_USE_SSL 控制是否使用安全链接。发送邮件最简单的方式就是使用 django.core.mail.send_mail()

send_mail():send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None, html_message=None)

  • 参数 subject, message, from_emailrecipient_list 是必须的。

  • recipient_list: 一个字符串列表,每项都是一个邮箱地址。recipient_list中的每一个成员均可以在邮件的 "收件人:" 中看到其余的收件人。
  • fail_silently: 为 Falsesend_mail() 发生错误时抛出 smtplib.SMTPException 。可在 smtplib 文档找到一系列可能的异常,它们都是 SMTPException 的子类。
  • auth_user: 可选的用户名,用于验证登录 SMTP 服务器。 若未提供,Django 会使用 EMAIL_HOST_USER 指定的值。
  • auth_password: 可选的密码,用于验证登录 SMTP 服务器。若未提供, Django 会使用 EMAIL_HOST_PASSWORD 指定的值。
  • connection: 可选参数,发送邮件使用的后端。若未指定,则使用默认的后端。查询 邮件后端 文档获取更多细节。
  • html_message: 若提供了 html_message,会使邮件成为 multipart/alternative 的实例, message 的内容类型则是 text/plain ,而且 html_message 的内容类型是 text/html

返回值会是成功发送的信息的数量(只能是 01 ,由于同时只能发送一条消息)。

20.2 批量发送邮件

django.core.mail.send_mass_mail() 用于批量发送邮件。

send_mass_mail():(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)

datatuple 是一个元组,形式以下:

(subject, message, from_email, recipient_list)

datatuple 参数的每一个元素会生成一份独立的邮件内容。就像 send_mail() 中的同样, recipient_list 中的每一个收件人会在邮件的 "收件人:" 中看到其余收件人的地址同样.举个例子,如下代码会向两个不一样的收件人列表发送两封不一样的邮件,却复用了同一条链接:返回值是成功发送的消息的数量。

message1 = ('Subject here', 'Here is the message', 'from@example.com', ['first@example.com', 'other@example.com'])
message2 = ('Another Subject', 'Here is another message', 'from@example.com', ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)

20.3 显示所有收件人与独立收件人

如下发送了一封邮件给 john@example.comjane@example.com,他们都出如今 "收件人:":

send_mail(
    'Subject',
    'Message.',
    'from@example.com',
    ['john@example.com', 'jane@example.com'],
)

如下分别发送了一封邮件给 john@example.comjane@example.com,他们收到了独立的邮件:

datatuple = (
    ('Subject', 'Message.', 'from@example.com', ['john@example.com']),
    ('Subject', 'Message.', 'from@example.com', ['jane@example.com']),
)
send_mass_mail(datatuple)

20.4 防止头注入

Header injection 是一个开发漏洞,攻击者能够利用它在邮件头插入额外信息,以控制脚本生成的邮件中的 "收件人:" 和 "发件人:" 内容。Django 的邮件函数包含了以上全部的反头注入功能,经过在头中禁止新的行。若是 subjectfrom_emailrecipient_list 包含了新行(无论是 Unix,Windows 或 Mac 格式中的哪种),邮件函数(好比 send_mail() )都会抛出一个 django.core.mail.BadHeaderErrorValueError 的子类),这会中断邮件发送。你须要在将参数传给邮件函数前确保数据的有效性和合法性。

若是邮件的 内容 的开始部分包含了邮件头信息,这些头信息只会做为邮件内容原样打印。如下是一个实例视图,从请求的 POST 数据中获取 subjectmessagefrom_email,并将其发送至 admin@example.com ,成功后再重定向至 "/contact/thanks/"

from django.core.mail import BadHeaderError, send_mail
from django.http import HttpResponse, HttpResponseRedirect

def send_email(request):
    subject = request.POST.get('subject', '')
    message = request.POST.get('message', '')
    from_email = request.POST.get('from_email', '')
    if subject and message and from_email:
        try:
            send_mail(subject, message, from_email, ['admin@example.com'])
        except BadHeaderError:
            return HttpResponse('Invalid header found.')
        return HttpResponseRedirect('/contact/thanks/')
    else:
        # In reality we'd use a form class
        # to get proper validation errors.
        return HttpResponse('Make sure all fields are entered and valid.')

关于发送邮件的单元测试资料,参见测试文档中 Email services 章节。

(21)分页

21.1 分页基本操做

>>> from django.core.paginator import Paginator
>>> objects = ['john', 'paul', 'george', 'ringo']
>>> p = Paginator(objects, 2)
>>> p.count
4
>>> p.num_pages
2

>>> page2 = p.page(2)
>>> page2.object_list
['george', 'ringo']

>>> page2.has_next()
False
>>> page2.has_previous()
True
>>> page2.has_other_pages()
True
>>> page2.previous_page_number()
1
>>> page2.start_index() # The 1-based index of the first item on this page
3
>>> page2.end_index() # The 1-based index of the last item on this page
4

21.2 在视图中使用 Paginator

The view function looks like this:

from django.core.paginator import Paginator
from django.shortcuts import render

def listing(request):
    contact_list = Contacts.objects.all()
    paginator = Paginator(contact_list, 25) # Show 25 contacts per page

    page = request.GET.get('page')
    contacts = paginator.get_page(page)
    return render(request, 'list.html', {'contacts': contacts})

In the template list.html, you'll want to include navigation between pages along with any interesting information from the objects themselves:

{% for contact in contacts %}
    {# Each "contact" is a Contact model object. #}
    {{ contact.full_name|upper }}<br>
    ...
{% endfor %}

<div class="pagination">
    <span class="step-links">
        {% if contacts.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ contacts.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
        </span>

        {% if contacts.has_next %}
            <a href="?page={{ contacts.next_page_number }}">next</a>
            <a href="?page={{ contacts.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>

21.3 相关方法与属性

  • Page.has_next()
  • Page.has_previous()
  • Page.has_other_pages()
  • Page.next_page_number()
  • Page.previous_page_number()
  • Page.start_index()
  • Page.end_index()
  • Page.object_list:此页上的对象列表。
  • Page.number:此页的基于 1 的页码。
  • Page.paginator:关联的 Paginator 对象。

(22)性能与优化

22.1 性能优化介绍

清楚地理解你所说的“绩效”是什么很重要,由于它不只仅是一个指标。提升速度多是程序最明显的目标,但有时可能会寻求其余性能改进,例如下降内存消耗或减小对数据库或网络的要求。一个领域的改进一般会提升另外一个领域的性能,但并不老是如此;有时甚至会牺牲另外一个领域的性能。例如,一个程序速度的提升可能会致使它使用更多的内存。更糟糕的是,若是速度提升太过内存不足,以至于系统开始耗尽内存,那么你所作的弊大于利。还有其余的权衡。你本身的时间是一个宝贵的资源,比CPU时间更宝贵。一些改进可能太难实现,或者可能影响代码的可移植性或可维护性。并不是全部的性能改进都值得付出努力。因此,你须要知道你的目标是什么样的性能改进,你也须要知道你有一个很好的理由去瞄准那个方向——并且你须要:

django-debug-toolbar https://github.com/jazzband/django-debug-toolbar/ 是一个很是方便的工具,它能够深刻了解您的代码正在作什么以及花费了多少时间。特别是它能够显示您的页面生成的全部SQL查询,以及每一个查询所用的时间。第三方面板也可用于工具栏,能够(例如)报告缓存性能和模板呈现时间。

22.2 性能优化的几个方面

  • 内存与索引
  • 数据库查询优化
  • 中间件优化
  • 访问时间
  • 缓存
  • 禁用 DEBUG = False

(23)WSGI部署Django

23.1 使用 WSGI 进行部署

WSGI,PythonWeb服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是Python应用程序或框架和Web服务器之间的一种接口,已经被普遍接受, 它已基本达成它的可移植性方面的目标。WSGI是做为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提高可移植Web应用开发的共同点。WSGI是基于现存的[[CGI]]标准而设计的。

  • application对象

用 WSGI 部署的关键是 application callable,应用服务器用它与你的代码交互。 application callable 通常以一个位于 Python 模块中,名为 application 的对象的形式提供,且对服务器可见。startproject 命令建立了文件 <project_name>/wsgi.py,其中包含了 application callable。Django 开发服务器和生产环境的 WSGI 部署都用到了它。

WSGI 服务器从其配置中获取 application callable 的路径。Django 的默认服务器( runserver 命令),从配置项 WSGI_APPLICATION 中获取。默认值是 <project_name>.wsgi.application,指向 <project_name>/wsgi.py 中的 application callable。

  • 配置setting模块

wsgi.py 默认将其设置为 mysite.settingsmysite 即工程名字。这就是 runserver 默认的发现默认配置行为。

注解:因为环境变量是进程级的,因此若是在同一进程运行多个 Django 站点将出错。这在使用 mod_wsgi 时会出现。要避免此问题,为每一个站点在后台进程使用 mod_wsgi 的后台模式,或者在 wsgi.py 中经过 os.environ["DJANGO_SETTINGS_MODULE"]= "mysite.settings" 重写来自环境变量的值。

  • 应用WSGI中间件

要应用 WSGI 中间层,你只需简单包裹应用对象。举个例子,你能够在 wsgi.py 末尾添加如下代码:

from helloworld.wsgi import HelloWorldApplication
application = HelloWorldApplication(application)

若是你想将 Django 应用于一个 WSGI 应用或其它框架联合起来,能够用自定义 WSGI 应用替换 Django 的 WSGI 应用,前者会在稍晚时候将任务委托给 WSGI 应用。

23.2 使用 Apache 和 mod_wsgi 托管 Django

mod_wsgi 是一个 Apache 模块,它能够管理任何 Python WSGI 应用,包括 Django。Django 支持全部支持 mod_wsgi 的 Apache 版本。官方 mod_wsgi 文档 介绍了如何使用 mod_wsgi 的所有细节。你可能更喜欢从 安装和配置文档 开始。

安装并激活 mod_wsgi 后,编辑 Apache 服务器的 httpd.conf 文件,并添加如下内容。若你正在使用的 Apache 版本号早于 2.4,用 Allow from all 替换 Require allgranted,并在其上添加一行 Order deny,allow

WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonHome /path/to/venv
WSGIPythonPath /path/to/mysite.com

<Directory /path/to/mysite.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
  • WSGIScriptAlias 行的第一项是你所指望的应用所在的基础 URL 路径( / 根 url),第二项是 "WSGI 文件" 的位置——通常位于项目包以内(本例中是 mysite)。这告诉 Apache 用该文件中定义的 WSGI 应用响应指定 URL 下的请求。
  • 若是你在某个 virtualenv 内为应用安装项目的 Python 依赖,将该 virtualenv 的路径添加至 WSGIPythonHome 。参考 mod_wsgi virtualenv 指引 获取更多细节。
  • WSGIPythonPath 行确保你的项目包能从 Python path 导入;换句话说, importmysite 能正常工做。
  • <Directory> 片断仅确保 Apache 能访问 wsgi.py 文件。
  • 下一步,咱们须要确认 wsgi.py 文件包含一个 WSGI 应用对象。从 Django 1.4 起, startproject 会自动建立;换而言之,你无需手动建立。查阅 WSGI 概述文档 获取你须要配置的默认内容,以及其它可配置项。

注意1:

若是多个 Django 站点运行在同一 mod_wsgi 进程,它们会共用最早启动的站点配置。能经过如下修改改变行为:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")

wsgi.py 中也这么改:

os.environ["DJANGO_SETTINGS_MODULE"] = "{{ project_name }}.settings"

或经过 使用 mod_wsgi 的后台模式 确保每一个站点都运行于独立的后台进程。

注意2:

为文件上传修复 UnicodeEncodeError

上传名称包含非 ASCII 字符的文件时,若抛出 UnicodeEncodeError,确认 Apache 是否被正确配置,能接受非 ASCII 文件名:

export LANG='en_US.UTF-8'
export LC_ALL='en_US.UTF-8'

常见的配置文件路径是 /etc/apache2/envvars

参考 Unicode 参考指引的 Files 章节获取细节信息。

  • 使用 mod_wsgi 后台模式

为了建立必要的后台进程组并在其中运行 Django 实例,你须要添加合适的 WSGIDaemonProcessWSGIProcessGroup 指令。上述配置在你使用后台模式时须要点小修改,即你不能使用 WSGIPythonPath;做为替换,你要在 WSGIDaemonProcess 中添加 python-path 选项,例如:

WSGIDaemonProcess example.com python-home=/path/to/venv python-path=/path/to/mysite.com
WSGIProcessGroup example.com

若是你想在子目录中开放你的项目(本例中 https://example.com/mysite),你可在上述配置中添加 WSGIScriptAlias

WSGIScriptAlias /mysite /path/to/mysite.com/mysite/wsgi.py process-group=example.com

参考官方 mod_wsgi 文档获取 配置后台模式的细节

23.3 Apache 利用 Django 的用户数据库进行验证

使用 Apache 时,保持多个身份认证数据同步是一个常见的问题,你可让 Apache 直接使用 Django 的 验证系统。这要求 Apache 版本 >= 2.2,且 mod_wsgi >= 2.0。例如这样:

  • 仅为已受权的用户直接从 Apache 提供 static/media 文件。
  • 仅为有特定权限的 Django 用户提供 Subversion 仓库访问。
  • 容许某些用户链接到 mod_dav 建立的 WebDAV 共享。

确保你已按照 Apache 配合 mod_wsgi 文档正确安装并激活了 mod_wsgi。而后,编辑 Apache 配置,添加只容许受权用户查看的位置:

WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonPath /path/to/mysite.com

WSGIProcessGroup %{GLOBAL}
WSGIApplicationGroup %{GLOBAL}

<Location "/secret">
    AuthType Basic
    AuthName "Top Secret"
    Require valid-user
    AuthBasicProvider wsgi
    WSGIAuthUserScript /path/to/mysite.com/mysite/wsgi.py
</Location>

WSGIAuthUserScript 指令告诉 mod_wsgi 在指定 wsgi 脚本中执行 check_password 函数,并传递从提示符获取的用户名和密码。在本例中, WSGIAuthUserScriptWSGIScriptAlias 同样,后者 由 django-admin startproject 建立,定义了应用。

最后,编辑 WSGI 脚本 mysite.wsgi,经过导入 check_password 函数,将 Apache 的认证受权机制接续在你站点的受权机制以后:

import os

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

from django.contrib.auth.handlers.modwsgi import check_password

from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()

/secret/ 开头的请求如今会要求用户认证。

mod_wsgi 可达性控制机制文档 提供了其它受权机制和方法的更多细节和信息。

利用 mod_wsgi 和 Django 用户组(groups)进行受权

mod_wsgi 也提供了将组成员限制至特定路径的功能。

在本例中,Apache 配置应该看起来像这样:

WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py

WSGIProcessGroup %{GLOBAL}
WSGIApplicationGroup %{GLOBAL}

<Location "/secret">
    AuthType Basic
    AuthName "Top Secret"
    AuthBasicProvider wsgi
    WSGIAuthUserScript /path/to/mysite.com/mysite/wsgi.py
    WSGIAuthGroupScript /path/to/mysite.com/mysite/wsgi.py
    Require group secret-agents
    Require valid-user
</Location>

要支持 WSGIAuthGroupScript 指令,一样的 WSGI 脚本 mysite.wsgi 必须也导入 groups_for_user 函数,函数会返回用户所属用户组的列表。

from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user

/secret 的请求如今也会要求用户是 "secret-agents" 用户组的成员。

23.4 如何使用 Gunicorn 托管 Django

Gunicorn ('Green Unicorn') 是一个 UNIX 下的纯 Python WSGI 服务器。它没有其它依赖,容易安装和使用。安装 gunicorn 很是简单,只要执行 pip install gunicorn 便可。

安装 Gunicorn 以后,可使用 gunicorn 命令启动 Gunicorn 服务进程。最简模式下,只须要把包含了 WSGI 应用对象的 application 模块位置告诉 gunicorn,就能够启动了。所以对于典型的 Django 项目,像这样来调用 gunicorn:

gunicorn myproject.wsgi

这样会建立一个进程,包含了一个监听在 127.0.0.1:8000 的线程。前提是你的项目在 Python path 中,要知足这个条件,最简单的方法是在 manage.py 文件所在的目录中运行这条命令。

23.5 如何用 uWSGI 托管 Django

uWSGI 是一个快速的,自我驱动的,对开发者和系统管理员友好的应用容器服务器,彻底由 C 编写。uWSGI 百科介绍了几种 安装流程。Pip (Python 包管理器)能让你仅用一行代码就安装任意版本的 uWSGI。例子:

# Install current stable version.
$ pip install uwsgi

# Or install LTS (long term support).
$ pip install https://projects.unbit.it/downloads/uwsgi-lts.tar.gz

假设你有个叫作 mysite 的顶级项目包,期中包含一个模板 mysite/wsgi.py,模块包含一个 WSGI application 对象。若是你使用的是较新的 Django,这就是你运行 django-admin startproject mysite (使用你的项目名替换 mysite)后获得的目录结构。若该文件不存在,你须要建立它。参考文档 如何使用 WSGI 进行部署 看看你须要配置的默认内容,以及你还能添加什么。

Django 指定的参数以下:

  • chdir:须要包含于 Python 的导入路径的目录的路径——例如,包含 mysite 包的目录。
  • module:要使用的 WSGI 模块——多是 startproject 建立的 mysite.wsgi的模块。
  • env:至少要包括 DJANGO_SETTINGS_MODULE
  • home: 可选的路径,指向你工程的 virtualenv。

示例 ini 配置文件:

[uwsgi]
chdir=/path/to/your/project
module=mysite.wsgi:application
master=True
pidfile=/tmp/project-master.pid
vacuum=True
max-requests=5000
daemonize=/var/log/uwsgi/yourproject.log

示例 ini 配置文件语法:

uwsgi --ini uwsgi.ini

(24)Django FAQ0

  • 发音:Django 发音为 JANG,使用 FANG 来押韵,字母 "D "是不发声的.

    咱们也记录了一段 发音的音频片断.

  • 稳定性:至关稳定。使用 Django 搭建的网站能承受每秒 50000 次点击的流量峰值。

  • 扩展性:能够在任何级别添加硬件——数据库服务器,缓存服务器或 Web /应用程序服务器。

  • MVC 框架:在咱们对 MVC 的解释中,“视图”描述了呈现给用户的数据。数据看起来怎么样并不重要,重要的是哪些数据被呈现。 Django "MTV " 框架--即 "模型(Model) "、 "模板(Template)" 和 "视图(View). "视图(view)"是 Python 中针对一个特定 URL 的回调函数,此回调函数描述了须要展现的数据。展现效果就是模板。在 Django 里面,一个视图(view)描述了哪些数据会被展现。那控制器(Controller)在什么位置?在 Django 中,会根据 Django 的 URL 配置,将请求分发到适当的视图(view)。

  • 数据库:官方文档推荐PostgreSQL

  • 版本选择:Django2.1,2.2支持python3.5+。Django 的第三方插件能够自由设置他们的版本要求。生产中使用 Django,你应该使用稳定版本。

  • 使用图片和文件字段。在模型中使用 FileFieldImageField ,你还须要完成以下步骤:

    1. 在你的 setting 文件中,你须要定义:setting: MEDIA_ROOT 做为 Django 存储上传文件目录的完整路径。(为了提升性能,这些文件不会储存在数据库中)定义: setting: MEDIA_URL 做为该目录的基本公共 URL, 确保该目录可以被 Web 服务器的帐户写入。
    2. 在你的模型中添加 FileField 或者 ImageField ,能够经过定义:attr:~django.db.models.FileField.upload_to 在 MEDIA_ROOT 中明确一个子目录用来上传文件。
    3. 全部将被储存在数据库中的文件路径相同(相对于:setting: MEDIA_ROOT)。你很想用由 Django 提供的:attr:~django.db.models.fields.files.FieldFile.url,好比, 若是:class:~django.db.models.ImageField 被叫作mug_shot, 你就能够获得{{ object.mug_shot.url }}` 图片模板的绝对路径。
  • 导入已有数据库:https://docs.djangoproject.com/zh-hans/2.2/howto/legacy-databases/
  • django只支持单列主键
  • Django 官方不支持 NoSQL 数据库

相关文章
相关标签/搜索