Django Step by Step (十二)

1 引言

如果通讯录中的记录很多,我希望有一种搜索的方法,下面就让我们加一个搜索功能吧。当然,这个搜索功能是很简单的。在 Django 邮件列表中看到 WorldOnline(好象是它)有一个搜索的框架,可以定义哪些模块的哪些字段要参加搜索。这样在处理时会自动将相应的信息加入到搜索数据库中进行预处理。现在这个框架并没有开放源码,而且它底层使用的搜索的东西并不是 Django 本身的。这里我只是对姓名字段进行查找。

2 修改 templates/address/list.html

[...]
<hr>
<div id="content-main">
    <table border="0" width="500">
    <tr align="right"><td>
    <form method="GET" action="/address/search/">
    搜索姓名:<input name="search" type="text" value="{{ searchvalue }}"/>
    <input type="submit" value="提交"/>
    </form>
    </td></tr>
    </table>
    <table border="0" width="500">
    <tr align="right">
      <td>{% if has_previous %}
[...]

在显示分页的代码上面增加了搜索的处理。

从上面可以看到,条件输入处我增加了一个 searchvalue 的变量,希望在提交一个搜索后,显示页面的同时显示当前显示时使用的条件。

由于搜索结果页面也是一个列表页面,我们希望能够用第九讲介绍过的generic view来显示结果,因为列表页面的处理非常简单:

class IndexView(generic.ListView):
    model = Address
    template_name = 'address/list.html'
    paginate_by = 2

