Error: OID not increasing

调SNMP设备时候遇到了某厂家的奇葩输出,用命令行下的snmpwalk:

$ snmpwalk x.x.x.x -c "public" -v 2c 1.3.6.1.4.1.5105.80.6.2.1.47
SNMPv2-SMI::enterprises.5105.80.6.2.1.47.3589396343 = INTEGER: 8
SNMPv2-SMI::enterprises.5105.80.6.2.1.47.3589497833 = INTEGER: 8
SNMPv2-SMI::enterprises.5105.80.6.2.1.47.3589501999 = INTEGER: 8
SNMPv2-SMI::enterprises.5105.80.6.2.1.47.3589331126 = INTEGER: 8
Error: OID not increasing: SNMPv2-SMI::enterprises.5105.80.6.2.1.47.3589501999
 >= SNMPv2-SMI::enterprises.5105.80.6.2.1.47.3589331126

查了一下,常规SNMP OID是应该在GETNEXT请求下顺序输出的,每个值是之前一个的+1,为了避免出现数值循环,snmpwalk默认检查数字是否递增;

上面例子中,显然不是如此,这个snmp agent并不使用递增识别码。

这还是好解决的,snmpwalk有个-Cc参数:

-Cc    Do not check whether the returned OIDs are increasing. Some agents (LaserJets are an example) return OIDs out of order, but can complete the walk anyway. Other agents return OIDs that are out of order and can cause snmpwalk to loop indefinitely. By default, snmpwalk tries to detect this behavior and warns you when it hits an agent acting illegally. Use -Cc to turn off this check.

不过在PHP中坑就大了,首先最普通的snmp2_walk函数完全没有这些特性支持,wait,还有个snmp2_​real_​walk?难道之前那个是fake的?搞了半天原来real_walk返回的数组键值是全OID数值,没real那个的键值则仅仅是识别码。这命名还真是够PHP Style啊!

回到原点,把php-snmp模块的函数逐个都点过了,就没有关于OID increase的设置,看了stackoverflow上的某回答,自己实现了用snmp2_​getnext来遍历,好我也实现一个,结果没几下就被设备封IP了,原来snmpwalk用的是snmpbulkget 的请求,自己的getnext,则会被设备认为请求过多封了!

还是Google帮我找到了PHP下oid_increasing_check关键字,原来藏在SNMP class(5.4+),所以好好一个snmp2_walk函数就变成这样了:

    $snmp = new SNMP(SNMP::VERSION_2C, HOST, COMMUNITY);
    $snmp->oid_increasing_check = FALSE;
    $snmp->quick_print = TRUE;
    $snmp->valueretrieval = SNMP_VALUE_LIBRARY;
    $snmp->oid_output_format = SNMP_OID_OUTPUT_NUMERIC;
    $data = $snmp->walk($cmd, TRUE);

对了,walk()的那个TRUE参数,不TRUE就是以前的real_walk,TRUE了就是fake walk~~~~

文章分类 Networking

RHEL/CentOS系系统的社区维护资源

Linux各个发行版的技术上虽然有差别,但一般不至于有很大鸿沟,实际上更复杂的其实是各个发行版的维护社区的工作方式和交流文化的差别,如果不了解去利用相应的社区资源,就会觉得维护这个发行版异常吃力,从而产生“XXX发行版不好用”的错觉。

因为工作原因最近我接触维护的系统多为CentOS,之前对CentOS的印象都是“又古老又难维护”,不过几个月的积累下来,发现RH系的社区资源并不比Debian/Ubuntu的少,只不过是国内的维护文化和他们的相去甚远,几乎无法兼容,以致很多人都缺乏了解,所以觉得需要撰文列举下这些资源。

以下很多第三方仓库都在Centos Wiki有介绍.

仓库列表

维护仓库的通常是一群维护者,有个论坛、邮件列表等,有什么需求,或者有什么BUG,可以直接去和维护者沟通。下面都是列出了主页的一些仓库,留意主页的链接可以找到交流方法了。

