每日一句: Build your own dreams, or someone else will hire you to build theirs. 打造自己的梦想,否则你就会被雇用去打造别人的梦想。 跟读

汉语站

2017年12月11日 星期一

丁酉(鸡)年十月廿四

我也要加入《我为WIKI狂》,万人共写一本书!

[本期维客]看美女

django开发wiki
文/ 看美女

  • 做一个简单的wiki,要可以修改当前页面,即在页面下面提供一个编辑的按钮。然后还要识别页面中的两个开头大写的单词为页面切换点,可以进入一个已经生成好的页面,或提示创建一个新页面。

下面我们将开始创建 Django 中的 app 了。

先说一下。如果你看过官方版的教程,它就是讲述了一个 Poll 的 app 的生成过程。那么一个 app 就是一个功能的集合,它有自已的 model ,view 和相应的模板,还可以带自已的 urls.py 。那么它也是一个独立的目录,这样一个 app 就可以独立地进行安装,你可以把它安装到其它的 Django 服务器中去。因此采用 app 的组织形式非常有意义。而且 adango-admin.py 也提供了一个针对 app 的命令,一会我们就会看到。而且 Django 提供一些自动功能也完全是针对于 app 这种结构的。Model, Template, View 就合成了 MTV 这几个字母。 Model 是用来针对数据库,同时它可以用来自动生成管理界面, View 在前面我们一直都用它,用来处理请求和响应的相当于MVC框架中的 Controller 的作用, Template 用来生成界面。

2创建 wiki app

manage.py startapp wiki

Note

在 0.92 版之前, app 都是放在 apps 目录下的。不过到了 0.92 版,apps 目录不自动创建了。因此你就可以直接放在项目目录下了。

这样在 wiki 子目录下有以下文件:

__init__.py
表示 wiki 目录是一个包。
views.py
用来放它的 view 的代码。
models.py
用来放 model 代码。

3编辑 wiki/models.py

from django.db import models

# Create your models here.
class Wiki(models.Model):
    pagename = models.CharField(maxlength=20, unique=True)
    content = models.TextField()

每个 model 其实在 Django 中就是一个表,你将用它来保存数据。在实际的应用中,一般都要与数据库打交道,如果你不想用数据库,那么原因可能就是操作数据库麻烦,创建数据库环境也麻烦。但通过 Django 的 model 处理,它是一种 ORM (Object Relation Mapping, 对象与关系的映射),可以屏蔽掉底层数据库的细节,同时提供以对象的形式来处理数据。非常方便。而且 Django 的 model 层支持多种数据库,如果你改变数据库也不是什么问题,这也为以后的数据库迁移带来好处。总之,好处多多,大家多多体会吧。

Wiki 是 model 的名字,它需要从 models.Model 派生而来。它定义了两个字段,一个是字段是 pagename , 用来保存 wiki 页面的名字,它有两个参数,一个是最大长度(不过从这点上不如 SQLAlchemy 方便, SQLAlchemy并不需要长度,它会根据有无长度自动转为 TEXT 类型),目前 CharField 需要这个参数;另一个是 unique 表示这个字段不能有重复值。还有一个字段是 content ,用来保存 wiki 页面的内容,它是一个 TextField 类型,它不需要最大长度。

Note

models.Model 在 0.92 版以前是 meta.Model 。而 django.db 在 0.92 版之前是 django.core 。

现在不太了解 model 没有关系,关键是看整个生成过程。

一旦你定义好了 model ,在运行时, Django 会自动地为这个 model 增加许多数据操作的方法。关于 model 和 数据库操作API的详细内容参见 Model referenceDatabase API reference 的文档。

4修改 settings.py, 安装 app

虽然我们的其它工作没有做完,但我还是想先安装一下 app 吧。每个一 app 都需要安装一下。安装一般有两步:

  1. 修改settings.py
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'newtest.wiki',
)

