存档

‘django’ 分类的存档

django打包成客户端软件

2009年11月23日 roboter 没有评论

用django做了一个网站,希望断网的时候,也能够在本地使用。google的gears功能很弱,需要一大堆js来操作sqlite数据库,感觉很麻烦。因此还是需要自己弄一个客户端软件。

py2exe可以提供打包功能,将python打包成一个可执行的exe文件。google了一下“django py2exe” ,已经有了很多详细的文档。这个老外的博客比较详细,按照这个流程下来就基本搞定了。

首先确定要打的包:

  1. django
  2. cherrypy,是多线程的http服务器,替换django的http测试服务器
  3. 自己的app
  4. python的email

编写一个用于启动的python文件,从原作者处拷贝过来,稍微做了一些修改,命名为startdesktop.py

import os
import sys

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

import settings
from cherrypy import wsgiserver
import cherrypy
import webbrowser
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandler

if __name__ == "__main__":
  print ‘To exit DemoSite close this window.’

  # Set up site-wide config first so we get a log if errors occur.
  cherrypy.config.update({
    ‘environment’: ‘production’,
    ‘log.error_file’: ’site.log’,
    ‘log.screen’: True,
  })

  # run CherryPy and open browser
  try:
    full_media_path = os.path.dirname( os.path.abspath( sys.argv[ 0 ])) + settings.ADMIN_MEDIA_PREFIX

    cherrypy.tree.graft(
      AdminMediaHandler(
        WSGIHandler(),
        media_dir=full_media_path
      ),
      ‘/’
    )
    cherrypy.server.socket_port = settings.WEB_SERVER_PORT
    cherrypy.server.start()
    cherrypy.engine.start_with_callback( webbrowser.open, ( settings.START_URL,), )   
    cherrypy.engine.block()
  except KeyboardInterrupt:
    cherrypy.server.stop()

再然后就是py2exe的setup.py文件的编写了,基本上照抄下来,原作者使用的django版本很老,有一些模块已经没有了,同时又增加了很多新模块。同时目前的网站代码还是比较多,要手工一个个写是在太麻烦,就写一个函数搜一下,自动把当前的app文件添加进去。同时用正则表达式查找导入的django模块,发现一个就自动添加一个。修改后的setup.py文件如下:

# -*- coding: utf-8 -*-
from distutils.core import setup
import py2exe
import os

def add_path_tree( base_path, path, skip_dirs=[ '.svn', '.git' ]):
  path = os.path.join( base_path, path )
  partial_data_files = []
  for root, dirs, files in os.walk( os.path.join( path )):
    sample_list = []
    for skip_dir in skip_dirs:
      if skip_dir in dirs:
        dirs.remove( skip_dir )
    if files:
      for filename in files:
        sample_list.append( os.path.join( root, filename ))
    if sample_list:
      partial_data_files.append((
        root.replace(
          base_path + os.sep if base_path else ”,
          ”,
          1
        ),
        sample_list
      ))
  return partial_data_files

def add_python_file(includes,curpath,prefix=”):
    files = os.listdir(curpath)
    for f in files:
        fpath = os.path.join(curpath, f)
        if os.path.isdir(fpath):
            if f == ‘.svn’:
                continue
            add_python_file(includes,fpath,prefix+’%s.’ % f)
        else:
            name,ext = os.path.splitext(f)
            if ext == ‘.py’:
                includes.append(’%s%s’ % (prefix,name))
                add_django_import(includes,fpath)

