分类目录归档:编程

使用nginx做pypi的反向代理,搭建pypi官方镜像

受国内的网络环境限制,直接使用python官方的pypi源速度不稳定还经常抽风。对公司来说,搭建一个自己的pypi镜像非常有必要。

搭建pypi镜像的目的以及相关方案

方案优点缺点
PyPiImplementations功能强大,提供用户注册、包管理等各项功能。配置复杂,需对pypi源进行全量同步。全量同步速度慢,且经常失败。
PyPiImplementations
Simple repository with fallback using Apache
配置相对简单。可通过ftp等方式实现私有包的管理。增加反向代理缓存后可实现官方pypi源的加速。部分python包只是在pypi上放了一个链接,真正的包放在自己的服务器上(极少数包),对这类包无法通过反向代理进行加速。
权限管理功能稍弱。
私有包需要通过ftp等方式进行管理。
注:私有包管理的问题可通过安装web版的文件管理工具来规避。

考虑到部门内部pypi镜像的需求简单,不需涉及太多权限功能方面的工作,建议使用“Simple repository with fallback using Apache”方案。

反向代理方案

“Simple repository with fallback using Apache”中只是将本地无法处理的pypi请求直接转发到官方pypi源。在对官方源进行同步时无法实现对官方pypi源进行加速。为解决该问题,需要对官网的请求进行反向代理并缓存。
关于反向代理的支持请参考:代理、反向代理知识普及squid apache-mod_proxy lighttpd nginx
squid是老牌的反向代理服务器,但在实际使用过程中发现squid太过强大的功能导致配置复杂,不推荐使用。
nginx发展迅速的新兴服务器,对反向代理提供良好的支持,并被多家大型网站使用。最重要的是nginx的配置比squid要容易太多。

nginx配置要点

  • 参考:
  • 安装
    • 官方安装文档 ubuntu官方源版本比较老,建议添加ppa源再安装。
    • 按照说明添加ppa源不可用时,可查看 /etc/apt/sources.list.d 中的nginx ppa源配置并修正
  • nginx配置
    • nginx默认启用了gzip支持。pip无法识别gzip后的网页,需要关闭gzip支持。
proxy_cache_path  /var/lib/nginx/cache/ levels=1:1:2 inactive=24000h keys_zone=cache:100m;
server {
        listen   8000 default;
        server_name  localhost;
        access_log  /var/log/nginx/localhost.access.log;
        #中间省略部分默认配置
        location /pypi {
                proxy_pass http://pypi.python.org/simple;
        proxy_cache cache;
        proxy_cache_valid  any 2400h;
        }
        location /packages {
                proxy_pass http://pypi.python.org/packages;
        proxy_cache cache;
        proxy_cache_valid  any 24000h;
        }
}

相关链接

timeline项目开发日志–登陆、注册模块

利用twitter/bootstrap,项目的基础模板算是顺利搞定。接下来开始处理用户中心。
用户中心主要包括用户登陆、注册以及头像等个人信息维护。此前,用户的注册管理我一直使用django-registration。只是这个APP有些不思进取,09年发布了0.8alpha版后就一直没什么动静。这次决定尝试另外一个用户模块组件django-userena
相比django-registration,django-userena的功能要完善的多。除基础的登陆注册模块外django-userena甚至还带了站内消息功能。django-userena的易用性方面也做的非常的不错。django-userena自带了默认模板,并有提供一个完整的演示项目,让你可以轻松上手。这里有个官方的在线demo,感兴趣可以去看看

django-userena同twitter/bootstrap的整合

我们自然是希望所有的APP不用做任何修改,拿来就能用了。不过事与愿违,在整合的过程中多多少少都会遇到一些问题。django-userena默认的模板在项目中显示的非常难看。我们需要重写django-userena的默认模板,并且用django-bootstrap来生成form。
forms.py

#为原始form添加BootstrapMixin
from bootstrap.forms import BootstrapMixin
class BsAuthenticationForm(AuthenticationForm, BootstrapMixin):
    def __init__(self, *args, **kw):
        super(BsAuthenticationForm, self).__init__(*args, **kw)
        self.__bootstrap__()

urls.py