这个在文件的最后,前4个是缺省定义的。给出指定 wiki 包的引用名来。这一步是为了以后方便地导入所必须的。因为我们的目录都是包的形式,因此这里就是与目录相对应的。

  1. 执行(在newtest目录下)
manage.py install wiki

如果没有报错就是成功了。这一步 Django 将根据 model 的信息在数据库中创建相应的表。表就是这样创建出来的。

5在命令行下加入首页(FrontPage)

我们假设首页的名字为 FrontPage ,并且我们将在命令行下增加它,让我们熟悉一下命令行的使用

进入 newtest 目录,然后:

set PYTHONPATH=d:\test
set DJANGO_SETTINGS_MODULE=newtest.settings

Note

在 Linux 环境下要相应的使用 export 和与你相配置的目录。

注意 PYTHONPATH 需要设为 newtest 的父目录,我的 newtest 的全路径为: d:\test\newtest 。你的可能与我不同,请注意修改。不这样做不行, manage.py 只是在运行它的时候才起作用,但在命令行下使用 Python 则需要手工设置。你可以把这种设置写成一个批处理,以后就方便了。

Note

如果使用 NewEdit 来创建 Django 工程,则会自动生成 run.batrun 两个文件。分别用于 Windows 和 Linux 平台下设置环境变量。

Note

还可以使用:

manage.py shell

来进入命令行,并且不需要设置上面的环境变量。感谢xlp223提醒。

进入 python

>>> from newtest.wiki.models import Wiki
>>> page = Wiki(pagename='FrontPage', content='Welcome to Easy Wiki')
>>> page.save()
>>> Wiki.objects.all()
[]
>>> p = Wiki.objects.all()[0]
>>> p.pagename
'FrontPage'
>>> p.content
'Welcome to Easy Wiki'

Note

因为在写这篇教程时是在magic-removal分枝下进行的操作,因此有些 API 并不稳定。象 objects 的方法以前是沿用model的方法,但后来进行了简化,比如 get_list() 变为 all() 。还有一系统的变化。具体的可以参见 Removing The Magic 文档中关于 Descriptor fields 的说明。

在 Django 中,对于数据库的记录有两种操纵方式,一种是集合方式,一种是对象方式。集合方式相当于表级操作,在新版的 0.92 中可以使用 model.objects 来处理。 objects 对象有一些集合方式的操作,如 all() 会返回全部记录, filter() 会根据条件返回部分记录。而象插入新记录则需要使用记录方式来操作,些时要直接使用 model 类。

Note

在 0.92 版之前要比这麻烦得多,也不容易理解。好在情况已经发生了变化。

6修改 wiki/views.py

#coding=utf-8
from newtest.wiki.models import Wiki
from django.template import loader, Context
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response

def index(request, pagename=""):
    """显示正常页面,对页面的文字做   #查找是否已经存在页面
#        pages = Wiki.objects.get_list(pagename__exact=pagename)
        pages = Wiki.objects.filter(pagename=pagename)
        if pages:
            #存在则调用页面模板进行显示
            return process('wiki/page', pages[0])
        else:
            #不存在则进入编辑画面
            return render_to_response('wiki/edit', {'pagename':pagename})

    else:
#        page = Wiki.objects.get_object(pagename__exact='FrontPage')
        page = Wiki.objects.get(pagename='FrontPage')
        return process('wiki/page', page)

def edit(request, pagename):
    """显示编辑存在页面"""
#    page = Wiki.objects.get_object(pagename__exact=pagename)
    page = Wiki.objects.get(pagename=pagename)
    return render_to_response('wiki/edit', {'pagename':pagename, 'content':page.content})

def save(request, pagename):
    """保存页面内容,老页面进行内容替换,新页面生成新记录"""
    content = request.POST['content']
#    pages = Wiki.objects.get_list(pagename__exact=pagename)
    pages = Wiki.objects.filter(pagename=pagename)
    if pages:
        pages[0].content = content
        pages[0].save()
    else:
        page = Wiki(pagename=pagename, content=content)
        page.save()
    return HttpResponseRedirect("/wiki/%s" % pagename)

import re

r = re.compile(r'\b([A-Z][a-z]+[A-Z][a-z]+)\b')
def process(template, page):
    """处理页面链接,并且将回车符转为