但是这里存在一个困难:如何把搜索条件,搜索字符串与generic view 相关联呢?通过 urls.py 我想是不行的,因为它只从 url 解析,而且对于 QUERY_STRING 是不进行解析的(QUERY_STRING 是指: http://example.com/add/?name=test? 后面的东西,也就是 name=test )。对于搜索条件,我会使用一个 form 来处理, method 会设为 GET ,因此生成的 url 中,查询条件正如这个例子,如: http://localhost:8000/address/search/?search=limodou 。这样无法变成上面所要用到的参数。

这里我们需要对generic view进行一下扩充,我们需要实现get_querysetget_context_data这两个方法。分别用来指定结果集和模板渲染的参数,我们先来看看新的view方法怎么写:

3 修改 address/views.py

class SearchView(generic.ListView):

    template_name = 'address/list.html'
    paginate_by = 2

    def get_queryset(self):
        if self.request.GET.get('search'):
            self.name = self.request.GET['search']
            return Address.objects.filter(name = self.name)
        else:
            self.name = None
            return Address.objects.all()

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.name:
            context['searchvalue'] = self.name
        return context

我们使用get_queryset方法代替了model = Address,这样可以更加灵活的定义返回的结果集。

我们使用get_context_data指定了可以传入到模板中的上下文字典。

self.request.GET['search'] 从 GET 中得到数据,是一个方便的用法。它将得到提交的查询姓名条件,如果有这个参数,那么我们使用filter函数对结果进行过滤。如果没有提交,则显示全部数据。

4 修改 address/urls.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', helloworld.index),
    url(r'^add/$', add.index),
    url(r'^list/$', list.index),
    url(r'^xls/(?P<filename>\w+)/$', xls_test.output),
    url(r'^login/$', login.login),
    url(r'^logout/$', login.logout),
    url(r'^wiki/', include('wiki.urls')),
    url(r'^address/', include('address.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

增加了一个 search 的 url 链接映射。

5 启动 server 测试

感觉这个通讯录也差不多了,现在让我们将其部署到 Apache 上去跑一跑吧。

但部署到 apache 时才知道,问题很多啊。主要问题如下:

  • CentOS 7服务器默认自带的Python版本太低

CentOS 7自带的Python版本为Python2.7,我们希望能够使用最新的Python 3.6

  • 相对路径的问题

    许多使用相对路径的地方都不对了。必须使用绝对路径。不过这一点对于部署来说的确有些麻烦,好在要改动的地方不多,主要在 settings.py 中。如数据库名字(sqlite3),模板的位置。

其它的就是要注意的地方了。

6 部署到 Apache 上的体验

只能说是体验了,因为我不是 Apache 的专家,也不是 mod_wsgi 的专家,因此下面的内容只能算是我个人的配置记录,希望对大家有所帮助。

6.1 服务器安装Python 3.6

下面的操作我们都假定环境是CentOS 7的环境,您可以在阿里云、腾讯云等公有云服务商购买ECS服务器,会自动给你安装好相应的操作系统,最后给你一个root的用户名和密码。

使用你自己熟悉的SSH环境,用root用户登录即可,首先安装Python 3.6,执行下面的命令。

yum install -y python36
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python36 get-pip.py
pip install virtualenv

这样的话你安装的pip会默认使用Python3.6,我们顺手安装好了virtualenv环境。操作系统的Python环境不要安装太多的库文件,都放到自己应用的venv环境中,创建一个虚拟的环境。

6.2 安装 mod_wsgi 模块

mod_wsgi的安装有很多种方法,这里介绍的是官方推荐的办法,使用pip安装,首先需要安装http的开发包,然后使用pip安装mod_wsgi到系统的lib库中,执行下面的命令。

yum install -y http-devel python36-devel
pip install mod_wsgi

然后我们需要将mod_wsgi安装到apache服务器module中去。

cd /etc/httpd/modules
ln -s /usr/lib64/python3.6/site-packages/mod_wsgi/server/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so mod_wsgi.so

我们通过在/etc/httpd/modules下面创建符号链接的方式,让apache在启动的时候自动加载mod_wsgi.so。

然后我们需要在/etc/httpd/conf.modules.d中创建一个文件,加载mod_wsgi.so,使用vi /etc/httpd/conf.modules.d/10-wsgi.conf命令创建配置文件,然后录入下面的内容:

LoadModule wsgi_module modules/mod_wsgi.so

之后使用systemctl restart httpd重启apache服务即可。

6.2 创建配置文件

假定我们的django工程在/var/www/proc/newtest,那么我们应该创建/etc/httpd/conf.d/wsgi.conf

WSGIScriptAlias /newtest /var/www/proc/newtest/newtest/wsgi.py process-group=newtest
WSGIPythonHome /var/www/proc/newtest/venv
WSGIPythonPath /var/www/proc/newtest

<Directory /var/www/proc/newtest/newtest>
    <Files wsgi.py>
        Require all granted
    </Files>
</Directory>

#Deamon模式设置
WSGIDaemonProcess newtest python-home=/var/www/proc/newtest/venv python-path=/var/www/proc/newtest
WSGIProcessGroup newtest

#静态文件
Alias /newtest/robots.txt /var/www/proc/newtest/static/robots.txt
Alias /newtest/favicon.ico /var/www/proc/newtest/static/favicon.ico

Alias /newtest/media/ /var/www/proc/newtest/media/
Alias /newtest/static/ /var/www/proc/newtest/static/

<Directory /var/www/proc/newtest/static>
Require all granted
</Directory>

<Directory /var/www/proc/newtest/media>
Require all granted
</Directory>

WSGIPythonHome 是Python运行环境的绝对路径,这里指向我们virtualenv的目录

这里我还设了两个别名,用来指向 mediastatic 目录。在 mediastaticLocation 中设置不进行脚本的解析。

上面的 media 路径是指向 Django 在 Python 上的安装目录。你完全可以将其拷贝出来,这样可能要方便得多。另外在 linux 下使用 ln 也相当的方便。

6.3 测试

http://localhost:8888/address

更详细的内容请参见 mod_wsgi 文档。关于 admin 的 media 和 template 好象并不需要配置,大家有什么结果可以告诉我。

同时如果你不想每次重启 Apache 来进行测试,可以将:

MaxRequestsPerChild 0

改为:

MaxRequestsPerChild 1

7 后话

上面的步骤是直接把开发的东西发布到了 Apache 中去,但实际中开发与运行可能环境根本不一样,最主要可能就是数据库方面的变化,如果model变化,则有可能要编写数据切换程序。许多实际的问题都需要仔细地考虑。

results matching ""

    No results matching ""