官方仓库

默认安装的CentOS的yum,/etc/yum.repos.d/CentOS-Base.repo是基本的源仓库;里面各个仓库名下mirrorlist是官方列表,yum的fastestmirror插件会从其中选择一个来更新;而如果注释了mirrorlist写baseurl,就只从这一个仓库更新了。可以参考163源的CentOS5-Base-163.repo

这些是CentOS/RedHat官方维护的,就是那些“老旧过时”而且“几乎什么都没”,只要不是出现严重漏洞都不会更新那些。

FedoraProject for EPEL

Fedora和Redhat的关系就不详述了,就是FedoraProject里有个“EPEL Special Interest Group”,为EPEL系维护的一个社区仓库,基本上加上这个仓库后就能丰富了整个EPEL生态了,在Debian系里面“理所当然源里就有”的那些软件就会有了,比如openvpn,htop,ipcalc,git … 虽然版本不会很新,但起码能用了。

用法:安装这些链接页面里面的.rpm。

RPM Fusion

这个仓库说提供的是FedoraProject跟RedHat都不想提供的程序,提供的分类就知道怎么回事了,基本都是Sound and VideoGames and EntertainmentHardware Support等等。首先是Linux平台下多媒体支持方面的版权问题非常复杂,ffmpeg/x264等通常都有一些争议行的授权,当然也有nvidia/ati等硬件的闭源驱动、Oracle的闭源版Virtualbox等,把他们独立出来避免争端。

另外这个仓库基本提供的更新都是for Fedora,EPEL5/6的几乎没更新。可以说RPMFusion是个“桌面仓库”,而且国内163源提供了RPMFusion的镜像

用法: 见Configuration

RepoForge

原叫RPMForge,和CentOS社区较紧密,提供的包也比较海量的,很难评价分什么方向,CentOS Wiki专门有页面提供安装指导,因为包的数量太海量了很难和“FedoraProject for EPEL”做比较。

用法: 见Usage

Remi

这个仓库依赖EPEL。

提供了php54 / mysql55 / firefox 等等的更新,选的软件比较符合Web开发者工作的需要,当然服务器最好也是维护相同版本。这个仓库使用了github来管理软件包的spec,可以直接看他提供了什么包。更新非常紧贴各个软件的官方发布。

用法: 安装主页相应的remi-release-XX.rpm

KBS-Extras

CentOS本来的维护团队,有趣的一点是这仓库基本全在-testing里面提供软件包。

FedoraHosted – SoftwareCollections

这是重点推荐的。这不是一个仓库,是很多个。里面的软件包和上述那些仓库不大一样,都是在/opt下建立一套专用的目录,避免在/usr里面打架的软件包;这里提供了php/python/ruby/perl/mysql/postgre/apache等常用“服务器生态”。

用法:各个Collection的repo链接。

FedoraHosted

上述的只是FedoraHosted内一个子仓库,FedoraHosted是类似Ubuntu的PPA社区的环境,维护者可以通过建立自己的帐号然后建立一些自选软件的仓库。里面应该还有很多有用的东西待发掘。

Fedora People Repositories

一样是类似Ubuntu的PPA,不过这里就多数偏向Fedora的更新,也有些有EPEL6。

Pramberger, pp

这个仓库主要提供EPEL 3/4/5等旧版本的一些包的更新,有php,python的第三方模块、qt、squid等的更新,大概还是偏向更新服务器环境的吧。

用法:保存http://devel.pramberger.at/getrepo?release=<version>/etc/yum.repos.d,注意替换release参数(3|4|5)。

ELRepo

偏内核的新硬件支持模块。

IUS Community Repo

提供PHP, Python, MySQL更新,不过感觉更新不够Remi紧密。

PS维护技巧

yum的仓库选择

/etc/yum.repos.d/下的文件记录着各个仓库的信息,上述很多仓库在安装之后会在这里生成一个.repo,但里面的仓库不一定被启用了,里面可能写了enabled=0