""" t = loader.get_template(template) content = r.sub(r'\1', page.content) content = re.sub(r'[\n\r]+', '
', content) c = Context({'pagename':page.pagename, 'content':content}) return HttpResponse(t.render(c))

Note

将原来老的 model 方法加了注释,目前改用最新的 API 了。

代码有些长,有些地方已经有说明和注释了。简单说一下:

  • index() 用来显示一个 wiki 页面。它需要一个参数就是页面的名称。如果在数据库中找得到,则调用 process() 方法(process() 方法是一个自定义方法,主要用来对页面的文本进行处理,比如查找是否有满足 wiki 命名规则的单词,如果有则替换成链接。再有就是将回车转为
    )。如果没有找到,则直接调用编辑模板显示一个编程页面。当然,这个页面的内容是空的。只是它的页面名字就是 pagename 。如果 pagename 为空,则进入 FrontPage 页面。 Wiki.objects 对象有 filter() 方法和 get() 方法,一个返回一个结果集,一个返回指定的对象。这里为什么使用 filter() 呢,因为一旦指定文件不存在,它并不是返回一个 None 对象,而是抛出异常,而我没有使用异常的处理方式。通过 filter() 如果存在则结果中应有一个元素,如果不存在则应该是一个 [] 。这样就知道是否有返回了。

Note

filter() 中使用的参数与一般的 db-api 是一样的,但如果是比较相等,可以为: pagename__exact=pagename 也可以简化为 pagename=pagename

Note

在 Django 中,一些字段的比较操作比较特殊,它是在字段名后加 __ 然后是比较条件。这样看上去就是一个字符串。具体的参见 The Database API

Note

回车转换的工作其实可以在模板中使用 filter 来完成。

  • 从对模板的使用 (wiki/edit) 可以猜到在后面我们要在 templates 中创建子目录了。的确,对于不同的 app ,我们可以考虑将所有的模板都放在统一的 templates 目录下,但为了区分方便,一般都会针对 app 创建不同的子目录。当然也可以不这样,可以放在其它的地方,只要修改 settings.py ,将新的模板目录加进去就行了。

因为我们在设计 model 时已经设置了 pagename 必须是唯一的,因此一旦 filter() 有返回值,那它只能有一个元素,而 pages[0] 就是我们想要的对象。

  • page = wikis.get(pagename='FrontPage')

    是表示取出 pagenameFrontPage 的页面。你可能要说,为什么没有异常保护,是的,这也就是为什么我们要在前面先要插条记录在里面的原因。这样就不会出错了。再加上我要做的 wiki 不提供删除功能,因此不用担心会出现异常。

  • edit() 用来显示一个编辑页面,它直接取出一个页面对象,然后调用 wiki/edit 模板进行显示。也许你还是要问,为什么不考虑异常,因为这里不会出现。为什么?因为 edit() 只用在已经存在的页面上,它将用于存在页面的修改。而对于不存在的页面是在 index() 中直接调用模板来处理,并没有直接使用这个 edit() 来处理。也许你认为这样可能不好,但由于在 edit() 要重新检索数据库,而在 index() 已经检索过一次了,没有必要再次检索,因此象我这样处理??人意见。

  • save() 用来在编辑页面时用来保存内容的。它先检查页面是否在数据库中存在,如果不存在则创建一个新的对象,并且保存。注意,在 Django 中,对对象处理之后只有调用它的 save() 方法才可以真正保存到数据库中去。如果页面已经存在,则更新页面的内容。处理之后再重定向到 index() 去显示这个页面。

8编辑 templates/wiki/page.html

{{ pagename }}

{{ content }}


它用来显示页面,同时提供一个“编辑”按钮。当点击这个按钮时将调用 view 中的 edit() 方法。

9编辑 templates/wiki/edit.html

编辑:{{ pagename }}


它用来显示一个编辑页面,同时提供“保存”按钮。点击了保存按钮之后,会调用 view 中的 save() 方法。

10修改 urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    # Example:
    # (r'^testit/', include('newtest.apps.foo.urls.foo')),
    (r'^$', 'newtest.helloworld.index'),
    (r'^add/$', 'newtest.add.index'),
    (r'^list/$', 'newtest.list.index'),
    (r'^csv/(?P\w+)/$', 'newtest.csv_test.output'),
    (r'^login/$', 'newtest.login.login'),
    (r'^logout/$', 'newtest.login.logout'),
    (r'^wiki/$', 'newtest.wiki.views.index'),
    (r'^wiki/(?P\w+)/$', 'newtest.wiki.views.index'),
    (r'^wiki/(?P\w+)/edit/$', 'newtest.wiki.views.edit'),
    (r'^wiki/(?P\w+)/save/$', 'newtest.wiki.views.save'),

    # Uncomment this for admin:
#     (r'^admin/', include('django.contrib.admin.urls')),
)

增加了 wiki 等4个 url 映射。

这里要好好讲一讲 URL 的设计(个人所见)。

一般一个 wiki ,我们访问它的一个页面可能为:wiki/pagename。因此我设计对 index() 方法的调用的 url 为:

r'^wiki/(?P\w+)/$'

也就是把 wiki/后面的解析出来作为 pagename 参数。但这样就带来一个问题,如果我想实现 wiki/edit 表示修改, pagename 作为一个参数通过 POST 来提交好象就不行了。因为上面的解析规则会“吃”掉这种情况。因此我采用 Zope 的表示方法:把对象的方法放在对象的后面。我可以把 pagename 看成为一个对象, edit , save 是它的方法,放在它的后面,也简单,也清晰。当然如果我们加强上面的正则表达式,也可以解析出 wiki/edit 的情况,但那就是你设计的问题了。这里就是我的设计。

因此 wiki/pagename 就是显示一个页面,wiki/pagename/edit 就是编辑这个页面, wiki/pagename/save 就是保存页面。而 pagename 解析出来后就是分别与 index() , edit() , save()pagename 参数相对应。

下面你可以运行了。

11启动 server

进入 http://localhost:8000/wiki

首先进入这个页面:

然后你点编辑,则进入FrontPage的编辑界面:

然后我们加上一个 TestPage ,它符合 wiki 的名字要求,两个首字母大写的单词连在一起。然后点击保存。

看见了吧。页面上的 TestPage 有了链接。点击它将进入:

这是 TestPage 的编辑页面。让我们输入中文,然后输入 FrontPage 。然后保存。好了,剩下的你来玩吧。点击 FrontPage 将回到首页。

--敬请期待下期《我为WIKI狂》!

加入《我为WIKI狂》赚取我的稿费!

查看《我为WIKI狂》所有精彩内容!

加入《我为WIKI狂》的步骤如下:

1、先加入这个小组,成为我们的一员;

2、写出你与WIKI的故事,范围可以很广,只要牵扯到WIKI都行;或者写出你对WIKI的认识、使用心得,甚至批评。具体先看这儿,例子请参考这儿

3、创建几个属于你的条目,这个很自由,比如条目可以是百科知识,可以是你的某篇blog,可以是你的原创文章,也可以是日记。如不了解创建规则,请看新手帮助

4、写出自我介绍,推销你自己或者你喜欢的东西。我们的目的是让每个人都成为作者和明星,并与他人形成交友圈子。在此基础上大家共创最大的百科全书,共享属于全人类的知识!

词条内容仅供参考,如果您需要解决具体问题
(尤其在法律、医学等领域),建议您咨询相关领域专业人士。

标签: 《我为WIKI狂》第11期

同义词: 暂无同义词

词条统计

浏览次数 : 2007 次

编辑次数 : 1 次 历史版本

更新时间 : 2009-03-15

双语连环画