import re
pat = r’from +django\.(.*?) +import +(.*?)( +as +|\n)’
def add_django_import(includes,filepath):
    #print filepath
    f = open(filepath,’r')
    data = f.read()
    f.close()
    models = re.findall(pat,data)
    for m in models:
        prefix = ‘django.%s.’ % m[0]
        pys = m[1].split(’,')
        for p in pys:
            p = p.lower().strip()
            dmodel = ‘%s%s’ % (prefix,p)
            add_django_file(includes,dmodel)
        prefix = ‘django.%s’ % m[0]
        add_django_file(includes,prefix)

def add_django_file(includes,dmodel):
    if dmodel in includes:
        return
    item = dmodel.replace(’.',’/')
    mpath = os.path.join(django_path,item)
    if os.path.exists(mpath):
        print ‘1′,dmodel
        includes.append(dmodel)
        #如果是django的目录,则继续寻找
        mpath = os.path.join(mpath,’__init__.py’)
        #print mpath
        if os.path.exists(mpath):
            add_django_import(includes,mpath)
    else:
        mpath = os.path.join(dja
ngo_path,’%s.py’ % item)
        if os.path.exists(mpath):
            #如果是django的文件,则继续寻找
            print ‘2′,dmodel
            #print mpath
            includes.append(dmodel)
            add_django_import(includes,mpath)

py2exe_options = {
  ‘py2exe’: {
    ‘compressed’: 1,
    ‘optimize’: 2,
    ‘ascii’: 1,
    ‘bundle_files’: 1,
    ‘dist_dir’: ‘../setup-client’,
    ‘packages’: [ 'encodings' ],
    ‘excludes’ : [
      'pywin',
      'pywin.debugger',
      'pywin.debugger.dbgcon',
      'pywin.dialogs',
      'pywin.dialogs.list',
      'Tkconstants',
      'Tkinter',
      'tcl',
    ],
    ‘dll_excludes’: [ 'w9xpopen.exe', 'MSVCR71.dll' ],
    ‘includes’: [
    'django.template.loaders.filesystem',
    'django.template.loaders.app_directories',
      'django.middleware.common',
        'django.contrib.auth.backends',
        'django.contrib.sessions.middleware',     
        'django.middleware.doc',     
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sessions.backends.db',
        'django.contrib.sites',
        'django.contrib.admin',
        'django.core.cache.backends',
        'django.db.backends.sqlite3.base',
        'django.db.backends.sqlite3.introspection',
        'django.db.backends.sqlite3.creation',
        'django.db.backends.sqlite3.client',
        'django.template.defaulttags',
        'django.template.defaultfilters',
        'django.template.loader_tags',
        'django.conf.urls.defaults',
        'django.contrib.admin.views.main',
        'django.core.context_processors',
        'django.contrib.auth.management',
        'django.contrib.auth.management.commands',
        'django.contrib.auth.handlers',     
        'django.contrib.auth',
        'django.contrib.auth.middleware',
        'django.views.static',
        'django.contrib.admin.templatetags.adminmedia',
        'django.contrib.admin.templatetags.admin_list',
        'django.contrib.admin.templatetags.admin_modify',
        'django.contrib.admin.templatetags.log',
        'django.conf.urls.shortcut',
        'django.views.defaults',
        'django.core.cache.backends.locmem',
        'django.templatetags.i18n',
        'django.views.i18n',
      # also used by django?
      'email.mime.audio',
      'email.mime.base',
      'email.mime.image',
      'email.mime.message',
      'email.mime.multipart',
      'email.mime.nonmultipart',
      'email.mime.text',
      'email.charset',
      'email.encoders',
      'email.errors',
      'email.feedparser',
      'email.generator',
      'email.header',
      'email.iterators',
      'email.message',
      'email.parser',
      'email.utils',
      'email.base64mime',
      'email.quoprimime',
      'traceback',
    ],
  }
}

# Take the first value from the environment variable PYTHON_PATH
python_path = os.environ[ 'PYTHONPATH' ].split( ‘;’ )[ 0 ]

django_path = os.path.normpath( python_path + ‘/lib/site-packages/’)

django_admin_path = os.path.normpath( python_path + ‘/lib/site-packages/django/contrib/admin’ )
py2exe_data_files = []

lc_path = os.path.normpath( python_path + ‘/lib/site-packages/django/conf/locale’)

# django admin files
py2exe_data_files += add_path_tree( django_admin_path, ‘templates’ )
py2exe_data_files += add_path_tree( django_admin_path, ‘media’ )
# project files
py2exe_data_files += add_path_tree( ”, ‘newmedia’ )
py2exe_data_files += add_path_tree( ”, ‘db’ )

exedict = py2exe_options['py2exe']
includes = exedict['includes']
curpath = os.path.dirname(__file__)
add_python_file(includes,curpath)

django_auth_path = os.path.normpath( python_path + ‘/lib/site-packages/django/contrib/auth’ )
add_python_file(includes,django_auth_path,prefix=’django.contrib.auth.’)
add_python_file(includes,django_admin_path,prefix=’django.contrib.admin.’)

print includes

setup(
  options=py2exe_options,
  data_files=py2exe_data_files,
  zipfile = None,
  console=[ 'startdesktop.py' ],
)

最后再执行setup.py py2exe就开始打包了,最后得到的exe为6M多,有点大。

在这个过程中,还会碰到其他问题:

  1. template文件太多,不能暴露出去,这个时候可以预先装载template文件到一个py文件中,再编写一个load_template_source函数,放到settings中的TEMPLATE_LOADERS,用于模板查找就可以了。
  2. admin界面不能
    使用,因为django使用imp来查找app的admin.py文件,而imp不能搜索.zip文件。可以使用woobiz的方法解决,这个暂时还没有试验。
分类: django 标签:

django 发送错误报告

2009年10月13日 roboter 1 条评论

网站发布之后,需要设置debug=false。这时候如果发生500错误,就不能直接看到出错信息了,django会通过email发送相关的错误信息。需要在settings中进行相关设置即可。老外有一篇文章写得很详细,主要设置如下字段:

EMAIL_HOST=’smtp.xxx.com’
EMAIL_PORT=25
EMAIL_HOST_USER=’my_mailbox_name’
EMAIL_HOST_PASSWORD=’my_mailbox_password’
SERVER_EMAIL=’webmaster@yyy.com’
ADMINS=( (’PK Shiu’, ’support@yyy.com’),)
DEBUG=False

gmail功能强大,可以使用gmail来发送,可以新注册一个gmail信箱专门用于发送错误报告。在settings中需要再加一个设置:

EMAIL_USE_TLS = True

分类: django 标签:

django admin界面处理

2009年4月15日 roboter 没有评论

django的admin很强大,可以很方便管理后台数据。近期的一个网站需要处理一堆关联的数据,如果简单使用admin来管理,会碰到一些问题。

情形如下:有A和B两个model

class A(models.Model):
    …

class B(models.Model):
    a = models.ForeignKey(A)
    …

在admin界面中A比较好编辑,在数据量不多的时候,添加B也方便。但是如果A有了几千条数据,添加B的时候要从这么一堆A数据中选择一条,从可用性和系统性能方面来说都是是根本不可能的。于是django admin提供了raw_id_fields字段,ForeignKey字段使用了input编辑框,用户需要手工输入A对象的id即可,或者在弹出的对话框中对A对象集合进行筛选。

这样处理感觉不够方便,数据维护人员需要记住A的id,这很容易填错id。不希望让维护人员了解这些ID,所以就希望能够在A对象的列表界面加一个“添加B对象”字段,当点击这个字段的时候,即可调出B对象的添加界面,django admin支持这种自定义字段,在A对象中如下设置即可:

    def new-B(self):
        return u’<a href="/admin/xxapp/B/add/?a=%d">新建 B</a>’  %  self.id
    new-B.allow_tags = True

通过这种自定义的方式可以添加很多操作,链接会带到任何想执行的功能。

B对象的添加界面出来之后,不希望还要了解A对象的id,因为这是从一个A对象发起的添加操作。所以需要把A对象ID传到B的添加界面中。参数的传递只能通过GET进行了,考察了django.contrib.admin.options,他是支持GET参数的,于是就这样把A对象的ID细节给隐藏掉了。如果怕用户不小心修改了A对象的ID,则可以使用javascript将这个input编辑框给disable掉。

django admin支持添加css和javascript:如下例:

    class ArticleAdmin(admin.ModelAdmin):
        class Media:
            css = {
                "all": ("my_styles.css",)
            }
            js = ("my_code.js",)

B添加完成之后,还希望继续添加另外一个B,也是基于同样的A对象,点击“保存并增加另一个”,这时候GET信息丢失了,A对象ID没有了。第一反映是希望用javascript把“保存并增加另一个”的链接加上GET信息,查看了页面的源代码,是一个submit的操作,没有链接可以修改的。

input type="submit" value="保存并增加另一个" name="_addanother" /

还是到服务端来考察,在django.contrib.admin.options中查找_addanother,在response_add函数中针对_addanother进行了处理,于是事情就简单了,在B的ModelAdmin对象中重写response_add函数:

def response_add(self, request, obj, post_url_continue=’../%s/’):       
        if request.POST.has_key("_addanother"):
            from django.utils.translation import ugettext as _
            from django.utils.encoding import force_unicode
            from django.http import HttpResponseRedirect
            opts = obj._meta
            pk_value = obj._get_pk_val()           
            msg = _(’The %(name)s "%(obj)s" was added successfully.’) % {’name’: force_unicode(opts.verbose_name), ‘obj’: force_unicode(obj)}           
            self.message_user(request, msg + ‘ ‘ + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))

            #新增加代码,记录下GET的信息
            getinfos = request.GET.items()
            gets = ‘?’
            for info in getinfos:
                gets += ‘%s=%s&’ % (info[0],info[1])
            path = request.path + gets

            return HttpResponseRedirect(path)
        return super(admin.ModelAdmin, self).response_add(request,obj,post_url_continue)

这样GET信息就可以保存下来了。

查看了django的最新版本,GET信息还是没有保存,这个不应该,提了一个ticket上去。

分类: django, python 标签:

ubuntu nginx django安装记录

2009年3月26日 roboter 1 条评论

需要安装一台服务器,主要作为图片的处理存储显示等功能,选择了ubuntu作为服务器,ubuntu的服务器版本只有500多M,非常小巧,同时使用人数众多,有什么问题能够很好解决。

使用的是2M带宽,ubuntu 8.04只用40多分钟就全部下下来了。安装很快完成,进入了配置。

首先尝试安装django,直接使用sudo apt-get install python-django,结果把0.96.1的版本给弄下来了,只能删除这个包了,sudo apt-get remove python-django,直接wget http://www.djangoproject.com/download/1.0.2/tarball/,下了一个1.0.2版本,顺利安装,然后又安装了PIL图形处理库,这次直接用sudo apt-get install python-imaging,PIL的最后一个版本在06年底发布,两年多了,没有再发布新版本,但还是有很多地方在用。

nginx很牛,所以想试试。nginx 8.04已经有了一个0.5版本,太老了,从nginx网站下了一个当前最新的0.7.44版本,开始configure,报没有pcre,zlib等模块。打算使用zlib,pcre等的最新版本,就使用wget下了最新的模块,config没有问题,却报没有make,ubuntu 8.04.2服务版本连make都没有,真是够简洁的。没有make,也没有gcc,g++等工具了,不过都可以用apt下载安装,还是方便。

nginx0.5 已经在服务器中配置好了,包括在/etc/init.d中的脚本,所以就打算覆盖安装,可以省一些事情。nginx 0.5的配置参数为

–conf-path=/etc/nginx/nginx.conf –error-log-path=/var/log/nginx/error.log –pid-path=/var/run/nginx.pid –lock-path=/var/lock/nginx.lock –http-log-path=/var/log/nginx/access.log –http-client-body-temp-path=/var/lib/nginx/body –http-proxy-temp-path=/var/lib/nginx/proxy –http-fastcgi-temp-path=/var/lib/nginx/fastcgi –with-debug –with-http_stub_status_module –with-http_flv_module –with-http_ssl_module –with-http_dav_module。

将相关的路径参数拷贝下来,再加上设定–prefix=/usr。其他的参数不要,这样就顺利安装了。执行/etc/init.d/nginx start ,看到了欢迎页面。

还需要安装flup,django站点中的flup链接报错,google了一下,找到1.0.1的下载地址http://www.saddi.com/software/flup/dist/flup-1.0.1.tar.gz,直接下载安装。

拷贝django提供的脚本,进行部分修改,可以方便启动django:

#!/bin/bash

# Replace these three settings.
PROJDIR="/home/user/myproject"
PIDFILE="$PROJDIR/mysite.pid"
SOCKET="$PROJDIR/mysite.sock"

cd $PROJDIR
if [ -f $PIDFILE ]; then
    kill `cat -- $PIDFILE`
    rm -f -- $PIDFILE
fi
exec python manage.py runfcgi pidfile=$PIDFILE host=127.0.0.1 port=8080

可以将这个脚本放入/etc/init.d/目录下,再在rcx.d目录加上一个链接,就可以实现开机自动启动django。

在/etc/nginx/nginx.conf文件中,主要server中配置,加上如下语句:

server {
listen 80;
server_name localhost;
access_log    /var/log/nginx/localhost.access.log;
location / {
# host and port to fastcgi server
fastcgi_pass 127.0.0.1:8080;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_pass_header Authorization;
fastcgi_param REMOTE_ADDR           $remote_addr;
fastcgi_param SERVER_PROTOCOL       $server_protocol;
fastcgi_param SERVER_PORT           $server_port;
fastcgi_param SERVER_NAME           $server_name;
fastcgi_intercept_errors off;
}
location ^~ /media/ {
alias  /usr/lib/python2.5/site-packages/django/contrib/admin/media/;
}
location ~* ^.+\.(gif|png|jpg|jpeg|css|swf|htm|html|asp|php|jsp|js|doc|txt)$ {
root   /usr/lib/python2.5/site-packages/django/contrib/admin/media/;
access_log   off;
}
}

现在可以看到页面了,但是,还有问题!

使用了mysql作为django的后台数据库,mysql是ubuntu帮忙安装好的,结果python manage.py syncdb的时候报错:

_mysql_exceptions.Warning: Incorrect string value: ‘\xE4\xB8\x8A\xE4\xBC\xA0…’ for column ‘name’ at row 1

估计是字符编码的问题,难道mysql缺省不是utf8吗?查了一下,默认的是Latin1编码,在/etc/my.cnf中,[client]和[mysqld]下面均加上default-character-set=utf8,这样变成了utf8的编码,这个问题解决了。

然后可以看到首页内容了,以为应该差不多了。然后依然还有问题:进入admin界面,报错:

Unhandled Exception

An unhandled exception was thrown by the application.

检查了error.log,如下:

2009/03/25 23:26:05 [error] 17966#0: *6 FastCGI sent in stderr: “” while reading response header from upstream, client: 192.168.1.2, server: localhost, request: “GET /admin/ HTTP/1.1″, upstream: “fastcgi://127.0.0.1:8080″, host: “192.168.1.129″
2009/03/25 23:26:05 [error] 17966#0: *6 FastCGI sent in stderr: “Traceback (most recent call last):
File “/usr/lib/python2.5/site-packages/flup-1.0.1-py2.5.egg/flup/server/fcgi_base.py”, line 558, in run
protocolStatus, appStatus = self.server.handler(self)
File “/usr/lib/python2.5/site-packages/flup-1.0.1-py2.5.egg/flup/server/fcgi_base.py”, line 1116, in handler
result = self.application(environ, start_response)
File “/usr/lib/python2.5/site-packages/django/core/handlers/wsgi.py”, line 243, in __call__
response = middleware_method(request, response)
File “/usr/lib/python2.5/site-packages/django/contrib/sessions/middleware.py”, line 35, in process_response
request.session.save()
File “/usr/lib/python2.5/site-packages/django/contrib/sessions/backends/db.py”, line 52, in save
session_key = self.session_key,
File “/usr/lib/python2.5/site-packages/django/contrib/sessions/backends/base.py”, line 152, in _get_session_key
self._session_key = self._get_new_session_key()
File “/usr/lib/python2.5/site-packages/django/contrib/sessions/backends/base.py”, line 144, in _get_new_session_key
if not self.exists(session_key):
File “/usr/lib/python2.5/site-packages/django/contrib/sessions/backends/db.py”, line 25, in exists
Session.objects.get(session_key=session_key)
File “/usr/lib/python2.5/site-packages/django/db/models/manager.py”, line 93, in get
return self.get_query_set().get(*args, **kwargs)
File “/usr/lib/python2.5/site-packages/django/db/models/query.py”, line 304, in get
num = len(clone)
File “/usr/lib/python2.5/site-packages/django/db/models/query.py”, line 160, in __len__
self._result_cache = list(self.iterator())
File “/usr/lib/python2.5/site-packages/django/db/models/query.py”, line 275, in iterator
for row in self.query.results_iter():
File “/usr/lib/python2.5/site-packages/django/db/models/sql/query.py”, line 206, in results_iter
for rows in self.execute_sql(MULTI):
File “/usr/lib/python2.5/site-packages/django/db/models/sql/query.py”

尝试将数据库换成sqlite3,也是这个现象。google了一下,有人碰见过类似问题,但这个问题涉及的面太广,没有说出解决方案,只能靠自己了。准备第二天再来解决,关机休息。

第二天,机器重新启动之后,很奇怪,admin没有问题了。nginx首先自动启动,然后再手工启动fastcgi,前面好像顺序不对,不知道是不是这个理由。

分类: django, linux 标签:

ie6无法访问cookie

2008年10月27日 roboter 没有评论

在内部网用django+apache建了一个站点,用ie6无法登陆,提示浏览器不允许cookie,但是使用firefox可以登录。刚开始还以为是settings.py中没有设置SESSION_COOKIE_DOMAIN导致,但是加上了也不行。google了一堆,发现在ie6下有很多cookie的问题,牵涉到跨域的问题。解决的办法就是采用p3p的方法,开始Google p3p,在http://www.yxl.cn/Info/20060302,212041,5095.html大致了解了p3p的概念。需要在网站上放置一个p3p.xml的文件,或者在djiango的respon中加入p3p的CP代码。ibm有一个工具可以编写p3p.xml,但是用java写的,估计还得下载jdk之类。感觉过于麻烦,放弃,在网上google p3p的CP代码,看是否有通用的,发现了CP=”CAO PSA OUR”,这个代码的意思就是告诉浏览器,我不需要p3p,浏览器就可以不用考虑p3p创建cookie了。

感觉很奇怪,那么要p3p干什么?用户在本地浏览器的设置也不起作用了。ie6不是多此一举吗?firefox则根本不考虑p3p。

同时在网上google了一个django的解决方案:http://www.djangosnippets.org/snippets/1084/,创建了一个Middleware,这样就不用在每个view中加入p3p的说明了。其中的CP代码换成”CAO PSA OUR”即可。

P3P_COMPACT = ‘CP=”CAO PSA OUR”‘
class MiddlewareResponseInjectP3P(object):
    def __init__(self):
        self.process_response = self.inject

    def inject(self, request, response):
        response['P3P'] = P3P_COMPACT
        return response

分类: django 标签:

django apache配置

2008年9月30日 roboter 没有评论

不让django处理media目录

Alias /media /home/……/django/contrib/admin/media
<Location “/media/”>
    SetHandler None
</Location>

<LocationMatch “\.(jpg|gif|png)$”>
    SetHandler None
</LocationMatch>

报错

Invalid command ‘Alias’, perhaps misspelled or defined by a module not included in the server configuration

原因:没有安装alias_module

加上LoadModule alias_module modules/mod_alias.so 通过

分类: django 标签: