• Django


    Django是Python下的一款网络服务器框架。Python下有许多款不同的框架。Django是重量级选手中最有代表性的一位。许多成功的网站和APP都基于Django。虽然Django之于Python,达不到Rail之于Ruby的一统江湖的地位,但Django无疑也是Python在网络应用方面的一位主将。

    安装Django

    sudo pip install django

    启动

    使用下面的命令创建项目:

    django-admin.py startproject mysite

    在当前目录下,将生成mysite文件夹。其文件树结构如下:

    复制代码
    mysite
    ├── manage.py
    └── mysite
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
    
    1 directory, 5 files
    复制代码

    进入mysite,启动服务器:

    python manage.py runserver 8000

    上面的8000为端口号。如果不说明,那么端口号默认为8000。

    打开浏览器,访问http://127.0.0.1:8000,可以看到服务器已经在运行:

    虽然有一个能跑的服务器,但什么内容都没有。

    第一个网页

    在http协议中可以看到,网络服务器是“请求-回应”的工作模式。客户向URL发送请求,服务器根据请求,开动后厨,并最终为客人上菜。Django采用的MVC结构,即点单、厨房、储藏室分离。

    我们需要一个指挥员,将URL对应分配给某个对象处理,这需要在mysite/mysite下的urls.py设定。Python会根据该程序,将URL请求分给某个厨师。

    复制代码
    mysite
    ├── manage.py
    └── mysite
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
    
    1 directory, 5 files
    复制代码

    将urls.py修改为:

    复制代码
    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        # Examples:
        # url(r'^$', 'mysite.views.home', name='home'),
        # url(r'^blog/', include('blog.urls')),
    
        url(r'^admin/', include(admin.site.urls)),
        url(r'^$', 'mysite.views.first_page'),
    )
    复制代码

    我们添加了最后一行。它将根目录的URL分配给一个对象进行处理,这个对象是mysite.views.first_page。

    用以处理HTTP请求的这一对象还不存在,我们在mysite/mysite下创建views.py,并在其中定义first_page函数:

    复制代码
    # -*- coding: utf-8 -*-
    
    from django.http import HttpResponse
    
    def first_page(request):
        return HttpResponse("<p>世界好</p>")
    复制代码

    第一行说明字符编码为utf-8,为下面使用中文做准备。first_page函数的功能,是返回http回复,即这里的<p>世界好</p>。first_page有一个参数request,该参数包含有请求的具体信息,比如请求的类型等,这里并没有用到。

    页面效果如下:

    增加app

    一个网站可能有多个功能。我们可以在Django下,以app为单位,模块化的管理,而不是将所有的东西都丢到一个文件夹中。在mysite下,运行manange.py,创建新的app:

    $python manage.py startapp west

    这个新的app叫做west,用来处理西餐。

    我们的根目录下,出现了一个新的叫做west的文件夹。

    复制代码
    mysite/
    ├── manage.py
    ├── mysite
    │   ├── __init__.py
    │   ├── __init__.pyc
    │   ├── settings.py
    │   ├── settings.pyc
    │   ├── urls.py
    │   ├── views.py
    │   └── wsgi.py
    └── west
        ├── admin.py
        ├── __init__.py
        ├── models.py
        ├── tests.py
        └── views.py
    复制代码

    我们还需要修改项目设置,说明我们要使用west。在mysite/setting.py中,在INSTALLED_APPS中,增加"west":

    复制代码
    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'west',
    )
    复制代码

    可以看到,除了新增加的west,Django已经默认加载了一些功能性的app,比如用户验证、会话管理、显示静态文件等。我们将在以后讲解它们的用途。

     

    增加APP页面

    我们下面为APP增加首页。我们之前是在mysite/urls.py中设置的URL访问对象。依然采用类似的方式设置。

    另一方面,为了去耦合,实现模块化,我们应该在west/urls.py中设置URL访问对象。具体如下:

    首先,修改mysite/urls.py:

    复制代码
    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        # Examples:
        # url(r'^$', 'mysite.views.home', name='home'),
        # url(r'^blog/', include('blog.urls')),
    
        url(r'^admin/', include(admin.site.urls)),
        url(r'^$', 'mysite.views.first_page'),
        url(r'^west/', include('west.urls')),
    )
    复制代码

    注意新增加的最后一行。这里,我们提醒指挥员,对于west/的访问,要参考west/urls.py。

    随后,我们创建west/urls.py,添加内容:

    from django.conf.urls import patterns, include, url
    
    urlpatterns = patterns('',
        url(r'^$', 'west.views.first_page'),
    )

    将URL对应west下,views.py中的first_page函数。

    最后,在west下,修改views.py为:

    复制代码
    # -*- coding: utf-8 -*-
    
    from django.http import HttpResponse
    
    def first_page(request):
        return HttpResponse("<p>西餐</p>")
    复制代码

    访问http://127.0.0.1:8000/west,查看效果。

    连接数据库

    Django为多种数据库后台提供了统一的调用API。根据需求不同,Django可以选择不同的数据库后台。MySQL算是最常用的数据库。我们这里将Django和MySQL连接。

    在Linux终端下启动mysql:

    $mysql -u root -p

    在MySQL中创立Django项目的数据库:

    mysql> CREATE DATABASE villa DEFAULT CHARSET=utf8;

    这里使用utf8作为默认字符集,以便支持中文。

    在MySQL中为Django项目创立用户,并授予相关权限:

    mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON villa.* TO 'vamei'@'localhost' IDENTIFIED BY 'vameiisgood';

    在settings.py中,将DATABASES对象更改为:

    复制代码
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'villa',
            'USER': 'vamei',
            'PASSWORD': 'vameiisgood',
            'HOST':'localhost',
            'PORT':'3306',
        }
    }
    复制代码

    后台类型为mysql。上面包含数据库名称和用户的信息,它们与MySQL中对应数据库和用户的设置相同。Django根据这一设置,与MySQL中相应的数据库和用户连接起来。此后,Django就可以在数据库中读写了。

    创立模型

    MySQL是关系型数据库。但在Django的帮助下,我们不用直接编写SQL语句。Django将关系型的表(table)转换成为一个类(class)。而每个记录(record)是该类下的一个对象(object)。我们可以使用基于对象的方法,来操纵关系型的MySQL数据库。

    在传统的MySQL中,数据模型是表。在Django下,一个表为一个类。表的每一列是该类的一个属性。在models.py中,我们创建一个只有一列的表,即只有一个属性的类:

    复制代码
    from django.db import models
    
    class Character(models.Model):
        name = models.CharField(max_length=200)
        def __unicode__(self):
            return self.name
    复制代码

    类Character定义了数据模型,它需要继承自models.Model。在MySQL中,这个类实际上是一个表。表只有一列,为name。可以看到,name属性是字符类型,最大长度为200。

    类Character有一个__unicode__()方法,用来说明对象的字符表达方式。如果是Python 3,定义__str__()方法,实现相同的功能。

    命令Django同步数据库。Django根据models.py中描述的数据模型,在MySQL中真正的创建各个关系表:

    $python manage.py syncdb

    同步数据库后,Django将建立相关的MySQL表格,并要求你创建一个超级用户:

    Creating tables ...
    Creating table django_admin_log
    Creating table auth_permission
    Creating table auth_group_permissions
    Creating table auth_group
    Creating table auth_user_groups
    Creating table auth_user_user_permissions
    Creating table auth_user
    Creating table django_content_type
    Creating table django_session
    Creating table west_character

    You just installed Django's auth system, which means you don't have any superusers defined.
    Would you like to create one now? (yes/no): yes
    Username (leave blank to use 'tommy'): vamei
    Email address: vamei@vamei.com
    Password: 
    Password (again): 
    Superuser created successfully.
    Installing custom SQL ...
    Installing indexes ...
    Installed 0 object(s) from 0 fixture(s)

     数据模型建立了。打开MySQL命令行:

    $mysql -u vamei -p 

    查看数据模型:

    USE villa;
    SHOW TABLES;
    SHOW COLUMNS FROM west_character;

    最后一个命令返回Character类的对应表格:

    +-------+--------------+------+-----+---------+----------------+
    | Field | Type         | Null | Key | Default | Extra          |
    +-------+--------------+------+-----+---------+----------------+
    | id    | int(11)      | NO   | PRI | NULL    | auto_increment |
    | name  | varchar(200) | NO   |     | NULL    |                |
    +-------+--------------+------+-----+---------+----------------+
    2 rows in set (0.00 sec)

     可以看到,Django还自动增加了一个id列,作为记录的主键(Primary Key)。

    显示数据

    数据模型虽然建立了,但还没有数据输入。为了简便,我们手动添加记录。打开MySQL命令行,并切换到相应数据库。添加记录:

    INSERT INTO west_character (name) Values ('Vamei');
    INSERT INTO west_character (name) Values ('Django');
    INSERT INTO west_character (name) Values ('John');

    查看记录:

     SELECT * FROM west_character;

    可以看到,三个名字已经录入数据库。

    下面我们从数据库中取出数据,并返回给http请求。在west/views.py中,添加视图。对于对应的请求,我们将从数据库中读取所有的记录,然后返回给客户端:

    复制代码
    # -*- coding: utf-8 -*-
    
    from django.http import HttpResponse
    
    from west.models import Character
    
    def staff(request):
        staff_list = Character.objects.all()
        staff_str  = map(str, staff_list)
        return HttpResponse("<p>" + ' '.join(staff_str) + "</p>")
    复制代码

    可以看到,我们从west.models中引入了Character类。通过操作该类,我们可以读取表格中的记录

    为了让http请求能找到上面的程序,在west/urls.py增加url导航:

    from django.conf.urls import patterns, include, url
    
    urlpatterns = patterns('',
        url(r'^staff/','west.views.staff'),
    )

    运行服务器。在浏览器中输入URL:

    127.0.0.1:8000/west/staff

    查看效果:

    从数据库读出数据,显示在页面

    在之前的程序中,我们直接生成一个字符串,作为http回复,返回给客户端。这一过程中使用了django.http.HttpResponse()。

    在这样的一种回复生成过程中,我们实际上将数据和视图的格式混合了到上面的字符串中。看似方便,却为我们的管理带来困难。想像一个成熟的网站,其显示格式会有许多重复的地方。如果可以把数据和视图格式分离,就可以重复使用同一视图格式了。

    Django中自带的模板系统,可以将视图格式分离出来,作为模板使用。这样,不但视图可以容易修改,程序也会显得美观大方。

    模板初体验

    我们拿一个独立的templay.html文件作为模板。它放在templates/west/文件夹下。文件系统的结构现在是:

    mysite/
    ├── mysite
    ├── templates
    │   └── west
    └── west

    templay.html文件的内容是:

    <h1>{{ label }}</h1>

    可以看到,这个文件中,有一个奇怪的双括号包起来的陌生人。这就是我们未来数据要出现的地方。而相关的格式控制,即<h1>标签,则已经标明在该模板文件中。

    我们需要向Django说明模板文件的搜索路径,修改mysite/settings.py,添加:

    # Template dir
    TEMPLATE_DIRS = (
        os.path.join(BASE_DIR, 'templates/west/'),
    )

    如果还有其它的路径用于存放模板,可以增加该元组中的元素,以便Django可以找到需要的模板。

    我们现在修改west/views.py,增加一个新的对象,用于向模板提交数据:

    复制代码
    # -*- coding: utf-8 -*-
    
    #from django.http import HttpResponse
    from django.shortcuts import render
    
    def templay(request):
        context          = {}
        context['label'] = 'Hello World!'
        return render(request, 'templay.html', context)
    复制代码

    可以看到,我们这里使用render来替代之前使用的HttpResponse。render还使用了一个词典context作为参数。这就是我们的数据。

    context中元素的键值为'label',正对应刚才的“陌生人”的名字。这样,该context中的‘label’元素值,就会填上模板里的坑,构成一个完整的http回复。

    作为上节内容的一个小练习,自行修改west/urls.py,让http://127.0.0.1:8000/west/templay的URL请求可以找到相应的view对象。

    访问http://127.0.0.1:8000/west/templay,可以看到页面:

     

     

    流程

    再来回顾一下整个流程。west/views.py中的templay()在返回时,将环境数据context传递给模板templay.html。Django根据context元素中的键值,将相应数据放入到模板中的对应位置,生成最终的http回复。

    这一模板系统可以与Django的其它功能相互合作。上一回,我们从数据库中提取出了数据。如果将数据库中的数据放入到context中,那么就可以将数据库中的数据传送到模板。

    修改上次的west/views.py中的staff:

    def staff(request):
        staff_list  = Character.objects.all()
        staff_str  = map(str, staff_list)
        context   = {'label': ' '.join(staff_str)}
        return render(request, 'templay.html', context)

    练习: 显示上面的staff页面。

    循环与选择

    Django实际上提供了丰富的模板语言,可以在模板内部有限度的编程,从而更方便的编写视图和传送数据。

    我们下面体验一下最常见的循环与选择。

    上面的staff中的数据实际上是一个数据容器,有三个元素。刚才我们将三个元素连接成一个字符串传送。

    实际上,利用模板语言,我们可以直接传送数据容器本身,再循环显示。修改staff()为:

    def staff(request):
        staff_list = Character.objects.all()
        return render(request, 'templay.html', {'staffs': staff_list})

    从数据库中查询到的三个对象都在staff_list中。我们直接将staff_list传送给模板。

    将模板templay.html修改为:

    {% for item in staffs %}
    <p>{{ item.id }}, {{item}}</p>
    {% endfor %}

    我们以类似于Python中for循环的方式来定义模板中的for,以显示staffs中的每个元素。

    还可以看到,对象.属性名的引用方式可以直接用于模板中。

    选择结构也与Python类似。根据传送来的数据是否为True,Django选择是否显示。使用方式如下:

    复制代码
    {% if condition1 %}
       ... display 1
    {% elif condiiton2 %}
       ... display 2
    {% else %}
       ... display 3
    {% endif %}
    复制代码

    其中的elif和else和Python中一样,是可以省略的。

    模板继承

    模板可以用继承的方式来实现复用。我们下面用templay.html来继承base.html。这样,我们可以使用base.html的主体,只替换掉特定的部分。

    新建templates/west/base.html:

    复制代码
    <html>
      <head>
        <title>templay</title>
      </head>
    
      <body>
        <h1>come from base.html</h1>
        {% block mainbody %}
           <p>original</p>
        {% endblock %}
      </body>
    </html>
    复制代码

    该页面中,名为mainbody的block标签是可以被继承者们替换掉的部分。

    我们在下面的templay.html中继承base.html,并替换特定block:

    复制代码
    {% extends "base.html" %}
    
    {% block mainbody %}
    
    {% for item in staffs %}
    <p>{{ item.id }},{{ item.name }}</p>
    {% endfor %}
    
    {% endblock %}
    复制代码

    第一句说明templay.html继承自base.html。可以看到,这里相同名字的block标签用以替换base.html的相应block。

    html表格

    HTTP协议以“请求-回复”的方式工作。客户发送请求时,可以在请求中附加数据。服务器通过解析请求,就可以获得客户传来的数据,并根据URL来提供特定的服务。 

    (http协议的运作方式,详见http协议

    HTML文件中可以包含表格标签。HTML表格的目的是帮助用户构成HTTP请求,把数据用GET或者POST的方法,传递给某一URL地址。下面是一个表格的例子:

    <form action="/west/investigate/" method="get">
      <input type="text" name="staff">
      <input type="submit" value="Submit">
    </form>

    这里的form标签有两个属性。action用于说明URL地址,method说明请求的方法。

    表格中还包含有两个input标签,即两个输入栏目。根据type的不同,第一个为一个文本框,第二个为一个提交按钮。name为输入栏的名字。服务器在解析数据时,将以name为索引。

    我们可以将上面的表格直接存入模板form.html,并在west/views.py中定义一个视图form()来显示表格:

    from django.shortcuts import render
    
    def form(request):
        return render(request, 'form.html')

    设置urls.py,让对[site]/west/form/的访问,指向该视图。

    最后,我们在west/views.py中定义investigate()来处理该表格提交的数据:

    from django.shortcuts import render
    
    def investigate(request):
        rlt = request.GET['staff']
        return HttpResponse(rlt)

    可以看到,HTTP请求的相关信息,包括请求的方法,提交的数据,都包含在request参数中。

    表格是通过GET方法提交的。我们可以通过request.GET['staff'],来获得name为staff的输入栏的数据。该数据是一个字符串。investigate()将直接显示该字符串。

    设置urls.py,让该处理函数对应action的URL([site]/west/investigate/)。

    当我们访问http://127.0.0.1:8000/west/form时,将显示:

    提交表格后,页面将转到[site]/west/investigate。investigate()读取字符串后,在页面上显示出来。

    POST方法

    上面我们使用了GET方法。视图显示和请求处理分成两个函数处理。

    提交数据时更常用POST方法。我们下面使用该方法,并用一个URL和处理函数,同时显示视图和处理请求。

    先创建模板investigate.html

    复制代码
    <form action="/west/investigate/" method="post">
      {% csrf_token %}
      <input type="text" name="staff">
      <input type="submit" value="Submit">
    </form>
    
    <p>{{ rlt }}</p>
    复制代码

    我们修改提交表格的方法为post。在模板的末尾,我们增加一个rlt记号,为表格处理结果预留位置。

    表格后面还有一个{% csrf_token %}的标签。csrf全称是Cross Site Request Forgery。这是Django提供的防止伪装提交请求的功能。POST方法提交的表格,必须有此标签。

    在west/views.py中,用investigate()来处理表格:

    复制代码
    from django.shortcuts import render
    from django.core.context_processors import csrf
    
    def investigate(request):
        ctx ={}
        ctx.update(csrf(request))
        if request.POST:
            ctx['rlt'] = request.POST['staff']
        return render(request, "investigate.html", ctx)
    复制代码

    这里的csrf()是和上面的{% csrf_token %}对应。我们在这里不深究。

    看程序的其它部分。对于该URL,可能有GET或者POST方法。if的语句有POST方法时,额外的处理,即提取表格中的数据到环境变量。

    最终效果如下:

    存储数据

    我们还可以让客户提交的数据存入数据库。使用庄园疑云中创建的模型。我们将客户提交的字符串存入模型Character。

    修改west/views.py的investigate():

    复制代码
    from django.shortcuts import render
    from django.core.context_processors import csrf
    
    from west.models import Character
    
    def investigate(request):
        if request.POST:
            submitted  = request.POST['staff']
            new_record = Character(name = submitted)
            new_record.save()
        ctx ={}
        ctx.update(csrf(request))
        all_records = Character.objects.all()
        ctx['staff'] = all_records
        return render(request, "investigate.html", ctx)
    复制代码

    在POST的处理部分,我们调用Character类创建新的对象,并让该对象的属性name等于用户提交的字符串。通过save()方法,我们让该记录入库。

    随后,我们从数据库中读出所有的对象,并传递给模板。

    我们还需要修改模板investigate.html,以更好的显示:

    复制代码
    <form action="/west/investigate/" method="post">
      {% csrf_token %}
      <input type="text" name="staff">
      <input type="submit" value="Submit">
    </form>
    
    {% for person in staff %}
    <p>{{ person }}</p>
    {% endfor %}
    复制代码

    我们使用模板语言的for,来显示所有的记录。

    效果如下:

    表格对象

    客户提交数据后,服务器往往需要对数据做一些处理。比如检验数据,看是否符合预期的长度和数据类型。在必要的时候,还需要对数据进行转换,比如从字符串转换成整数。这些过程通常都相当的繁琐。

    Django提供的数据对象可以大大简化这一过程。该对象用于说明表格所预期的数据类型和其它的一些要求。这样Django在获得数据后,可以自动根据该表格对象的要求,对数据进行处理。

    修改west/views.py:

    复制代码
    from django.shortcuts import render
    from django.core.context_processors import csrf
    
    from west.models import Character
    
    from django import forms
    
    class CharacterForm(forms.Form):
        name = forms.CharField(max_length = 200)
    
    def investigate(request):
        if request.POST:
            form = CharacterForm(request.POST)
            if form.is_valid():
                submitted  = form.cleaned_data['name']
                new_record = Character(name = submitted)
                new_record.save()
    
        form = CharacterForm()
        ctx ={}
        ctx.update(csrf(request))
        all_records = Character.objects.all()
        ctx['staff'] = all_records
        ctx['form']  = form
        return render(request, "investigate.html", ctx)
    复制代码

    上面定义了CharacterForm类,并通过属性name,说明了输入栏name的类型为字符串,最大长度为200。

    在investigate()函数中,我们根据POST,直接创立form对象。该对象可以直接判断输入是否有效,并对输入进行预处理。空白输入被视为无效。

    后面,我们再次创建一个空的form对象,并将它交给模板显示。

    在模板investigate.html中,我们可以直接显示form对象:

    复制代码
    <form action="/west/investigate/" method="post">
      {% csrf_token %}
      {{ form.as_p }}
      <input type="submit" value="Submit">
    </form>
    
    {% for person in staff %}
    <p>{{ person }}</p>
    {% endfor %}
    复制代码

    如果有多个输入栏,我们可以用相同的方式直接显示整个form,而不是加入许多个<input>标签。

    效果如下:

    Django提供一个管理数据库的app,即django.contrib.admin。这是Django最方便的功能之一。通过该app,我们可以直接经由web页面,来管理我们的数据库。这一工具,主要是为网站管理人员使用。

    这个app通常已经预装好,你可以在mysite/settings.py中的INSTALLED_APPS看到它。

    默认界面

    admin界面位于[site]/admin这个URL。这通常在mysite/urls.py中已经设置好。比如,下面是我的urls.py:

    复制代码
    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    
    admin.autodiscover()                            # admin
    
    urlpatterns = patterns('',
        url(r'^admin/', include(admin.site.urls)),  # admin
        url(r'^west/', include('west.urls')),
    )
    复制代码

    为了让admin界面管理某个数据模型,我们需要先注册该数据模型到admin。比如,我们之前在west中创建的模型Character。修改west/admin.py:

    from django.contrib import admin
    from west.models import Character
    
    # Register your models here.
    admin.site.register(Character)

    访问http://127.0.0.1:8000/admin,登录后,可以看到管理界面:

    这个页面除了west.characters外,还有用户和组信息。它们来自Django预装的Auth模块。我们将在以后处理用户管理的问题。

    复杂模型

    管理页面的功能强大,完全有能力处理更加复杂的数据模型。

    先在west/models.py中增加一个更复杂的数据模型:

    复制代码
    from django.db import models
    
    # Create your models here.
    class Contact(models.Model):
        name   = models.CharField(max_length=200)
        age    = models.IntegerField(default=0)
        email  = models.EmailField()
        def __unicode__(self):
            return self.name
    
    class Tag(models.Model):
        contact = models.ForeignKey(Contact)
        name    = models.CharField(max_length=50)
        def __unicode__(self):
            return self.name
    复制代码

    这里有两个表。Tag以Contact为外部键。一个Contact可以对应多个Tag。

    我们还可以看到许多在之前没有见过的属性类型,比如IntegerField用于存储整数。

     

    同步数据库:

    $python manage.py syncdb

    在west/admin.py注册多个模型并显示:

    from django.contrib import admin
    from west.models import Character,Contact,Tag
    
    # Register your models here.
    admin.site.register([Character, Contact, Tag])

    模型将在管理页面显示。比如Contact的添加条目的页面如下:

     

    自定义页面

    我们可以自定义管理页面,来取代默认的页面。比如上面的"add"页面。我们想只显示name和email部分。修改west/admin.py:

    复制代码
    from django.contrib import admin
    from west.models import Character,Contact,Tag
    
    # Register your models here.
    class ContactAdmin(admin.ModelAdmin):
        fields = ('name', 'email')
    
    admin.site.register(Contact, ContactAdmin)
    admin.site.register([Character, Tag])
    复制代码

    上面定义了一个ContactAdmin类,用以说明管理页面的显示格式。里面的fields属性,用以说明要显示的输入栏。我们没有让"age"显示。由于该类对应的是Contact数据模型,我们在注册的时候,需要将它们一起注册。显示效果如下:

    我们还可以将输入栏分块,给每一块输入栏以自己的显示格式。修改west/admin.py为:

    复制代码
    from django.contrib import admin
    from west.models import Character,Contact,Tag
    
    # Register your models here.
    class ContactAdmin(admin.ModelAdmin):
        fieldsets = (
            ['Main',{
                'fields':('name','email'),
            }],
            ['Advance',{
                'classes': ('collapse',), # CSS
                'fields': ('age',),
            }]
        )
    
    admin.site.register(Contact, ContactAdmin)
    admin.site.register([Character, Tag])
    复制代码

    上面的栏目分为了Main和Advance两部分。classes说明它所在的部分的CSS格式。这里让Advance部分收敛起来:

    Advance部分旁边有一个Show按钮,用于展开。

    Inline显示

    上面的Contact是Tag的外部键,所以有外部参考的关系。而在默认的页面显示中,将两者分离开来,无法体现出两者的从属关系。我们可以使用Inline显示,让Tag附加在Contact的编辑页面上显示。

    修改west/admin.py:

    复制代码
    from django.contrib import admin
    from west.models import Character,Contact,Tag
    
    # Register your models here.
    class TagInline(admin.TabularInline):
        model = Tag
    
    class ContactAdmin(admin.ModelAdmin):
        inlines = [TagInline]  # Inline
        fieldsets = (
            ['Main',{
                'fields':('name','email'),
            }],
            ['Advance',{
                'classes': ('collapse',),
                'fields': ('age',),
            }]
    
        )
    
    admin.site.register(Contact, ContactAdmin)
    admin.site.register([Character])
    复制代码

    效果如下:

    列表页的显示

    在Contact输入数条记录后,Contact的列表页看起来如下:

    我们也可以自定义该页面的显示,比如在列表中显示更多的栏目,只需要在ContactAdmin中增加list_display属性:

    复制代码
    from django.contrib import admin
    from west.models import Character,Contact,Tag
    
    # Register your models here.
    class ContactAdmin(admin.ModelAdmin):
        list_display = ('name','age', 'email') # list
    
    admin.site.register(Contact, ContactAdmin)
    admin.site.register([Character, Tag])
    复制代码

    列表页新的显示效果如下:

    我们还可以为该列表页增加搜索栏。搜索功能在管理大量记录时非常有用。使用search_fields说明要搜索的属性:

    复制代码
    from django.contrib import admin
    from west.models import Character,Contact,Tag
    
    # Register your models here.
    class ContactAdmin(admin.ModelAdmin):
        list_display = ('name','age', 'email') 
        search_fields = ('name',)
    
    admin.site.register(Contact, ContactAdmin)
    admin.site.register([Character])
    复制代码

    效果如下:

    一个Web应用的用户验证是它的基本组成部分。我们在使用一个应用时,总是从“登录”开始,到“登出”结束。另一方面,用户验证又和网站安全、数据库安全息息相关。HTTP协议是无状态的,但我们可以利用储存在客户端的cookie或者储存在服务器的session来记录用户的访问。 

    Django有管理用户的模块,即django.contrib.auth。你可以在mysite/settings.py里看到,这个功能模块已经注册在INSTALLED_APPS中。利用该模块,你可以直接在逻辑层面管理用户,不需要为用户建立模型,也不需要手工去实现会话。

    创建用户

    你可以在admin页面直接看到用户管理的对话框,即Users。从这里,你可以在这里创建、删除和修改用户。点击Add增加用户daddy,密码为daddyiscool。

    在admin页面下,我们还可以控制不同用户组对数据库的访问权限。我们可以在Groups中增加用户组,设置用户组对数据库的访问权限,并将用户加入到某个用户组中。

    在这一章节中,我们创立一个新的app,即users。下文的模板和views.py,都针对该app。

    用户登录

    我们建立一个简单的表格。用户通过该表格来提交登陆信息,并在Django服务器上验证。如果用户名和密码正确,那么登入用户。

    我们首先增加一个登录表格:

    复制代码
    <form role="form" action="/login" method="post">
          <label>Username</label>
          <input type="text" name='username'>
          <label>Password</label>
          <input name="password" type="password">
          <input type="submit" value="Submit">
     </form>
    复制代码

    我们在views.py中,定义处理函数user_login(),来登入用户:

    复制代码
    # -*- coding: utf-8 -*-
    from django.shortcuts import render, redirect
    from django.core.context_processors import csrf
    from django.contrib.auth import *
    
    def user_login(request):
        '''
        login
        '''
        if request.POST:
            username = password = ''
            username = request.POST.get('username')
            password = request.POST.get('password')
            user     = authenticate(username=username, password=password)
            if user is not None and user.is_active:
                    login(request, user)
                    return redirect('/')
        ctx = {}
        ctx.update(csrf(request))
        return render(request, 'login.html',ctx)
    复制代码

    上面的authenticate()函数,可以根据用户名和密码,验证用户信息。而login()函数则将用户登入。它们来自于django.contrib.auth。

    作为替换,我们可以使用特别的form对象,而不自行定义表格。这将让代码更简单,而且提供一定的完整性检验。

    练习. 使用contrib.auth.forms.AuthenticationForm。来简化上面的模板和处理函数。

    登出

    有时用户希望能销毁会话。我们可以提供一个登出的URL,即/users/logout。登入用户访问该URL,即可登出。在views.py中,增加该URL的处理函数:

    复制代码
    # -*- coding: utf-8 -*-
    from django.shortcuts import redirect
    
    def user_logout(request):
        '''
        logout
        URL: /users/logout
        '''
        logout(request)
        return redirect('/')
    复制代码

    我们修改urls.py,让url对应user_logout()。访问http://127.0.0.1/users/logout,就可以登出用户。

    views.py中的用户

    上面说明了如何登入和登出用户,但还没有真正开始享受用户验证带来的好处。用户登陆的最终目的,就是为了让服务器可以区别对待不同的用户。比如说,有些内容只能让登陆用户看到,有些内容则只能让特定登陆用户看到。我们下面将探索如何实现这些效果。

    在Django中,对用户身份的检验,主要是在views.py中进行。views.py是连接模型和视图的中间层。HTTP请求会转给views.py中的对应处理函数处理,并发回回复。在views.py的某个处理函数准备HTTP回复的过程中,我们可以检验用户是否登陆。根据用户是否登陆,我们可以给出不同的回复。最原始的方式,是使用if式的选择结构: 

    复制代码
    # -*- coding: utf-8 -*-
    from django.http import HttpResponse
    
    def diff_response(request):
        if request.user.is_authenticated():
            content = "<p>my dear user</p>"
        else:
            content = "<p>you wired stranger</p>"
        return HttpResponse(content)
    复制代码

    可以看到,用户的登录信息包含在request.user中,is_authenticated()方法用于判断用户是否登录,如果用户没有登录,那么该方法将返回false。该user对象属于contrib.auth.user类型,还有其它属性可供使用,比如

    属性 功能
    get_username() 返回用户名
    set_password() 设置密码
    get_fullname() 返回姓名
    last_login 上次登录时间
    date_joined   账户创建时间

    练习. 实验上面的处理函数的效果。

    在Django中,我们还可以利用装饰器,根据用户的登录状况,来决定views.py中处理函数的显示效果。相对于上面的if结构,装饰器使用起来更加方便。下面的user_only()是views.py中的一个处理函数。

    复制代码
    from django.contrib.auth.decorators import login_required
    from django.http import HttpResponse
    
    @login_required
    def user_only(request):
        return HttpResponse("<p>This message is for logged in user only.</p>")
    复制代码

    注意上面的装饰器login_required,它是Django预设的装饰器。user_only()的回复结果只能被登录用户看到,而未登录用户将被引导到其他页面。

    Django中还有其它的装饰器,用于修饰处理函数。相应的http回复,只能被特殊的用户看到。比如user_passes_test,允许的用户必须满足特定标准,而这一标准是可以用户自定义的。比如下面,在views.py中增添: 

    复制代码
    from django.contrib.auth.decorators import user_passes_test
    from django.http import HttpResponse
    def name_check(user):
        return user.get_username() == 'vamei'
    
    @user_passes_test(name_check)
    def specific_user(request):
        return HttpResponse("<p>for Vamei only</p>")
    复制代码

    装饰器带有一个参数,该参数是一个函数对象name_check。当name_check返回真值,即用户名为vamei时,specific_user的结果才能被用户看到。

    模板中的用户

    进一步,用户是否登陆这一信息,也可以直接用于模板。比较原始的方式是把用户信息直接作为环境数据,提交给模板。然而,这并不是必须的。事实上,Django为此提供了捷径:我们可以直接在模板中调用用户信息。比如下面的模板:

    {% if user.is_authenticated %}
      <p>Welcome, my genuine user, my true love.</p>
    {% else %}
      <p>Sorry, not login, you are not yet my sweetheart. </p>
    {% endif %}

    不需要环境变量中定义,我们就可以直接在模板中引用user。这里,模板中调用了user的一个方法,is_authenticated,将根据用户的登录情况,返回真假值。需要注意,和正常的Python程序不同,在Django模板中调用方法并不需要后面的括号。

    练习. 增加处理函数,显示该模板,然后查看不同登录情况下的显示结果。

    用户注册

    我们上面利用了admin管理页面来增加和删除用户。这是一种简便的方法,但并不能用于一般的用户注册的情境。我们需要提供让用户自主注册的功能。这可以让站外用户提交自己的信息,生成自己的账户,并开始作为登陆用户使用网站。

    用户注册的基本原理非常简单,即建立一个提交用户信息的表格。表格中至少包括用户名和密码。相应的处理函数提取到这些信息后,建立User对象,并存入到数据库中。

    我们可以利用Django中的UserCreationForm,比较简洁的生成表格,并在views.py中处理表格:

    复制代码
    from django.contrib.auth.forms import UserCreationForm
    from django.shortcuts import render, redirect
    from django.core.context_processors import csrf
    
    def register(request): 
        if request.method == 'POST': 
            form = UserCreationForm(request.POST) 
            if form.is_valid(): 
                new_user = form.save() 
            return redirect("/") 
        else:
            form = UserCreationForm()
            ctx = {'form': form}
            ctx.update(csrf(request))       
            return render(request, "register.html", ctx)
    复制代码

    相应的模板register.html如下:

    <form action="" method="post">
       {% csrf_token %}
       {{ form.as_p }}
       <input type="submit" value="Register">
    </form>

    前面的文章研究了Django最主要的几个方面:数据库,模板,动态生成页面等。但都是使用python manage.py runserver来运行服务器。这是一个实验性的web服务器,不适用于正常的站点运行。我们需要一个可以稳定而持续的服务器。这个服务器负责监听http端口,将收到的请求交给Django处理,将Django的回复发还给客户端。

    这样的持续性服务器可以有很多选择,比如apache, Nginx, lighttpd等。这里将使用最常见的apache服务器。服务器和Django之间通过Python的web服务接口WSGI连接,因此我们同样需要apache下的mod_wsgi模块。

    下面的配置和说明,是在Ubuntu 13.10下进行的。在其它系统下将有所差别。

    安装

    首先需要安装apache2和mod_wsgi。在ubuntu下,我们可以使用apt-get安装:

    sudo apt-get install apache2
    sudo apt-get install libapache2-mod-wsgi

     mod_wsgi也可以在google code下载,自行编译安装。

    在apache的配置文件/etc/apache2/apache2.conf中增加下面的配置: 

    复制代码
    # Django
    WSGIScriptAlias / /home/vamei/mysite/mysite/wsgi.py
    WSGIPythonPath /home/vamei/mysite
    
    <Directory /home/vamei/mysite/mysite>
    <Files wsgi.py>
      Order deny,allow
      Require all granted
    </Files>
    </Directory>
    复制代码

    上面的配置中/home/ubuntu/mysite是Django项目所在的位置。而/home/ubuntu/mysite/mysite/wsgi.py是Django项目中z自动创建的文件。

    可以看到,利用WSGIScriptAlias,我们实际上将URL /对应了wsgi接口程序。这样,当我们访问根URL时,访问请求会经由WSGI接口,传递给Django项目mysite。

    配置好后,重启apache2

    sudo /etc/init.d/apache2 restart

    使用浏览器,可以检查效果:

     

    静态文件

    Django的主要功能是动态的生成HTTP回复。很多媒体文件是静态存储的,如.js文件,.css文件和图片文件。这些文件变动的频率较小。我们希望静态的提供这些文件,而不是动态的生成。这样既可以减小服务器的负担,也便于在浏览器缓存,提高用户体验。

    我们可以在apache2.conf中添加如下配置:

    复制代码
    Alias /media/ /home/vamei/media/
    Alias /static/ /home/vamei/static/
    
    <Directory /home/vamei/static/>
    Order deny,allow
    Require all granted
    </Directory>
    
    <Directory /home/vamei/media/>
    Order deny,allow
    Require all granted
    </Directory>
    
    # Django
    WSGIScriptAlias / /home/vamei/mysite/mysite/wsgi.py
    WSGIPythonPath /home/vamei/mysite
    
    <Directory /home/vamei/mysite/mysite/ >
    <Files wsgi.py>
        Order deny,allow
        Require all granted
    </Files>
    </Directory>
    复制代码

    这样,/static/和/media/这两个URL的访问将引导向存有静态文件的/home/vamei/static/和/home/vamei/media/,apache将直接向客户提供这两个文件夹中的静态文件。而剩下的URL访问,将导向WSGI接口,由Django动态处理。

    在/home/vamei/static/中放入文件revenge.jpg,访问http://localhost/static/revenge:

    其它

    云平台或者服务器的部署是一个大的课题,这里无法深入到所有的细节。幸运的是,在网上有丰富的资料。你可以根据自己的平台和问题,搜索相应的资料。

    在Django的debug模式下,我们可以在app文件夹中建立static目录,放入静态文件。Django将自动搜索到其中的静态文件。但这一方法有很大的安全隐患,只适用于开发。

    我的第一个python web开发框架(19)——产品发布相关事项

    我的第一个python web开发框架(18)——前台页面与接口整合

    我的第一个python web开发框架(17)——产品管理

    我的第一个python web开发框架(16)——产品分类管理

    我的第一个python web开发框架(15)——公司介绍编辑功能

    我的第一个python web开发框架(14)——后台管理系统登录功能

    我的第一个python web开发框架(13)——工具函数包说明(四)

    我的第一个python web开发框架(12)——工具函数包说明(三)

    我的第一个python web开发框架(11)——工具函数包说明(二)

    我的第一个python web开发框架(10)——工具函数包说明(一)

    我的第一个python web开发框架(9)——目录与配置说明

    我的第一个python web开发框架(8)——项目结构与RESTful接口风格说明

    我的第一个python web开发框架(7)——本地部署前端访问服务器

    我的第一个python web开发框架(6)——第一个Hello World

    我的第一个python web开发框架(5)——开发前准备工作(了解编码前需要知道的一些常识)

    我的第一个python web开发框架(4)——数据库结构设计与创建

    我的第一个python web开发框架(3)——怎么开始?

    我的第一个python web开发框架(2)——一个简单的小外包

    我的第一个python web开发框架(1)——前言

  • 相关阅读:
    [LeetCode]4Sum
    [LeetCode]3Sum
    [LeetCode]Two Sum
    [LeetCode]Maximal Rectangle
    [LeetCode]Largest Rectangle in Histogram
    [LeetCode]Sudoku Solver
    [LeetCode]Group Anagrams
    jQuery验证控件jquery.validate.js使用说明+中文API
    js操作cookie,实现登录密码保存
    Java中getResourceAsStream的用法
  • 原文地址:https://www.cnblogs.com/tester-l/p/8193885.html
Copyright © 2020-2023  润新知