一般来说,为了避免系统升级时候和第三方的包出现冲突,第三方的仓库都应该enabled=0,在需要使用、查找其中软件时候,使用yum的参数:

yum --enablerepo=remi install firefox-langpack-fr

下载SRPM

一定需要定制编译特定软件时候,这些仓库都提供SRPM仓库的,但是默认可能没开启。(yumdownloader需要安装yum-utils

yumdownloader --enablerepo=epel-source --source php

Yum/Rpm常用命令

Rpm/dpkg、yum/apt-get对照

http://www.pixelbeat.org/docs/packaging.html

文章分类 运维技术 标签: , ,

CASIO卡西欧PX130电钢琴拆机略解

最近入手了一台二手电钢琴CASIO PX130。去年就买过一台二手电子琴但是半年里面已经被我弹到断键两次,就想这种存在机械活动部件的电器的二手还是容易出问题,但是始终抵不住价格,还是入了二手的电钢。

PX130是这两年才上市的琴,理论上应该不至于磨损严重,然后了解过电钢的琴键结构比电子琴复杂,有良好的泄力传动,似乎不会有CASIO CTK系列那种会疲劳断裂的软塑料键的坑爹设计,就去拼下RP。

PX130电钢琴

扛了回家才一个月,果然就有问题了,不过不是琴键,有一边喇叭会沙沙的声音,后来就几乎没声音了,接耳机也一样,经验判断是功放部分有问题,后来发现蹂躏一下音量旋钮会改善,基本确定是这里了,但是这近20KG的琴要扛去维修,可是N个不愿意,就拖着,这天终于忍不住,自己拆机。

上网查了一圈没找到任何拆机资料(甚至一张完整的外壳底部图都好难找),估计会拆琴的多数是店里的师傅了,只好自己观察。

拆机流程

1.侧边面板

底部有好几十个螺丝,但不需要都拧的,先开左右侧板。拧开3个螺丝后从前面稍微用力就可以拆开一层壳。左边的壳是连着电源和耳机接口线路,最好别让其悬空。

PX130拆开的侧边盖

拆开后可以看到内壳,还有5个螺丝。两边都分别松下,别忙着拆这个挡板,把琴身翻过来。

2.底部螺丝

上面板主要是由底部两排螺丝固定。

如图提示,拆底部上排螺丝,然后开中间一排小盖子,松开里面的螺丝。

PX130底部螺丝拆解

3.上面板

这时候琴身的上盖已经可以完全打开了,小心把琴身翻到正面,往后翻开上盖,小心几根排线。

可以看到里面的主要电路模块,电路最精密的一块逻辑板,音源、MIDI功能都是上面的CPU处理,原件粗重的一块是电源和功放,和一般的音响区别不大。

PX130翻开面盖的电路

另外音量电位器是独立了一块小电路板,估计也是易损件,独立出来方便维修。

我的琴明显电位器是维修过,但是手法非常不专业,把电路板原来的线路都弄坏了,最后用几根绝缘铜丝链接起电位器,非常粗糙,喇叭杂音的原因应该就是其中有些绝缘层破损,有短路了。我看焊的还算牢固,就不重新处理了,用透明胶纸把几根重叠的铜丝做了布线分层,插上电源测试没问题了,果断装回去,收工。

Update:

电位器因为使用损耗,装回去不久后依然有问题,决定拆出来拿去电子市场找个换上。

PX130的音量调节-方形5引脚A103双联电位器

音量电位器是这个方形5引脚A103双联电位器,这里顺便一提电位器型号意义:

  • A/B/C: 阻值递增规律,A是阻值对数递增,B是线性递增,C是指数递增;
  • 103:即10 x 10^3,也即10K欧。

A型电位器用在功放前端,因为功放是按倍数放大信号电流,对数调整输入电流才能获得比较平滑的音量调节效果;而B型电位器用在功放后端,可以直接线性调整输出的电流。

逛了一圈天河的赛格完全没有各种电位器,然后去中山六路的音响配件市场,几经艰难才找到个外形类似的B103,换上后只是调节音量时候效果很不“线性”,但起码不影响使用了。

其他

查了网上其他需要拆机修理的问题,有些如塌键,重锤臂异位,更换琴键等,拆开上面板也基本可以维修了。底部的其他几排螺丝不过是固定琴键组件和底盖的,一般无需拆开。

文章分类 其他 标签: , , ,

双线Ubuntu服务器来源路由策略全自动配置脚本

国内IDC常见的所说的双线,就是给了两个不同ISP的接入IP,有些服务周到些,在路由器处追踪处理来源IP会话,但一旦没有,或者是自己拉的两个ISP直接到服务器,情况就麻烦了。

服务器对任意一个IP发来请求,那返回的包应原路返回,链接才能建立;但默认情况下,系统只有一个路由表,只有一个默认路由,来源IP只能从默认路由的gateway出去,那这样另外一个IP就没法用了。

这个情况需要配置多路由表的来源略路由(source policy routing),让不同IP的返回包查找不同的路由表,再根据其中的默认路由返回数据包。

Linux Advanced Routing & Traffic Control HOWTO详述了原理和配置步骤,google一下也会找到很多人家写好的配置脚本,不过总觉得用起来不方便。首先需要在脚本上定义各个网卡的IP/网关/网络等地址,这些数据和系统配置完全重复,一旦修改又得两边同步;然后还需手动修改/etc/iproute2/rt_tables,步骤复杂;其次Debian/Ubuntu的启动脚本比较复杂,这个情况的脚本很难在找个开机的合适时候启动。

经过考虑,决定使用/etc/network/if-up.d下的hook,这里的脚本可以从环境变量获得每一个启动的网卡的各个参数,使用sed等工具处理一下,完全可以实现全自动配置。

脚本放在github:

https://github.com/pentie/ptcoding/blob/master/multi-ip-route

脚本使用了ifquery,ubuntu里面是和ifup/ifdown同一个包的工具,理论上debian也会有。所以只需要配置好/etc/network.d/interfaces,扔好这个脚本,重启网络,双线策略就完美解决了。经证实,在eth0:1 eth0:2那样绑定的单网卡多IP情况使用也完全正常。

文章分类 Unix/Linux, 运维技术 标签: , , ,

Nginx + PHP (via php-fpm) on Ubuntu 环境最佳实践12.04版

前文Nginx + PHP (via php-fpm) on Ubuntu 环境最佳实践大致提了Ubuntu下的Ngnx+Php环境,但随着时间推移,旧版本组件之间维护程度的不协调问题会越来越多。

PHP 5.4带来了很多改进,以往运行环境必须的优化在5.4后都不必要了。

安装

1
2
3
4
5
6
7
8
9
apt-get install python-software-properties
add-apt-repository ppa:ondrej/php5
 
apt-get update
apt-get install nginx
apt-get install php5-cgi php5-mysql php5-fpm php5-curl php5-mcrypt php-pear php5-gd php-apc
 
service nginx start
service php5-fpm start

配置

Nginx

upstream php {
        server unix:/var/run/php-fpm/php-fpm.sock;
        #server 127.0.0.1:9000;
}
 
server
{
        listen       80;
 
        index index.html index.htm index.php;
        server_name     site.com;
        root    /srv/http/pt-sites/wordpress;
 
        client_max_body_size 32m;
        client_body_buffer_size 128k;
        server_tokens off;
 
        location / {
            try_files $uri $uri/ /index.php?$args;
        }
 
        location ~ \.php$ {
              include fastcgi.conf;
              #fastcgi_param  HTTPS on;
              fastcgi_intercept_errors on;
              #fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name; #Ubuntu 版本的nginx不需要这句,但Fedora或者可能其他发行版的自带没有定义,会返回空白的php,需要这句。
              fastcgi_pass php;
        }
}

PHP

设置timezone

sed -i '/;date.timezone/adate.timezone = "Asia/Shanghai"\ndate.default_latitude = 31.5167\ndate.default_longitude = 121.4500' /etc/php5/fpm/php.ini

另可参考(php.ini):

post_max_size = 16M
upload_max_filesize = 16M

FPM

php5-fpm默认参数启动的服务器还是比较耗资源的,如果环境不充裕(如512内存以下的VPS),可以做下配置。

这个包的fpm的默认配置文件是/etc/php5/fpm/main.conf,但对子进程的配置是在其包含的/etc/php5/fpm/pool.d/目录内,里面有个www.conf,可以对以下的参数做以下修改:

pm = dynamic              ;动态管理php-fpm的子进程
pm.max_children = 5       ;最多的时候开不超过5个
pm.start_servers = 2      ;启动服务时候开2个
pm.min_spare_servers = 2  ;空闲时候最少留2个
pm.max_spare_servers = 5  ;最多留5个
pm.max_requests = 300     ;每个子进程最多处理300个请求就退出换新的子进程。

按需调整这些参数可以达到最佳动态分配资源的效果。

如果在一台机器上部署多个应用,可以根据访问量,在pool.d中为各个应用配置不同的分配模式,能有效起到安全隔离效果。(注意pool.d内的文件,除了文件名不一样,里面定义的中括号[Name]也需要不一样。)

Fastcgi

Fastcgi和nginx的通信参数,可做以下配置:

加入到:/etc/nginx/fastcgi_params

fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 4k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors on;

php-apc

因为PHP的设计问题,opcode缓冲对大流量APP来说是必不可少的,APC是不错的选择。但是默认参数似乎效果有问题(通过munin监控发现没有设置一下参数,几乎没有使用system-cache,统统都是miss,一加上就好了,估计是BUG)。

加入到 /etc/php5/conf.d/20-apc.ini

apc.enabled = 1
apc.shm_segments = 1
apc.shm_size = 64
apc.ttl=7200
apc.user_ttl=7200

参考:Nginx and PHP-FPM Configuration and Optimizing Tips and Tricks

文章分类 Unix/Linux, web技术 标签: , ,

Virtualbox虚拟机使用USB的科学方式

Virtualbox 的USB Passthrough支持早就有了,但是一直以来的对其的印象都停留在’barely works’的阶段,因为虽然实验让U盘之类的简单玩意在虚拟机加载都是正常的,而实际操作比如挂载网银U盾、3G网卡、手机刷ROM的时候,虚拟机里面总是“不知道怎么”没法搞定,USB总是莫名断开,然后虚拟机里面的程序不认设备。

昨天尝试给一台国产Android平板做定制的Recovery,操作不当变了大砖,必须使用厂商的Windows下的线刷工具救砖。打开虚拟机来测试,果然还是和以往一样,让平板进入底层升级模式、通过vbox右下角的图标挂载到虚拟机,Win里面的升级程序愣了一下后就说找不到设备,升级失败。

观察了下,每次让虚拟机挂载了USB,一运行了升级程序,USB设备就从虚拟机里面断开了。以前都是认为是vbox的机制不完善,太BUG了。睡觉的时候才仔细想,这不大可能,肯定是打开的方式不对。

当年毕业设计做的东西是用USB通信的,所以对USB的协议有点了解,USB协议有点类似网卡收发的帧,理论上只要vbox实现了帧转发就能做到USB设备的穿透,而且是由USB Host以信令形式通信,不像PCI之类有复杂的CPU中断请求,这个机制应该不是非常复杂的。虽然需要处理在宿主机器操作系统的挂载优先问题,不过貌似已经解决很好了啊,U盘能够从宿主断开后才挂载到虚拟机的。所以猜测USB设备的断开是因为设备收到信号、切换了工作模式,让vbox认为是另外一个设备,没有继续挂载到虚拟机,于是就有断链的现象。

今天继续研究的时候,就开着终端watch lsusb看着USB设备的变化,果然升级程序运行后,设备消失了一下,然后新设备的bcdDevice属性出现了变化,之后vbox就没继续挂载这个设备了。

期间还去搜索了一下kvm 的 USB Passthrough,发现kvm的USB支持比vbox弱爆了,只能在关机状态下对虚拟机的配置手动添加对应设备的vendor id/product id。不过这里让我想起vbox的guest setting里面的USB Device Filters配置界面:

vbox-usb-setting.png

这里可以通过右边的按钮添加一个filter,也是需要输入设备的vendor id/product id,马上想到这里应该是可以预定义让vbox挂载那些设备的,于是赶紧重新操作一次,把平板升级过程变化的两个状态都添加到filter,重新操作,果然升级程序就认到了,救砖行动成功! 用不着找别人借用Windows了,呵呵呵。

总结科学的操作方式是:所有需要模式切换的USB设备,都需要在filters里面添加相应状态;不过一般情况下,同一个设备的状态的vendor id都会相同,所以仅需填写vendor id 即可识别其各个状态了,填写的条件越多,识别的情况越精确。详见VBox官方文档

文章分类 虚拟化 标签: , ,

An OpenVPN configuration menu based generator

It’s not extremly difficult to set up an openvpn server, but you have to deal with certificates, rsa keys, configs, subnet addresses, iptables … such trifles are annoying.

So I wrote this script to make my life easier, only to provide essential infomation like the server IP, and configuraion will be done at the background, then tared packages are ready for both servers and clients.

ovpn_menu.png

Source is available at Github. Pull requests are welcome.

The script is recommanded running at your work station, and then upload only the server part to the server, for secure considerations.

Features

  • Provide tared config which ready for any server distribution.
  • Random VPN subnet will be generated to avoid conflict.
  • Random digital subffixed server/client CommonName will be assigned (if you don’t provide one) for clearer management.
  • All those config files are based on examples that ship together within your distribution.
  • tls-auth enabled by default.

Usage

For new setup:

./much-easier-rsa-menu.sh

Just do as promoted. When select 5 to exit, all the files will be packed into a single NAME-all.tar.gz, you should save it to somewhere safe. And if you want to sign some more certificate from this root ca, put this tar file as the argument.

./much-easier-rsa-menu.sh /path/to/YOUR-VPN-all.tar.gz

At last the script also provide you iptables commands that can be useful to setup the server as a VPN gateway.

Download

Direct download via wget/curl should be ok:

https://github.com/pentie/much-easier-rsa-for-openvpn/raw/master/much-easier-rsa-menu.sh

文章分类 Unix/Linux 标签: ,

“Leave password blank if dont want to change” in a django admin field

Django is a complicated but useful python web-framework, comparing to light weight frameworks like Bottle, web.py. I recently switch from Bottle, making use of it’s powerful admin site to build a Email Account Management system.

An AdminSite offer interfaces to manage databases. It includes traditional authentications, permissions, data display and POST saving mechanisms, etc. , which are nasty trifles if you try implement them from scratch using bottle or other things.

Now I can build a decent system in about 100 lines code, after many many document reading and code digging, I want to share the story how I solve the problem encountered while implementing the “Leave password blank if don’t want to change it” requirement. As commonly seen in other applications when user try to update their profile.

Firstly the background. My data Model:

1
2
3
4
5
class Users(Model):
    account = models.CharField(max_length=128, unique = True)
    password = models.CharField(db_column = 'crypt',max_length=384, blank=True)
    name = models.CharField(max_length=768)
    #...

The password is stored hashed in the DB crypt column. If I just let the AdminSite pickup things like that, it just show the password value in the plain input text.

Firstly I tell django to show it as a password input widget.

1
2
3
4
5
6
7
8
class MailUsersForm(forms.ModelForm):
    class Meta:
        model = Users
        widgets = {
                'password': forms.PasswordInput(render_value = False),
        }
class MailUsersAdmin(admin.ModelAdmin):
    form = MailUsersForm

Ah, overriding the form in the ModelAdmin does the trick.

Now in the edit page, the Password field shows an empty input widget.

django-password-field.png

Now I need to hash the password before commit to the DB. I found the save_model method.

1
2
3
4
5
6
7
class MailUsersAdmin(admin.ModelAdmin):
    # ...
    def save_model(self, request, obj, form, change):
        new_psw = request.POST["password"]
        if len(new_psw):
            obj.password = obj._hash_password()
            obj.save()

This works, but buggy. The password field is defaultly blank. It will clean the field in the DB without processing. The problem is, by the time in the save_model method is triggered, the obj is already updated by the values from the POST request. There’s no way to get the old password value here. All I can do without changing the old password, is to skip the obj.save(). But what if I only want to update other things?

Through out the documents, I found some other places to approch the Model instance.

Firstly the django.forms.ModelForm.save method. The module instance is returned from this method (then passed to the save_model), so I inspect the object with ipdb to see if the data is updated here:

1
2
3
4
5
6
class MailUsersForm(forms.ModelForm):
 
    def save(self, *args, **kw):
        import ipdb; ipdb.set_trace()
        obj = super(MailUsersForm, self).save(*args, **kw)
        return obj

Unfortunately Yes. The instanced obj from the derived save is already updated to the POST value.

And django signals, pre_save, all the same way. (The signal thing is even emmited after the forms.ModelForm.save)

Googled around and nothing suitble, I decided to find the right way myself.

There must be somewhere in django codes that sets the attributes with values from the POST requsts. So I add a hook in the Model.

1
2
3
4
5
6
7
8
class Users(Model):
    #....
    def __setattr__(self, name, value):
        if name == "password":
            if self.id == 370:
                import ipdb; ipdb.set_trace()
 
        return Model.__setattr__(self, name, value)

The if self.id thing is for filtering the specific page I submit the save in the browser. Now ipdb let me in debug mode.

ipdb> w
 
#................. Many Many unrelated things
 
  /usr/lib/python2.7/site-packages/django/forms/forms.py(272)full_clean()
    270         self._clean_fields()
    271         self._clean_form()
--> 272         self._post_clean()
    273         if self._errors:
    274             del self.cleaned_data
 
  /usr/lib/python2.7/site-packages/django/forms/models.py(309)_post_clean()
    307         opts = self._meta
    308         # Update the model instance with self.cleaned_data.
 
--> 309         self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
    310
    311         exclude = self._get_validation_exclusions()
 
  /usr/lib/python2.7/site-packages/django/forms/models.py(51)construct_instance()
     49             file_field_list.append(f)
     50         else:
---> 51             f.save_form_data(instance, cleaned_data[f.name])
     52
     53     for f in file_field_list:
 
  /usr/lib/python2.7/site-packages/django/db/models/fields/__init__.py(454)save_form_data()
    452
    453     def save_form_data(self, instance, data):
--> 454         setattr(instance, self.name, data)
#---------------^ this matters!
    455
    456     def formfield(self, form_class=forms.CharField, **kwargs):
 
> /home/boypt/Projects/maildbadmin/maildbadmin/models.py(55)__setattr__()
     53             if self.id == 370:
     54                 import ipdb; ipdb.set_trace()
---> 55                 print "set :", value
     56 #            
     57 #

From the call stack, the setattr call to the Model changes the data. I check django/db/models/fields/__init__.py source, find that this is a Field base class, so the obvious solution is to override this method.

1
2
3
4
5
6
7
8
9
10
11
class PasswordCharField(models.CharField):
    def save_form_data(self, instance, data):
        if data != u'':
            data = instance._hashed_pwd(data)
            setattr(instance, self.name, data)
 
class Users(Model):
    #...
    password = PasswordCharField(db_column = 'crypt', max_length=384, blank=True)
 
    #.....

OK, now everything’s done here. The save_model overriding is no more needed, for the Model instance’s password will not be set to empty at all when the user left the field blank when they submit.

文章分类 Python, web技术 标签: ,