筆記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 數據庫

相關文章
相關標籤/搜索