#重写urls,指定使用的form
from django.conf.urls.defaults import *
from userena import views as userena_views
from profiles.forms import BsSignupForm, BsAuthenticationForm
urlpatterns = patterns('',
    url(r'^signup/

中文用户名问题

同django-admin一样,django-userena也无法使用中文进行注册。对于一个中文网站而言,不能使用中文注册ID似乎有些太不合理的。
django-userena使用正则表达式对用户名进行校验,重写注册form修改认证规则即可取消该限制。

USERNAME_RE = r'^\S+
, userena_views.signup,
        {'signup_form': BsSignupForm}, name='userena_signup'),
    url(r'^signin/

中文用户名问题

同django-admin一样,django-userena也无法使用中文进行注册。对于一个中文网站而言,不能使用中文注册ID似乎有些太不合理的。
django-userena使用正则表达式对用户名进行校验,重写注册form修改认证规则即可取消该限制。

USERNAME_RE = r'^\S+$'
attrs_dict = {'class': 'required'}
class BsSignupForm(SignupForm, BootstrapMixin):
    username = forms.RegexField(regex=USERNAME_RE,
                                max_length=30,
                                widget=forms.TextInput(attrs=attrs_dict),
                                label=_("Username"),
                                error_messages={'invalid': _('Username must contain only letters, numbers, dots and underscores.')})
    def __init__(self, *args, **kw):
        super(BsSignupForm, self).__init__(*args, **kw)
        self.__bootstrap__()

Django数据库迁移组件(South)

django提供syncdb命令,用于从models自动生成数据库。但在models结构变化后,syncdb并无法自动实现数据库的更新。South组件即是为了解决该问题而出现的。
下面简单介绍一下South的一些最常见用法,更详细的使用方法见South的官方手册。

假设我们创建了一个名叫southtut的app

  1. 生成初始化数据库的south脚本。允许上述命令后将在对于的app目录下生成 migrations目录,south的数据库迁移脚本即保存在该目录。
    ./manage.py schemamigration southtut --initial
  2. 在所用south后,syncdb命令不会为使用south托管的app生成数据库。migrate命令用于执行south的数据库迁移脚本, 实现数据库更改。在这里执行的操作是为southtut创建相关的数据库表。不带app名字时,将对project中的所有app进行migrate操 作。
    ./manage.py migrate southtut
  3. 在很多应用场景中,我们已经用syncdb将数据库给创建好了。这时候运行上面的migrate命令将会提示相关表已经存在,命令执行失败。这 时候我们需要告诉south需要跳过某些migrate操作。上面的命令将告诉south,系统已经执行过了0001号数据库迁移脚本。
    ./manage.py migrate southtut 0001 --fake
  4. 接下来我们对models进行了某些改动,修改后增减了某些字段。
  5. 该命令将自动生成数据库的迁移脚本。在migrations目录下可以看到新增加了文件0002_xxx.py,该文件即是此次models改 动的数据库升级脚本。打开文件后可看到其中有一个类Migration,类中有两个方法forwards和backwards。这两个方法分别实现数据库 升级和会滚时对数据库的操作,具体指令的含义参考south的数据库API
    ./manage.py schemamigration southtut --auto
  6. 应用models的改动到数据库
    ./manage.py migrate southtut

合理的组织django的settings文件

django在一个项目的目录结构划分方面缺乏必要的规范,因此不同人的项目组织形式也千奇百怪,而且也很难说谁的做法就比较好。我根据自己的项目组织习惯,发布了一个项目dj-scaffold
前些天在reddit上为我的项目dj-scaffold打了个“广告”(见:http://redd.it/kw5d4)。不想评价甚糟,甚至差点被打成负分。其中更也人将这个项目说的一文不值。面对负面声音虽然会有些不爽,但其中的建设性意见还是需要听取的,至于那些纯属个人偏好部分就自动过滤了。
在谈及settings文件如何组织时,coderanger建议参考The Best (and Worst) of Django中的做法。文中的主要观点是开发环境和生产环境的配置都需要放到VCS中进行版本控制。参考文中的做法,我对settings模块做了部分调整。注:代码 https://github.com/vicalloy/dj-scaffold/tree/master/dj_scaffold/conf/prj/sites/settings

local_settings的弊病

为将项目的默认配置和本地配置区分开,最常用的做法是增加一个local_settings.py文件,并在settings文件的最后对该文件进行import。

try:
    from local_settings import *
except:
    pass

由此引发的问题是你不能对local_settings.py进行版本控制,部署环境的配置万一丢失将难以找回。

解决方案

针对该问题,建议的解决方案如下

合理的配置文件组织方式

|~settings/
| |-__init__.py
| |-base.py   #默认配置信息
| |-dev.py    #开发环境的配置
| |-local.sample    #本地的扩展配置在dev和production的最后进行import
| |-pre.sample    #设置当前使用的配置为生产环境还是开发环境
| `-production.py    #生产环境的配置

使用方式

DJANGO_SETTINGS_MODULE

django的admin脚本提供了settings参数用于指定当前使用的配置文件

django-admin.py shell --settings=settings.dev

在wsgi脚本中则可直接设置需要使用的settings

deploy.wsgi
os.environ['DJANGO_SETTINGS_MODULE'] = settings.production

简化参数
当然,如果每次使用django-admin.py的时候都要带上settings参数还是非常恼人,所以推荐的做法是在pre.py中配置自己所需要使用的配置文件。

SETTINGS = 'production' #dev

Gliffy confluence插件的破解

Gliffy是一个在线画流程图的工具,或者简单的说Gliffy就是web版的Visio。Gliffy的用户体验非常的好,加打开浏览器就可以使用,使用起来非常的方便。Gliffy同时推出了confluence的插件版本。在安装插件后可在confluence中方便的编辑和插入流程图。

同事对Gliffy甚为垂涎,只是Gliffy还有些小贵。confluence插件版,500用户的许可要卖到2000$。

虽然同事的利诱有些不靠谱,但偶尔干干着方面的事也还算有趣,那就动手吧。

注:下面只是简单的讲解一些关键点,如果你对java一窍不通,那还是罢手吧。

java应用破解的通常做法是:将文件反编译,找到认证部分的处理,直接将认证结果返回true。java的反编译工具推荐Java Decompiler

Gliffy的jar包比较大,但其中java代码并不是很多。而且Gliffy采用的是仿君子不防小人的做法,里面的java代码并未混淆过。在代码中有个目录非常的扎眼\src\com\gliffy\core\license\。再做些简单的分析我们即可找到真正的关键点SimpleLicenseManager.java

不得不说Gliffy的命名还是非常规范的。以函数名为线索,很容易就可以找到我们要的函数validLicenseValues。简单粗暴的将函数返回值改为true。打包并重新安装插件。

如果问题就这么解决了,那也未免顺利的有些不太寻常。虽然可以成功安装,但运行的时候抛出一堆的异常。试着进入Gliffy的管理界面,依旧是一堆的异常。虽然我们强制的将认证结果设置为了true,但某些地方还需要获取license的到期日期等信息。由于读不到相关数据,直接出异常了。

既然如此,那我们需要先将license信息写入系统。

validLicenseValues还原,然后找到设置license的函数installLicense。在函数中注释掉license认证相关的代码,让系统在忽略认证结果的情况下强行写入注册信息。修改后的java文件在执行时还会报getHostedStatus的虚函数错误。按理说这个函数应当会在子类中被重写。不过我们先不管这么多,把它修改为普通函数并直接返回0。

重新打包安装,然后进入Gliffy的管理界面,license信息随便填写,然后保存。保存是成功的,但认证还是失败。修改validLicenseValues函数,重新打包安装。这次由于我们有写入注册信息,因此就不会再出现先前的空指针异常了。

享受Gliffy吧。

注:Gliffy确实是个好东西,如果喜欢,还是尽量说服公司出钱买吧。

Django标准化项目dj-scaffold

由于Django没有象rails一样指定项目的目录结构规范,很多人都对django项目的目录结构要如何组织而感到困惑。为此我又新创建了一个开源项目dj-scaffold(django的脚手架)。这个项目用于自动生成一个标注化的django项目和app。

项目地址:https://github.com/vicalloy/dj-scaffold

安装

已经发布到了pypi,所以你可以用pip或easy_install 来进行安装。

pip install dj-scaffold
easy_install dj-scaffold

使用

dj-scaffold主要提供了两个命令,dj-scaffold.pylbstartapp

dj-scaffold.py

该脚本用于取代django的startproject命令。使用方式如下:

dj-scaffold.py projectname 

在该命令执行后,将创建项目projectname。在项目的scripts目录中提供了脚本create_env.pyenv.rc

  • create_env.py 执行该脚本将自动初始化python虚拟环境。新生成的python虚拟环境在env目录。
  • env.rc 该脚本用户启动python虚拟环境(source env.rc)。该脚本同时为python manage.py设置了快捷方式$mg。你可以在任何目录调用$mg来执行django命令。比如你用$mg runserver来启动测试服务器。

项目对应的目录结构如下:

注:文件太多,去掉了部分不重要的文件
dj-scaffold.py projectname 
|+docs/    #用于存放项目的相关文档
|+env/     #python虚拟环境,由脚本自动生成
|~requirements/     #第三方依赖包的存放位置
| `-requirements.pip    #pip的依赖说明文件
|~scripts/    #系统相关的脚本
| |-create_env.py    #创建python虚拟环境(env目录)
| `-env.rc    #进入python虚拟环境。同时提供python manger.py的快捷方式$mg。可在任意目录使用$mg。
|~sites/    #Django的项目文件。在settings文件中增加了部分默认配置。如数据库默认使用sqlite,设置项目的模板以及静态文件目录。
| |+media/    #项目静态文件(用户上传)
| |+static/    #项目静态文件(css、js等)
| `+templates/    #项目模板
|+tools/    #一些项目依赖的第三方工具包。如python虚拟环境初始化脚本等。
`~wsgi/    #项目部署用的wsgi文件
  `-dj_scaffold.wsgi

lbstartapp

lbstartapp作为django的扩展命令提供。将dj_scaffold加到INSTALLED_APPS后即可使用该命令。该命令将生成一个标准的app,相比django自带的startapp,lbstartapp将那些不太常用的app默认目录也都给生成了出来。对应目录结构如下:

|+management/    #命令目录
|+static/    #静态文件目录
|+templates/    #模板目录
|+templatetags/    #tag目录
|-__init__.py
|-admin.py    #admin管理后台的models配置文件
|-forms.py
|-models.py
|-settings.py    #app自己的settings文件
|-tests.py
|-urls.py    #urls配置文件
`-views.py

NOTE

  • 项目的大多代码来自:https://github.com/lincolnloop/django-startproject
  • 类似项目:https://github.com/mozilla/playdoh 个人觉得这个项目还可以。不过我个人觉得自己写的更符合自己的习惯。
  • “摒弃魔法”是Django的哲学之一。为此Django没有为用户提供太多的默认操作,它希望一切对用户都是显示可见的。这本没太大的问题,但在我看来“no magic”并不代表连规范都不要。Django实在是太缺乏一些必要的规范。

利用mod_rewrite实现域名的切换

最初想将haoluobo.com的域名做其他用途,于是创建了子域名vik.haoluobo.com,并将博客挂在blog目录。最终haoluobo.com的域名一直被空了下来。最近想域名空着也是浪费,干脆将博客和知识库切换到haoluobo.com下。

切换后

博客地址为:http://haoluobo.com

知识库地址:/trac/

这时候问题来了。切换域名后,此前老域名上的所有链接都失效了。为了保证原有地址依然有效,我利用mod_rewrite将老地址的链接都转发到新地址。

博客的老地址:/

http://vik.haoluobo.com/ apache的静态文件目录www下创建目录blog,并在改目录下添加.htaccess文件

RewriteEngine On
RewriteRule (.*) /$1 [R=301]

知识库的处理类似,

知识库的老地址:http://vik.haoluobo.com/trac/

在www目录下创建trac目录,并在改目录下添加.htaccess文件

RewriteEngine On
RewriteRule (.*) /trac/$1 [R=301]

注:在http协议中,状态码301标示永久重定向,这样搜索引擎就知道你的老地址今后就不用了。

ruby中的block和yield

rails的同学极力鼓吹rails的简洁高效。虽一早就认定rails不太符合我的习惯,还是经不住诱惑再多看了rails两眼。
rails中大量使用了ruby的block语法。由于此前没有系统的看过ruby语法,block还是给我带来了不小的困惑。网上不少介绍block的文章,都说的不是太清楚。在看过《Programming Ruby》后才真正的开始理解block的用法。
代码块(Blocks)是指一块代码,用大括号({})或者do…end来标明起始和结束,代码块只能跟在方法调用后边。
yield语句:在方法内部使用yield语句来占位,当方法执行到yield时,实际执行的是调用方法时跟在后边的的代码块。
|x|:变量用一对’|’包裹,在代码块中使用,用于接受yield传递的参数。yield后跟的参数会传递给代码块中用| |标志的变量。
下面通过例子来更直观的认识block。

def x
  p "=start"
  yield 'a'
  yield 'b'
  p "=end"
end
x do |a|
  p "hello", a
end

程序的运行结果为:

"=start"
"hello"
"a"
"hello"
"b"
"=end"

定义了函数x,其中两次使用yield调用block中的代码块。block代码块支持一个参数。

django1.3的staticfiles

django1.3新加入了一个静态资源管理的app,django.contrib.staticfiles。在以往的django版本中,静态资源的管理一向都是个问题。部分app发布的时候会带上静态资源文件,在部署的时候你必须手动从各个app中将这些静态资源文件复制到同一个static目录。在引入staticfiles后,你只需要执行./manage.py collectstatic就可以很方便的将所用到app中的静态资源复制到同一目录。

staticfiles的引入,方便了django静态文件的管理,不过感觉staticfiles的文档写的并不是太清楚,初次使用的时候还是让我有些困惑。

下面简单的介绍一下staticfiles的主要配置:

  • STATIC_ROOT:运行manage.py collectstatic后静态文件将复制到的目录。注意:不要把你项目的静态文件放到这个目录。这个目录只有在运行collectstatic时才会用到。我最开始想当然的以为这个目录和MEDIA_ROOT的作用是相同的,致使在开发环境下一直无法找到静态文件。
  • STATIC_URL:设置的static file的起始url,这个只可以在template里面引用到。这个参数和MEDIA_URL的含义差不多。
  • STATICFILES_DIRS:除了各个app的static目录以外还需要管理的静态文件位置,比如项目公共的静态文件差不多。和TEMPLATE_DIRS的含义差不多。
  • 各个APP下static/目录下的静态文件django的开发服务器会自动找到,这点和以前APP下的templates目录差不多。
  • urls.py中加入静态文件处理的代码
    from django.contrib.staticfiles.urls import staticfiles_urlpatterns
    # ... the rest of your URLconf goes here ...
    urlpatterns += staticfiles_urlpatterns()

simple-todo (Django 版)

项目地址:https://github.com/vicalloy/django-simple-todo

缘起

simple-todo最早是web.py一个中文教程的例子。后来Uliweb的作者limodou 认为这个教程很不错,于是有了Uliweb版的simple-todo。接着又有了Bottle版和Flask版。这俨然成了一个FrameworksShow项目。既然是FrameworksShow, 那Django的总不应当缺了吧。
simple-todo: 一个简易的 todo 程序
http://simple-is-better.com/news/309
Simple Todo (Uliweb 版本) 教程 by @limodou
http://simple-is-better.com/news/312
Simple-TODO Bottle 实现版 by @zoomquiet
http://simple-is-better.com/news/509
Simple-TODO Flask实现版 by @wyattwang
http://simple-is-better.com/news/524

运行需求

Django>=1.3

安装及运行

初始化数据库: python manage.py syncdb
启动: python manage.py runserver
使用: 在浏览器中打开 http://127.0.0.1:8000/
Django Admin: 在浏览器中打开 http://127.0.0.1:8000/admin/

项目开发记录

  1. 创建django project和app:
    django-admin.py startproject simple_todo_site
    cd simple_todo_site/
    python manage.py startapp simpletodo
  2. 编辑settings.py完成数据库、模板、静态文件等配置,主要配置条目:
    #注:我认为django应当加更多的默认设置,这些配置改的挺烦
    DATABASES
    INSTALLED_APPS
    STATIC_ROOT
    STATICFILES_DIRS
    TEMPLATE_DIRS
  3. 编辑urls.py把django admin和static文件url配置加上。
  4. 编辑simpletodo/models.py,完成数据模型:
    from django.db import models
    from django.contrib import admin
    class Todo(models.Model):
        title = models.CharField( max_length=255)
        finished = models.IntegerField(default=0)
        def __unicode__(self):
            return self.title
  5. 创建数据库:
    python manage.py syncdb
  6. 跑起来,进django admin看看先:
    python manage.py runserver
    #http://127.0.0.1:8000/admin/
  7. 接下来,略…