import requests
import base64
import traceback
payload = {}
# 填入自己的 cookies
headers = {
'authority':
'portal.qiniu.com',
'dnt':
'1',
'x-reqid':
'abc',
'Cookie':
'xxx',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.62'
}
base_url = 'https://portal.qiniu.com'
# 要删除的文件所在的 bucket
bucket = 'hmp-public'
# 要删除的文件目录
prefix = 'sandbox3/channelSetMeal/img/'
query_url = '{}/api/kodov2/rsf/list?allversion=false&bucket={}&prefix={}&limit=50'.format(base_url, bucket, prefix)
delete_url = '{}/api/proxy/rs/delete/'.format(base_url)
has_next = True
del_file_size_byte = 0
del_file_count = 0
except_msgs = []
while has_next:
try:
query_res = requests.request("GET", query_url, headers=headers, data=payload)
if query_res.status_code != 200:
break
items = query_res.json()['items']
has_next = True if len(items) else False
if not has_next:
break
for item in items:
del_file_size_byte += item['fsize']
del_file_count += 1
sid = 'hmp-public:' + item['key']
# 对id进行base64编码
id = base64.b64encode(sid.encode('utf-8')).decode('utf-8')
del_res = requests.request("POST", delete_url + id, headers=headers, data=payload)
print('删除', sid, '结果', del_res.status_code, ',第', del_file_count, '个,',
'%.2f' % (del_file_size_byte / 1024 / 1024), 'M')
except Exception as e:
# 获取异常堆栈信息
except_msg = traceback.format_exc()
except_msgs.append(except_msg)
print(except_msg)
print('===删除', bucket, prefix, '下的文件', del_file_count, '个, 总计', '%.2f' % (del_file_size_byte / 1024 / 1024), 'M===')
print('异常信息', except_msgs)
单线程太慢,写了个多线程的,但自己用的时候注意控制下速率,操作太频繁会导致/api/kodov2/rsf/list
返回空items
import requests
import base64
import traceback
import threading
import time
# 请用网页端的登录信息更新headers
headers = {
'authority':
'portal.qiniu.com',
'dnt':
'1',
'x-reqid':
'abc',
'Cookie':
'xxx',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.62'
}
# 要删除的文件所在的 bucket
bucket = 'hmp-public'
# 要删除的文件目录
prefix = 'sandbox3/channelSetMeal/img/'
base_url = 'https://portal.qiniu.com'
query_url = '{}/api/kodov2/rsf/list?allversion=false&bucket={}&prefix={}&limit=50'.format(base_url, bucket, prefix)
delete_url = '{}/api/proxy/rs/delete/'.format(base_url)
del_file_size_byte = 0
del_file_count = 0
except_msgs = []
def query():
query_res = requests.request("GET", query_url, headers=headers)
return query_res
def del_file(item):
global del_file_size_byte
global del_file_count
del_file_size_byte += item['fsize']
del_file_count += 1
total_size = '%.2f' % (del_file_size_byte / 1024 / 1024)
total_count = del_file_count
sid = 'hmp-public:' + item['key']
# 对sid进行base64编码
id = base64.b64encode(sid.encode('utf-8')).decode('utf-8')
del_res = requests.request("POST", delete_url + id, headers=headers)
print('删除', sid, '结果', del_res.status_code, ',第', total_count, '个,', total_size, 'M')
def main():
has_next = True
while has_next:
try:
query_res = query()
if query_res.status_code != 200:
break
items = query_res.json()['items']
has_next = True if len(items) else False
if not has_next:
break
threads = []
for item in items:
t = threading.Thread(target=del_file, args=(item,))
threads.append(t)
time.sleep(0.1)
t.start()
for t in threads:
t.join()
except Exception as e:
# 获取异常堆栈信息
except_msg = traceback.format_exc()
except_msgs.append(except_msg)
print(e)
print(except_msg)
print('===删除', bucket, prefix, '下的文件', del_file_count, '个, 总计', '%.2f' % (del_file_size_byte / 1024 / 1024), 'M===')
print('异常信息', except_msgs)
if __name__ == '__main__':
main()
]]>背景:用户反馈了一个问题,短信里的链接点不开,排查后发现在部分手机上会偶现链接后面带空格了还会把后面内容当成链接内容的情况,因为我们放到短信里的都是我们自己的短链,所以不存在繁杂的请求路径的情况,所以就想一个服务端的解决方案,在接收到请求后把路径用空格分割下,只取第一段内容作为路径,就是把
xxxx.com/1ERXTa 确认您……
请求过来的路径参数1ERXTa 确认您……
处理成1ERXTa
, 写好代码自己编了一个测试数据测试通过,用用户短信里的链接数据测试失败,后面通过浏览器地址栏查看发现,自己编的测试数据空格经过 url 编码后是%20
, 用户短信里的链接点击打开后空格经过 url 编码后是% C2% A0
,两个空格竟然不一样。
经过一系列的测试,发现两个空格的确有区别
Python 3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib.parse
>>>
>>> s1 = ' '
>>> s2 = ' '
>>>
>>> s1.encode (encoding="utf-8")
b'\xc2\xa0'
>>> s2.encode (encoding="utf-8")
b' '
>>>
>>> urllib.parse.quote (s1)
'% C2% A0'
>>> urllib.parse.quote (s2)
'%20'
根据关键词 Google 了下,原来有一个叫 不间断空格 的东西。它有两个作用,一是阻止两端的内容被自动换行分开,二是防止连续的空格字符被合并成单个空格。我也数据测试了下这两种现象,表现的确有差异。
阻止两端的内容被自动换行分开(在网页中可以用
来表示不间断空格):
下面这段文字是用
(不间断空格)拼接
这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本
下面这段文字里是用
(空格)字符拼接
这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本 这 是 测 试 文 本
防止连续的空格字符被合并成单个空格:
下面尖括号中间有 10 个
(不间断空格)
< >
下面尖括号中间有 10 个
(空格)
< >
更加详细的说明可以看:https://www.cnblogs.com/lingyejun/p/13056754.html
]]>因为服务器上刚好装有docker,按照官方文档选择了最简单的docker安装。
docker pull gitea/gitea:latest
sudo mkdir -p /data/gitea
docker run -d --name=gitea -p 10022:22 -p 10080:3000 -v /data/gitea:/data gitea/gitea:latest
// 重启gitea
docker restart gitea
安装完成后遇到了页面有三个静态文件(css/js)加载不成功,导致页面排版混乱,F12查看控制台报错net::ERR_CONTENT_LENGTH_MISMATCH,google之,找到这篇文章
Nginx 做代理时浏览器报错 net::ERR_CONTENT_LENGTH_MISMATCH,按照此方法解决。
在服务器Nginx上配置反向代理
vi /etc/nginx/conf.d/gitea.conf
upstream gitea {
server 127.0.0.1:10080;
keepalive 2000;
}
server {
listen 80;
server_name git.i.example.com;
client_max_body_size 1024M;
location / {
proxy_pass http://gitea/;
proxy_set_header Host $host:$server_port;
}
}
重新加载配置
sudo nginx -s reload
git.i.example.com解析到当前服务器ip,并把服务器防火墙入方向的10022 tcp端口打开,以便使用ssh方式clone仓库时使用。
打开http://.i.example.com,进入初始化界面(如果没进随便点注册或登录就会进),除了数据库根据需要配置,几个域名和网址要修改下,邮箱和其它选项按需配置。以后如果想修改配置,可以直接修改/data/gitea/gitea/conf/app.ini文件配置说明,修改完成后重启下gitea即可生效。
因为我迁移的是团队项目,所以先通过Gitea提供的API把所有仓库以镜像方式(镜像方式同步过来仓库对成员为只读,并且可以设置间隔时间,默认8小时,定时从原始地址Gitee同步最新代码)同步过来**[操作1],然后为每个项目配置好协作者/团队/权限等设置,在这期间,团队成员还是往Gitee上提交代码,待全部设置完成后取消告知团队成员不要往Gitee提交代码,并调用Giea api把所有仓库从Gitee上同步一下最新代码[操作2],然后每个仓库从镜像仓库转为普通仓库,并让团队的所有在自己仓库根目录执行修改本地仓库Git远程仓库地址替换操作[操作3]**
[操作1]:登录Gitea后,界面右上角有一个加号,点开了后有一个迁移外部仓库的功能,只要填入外部仓库URL,授权验证信息等信息就可以一键把外部仓库的所有代码(包括所有branch和commit)迁移到Gitea,如果要迁移的仓库比较多,可以使用Gitea提供的Api来操作。对应此迁移操作的api是
POST /repos/migrate?access_token=<your gitea admin access token>
Request body
{
description: MigrateRepoForm form for migrating repository
auth_password: string
auth_username: string
clone_addr*: string
description: string
issues: boolean
labels: boolean
milestones: boolean
mirror: boolean
private: boolean
pull_requests: boolean
releases: boolean
repo_name*: string
uid*: integer($int64)
wiki: boolean
}
注:
access_token 请在有管理员权限的账号的设置>应用中创建;
Request body 中的uid即管理后台>账户管理/组织管理中的ID列值;
找了Gitee没找到可以获取账户下所有仓库信息的API,所以只好手写了一个Gitee仓库地址的文件,类似
vi gitee-url.txt
https://gitee.com/example/project_a.git
https://gitee.com/example/project_b.git
使用shell脚本逐行读取url,并调用Gitea api迁移仓库。
#!/bin/bash
for line in $(<gitee-url.txt);
do
# Windows注释下面这行
line=$(echo $line | sed -e 's/\r//g');
tmp=${line#https://gitee.com/xxx/};
project_name=${tmp%.git};
curl -X POST "http://git.i.example.com/api/v1/repos/migrate?access_token=<your gitea admin access token>" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"auth_password\": \"NDY2&F*K!hL75y*z\", \"auth_username\": \"korvin101@gmail.com\", \"clone_addr\": \"$line\", \"issues\": true, \"labels\": true, \"milestones\": true, \"mirror\": true, \"private\": true, \"pull_requests\": true, \"releases\": true, \"repo_name\": \"$project_name\", \"uid\": 2, \"wiki\": true}";
done
[操作2]:从Gitee上同步最新代码
for line in $(<gitee-url.txt);
do
line=$(echo $line | sed -e 's/\r//g');
tmp=${line#https://gitee.com/xxx/};
project_name=${tmp%.git};
curl -X POST "http://git.i.example.com/api/v1/repos/{owner}/$project_name/mirror-sync?access_token=<your gitea admin access token>" -H "accept: application/json"
done
***注:***owner为项目拥有者用户名/组织名
[操作3]:原本地仓库Git远程仓库地址替换
// http地址
// 原代码仓库http地址:https://gitee.com/example/project_a.git
// 新代码仓库http地址:http://git.i.example.com/JIANSU/project_a.git
// https://gitee.com/example > http://git.i.example.com/JIANSU
// 本地仓库使用此命令替换,可在包含所有项目的外层文件夹路径下执行批量替换
// Windows删除'.bak'
sed -i '.bak' 's/https:\/\/gitee\.com\/example/http:\/\/git\.i\.example.com\/JIANSU/g' */.git/config
// ssh地址
// 原代码仓库ssh地址:git@gitee.com:example/project_a.git
// 新代码仓库地址:ssh://git@git.i.example.com:10022/JIANSU/project_a.git
// git@gitee.com:example > ssh://git@git.i.example.com:10022/JIANSU
// 本地仓库使用此命令替换,可在包含所有项目的外层文件夹路径下执行批量替换
// Windows删除'.bak'
sed -i '.bak' 's/git@gitee\.com:example/ssh:\/\/git@git\.i\.example\.com:10022\/JIANSU/g' */.git/config
如果之前是用http地址进行克隆的仓库的话,现在就是在进行pull和push操作时,把账户密码换成Gitea的就可以了;
如果以前是用ssh克隆的仓库的话,现在在Gitea的设置>SSH / GPG 密钥里添加一下公钥就可以进行git pull/git push等操作了;
Gitea有自己的备份与恢复功能备份与恢复,这个备份比较全面,数据/代码/日志都可以备份,正是因为这样,如果仓库比较多这个备份的文件肯定会有点大,而且每次都是全量备份,所以频率肯定不能太高,而我只是想对仓库代码做一个高频率备份,所以写了一个Python3脚本调用Gitea api和 Git命令来进行所有仓库的所有分支代码备份,因为这个备份基于Git机制,所以虽然频率高,但备份始终只有一份。脚本如下:
backup.py
如果使用python2运行,分支名里有中文的话,请自行处理字符编码问题。
** python
#!/usr/bin/python3
import os
import platform
import requests
current_dir = os.path.abspath(os.path.dirname(__file__))
access_token = "<your access token>"
repos_url = 'http://git.i.example.com/api/v1/repos/search?access_token={}&page={}&limit={}'
branches_url = 'http://git.i.example.com/api/v1/repos/{}/branches?access_token={}'
repo_key_url = 'http://git.i.example.com/api/v1/repos/{}/{}/keys?access_token={}'
def repos():
page = 1
limit = 50
has_next = True
while has_next:
r = requests.get(repos_url.format(access_token, page, limit))
for repo in r.json()['data']:
yield repo
page += 1
has_next = len(r.json()['data']) == limit
"""拉取项目所有分支代码到本地"""
def sync_repo():
repo_index = 0
for repo in repos():
repo_index += 1
# 克隆仓库
os.chdir(current_dir)
print('克隆第 {} 个仓库 {} '.format(repo_index, repo['name']))
os.system("git clone {}".format(repo['ssh_url']))
os.chdir(os.path.join(current_dir, repo['name']))
# 更新仓库
print('同步 {} 仓库所有分支'.format(repo['name']))
os.system('git fetch --all')
# if platform.system() == 'Windows':
# Windows
branches = requests.get(branches_url.format(
repo['full_name'], access_token)).json()
for branch in branches:
branch_name = branch['name']
os.system('git branch --track {} origin/{}'.format(branch_name, branch_name))
# 用reset而不用pull是因为如果分支被强推了pull下来会有合并冲突,用rest就不会有冲突问题
os.system('git checkout {} && git reset --hard origin/{}'.format(branch_name, branch_name))
# else:
# # Linux/macOS
# # git branch -r | grep -v '\->' | while read remote; do git branch --track ${remote#origin/} $remote; done && git fetch --all && git pull --all
# # os.system("git branch -r | grep -v '\->' | while read remote; do git branch --track ${remote#origin/} $remote; done && git fetch --all && git pull --all")
# # # 用reset而不用pull是因为如果分支被强推了pull下来会有合并冲突,用rest就不会有冲突问题
# os.system("git branch -r | grep -v '\->' | while read remote; do git branch --track ${remote#origin/} $remote; git checkout ${remote#origin/}; git reset --hard $remote; done")
"""设置项目部署公钥"""
def set_pub_key():
repo_index = 0
body = {
"key": "ssh-rsa aabbcc",
"read_only": True,
"title": "SandBox"
}
for repo in repos():
repo_index += 1
print('==={}. {}==='.format(repo_index, repo['name']))
r = requests.post(repo_key_url.format(
repo['owner']['username'], repo['name'], access_token), data=body)
print(r.json())
if __name__ == '__main__':
sync_repo()
# set_pub_key()
可以把脚本放在本地,使用cron(Linux/macOS)/计划任务(Windows)定时运行python backup.py
@PostMapping("/test")
public Mono testA(@RequestParam boolean exception) {
return embedService.saveAC(new ADocument("张三"), new CDocument("李四"), exception);
}
@Override
public Mono<Boolean> saveAC(ADocument aDocument, CDocument cDocument, boolean exception) {
return reactiveMongoTemplate.inTransaction()
//所有文档的持久化操作都只能在单独一个execute函数中汇总实现
.execute(action -> action.insert(aDocument)
.flatMap(a -> {
cDocument.setName(a.getName() + "copy");
return action.insert(cDocument)
.map(d -> {
if (exception) {
//测试跨文档的异常回滚
throw Exceptions.propagate(new RuntimeException("模拟异常的出现"));
}
return d;
});
})
)
//如果里面是个mono,则用next取出第一个元素就是里面的mono
.next()
.map(list -> {
//需要注意,在execute之外的函数中产生的异常,不会触发事务的回滚。
// if (exception) {
// throw Exceptions.propagate(new RuntimeException("模拟异常的出现"));
// }
return Boolean.TRUE;
});
}
flux的数据库操作,在有事务的前提下不能用flatMap,要用事务不能用flatMap要用concatMap保持有序
@PostMapping("/test")
public Mono testA(@RequestParam boolean exception) {
return embedService.saveAC(new ADocument("张三"), new CDocument("李四"), exception);
}
@Override
public Mono<Boolean> saveAC(ADocument aDocument, CDocument cDocument, boolean exception) {
return reactiveMongoTemplate.inTransaction()
//所有文档的持久化操作都只能在单独一个execute函数中汇总实现
.execute(action -> Flux.fromIterable("1", "2", "3")
//如果是个flux此处要用concatMap保持有序不能用flatMap
.concatMap(i -> action.insert(aDocument)
.flatMap(a -> {
cDocument.setName(a.getName() + "copy");
return action.insert(cDocument)
.map(d -> {
if (exception) {
//测试跨文档的异常回滚
throw Exceptions.propagate(new RuntimeException("模拟异常的出现"));
}
return d;
});
}));
)
//如果里面返回的就是一个flux则不需要使用next
//.next()
.map(list -> {
//需要注意,在execute之外的函数中产生的异常,不会触发事务的回滚。
// if (exception) {
// throw Exceptions.propagate(new RuntimeException("模拟异常的出现"));
// }
return Boolean.TRUE;
});
}
]]>switch
ConditionalOperators.Switch.CaseOperator cond = ConditionalOperators.Switch.CaseOperator.when(
BooleanOperators.And.and(
ComparisonOperators.Eq.valueOf("channelBillStatus1").equalToValue("已结算"),
ComparisonOperators.Eq.valueOf("channelBillStatus2").equalToValue("已结算")
)
).then("已结清");
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.project("channelBillStatus1", "channelBillStatus2")
.and(ConditionalOperators.switchCases(cond).defaultTo("未结清")).as("channelBillStatus")
);
reactiveMongoTemplate.aggregate(aggregation, PlatformBillItem.class, PlatformBillBo.class);
lookup及id类型转换
//把_id转成String并赋值给id
Aggregation.project("internalId", "name", "isAvailable", "isCanAdd", "fitGender", "fitAge", "fitMaritalStatus", "price", "sortNo", "createdAt")
//如果需要把String转Object使用ConvertOperators.ToObjectId.toObjectId()
.and(ConvertOperators.ToString.toString("$_id")).as("id");
//用当前表的id值去匹配chn_section表的sectionId字段值,并把结果存入chnSections数组
Aggregation.lookup("chn_section", "id", "sectionId", "chnSections");
//如有需要,把chnSections数组拆出来,chnSections数组有几个元素,当前这条数据就会被拆成多少条,chnSections值会变成元素值而不再是原来的数组
//如果chnSections数组无值,默认会丢弃这条数据,如果要保留设置preserveNullAndEmptyArrays=true
Aggregation.unwind("chnSection", true);
//只输出这些字段
Aggregation.project("internalId", "name", "isAvailable", "isCanAdd", "fitGender", "fitAge", "fitMaritalStatus", "price", "sortNo", "createdAt", "chnSections");
reactiveMongoTemplate.aggregate(aggregation, PlatformBillItem.class, PlatformBillBo.class);
如果lookup时,如果要对匹配的数据进行筛选(参考链接:https://stackoverflow.com/questions/51107626/spring-data-mongodb-lookup-with-pipeline-aggregation)
//原始mongo
//{
// $lookup:
// {
// from: <collection to join>,
// let: { <var_1>: <expression>, …, <var_n>: <expression> },
// pipeline: [ <pipeline to execute on the collection to join> ],
// as: <output array field>
// }
//}
//自定义一个AggregationOperation类
public class CustomProjectAggregationOperation implements AggregationOperation {
private String jsonOperation;
public CustomProjectAggregationOperation(String jsonOperation) {
this.jsonOperation = jsonOperation;
}
@Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return aggregationOperationContext.getMappedObject(Document.parse(jsonOperation));
}
}
private static String getJsonOperation() {
return "{" +
" $lookup: " +
" {" +
" from: 'chn_set_meal'," +
" let: {" +
" id: { $toString: '$_id' }" +
" }," +
" pipeline: [" +
" {" +
" $match: " +
" {" +
" $expr: " +
" {" +
" $and: " +
" [" +
" {" +
" $eq: ['$setMealId', '$$id']" +
" }," +
" {" +
" $eq: ['$cooperationState', '合作中']" +
" }" +
" ]" +
" }" +
" }" +
" }," +
" {" +
" $project: {" +
" channelId: 1," +
" channelName: 1" +
" cooperationState: 1" +
" }" +
" }" +
" ]," +
" as: 'channels'" +
" }" +
"}}";
}
AggregationOperation aggregationOperation = new CustomProjectAggregationOperation(getJsonOperation());
return reactiveMongoTemplate.aggregate(Aggregation.newAggregation(aggregationOperation), SetMeal.class, SetMealListBo.class);
group
//背景:查询交易表,订单和交易一对多
Aggregation.group("orderNo")
//单一组的金额汇总
.sum("amount").as("totalAmount")
//组的最后一个订单号
.last("orderNo").as("orderNo")
//组里数据条数
.count().as("tradeCount")
//把一组数据里每条数据的状态放到一个statuses数组里
.addToSet("status").as("statuses")
//把一组数据里的一些字段信息重新组装成一个对象放到billItems的对象数组里
.push(new BasicDBObject("tradeContent", "$tradeContent")
.append("tradeNo", "$tradeNo")
.append("amount", "$amount")
).as("billItems");
]]>Alfred(macOS)
Mac上的效率启动神器
uTools(macOS/Windows/Linux)
你的生产力工具集.
uTools是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。
当你熟悉它后,能够为你节约大量时间,让你可以更加专注地改变世界。
除了插件中心提供的工具,随手就可以用自己熟悉的语言写一个简单的小工具放上面,感觉比Alfred还好用,哈哈
WGestures(Windows)
系统鼠标手势
Snipaste(macOS/Windows)
截图/贴图
Xnip(macOS)
截图应用
iShot(macOS)
截图/长截图/贴图/录屏
Everything(Windows)
Windows本地搜索,快如闪电,用过就回不去
Rolan(Windows)
Windows上的快速启动器
Rime(macOS/Windows/Linux)
输入法
Synergy(macOS/Windows/Linux)
多台设备共用键鼠
[Shadowsocks/v2rayNG/v2ray/V2rayU](macOS/Windows/Linux/Android)
科学上网
AppCleaner(macOS)
应用卸载器
Typora(macOS/Windows/Linux)
markdown笔记写作应用,配合坚果云或Dropbox同步,爽
SumatraPDF(Windows)
PDF阅读器
EagleGet(Windows)
下载器
Free Download Manager(macOS/Windows)
下载器
Bandizip(macOS/Windows)
压缩/解压软件
MacZip(macOS)
压缩/解压软件
reveal.js(Web)
程序员的PPT工具
pap.er(macOS)
壁纸应用
aText(macOS/Windows)
文字输入效率提升工具
Itsycal(macOS)
macOS 菜单栏上自定义显示日历与时间
Cmder(Windows)
Windows默认命令行替代品
Apifox(macOS/Windows/Linux)
跨平台的REST客户端
Postman(macOS/Windows/Linux/Chrome App)
跨平台的接口调试工具,有mocks服务和接口文档生成功能
iTerm2(macOS)
终端应用
Fiddler(macOS/Windows/Linux)
抓包工具
Charles(macOS/Windows/Linux)
抓包工具
mitmproxy(macOS/Windows/Linux)
可编程抓包
frp(macOS/Windows/Linux)
内网穿透
全能终端神器
远程管理工具
广告拦截器,谁用谁知道
Gmail/Inbox插件,不用打开网页处理邮件
黑色主题,适用于任何网站。关爱眼睛,就使用Dark Reader进行夜间和日间浏览
扩展管理工具,可以对扩展进行分组,并进行批量快速的启用、禁用
探测当前网页正在使用的开源软件或者js类库
了解谁在跟踪您的网页浏览操作,并可禁用跟踪行为。
给网站添加自定义脚本
给网站添加自定义样式表
快速搭建本地Web服务器(当然,你也可以python -m http.server
)
在浏览器左侧树形展示Github代码。
你懂的
优雅便捷的 V2EX 扩展
图形化创建爬虫,爬取网站数据
记事和清单
灵感记录
阅读模式 + 标注 + 稍后读,自己最常用的还是网页转阅读模式/网页转 Markdown 同步到坚果云,或是导出成PDF。
浏览器内置书签同步工具,目前支持Firefox、Chrome、Edge 和其它能装 Chrome 插件的浏览器,可配合坚果云使用。
生日:2018年09月23日
性别:妹子
品种:英短银渐层+虎斑,有部分折耳基因
爷爷:英短银渐层
奶奶:美短虎斑
爸爸:美短虎斑
妈妈:英短银渐层(有折耳基因)
1.刚带回家应该先放到猫砂里,拿它的爪子刨几 猫砂,让它知道那里是拉屎撒尿的地方,最好让小猫在里面待一会儿
2.如果小猫在其他地方拉屎撒尿了,把排泄物处理掉放到猫砂里去,让后把小猫也放到猫砂里,小猫闻到那气味让它知道应该在猫砂里厕所,然后用的爪子刨猫砂把排泄物盖起来
1.可以买个猫抓盘或猫抓板猫喜欢到处抓东西,如果有纸箱子猫也会经常抓
2.可以买个猫爬架,让猫玩,猫喜欢爬高高
3.猫到夏天可能会掉毛比较多,网上买个猫毛梳子 或噜猫手套时常给猫去去毛
4.因为猫会掉毛,抱它玩了之后,衣服上经常会粘 上毛,可以淘宝买个粘毛滚筒粘衣服上或是其它地方的猫毛,十分有效
可以买激光笔啊,逗猫棒之类的猫玩具,让小猫玩
可以自己在家洗(容易被抓伤)或是带去宠物店(100多一次)
请在适当的时候带猫去做绝育
猫的狂犬和驱虫每年要打,人的狂犬疫苗自己决定要不要打,不提供建议
前面说了,猫喜欢爬高高,所以猫能够跳上去或爬上去的窗户阳台要关掉,猫不知道高空危险,会跳楼的,很多猫都是坠楼死掉的
{% gp 14-0 %}
{% endgp %}
工作区vs暂存区: git diff
暂存区vs本地仓库: git diff —cached
本地仓库vs远程仓库: git diff <分支名> origin/<分支名>
撤消工作区修改: git reset —hard
撤消(1)git add
: git reset && git checkout .
或git reset —-hard
(会还原所有修改)
撤消(2) git commit
: git reset --hard origin/master
(使用远端的master分支恢复到本地)
撤消(3) git push
: git reset --hard HEAD^ && git push -f
(先在本地回到上一个版本,然后强推到远端)
Git别名设置
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch
git config --global alias.cp cherry-pick
git config --global alias.unstage 'reset HEAD'
# 可用git pull -r代替
git config --global alias.fr '!f() { git fetch && git rebase $@; }; f';
# git提交日志
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset - %Cgreen(%cd)%C(yellow)%d%Creset %s %C(blue)[%an/%cn]%Creset' --date=format:'%Y-%m-%d %H:%M:%S' --abbrev-commit"
删除别名
git config --global --unset alias.xxx
以下两个命令设置git alias和zsh alias都失败,暂没找到方法可以设置别名
# 查看仓库提交者排名前 5
git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5
# 统计每个人增删行数
git log --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done
git lg命令效果图
# 前提条件:手机和电脑处理同一网段
# 第一次手机先使用USB连接电脑执行以下命令让手机上的某一端口处于监听状态
adb tcpip <port>
# 在手机上查看ip地址或使用以下命令查看ip
adb shell ifconfig
# 连接手机(在同一个环境下,一般手机/电脑不重启就会一直连接着)
adb connect <ip> :<port>
# 查看连接的设备
adb devices
# 截图并保存到手机sd卡的下
adb shell screencap -p /sdcard/screenshot.png
便捷脚本(截图并自动复制到电脑剪切板/保存到电脑本地)
因脚本里调用了linux/macOS的命令,所以只适用于macOS系统,windows请自行修改脚本。
vi shot.sh
#!/bin/bash
# Android截图,定位和预览默认关闭,请取消注释
dd=`date +%Y-%m-%d-%H-%M-%S`
pwd=`pwd`
adb shell screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png
adb shell rm /sdcard/screenshot.png
mv screenshot.png $dd.png
echo "截图已保存为当前目录下的"$dd.png
# 修改图片尺寸,长或宽最大不超过960,等比缩放
echo "压缩图片..."
sips -Z 960 $pwd/$dd.png
# 定位到文件
open ./$dd.png -R
# 打开预览
open -a Preview $dd.png
# 复制到剪切板
osascript -e 'on run args' -e 'set the clipboard to POSIX file (first item of args)' -e end $pwd/$dd.png
echo "截图已复制到剪切板"
授予执行权限
chmod a+x shot.sh
使用方法
./shot.sh
⌘+v试试
可把命令添加alias别名
# 执行录屏并保存到手机sd卡目录下(默认时长180s)
# 可配置参数
# --time-limit: 录制时长,单位秒
# --size: 分辨率,如1280*720,不指定默认使用手机的分辨率
# --bit-rate: 视频的比特率,如6Mbps为6000000
# --verbose: 命令行显示log
adb shell screenrecord /sdcard/demo.mp4
便捷脚本(录屏并自动复制到电脑剪切板/保存到电脑本地)
因脚本里调用了linux/macOS的命令,所以只适用于macOS系统,windows请自行修改脚本。
vi record.sh
#!/bin/bash
# Android录屏
dd=`date +%Y-%m-%d-%H-%M-%S`"-$1s"
pwd=`pwd`
adb shell screenrecord --time-limit $1 /sdcard/screenrecord.mp4
adb pull /sdcard/screenrecord.mp4
adb shell rm /sdcard/screenrecord.mp4
mv screenrecord.mp4 $dd.mp4
echo "$1秒视频已保存为当前目录下的"$dd.mp4
# 定位到文件
open ./$dd.mp4 -R
# 复制到剪切板
osascript -e 'on run args' -e 'set the clipboard to POSIX file (first item of args)' -e end $pwd/$dd.mp4
echo "$1秒视频已复制到剪切板"
授予执行权限
chmod a+x record.sh
使用方法
# 3为录制秒数,可修改
./record.sh 3
]]>this.searchKeyword
this.$http({
url: this.searchUrl,
method: this.remoteRequestMethod,
params: Object.assign({}, this.searchParams, this.pager),
data: Object.assign({ [this.searchKeyword]: query }, this.searchBody, this.pager)
})
]]>this.searchKeyword
this.$http({
url: this.searchUrl,
method: this.remoteRequestMethod,
params: Object.assign({}, this.searchParams, this.pager),
data: Object.assign({ [this.searchKeyword]: query }, this.searchBody, this.pager)
})
全文完🙈
]]>对flux进行分组。
channelOnlineCityBoFlux.sort((s1, s2) -> Objects.requireNonNull(s1.getInitial()).compareTo(s2.getInitial()))
.groupBy(city -> city.getInitial().substring(0, 1).toUpperCase())
.sort((s1, s2) -> Objects.requireNonNull(s1.key()).compareTo(s2.key()))
.flatMap(gf -> gf.collectList()
.map(cityList -> {
ChannelOnlineCityGroupByPinYinBo cityGroupByPinYinBo = new ChannelOnlineCityGroupByPinYinBo();
cityGroupByPinYinBo.setLetter(gf.key());
cityGroupByPinYinBo.setCities(cityList);
return cityGroupByPinYinBo;
}));
handle作用相当于是filter和map的组合。
]]>📝 你可以使用最酷的 Markdown 语法,进行快速创作
🌉 你可以给文章配上精美的封面图和在文章任意位置插入图片
🏷️ 你可以对文章进行标签分组
📋 你可以自定义菜单,甚至可以创建外部链接菜单
💻 你可以在 Windows,MacOS 或 Linux 设备上使用此客户端
🌎 你可以使用 𝖦𝗂𝗍𝗁𝗎𝖻 𝖯𝖺𝗀𝖾𝗌 或 Coding Pages 向世界展示,未来将支持更多平台
💬 你可以进行简单的配置,接入 Gitalk 或 DisqusJS 评论系统
🇬🇧 你可以使用中文简体或英语
🌁 你可以任意使用应用内默认主题或任意第三方主题,强大的主题自定义能力
🖥 你可以自定义源文件夹,利用 OneDrive、百度网盘、iCloud、Dropbox 等进行多设备同步
🌱 当然 Gridea 还很年轻,有很多不足,但请相信,它会不停向前 🏃
未来,它一定会成为你离不开的伙伴
尽情发挥你的才华吧!
😘 Enjoy~
]]>随着项目业务需求的不断变更,数据库的表结构修改难以避免,此时就需要对数据库的修改加以记录和控制,便于项目的版本管理和随意的升级和降级。
Alembic 就可以很好的解决这个问题。Alembic 是 SQLAlchemy 作者开发的 Python 数据库版本管理工具。
]]>随着项目业务需求的不断变更,数据库的表结构修改难以避免,此时就需要对数据库的修改加以记录和控制,便于项目的版本管理和随意的升级和降级。
Alembic 就可以很好的解决这个问题。Alembic 是 SQLAlchemy 作者开发的 Python 数据库版本管理工具。
pip install alembic
通过 pip 命令安装,如果使用虚拟环境,记得激活虚拟环境后再执行 pip 命令
同时需要安装的还有 SQLAlchemy 和 PyMysql
pip install sqlalchemy
pip install pymysql
在使用 alembic 之前,需要进行初始化操作。
alembic init <YOUR_ALEMBIC_DIR>
YOUR_ALEMBIC_DIR,可以取一个符合项目名称规范的目录名,如
alembic init alembic
此时需要注意,如果之前是在虚拟环境中安装的 alembic,需要激活虚拟环境后,在执行上述命令。
同时,建议 cd 到项目根目录再执行初始化操作,因为 YOUR_ALEMBIC_DIR 会在当前目录下创建。
显示类似结果即初始化成功。
Creating directory D:\Project\py_sqlalchemy_demo\alembic ... done
Creating directory D:\Project\py_sqlalchemy_demo\alembic\versions ... done
Generating D:\Project\py_sqlalchemy_demo\alembic.ini ... done
Generating D:\Project\py_sqlalchemy_demo\alembic\env.py ... done
Generating D:\Project\py_sqlalchemy_demo\alembic\README ... done
Generating D:\Project\py_sqlalchemy_demo\alembic\script.py.mako ... done
Please edit configuration/connection/logging settings in 'D:\\Project\\py_sqlalchemy_demo\\alembic.ini' befor
e proceeding.
初始化成功后,会在执行初始化命令的目录下,生成一个 alembic.ini 的配置文件,及一个 alembic 目录,目录名就是之前设置的 YOUR_ALEMBIC_DIR。
接下来对 alembic.ini 的信息进行修改。
主要修改的是配置文件中的数据库连接部分。
sqlalchemy.url = driver://user:pass@localhost:port/dbname
将配置文件中,此部分替换成对应的数据库连接,这个数据库连接的写法是与 SQLAlchemy 创建 engine 时是一样的。
如我在 demo 中使用的是 SQLAlchemy 与 PyMysql,那数据库连接就是类似如下
mysql+pymysql://demo_user:demo123456@127.0.0.1:3306/demo_db
除修改配置文件外,还需要对 YOUR_ALEMBIC_DIR 目录下的 env.py 文件进行修改。
在 env.py 中,将 target_metadata 设置成项目的 model,使 alembic 能获取到项目中 model 定义的信息。
将原先的
target_metadata = None
修改成项目中的 model
import os
import sys
sys.path.append(dirname(dirname(abspath(__file__))))
from app import db
target_metadata = db.metadata
用 alembic revision -m + 注释 创建数据库版本
alembic revision --autogenerate -m "init db"
运行后,类似如下结果,即创建版本成功
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected removed table 'user'
Generating D:\Project\py_sqlalchemy_demo\alembic\versions\7b55b3d83158_create_tables.py ... done
每次修改过 SQLAlchemy 的 model,执行此命令即可创建对应的版本。
执行成功后,会在项目根目录下的 alembic/versions / 下生成的对应版本的 py 文件。命令规则是版本号 + 注释。(这个命名规则是在配置文件中定义的)
在每次创建新版本后,需要执行将数据库升级到新版本的命令,才能继续更新版本。
在每次创建新版本后,需要执行将数据库升级到新版本的命令,才能继续更新版本
将数据库升级到最新版本
alembic upgrade head
运行结果类似
(venv_win) D:\Project\py_sqlalchemy_demo>alembic upgrade head
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade 7b55b3d83158 -> b034414f04cd, create tables02
其中,命令中的 head 和 base 特指最新版本和最初版本。当需要对数据库进行升级时,使用 upgrade,降级使用 downgrade。
将数据库降级到最初版本
alembic downgrade base
将数据库降级到执行版本,使用 alembic downgrade + 版本号,不包含注释部分
alembic downgrade <version>
如
alembic downgrade 7b55b3d83158
运行结果
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running downgrade b034414f04cd -> 7b55b3d83158, create tables02
升级也是同样的道理,alembic upgrade + 版本号
在某些不适合在线更新的情况,可以采用生成 sql 脚本的形式,进行离线更新:
alembic upgrade <version> --sql > migration.sql
如:
alembic upgrade ae1027a6acf --sql > migration.sql
从特定起始版本生成 sql 脚本:
alembic upgrade <vsersion>:<vsersion> --sql > migration.sql
如:
alembic upgrade 1975ea83b712:ae1027a6acf --sql > migration.sql
如果是数据库降级操作,把 upgrade 替换为 downgrade。
在对数据库进行升级或降级后,会在当前操作的数据库中新增一个表;alembic_version。
表中的 version_num 字段记录了当前的数据库版本号。
如果需要清除所有的版本,将 versions 删除掉,同时删除数据库的 alembic_version 表。
class DisableMigrations(object):
def __contains__(self, item):
return True
def __getitem__(self, item):
return "notmigrations"
MIGRATION_MODULES = DisableMigrations()
## 示例代码
```python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from rest_framework import status
from rest_framework.test import APITestCase
from apps.account.models import User
from apps.account.tests.test_utils import TestCaseUtils
__author__ = 'jeff'
class UserAPITests(APITestCase, TestCaseUtils):
# 初始数据加载,可使用manage.py dumpdata [app_label app_label app_label.Model]生成
# xml/yaml/json格式的数据
# 一般放在每个应用的fixtures目录下, 只需要填写json文件名即可,django会自动查找
# 此测试类运行结束后,会自动从数据库里销毁这份数据
# fixtures = ['user.json']
def setUp(self):
# 在类里每个测试方法执行前会运行
# 在此方法执行前,django会运行以下操作
# 1. 重置数据库,数据库恢复到执行migrate后的状态
# 2. 加载fixtures数据
# 所以每个测试方法里对数据库的操作都是独立的,不会相互影响
kwargs = dict(mobile_phone='15999999999', password='111111')
self.user = User.app_user_objects.create(**kwargs)
def tearDown(self):
# 在类里每个方法结束执行后会运行
pass
@classmethod
def setUpClass(cls):
# 在类初始化时执行,必须调用super
super(UserAPITests, cls).setUpClass()
cls.token = ''
@classmethod
def tearDownClass(cls):
# 在整个测试类运行结束时执行,必须调用super
super(UserAPITests, cls).tearDownClass()
def test_app_user_login_success(self):
"""APP用户登录接口成功情况"""
# path使用硬编码,不要使用reverse反解析url,以便在修改url之后能及时发现接口地址变化,并通知接口使用人员
path = '/api/api-token-auth/'
data = {'mobile_phone': '15999999999', 'password': '111111'}
response = self.client.post(path, data)
# response.data是字典对象
# response.content是json字符串对象
self.assertEquals(response.status_code,
status.HTTP_200_OK,
'登录接口返回状态码错误: 错误信息: {}'.format(response.content))
self.assertIn('token', response.data, '登录成功后无token返回')
def test_app_user_login_with_error_pwd(self):
path = '/api/api-token-auth/'
data = {'mobile_phone': '15999999999', 'password': '123456'}
response = self.client.post(path, data)
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertJSONEqual('{"errors":["用户名或密码错误。"]}', response.content)
def test_get_app_user_profile_success(self):
"""成功获取app用户个人信息接口"""
path = '/api/account/user/profile/'
headers = self.get_headers(user=self.user)
response = self.client.get(path, **headers)
# 校验一些关键数据即可
# 如果是创建新数据,不仅要校验返回的状态码和数据,
# 还需要到使用Django ORM去数据库查询数据是否创建成功
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(6, len(response.data))
self.assertIn('url', response.data)
self.assertIn('mobile_phone', response.data)
self.assertIn('avatar', response.data)
self.assertIn('company_name', response.data)
self.assertIn('username', response.data)
self.assertIn('is_inviter', response.data)
def test_get_app_user_profile_without_token(self):
"""不传token请求获取用户信息接口"""
path = '/api/account/user/profile/'
response = self.client.get(path)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
# 来自unittest.case.TestCase
assertFalse(expr, msg=None)
assertTrue(expr, msg=None)
assertEqual(first, second, msg=None)
assertNotEqual(first, second, msg=None)
assertAlmostEqual(first, second, places=None, msg=None, delta=None)
assertNotAlmostEqual(first, second, places=None, msg=None, delta=None)
assertSequenceEqual(seq1, seq2, msg=None, seq_type=None)
assertListEqual(list1, list2, msg=None)
assertTupleEqual(tuple1, tuple2, msg=None)
assertSetEqual(set1, set2, msg=None)
assertIn(member, container, msg=None)
assertNotIn(member, container, msg=None)
assertIs(expr1, expr2, msg=None)
assertIsNot(expr1, expr2, msg=None)
assertDictEqual(d1, d2, msg=None)
assertDictContainsSubset(expected, actual, msg=None)
assertItemsEqual(expected_seq, actual_seq, msg=None)
assertMultiLineEqual(first, second, msg=None)
assertLess(a, b, msg=None)
assertLessEqual(a, b, msg=None)
assertGreater(a, b, msg=None)
assertGreaterEqual(a, b, msg=None)
assertIsNone(obj, msg=None)
assertIsInstance(obj, cls, msg=None)
assertNotIsInstance(obj, cls, msg=None)
assertRaisesRegexp(expected_exception, expected_regexp,
callable_obj=None, *args, **kwargs)
assertRegexpMatches(text, expected_regexp, msg=None)
assertNotRegexpMatches(text, unexpected_regexp, msg=None)
测试接口地址建议使用硬编码,不要使用reverse
反解析url,原因是接口地址尽量避免改变,如果必须修改,需要以很明显的方式来提醒开发人员以便开发人员通知接口使用人员。
有如下两种方法准备测试数据
setUp()
里来创建;fixtures
属性;[
{
"model": "myapp.person",
"pk": 1,
"fields": {
"first_name": "John",
"last_name": "Lennon"
}
},
{
"model": "myapp.person",
"pk": 2,
"fields": {
"first_name": "Paul",
"last_name": "McCartney"
}
}
]
在Pycharm
里可以通用右键项目,选择Run 'Test:' with Coverage
来查看测试的覆盖率。也可以通过其它第三方包查看测试覆盖率,具体请自己查询。
#停止Jenkins
sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist
# 修改Group和User
# <用户名>填写你的MacOS用户名,不知道的可以在命令行使用whoami查看,不需要尖括号
$ sudo vim +1 +/daemon +’s/daemon/staff/’ +/daemon +’s/daemon/<用户名> +wq org.jenkins-ci.plist
# 可能相应文件夹的权限
sudo chown -R <用户名>:staff /Users/Shared/Jenkins/
sudo chown -R <用户名>:staff /var/log/jenkins/
# 启动Jenkins
sudo launchctl load /Library/LaunchDaemons/org.jenkins-ci.plist
]]>A: 公网电脑
B: 内网电脑
从[releases](Releases · fatedier/frp · GitHub)下载系统对应的压缩包,Mac可使用darwin amd64
的包,在公网电脑和本地电脑各放一份。
公网电脑上frps.ini
[common]
# 用于接收 frpc 连接的端口
bind_port = 7000
# 通过此端口访问http服务
vhost_http_port = 8080
# 日志文件输出位置
log_file = ./frps.log
# 日志等级
log_level = info
# 域名
subdomain_host = example.com
# frp管理后台端口
dashboard_port = 7500
# frp管理后台用户名
dashboard_user = admin
# frp管理后台密码
dashboard_pwd = admin
本地电脑上frpc.ini
[common]
# 公网电脑IP
server_addr = 111.111.111.111
# frp连接的端口
server_port = 7000
[web]
type = http
# 本地http服务端口
local_port = 8080
# 子域名前缀, 子域名前缀里不要使用下划线"_",不然可能会出现莫名其妙的400错误可以用"-"代替。
subdomain = iblogc
配置域名example.com
的A记录的泛解析
*.example.com
指向公网电脑IP111.111.111.111
8080
端口运行http
服务./
)./frps -c ./frps.ini
./
)./frpc -c ./frpc.ini
在任何一台能联网的机器上访问 http://iblogc.example.com:8080
即可访问内网电脑B上的http服务。
在任务一台能联网的机器上访问example.com:7500
即可访问frp的管理后台。
server {
listen 80;
server_name *.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_redirect http://$host/ http://$http_host/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}
]]>Model
第一种
def validate_begins(value):
if not value.startswith(u'new'):
raise ValidationError(u'Must start with new')
class Post(models.Model):
title = models.CharField('标题', max_length=100,
validators=[validate_begins])
body = models.CharField('内容', max_length=1000)
第二种
class Post(models.Model):
title = models.CharField('标题', max_length=100,
validators=[validate_begins])
body = models.CharField('内容', max_length=1000)
def clean_fields(self, exclude=None):
# 验证模型的所有字段
if exclude:
# 禁用掉自定义的字段验证
exclude.append('body')
super(Post, self).clean_fields(exclude)
if not self.title.startswith(u'new'):
raise ValidationError({'title':u'Must start with new'})
第三种
class Post(models.Model):
title = models.CharField('标题', max_length=100,
validators=[validate_begins])
body = models.CharField('内容', max_length=1000)
def clean(self):
super(Post, self).clean()
if not self.title.startswith(u'new'):
raise ValidationError({'title':u'Must start with new'})
Form
第四种
def validate_begins(value):
if not value.startswith(u'new'):
raise ValidationError(u'Must start with new')
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'body']
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.fields["title"].validators.append(validate_begins)
第五种
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'body']
def clean_title(self):
title = self.cleaned['title']
if not title.startswith(u'new'):
raise ValidationError(u'Must start with new')
return title
第六种
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'body']
# django1.7之前此方法必须返回一个cleaned_data字典,但现在可选
def clean(self):
cleaned_data = super(PostForm, self).clean()
if not cleaned_data['title'].startswith('new'):
raise forms.ValidationError({'title': u'Must start with new'})
第七种
def validate_begins(value):
if not value.startswith(u'new'):
raise ValidationError(u'Must start with new')
class PostForm(forms.ModelForm):
author = forms.CharField('标题', max_length=100,
validators=[validate_begins])
class Meta:
model = Post
fields = ['title', 'body']
第八种
from django import forms
from django.core.validators import validate_email
class MultiEmailField(forms.Field):
def to_python(self, value):
"Normalize data to a list of strings."
# Return an empty list if no input was given.
if not value:
return []
return value.split(',')
def validate(self, value):
"Check if value consists only of valid emails."
# Use the parent's handling of required fields, etc.
super(MultiEmailField, self).validate(value)
for email in value:
validate_email(email)
]]>自动
使用depth
参数指定外键深度
手动指定
使用外键对应model
的小写为属性,外键对应的model
序列化程序为值
以下例子在HospitalPic
序列化结果里嵌套显示Hospital
models.py
from django.db import models
class Hospital(models.Model):
name = models.CharField()
class HospitalPic(models.Model):
hospital = models.ForeignKey(Hospital)
serializers.py
from rest_framework import serializers
class HospitalSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Hospital
fields = '__all__'
class HospitalPicSerializer(serializers.HyperlinkedModelSerializer):
hospital = HospitalSerializer()
class Meta:
model = HospitalPic
fields = '__all__'
***反向关系嵌套***
在`Hospital`序列化结果里嵌套显示`HospitalPic`
serializers.py
```python
from rest_framework import serializers
class HospitalPicSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = HospitalPic
fields = '__all__'
class HospitalSerializer(serializers.HyperlinkedModelSerializer):
hospitalpic_set = HospitalPicSerializer(many=Ture)
class Meta:
model = Hospital
fields = '__all__'
定义一个serializer Field
,并添加参数source
指向外键对对应的字段(source
值其实是从当前序列化的实例的属性)
my_address= serializers.ReadOnlyField(source='address.full_address')
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
ViewSet
,并不有设置queryset
,而是重写了get_queryset
时,需要在router
里增加base_name
参数(base_name
为router
为ViewSet
注册url时自动添加的name前缀,如果未设置则从ViewSet
的queryset
里取,使用ViewSet
自动生成的url name为<base_name>-list <base_name>-detail 等)views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return self.request.user.contact_set.all()
urls.py
router.register(r'contact', ContactViewSet, base_name='contact')
未设置base_name
会报下面错误
'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.
namespace
urls.py
url(r'^api/', include(router.urls, namespace='api')),
需要对HyperlinkedRelatedField
字段的参数进行修改
serializers.py
class HospitalPicSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = HospitalPic
fields = '__all__'
extra_kwargs = {
'url': {'view_name': 'api:hospitalpic-detail'},
'hospital': {'view_name': 'api:hospital-detail'}
}
不然会出现以下错误
Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
不过话说我们全api的url加namespace
一般是为了版本控制,所以有一种简单的方法,只要在settings.py添加基于namespace
的版本控制,这样就不需要修改HyperlinkedRelatedField
字段的view_name
了
urls.py
url(r'^api/v1/', include(router.urls, namespace='v1')),
url(r'^api/v2/', include(router.urls, namespace='v2')),
settings.py
REST_FRAMEWORK = {
……
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
……
}
LANGUAGE_CODE = 'zh-CN'
如果设置为
LANGUAGE_CODE = 'zh-Hans'
虽然django默认表单错误会输出中文,但drf还是输出英文
validators
可以直接在drf中使用,不需要做任何修改editable=False
时,ModelSerializer
里该字段会抛弃model
里显式和隐式(unique)的所有validators
Serializer
里write_only
写在field
里和写在extra_kwargs
里是有区别的,class UserRegisterSerializer(serializers.ModelSerializer):
"""用户注册Serializer"""
code = serializers.CharField(min_length=4, max_length=6, label=_('验证码'),
help_text=_('验证码'), write_only=True)
re_password = serializers.CharField(label=_('重复密码'), help_text=_('重复密码'),
validators=validators.password_validators(),
write_only=True)
class Meta:
model = User
fields = ('mobile_phone', 'code', 'password', 're_password')
extra_kwargs = {'password':
{'write_only': True}
}
def validate(self, attrs):
"""
Check that the start is before the stop.
"""
if attrs['password'] != attrs['re_password']:
raise serializers.ValidationError(_('密码不一致'))
# 校验验证码
verify_result = Sms(attrs['mobile_phone']).verify_sms_code(
attrs.pop('code'))
if not verify_result:
error = verify_result.get('error')
raise ParseError(error)
return attrs
def create(self, validated_data):
user = User(
username=validated_data['mobile_phone'],
mobile_phone=validated_data['mobile_phone'],
)
user.set_password(validated_data['password'])
user.save()
return user
因为create()
这个方法return了一个user
实例,User
里没有的字段code
和re_password
需要将write_only
写在field
参数里,不然会报以下错误
AttributeError: Got AttributeError when attempting to get a value for field `code` on serializer `UserRegisterSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'code'.
django-rest-swagger
报以下错误Can't read from server. It may not have the appropriate access-control-origin settings.
注释掉设置里的
# 'base_path': '127.0.0.1:8000/docs',
serializer.data
和serializer.validated_data
在serializer
只使用data
参数实例化的时:
serializer.data
是原始数据(字符串),serializer.validated_data
是进行数据验证并转换成对应数据类型的数据。serializer
调用is_valid
方法后才能调用serializer
只使用instance
参数实例化时:serializer.data
没有serializer.validated_data
,并且serializer.data
里的数据也是字符串;is_valid
;is_valid
和validated_data
只在有data参数实例化时才可调用;serializer
里获取原始请求信息默认的,上下文信息会被传递到serializer
里,所以在serializer
可以直接使用self.context['request']
来获取请求信息。(在要继承自viewsets.GenericViewSet
的类里使用的serializer
才能取到,如果是继承APIView
的,自己传入即可serializer = self.serializer_class(data=request.data, context={'request': request})
)
serializer
字段自定义字段继承serializers.Field
,to_representation
方法处理出来的数据用来序列化显示,to_internal_value
处理接收到的数据,get_attribute
方法指定这个字段访问的实例属性,get_value
方法指定
class QiNiuField(serializers.Field):
def get_attribute(self, instance):
# (序列化时)从模型实例中取一个值给这个字段处理,也可以使用`source`参数指定
return instance.key
def get_value(self, dictionary):
# (反序列化时)从传入数据中提取一个值给这个字段处理
return super(QiNiuField, self).get_value(dictionary)
def to_representation(self, value):
# (序列化时)处理出来的数据用来序列化显示
return value.url
def to_internal_value(self, data):
# (反序列化时)处理接收到的数据
return data['key']
官方文档中有这么一个例子Dealing with nested objects
如果是以Content-Type:application/json
形式传数据格式传数据,直接嵌套传就可以了{'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'}
,但如果是以,
但是如果以Content-Type:form-data
或Content-Type:x-www-form-urlencoded
上传,则上传user
信息进不是嵌套,而是就.
连接了,"user.email":"foobar"
.
2016-01-29 初稿
ViewSet
没有写serializer_class
属性,而是重写了get_serializer_class()
方法,出现
Cannot use OrderingFilter on a view which does not have either a 'serializer_class' or 'ordering_fields' attribute.
原因:因为启用了rest_framework.filters.OrderingFilter
而没有设置ordering_fields
解决方法:ViewSet
里加ordering_fields
属性,可是禁用rest_framework.filters.OrderingFilter
ViewSet
没有写queryset
属性,而是重写了get_queryset()
方法,出现
'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.
解决方法:需要在urls.py
里给ViewSet
注册Router
时添加base_name
(base_name
为router
为ViewSet
注册url时自动添加的name前缀,如果未设置则从ViewSet
的queryset
里取,使用ViewSet
自动生成的url name为<base_name>-list <base_name>-detail 等)
urls.py
router.register(r'users', UserViewSet, base_name='user')
给url设置了namespace
urls.py
url(r'^api/', include(router.urls, namespace='api')),
访问部分接口出现
Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
解决方法1:给所有的serializer
里包含的外键字段手动设置view_name
值(注意,继承HyperlinkedModelSerializer
,会隐式添加一个HyperlinkedRelatedField
字段url
,而所有的外键都会变成HyperlinkedRelatedField
字段,所以需要对两种类型字段手动设置view_name
值)
serializers.py
class ContactSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Contact
fields = '__all__'
extra_kwargs = {
'url': {'view_name': 'api:contact-detail'},
'user':{'view_name':'api:user-detail'}
}
解决方法2:启动drf基于NameSpace
的版本控制
settings.py
REST_FRAMEWORK = {
……
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
……
}
]]>2016-08-25 初稿
Django Rest framework
有自带的频率控制配置
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
# 开启匿名用户接口请求频率限制
'rest_framework.throttling.AnonRateThrottle',
# 开启授权用户接口请求频率限制
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
# 频率限制有second, minute, hour, day
# 匿名用户请求频率
'anon': '100/day',
# 授权用户请求频率
'user': '1000/day'
}
}
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView
class ExampleView(APIView):
throttle_classes = (UserRateThrottle,)
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
@api_view(['GET'])
@throttle_classes([UserRateThrottle])
def example_view(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
方法一:
class BurstRateThrottle(UserRateThrottle):
scope = 'burst'
class SustainedRateThrottle(UserRateThrottle):
scope = 'sustained'
...and the following settings.
settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'example.throttles.BurstRateThrottle',
'example.throttles.SustainedRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'burst': '60/min',
'sustained': '1000/day'
}
}
然后在视图里设置throttle_classes
即可。
方法二:
settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.ScopedRateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'contacts': '1000/day',
'uploads': '20/day'
}
}
然后在类视图中设置throttle_scope
class ContactListView(APIView):
throttle_scope = 'contacts'
...
class ContactDetailView(APIView):
throttle_scope = 'contacts'
...
class UploadView(APIView):
throttle_scope = 'uploads'
...
1. 匿名用户频率如果设置大于授权用户频率,则以授权用户频率为准。
2. 频率限制是针对单个接口的频率,而不是所有接口的频率。
yum install mysql
yum install mysql-devel
wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm
rpm -ivh mysql-community-release-el7-5.noarch.rpm
yum install mysql-community-server
service mysqld restart
初次安装mysql需要设置root密码
mysql -uroot
set password for 'root'@'localhost' =password('password');
在/etc/my.cnf
文件中[mysql]和[mysql]中添加以下内容
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8
字符编码保持和/usr/share/mysql/charsets/Index.xml
中的一致。
把在所有数据库的所有表的所有权限赋值给位于所有IP地址的root用户。
mysql> grant all privileges on *.* to root@'%'identified by 'password';
如果是新用户而不是root,则要先新建用户
mysql>create user 'username'@'%' identified by 'password';
此时就可以进行远程连接了。
安装epel扩展源
yum install epel-release
安装pip
yum install python-pip
安装virtualenv和virtualenvwrapper
pip install virtualenv virtualenvwrapper
编辑~/.bashrc
文件,结尾添加以下内容
export WORKON_HOME=~/.virtualenvs
source /usr/bin/virtualenvwrapper.sh
然后执行以下命令使配置生效
source ~/.bashrc
创建env
mkvirtualenv explame
使用pip安装项目需要的包
在项目目录下新建nginx_wsgi.py
文件
touch nginx_wsgi.py
添加如下内容
import sys
import site
import os
# site-packages
site.addsitedir('/home/nginxuser/.virtualenvs/example/lib/python2.7/site-packages')
# Add the project directory
# sys.path.append('/home/nginxuser/nginxuser')
PROJECT_DIR = '/home/nginxuser/projects/example'
sys.path.insert(0, PROJECT_DIR)
os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings.prod'
# Activate your virtual env
activate_env = os.path.expanduser("/home/nginxuser/.virtualenvs/example/bin/activate_this.py")
execfile(activate_env, dict(__file__=activate_env))
# after activite env
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
yum install nginx
nginx -t -c /etc/nginx/nginx.conf
service nginx start
systemctl enable nginx
useradd nginxuser
passwd nginxuser
vim /etc/nginx/nginx.conf
非注释首行
user nginx
改为
user nginxuser
不然可能会出现网站静态文件访问报403问题。
vim /etc/nginx/conf.d/example.conf
server {
listen 80;
server_name example.com;
charset utf-8;
client_max_body_size 75M;
access_log /home/nginxuser/projects/example/nginxlogs/access.log;
error_log /home/nginxuser/projects/example/nginxlogs/error.log;
location /static {
alias /home/nginxuser/projects/explame/static;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
} jk
pip install gunicorn
项目根目录下添加gunicorn运行配置文件gunicorn.conf.py
import multiprocessing
bind = "127.0.0.1:8000"
workers = 2
errorlog = "/home/nginxuser/example/gunicorn.error.log"
#loglevel = "debug"
proc_name = "gunicorn_example"
sudo gunicorn example.nginx_wsgi:application -c /home/nginxuser/projects/example/gunicorn.conf.py
后台运行
sudo nohup gunicorn example.nginx_wsgi:application -c /home/nginxuser/projects/example/gunicorn.conf.py&
如果运行报错先使用以下命令检查下nginx配置是否有错
nginx -t -c /etc/nginx/nginx.conf
pip install supervisor
vim /etc/supervisord.d/example.ini
(需要注意:用 supervisord 管理时,gunicorn 的 daemon 选项需要设置为 False)
[program:example]
directory = /home/nginxuser/projects/example ; 程序的启动目录
command = gunicorn example.nginx_wsgi:application -c /home/nginxuser/projects/example/gunicorn.conf.py ; 启动命令,可以看出与手动在命令行启动的命令是一样的
autostart = true ; 在 supervisord 启动的时候也自动启动
startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true ; 程序异常退出后自动重启
startretries = 3 ; 启动失败自动重试次数,默认是 3
user = nginx ; 用哪个用户启动
redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20 ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /data/logs/usercenter_stdout.log
; 可以通过 environment 来添加需要的环境变量,一种常见的用法是修改 PYTHONPATH
; environment=PYTHONPATH=$PYTHONPATH:/path/to/somewhere
冒号后面要有空格
使用-c
指定配置文件。
supervisord -c /etc/supervisord.conf
如果启动时遇到以下报错信息
Error: Another program is already listening on a port that one of our HTTP servers is configured to use. Shut this program down first before starting supervisord.
For help, use /use/bin/supervisord -h
可以使用以下命令解决
sudo unlink /var/run/supervisor/supervisor.sock
启动时需要使用和supervisorctl
使用一样的配置文件。
supervisorctl -c /etc/supervisord.conf
启动后进入supervisorctl
的shell,在此shell里可以执行以下命令
status # 查看程序状态
start example # 启动example程序
stop example # 关闭example程序
restart example # 重启example程序
reread # 读取有更新(增加)的配置文件,不会启动新添加的程序
update # 重启配置文件修改过的程序
也可以不进shell执行以上命令
supervisorctl status # 查看程序状态
supervisorctl start example # 启动example程序
supervisorctl stop example # 关闭example程序
supervisorctl restart example # 重启example程序
supervisorctl reread # 读取有更新(增加)的配置文件,不会启动新添加的程序
supervisorctl update # 重启配置文件修改过的程序
如果要开启web管理界面,打开/etc/supervisord.conf
把下面几行取消注释即可
:[inet_http_server] ; inet (TCP) server disabled by default
:port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface)
:username=user ; (default is no username (open server))
:password=123 ; (default is no password (open server))
]]>本文系转载 原文地址
背景:我是 2012 届毕业的,大学前两学期(2008-2009/2009-2010)是在文一校区度过的,在网上偶然间看到这遍文章很有感触,就想把这篇回忆永久保存。当然我就读时文一校区叫:杭州电子科技大学信息工程学院。
“文一路多熟悉啊,我的大学就在那里,旁边还有好多大学,不过现在搬了,有机会要回去看看”
———郑钧
以我的学长郑钧这段话开始我对杭电特别是文一路校区一些琐碎的回忆,权当给后来者更或说是自己留些关于老校区的声色影像吧!
杭州,西湖区,文一路,杭州电子工业学院。
文一路相当繁华,特别是对学生来说,有数不尽的小店。吃的比较出名的有:知味观——杭电校外食堂;太一——以廉价出名,是男人拼酒的首选;一品——冬天最爱,骨头砂锅相当不错;此外,教工路上雅兔对面的衢州三头够辣,财经东方分部门口的山东升风水饺很不错,上过快报推荐的。买衣服的话,以我爱我家为界,西边都是专卖店,东边则都是个性小店,大都挂外贸服饰的招牌。其他店家,东方对面有个藏银,据说东西都是西藏过来的,是否真实就不得而知了;杭师门口的游戏人间总卖一些新奇玩意,仔细淘的话,有不少好东东;再过去快乐小站,是个水吧,也是我们的一大据点,东西一般,但正对杭师院大门,坐靠窗位置,可阅美女无数;网吧,教工路上风影看电影的多,雅兔则是游戏天堂,杭电人相当的多;书店则首推江郎,很多学生自己去那里买教材的,可以打折的。
杭电两侧各有一个大超市,翠苑的物美感觉东西多些,而且楼上就是翠苑电影大世界,外围全是座位,可以在物美或者 kfc 买好吃的,在楼上和 mm 坐一个下午,风景不错;东边的华润是今年新开的,去的不多,总感觉东西偏少,而且学生顾客没有物美多;说到超市,其实最喜欢的还是黄龙好又多,很多人宁可多跑路,也要去好又多的,虽然只有一层,但感觉就比较好,而且很多人喜欢那里的电炉烤鸡。小超市,最爱是可的,以前是在杭电诊所楼下的,经常有人半夜跑出去买东西吃,后来文一路整顿,搬了,附近就没什么好的地方了,现在的店都是后来开的。
学校四周,比较有印象的还有:京典——洗头比理发好,和 11 路站的阿俊、杭师对面的千丝万缕算是这一带比较出名的;一家小店,在工程门口,现在叫不上名字了,家常菜做的不错,关键是价廉量足,非典过后那段时间每天吃饭都要排队的;天马——以前是网吧老大,后来没落,主要经营歌厅和陶吧;撞击酒吧——学校外教最喜欢去的地方,经常有这方面的活动;文二夜市——杭电人逛的最多的地方,淘碟、点卡、明星壁画还有其他小玩意;文二电影院——平时几乎都是情侣专场,只有有球赛的日子才有上座率,00 欧锦赛,02 世界杯,大家很多时候在这里度过。
公交车,最熟悉的自然是 11 路,到保俶路站转 328 去下沙,到武林广场站去银泰,终点站城站回家,到现在我到杭州也总是要坐坐 11 路;58 路,是个环线,可以到杭大、湖墅电子市场、武林广场等,我在杭大学法语时天天坐这班;155 是到延安路的,25 到吴山,816 到舟山东路,等等。其他就记不大清了,不过有一点特别,杭电门口的站叫省委党校,而文一和教工路口站才叫电子学院。
外面绕了一大圈,回到杭电。学校大门最大的特点就是那棵老树,估计也是有点年头的,正说明了杭电校园的特点:绿化好;校名写的很小,不注意还发现不了,经常有女生整个寝室来拍照片的;门口门卫都很年轻,离校前在草坪聚会时,找他们吹过瓶子;旁边是邮递房,新生时信很多,越大就越懒了,记得我们的邮箱是 233;进校后的大草坪,是杭电一大聚会地点,迎新晚会、班级聚会、老乡聚会很多在这里,右边有几棵树,形成一个隐秘的小空间,情侣很多,记得吃完散伙饭那天我们霸占了草坪,赶走了 n 多情侣,呵呵。
正对校门是实验楼,也是杭电最宏伟的建筑,好象是 9 层还是 10 层吧。每两个分院共用一层,各分院的办公室、实验室还有机房。我们财经是和管理一起在四层,以前三个分院的时候我们是一起的,叫二分院(工商管理),辩论超强,出过不少牛人。现在的财经是杭电第一大院,以人数称雄,体育也很强。实验楼有个很大的好处,夏天很凉快,所以不少学生晚上睡这里的,理由当然很多,有的是做实验,有的是看门,不一而足。这里的另一特色是电梯超慢,人狂多,经常等电梯还没走路快。二楼是文理分院,可能是大家除了本分院外最常去的地方,因为英语和数学教研室在这里,所以大英、高数、线代这些公共基础课的重修补考费就是这里缴的,心痛啊。里面有个老师是杭电第一名捕,常监考数学,捕人无数,我亲眼看到过的就不下二十来人。
实验楼东侧,是图书馆,藏书好像还可以吧,算不上多,也不会太少,就是旧了点。借书很有意思,正门很少人走的,都从旁边楼梯直接上三楼侧门,脱鞋进去后,里面就是夏天也是感觉冷的。借书是有限制的,我记得是文艺类一次只能借两本吧,超期要罚的,我弄丢了《欧美音乐赏析》,超期加赔花了 100 多。一楼自由阅览室是考研基地,位置永远紧俏的,经常晚上和早上晨跑时就有人抢了。在这里你看到的是杭电最努力最用功的一批人,至于成效如何,我们不得而知。而杭电最精英的人,此刻要么在实验室里埋头研究,要么就在寝室里砍 cs、星际,再或者就是觥筹交措、呼朋唤友中,总之,各有各的精彩。二楼和四楼记得都有期刊阅览室,这里是写论文的好地方,所以大四学生居多。
图书馆后是花圃,还有一条越来越干净的西溪河,这里也是情侣的天堂,美中不足的是这里有住校工,而且有养狗。河拐出去的地方是东门,是后来新开的,这里管的比较松,24 小时可以进出。东门进来是杭电招待所,比较一般,好像是 150 标间,非典时外地回来学生在这里隔离,只要交每天 10 块钱伙食费就可以待一周,吃的不错,还有冠军杯看,羡慕。学校的宴席大都在这里摆,我吃过几次,味道一般。对面是以前的研究生楼,房间不大,两人一间吧,带厨房,感觉太暗,有些潮。一栋新楼是教师宿舍,学校老师基本上是住翠苑和文二的,这里住的大都是单身的还有外教,我们外教还养过小狗,非常可爱。楼侧面的墙是练习网球的好地方。
这一段绿化特别的好,绿树丛中,还有个小院,不大容易发现,这里是 6 号楼,最早是诊所兼教师宿舍,后来住成教,再后来人越来越多了,就住的是千届学生,我记得是 2000 工管的,回字型的房子,男女生分住两头,我们曾经猜测过是否有人可以趁管理员不注意溜到对面去,无从考究。这里管理员好像对外来的不大友善,很多人就在院子里直接叫人,不过容易暴露身份。反正我是很不喜欢这个楼,从来只打电话的。
再往里走,是两栋教学楼,一曰东教,一曰新东教,这也是杭电一大笑话,造新东教时学校还征集过楼名,记得还有不少人投了,结果最后命名新东教,举校倾倒。先说东教,杭电老建筑,也算得上雕梁画栋了,以前杭电的明信片上有图。木地板、木桌、铁椅,大而空,感觉就是两个字:阴冷,据说闹鬼,以前有人上吊过的,晚上很少人自习的。这里每层都有大教室,所以很多哲学这种公共课在这里上的,很多辩论赛也是在这里搞的,我在这里作过很多次的 dj。杭电的大教室有个共同点,就是后面的高一级,所以在两段高低上的位置是考试最佳,你视力好的话前面一览无余。顺便说一句,东教对面是杭商的女生宿舍,有时候能和她们聊上几句。
说起新东教,话题可就多了。这里最早是杭电花圃,有一条长廊,据说是爬满葡萄藤的,是杭电最出名的情人长廊,一到了晚上,非常……,演绎了很多杭电浪漫经典。好像说有些坏小子会偷偷跑去捣乱,呵呵,杭电毕竟还是光棍多啊。很可惜我们 99 届进校后,花圃就拆了盖楼,不过到现在我还记得那时我们的美女辅导员带我们去看长廊,讲过去的往事。变成新东教后,学校还很搞笑的在旁边原地建了一个亭子,种上了竹子什么的,可是亭子正对教室,恐怕没有人愿意在众目睽睽下卿卿我我吧。新东的教室,4 和 6 是大教室,很大的那种,好像所有分院都是在这里开年级会议的,我们 99 财经都是在 306。这里还是考试集中的地方,凡是两个班以上的大课,大都在这里上和考试,四级也是在这里,六级则在新教。新东最好的一点是阳光充足,特别是 4 和 6 的大教室,坐在后排都沐浴在和煦的阳光中,所以,嘿嘿,这里也是要抢的,好像有很多次前面五六排几乎没人。新东一个特点是逃课很难,想偷偷溜走不被发现,非常难,不过高手还是很多,羡慕!在这里上过的课中印象最深的是大二时候的美术欣赏,上的人很多,而且以男生居多,知道原因了吧,因为传说中教这门的 miss l 是个美女,所以很多可以说是完全没有艺术细胞的人也挤来凑热闹。你如果要问,传说是否属实,那就到杭电上美术欣赏吧,不过她挺凶的。新东四楼比较偏僻,上课自习的人不多,也是个小小的禁区吧,因为经常一个教室里就两个人,*#•¥ (以下省略 100 字),顶上好像是个天台,以前情侣很多的,不过出过一次事后就人少了,去年吧,有人从上面摔下来死了,原因众说不一。
新东前面,是杭电一个好地方,是一大块草坪,完全沐浴在阳光中,冬天在这里聊天嗑瓜子打牌的很多,看报纸睡觉的也很多,我经常和同学在这里背书,要考试了。旁边则是中心花园,杭电喜欢用方位来命名地点。这个花园两部分,靠行政楼是草坪,也是冬天背书的好地方,不过我们一直奢望有天能在上面踢场球,这里最有特色是有棵孤零零的小树,什么树我忘了,可能是柳树一类吧,就这么一棵,非常滑稽,这边也是拍照好地方。靠实验楼一边比较乱,有亭子假山,这里是我们背书打牌的地方;有小林子,情侣的地盘;有一排石桌,这里是早上起来背英语人的。这个花园里好像很多乱七八糟的植物,名字都怪怪的,记得我们英语听说课,有次老外就要我们写中心花园,晕,那些名字看了就头痛哦。
花园旁一条路兼是停车场,好车不多,都是老师的吧。学生,我知道的不多,只是听说自动化有个家伙开别克赛欧的。
路的尽头是工字楼,我以前文章提过,是我们的老巢,住了三年。结果大家都住出感情来了,好处很多:位置优越,四通八达,吃饭打水踢球上课出门都很近;独门独院,没有人和声音骚扰;有自己独立的停车棚和晒衣场,不用和别人挤;房间小些,但只住六人,舒服多了;没有公共电扇,可以通宵供电…… 所以大四搬走后,经常夜里和凌晨会听到有人高叫:“打回工字楼,还我工字楼”可见怀念之深,后来这里成了研究生楼,郁闷!工字楼晚是要锁门的,以前还可以翻窗,后来封了,就只能叫门了,看夜的是个老头子,反应比较慢,有次我们一个兄弟喝高了,等久了,叫把玻璃给砸了,生猛啊,结果后来入不成党,可惜啊!杭电规矩是男生不得入女生楼,女生进男生宿舍呢,则要登记,还隔会就打传呼来催,工字楼好的是后门比较好进,所以常常有人在门口掩护,女生悄悄溜进来。不过有几个家伙和管理员混熟了,女朋友可以随意进出的,所以有时会遇上尴尬,大家都知道男生在寝室穿着是怎么的咯!我们班住在二楼的最东头,也是最热闹的地段,晚上熄灯后好多人聚在门口,打双扣的,看小说的,聊天的,吃东西的,比熄灯前还热闹,经常还有人会提议去 “调戏” 某人,于是,惨叫声起,一出悲剧在上演。
从我们工南窗口望出去,是新教,这是在新东教之前东西教之后盖的,故曰新教楼。不过也很有点旧了,前年考试前,我们在寝室里复习时,就是轰的一声,四楼的楼梯断了,幸好无人受伤。新教是老生最爱,98 和 99 的喜欢在这里自习,千届则大都在新东教,还有些勇敢的则在东西教。新教单数是大教室,逃课最好的战场,双数是小教室,大部分小课在这里上。这里给我印象最深的是徐旭初老师的课,可能很多杭电人都应该知道的,他也算是杭电最受欢迎的老师吧,据说是出身于高官家庭。本人很有些才华,颇有些读书人的风骨。他的讲座在杭电也是很受欢迎的,我还曾经请他到学校电台作过几次节目,聊的还是很投机的。此外,出身社科部的几位如钱升老师、郑建功老师等在学校也是比较受欢迎的,或严谨,或风趣,个人认为是为杭电读书人之望。还有一位老夫子,是曾经教我们英语的,张唯新老师,一看就是那种老底子的人,每次看到他我就会想到上海的老克腊,虽然和他打的交道不多,但心里还是很敬佩他的。
新教两侧就是前面提到过的行政楼和西教楼。行政楼和我们学生联系不大,我只是因为广播台的关系常去宣传部,也没什么特别的地方,顶楼会议室常有些招聘会什么的,学校派出所也在这里,打证明什么的要到这里。西教故事和东教差不多。过去一点还有学校的印刷厂,服务态度不好。
再过去就是南门了,也就是学校的后门,好像很多人把学校后门外叫堕落街,可能也是有点吧。南门出去,有几栋居民楼,好像有些房子出租的,不过很紧俏,很难租到;临街门口有几个小摊,卖煎饼的,卖水果的,卖烧烤的,还有糯米团子,课间很多人出来解决早饭问题。往外走,是杭商东院,以前是中专,杭电没抢过杭商,结果让他们把学校伸到教工路东面来了,课间时候很多人进出,有不少美女。靠着杭商的门,一排依次是杭商招待所,房间很小,好像也是 150;速食店,开始主卖珍珠奶茶,后来卖炒粉、炒饭什么的,生意极其火暴,这里做的最好的是卤肉饭,5 元一份,浇上汤非常好吃,光我们班就平均每天有六七个人吃的,常年如此;一家超市,门口卖台湾香肠和肉饼,味道还可以;网吧,换了好几个老板,最后开网吧才稳定下来;街口是烧烤店,生意也很好,吃鱿鱼的人很多,卖的甘蔗不错。再出去就是文二路了,短短一段街上好几家中专大专学校,所以相应的店也很多,不过和文一路不同,这里大都是小店。
再回到学校,南门口是跃进楼,成教的地盘,也是校学生会驻地,这里印象不深,很少有这里的课。靠着跃进楼是篮球场,自然是灌蓝高手们的天堂了,据我所知,杭电好像只有一个人能扣篮,是个体育老师。篮球场有三块,八个篮筐,杭电篮球自 98 届之后,就很少高手了,98 届打篮球很有几个出名人物的,不知道下沙那边水平如何?我只知道财经 01 届有几个特招生,所以女篮还打进过 cuba。旁边是排球场,学校打排球风气还是比较盛的,好像 97 时候水平不错,到了 99 就不行了,上次在杭电举行的浙江省大运会女排比赛,我们就打的很烂。说到排球,我们班排球很强,几次校内比赛,都成绩很好,也拿过冠军的。不过排球场和篮球场都经常被我抢来踢球的,永远怀念那些小场地比赛,衣服脱下一摆门,几个人一凑就开始了,还经常喜欢下雨天冒雨踢,真的特别痛快,那些岁月……
再西边,就是足球场了。说实话,场地很烂,基本上没什么草,可每天都挤满了人,有时候抢场地还会吵起来甚至打架。通常整个场地里同时可以进行 10 几场比赛,纵横交错,合理利用每一寸土地,大概杭电的男生工程制图都学的很好吧,划分的很有效率。这里,是我们的 easy band,凝聚着我们太多的回忆,进球后的疯狂庆祝,赢球时的痛快高歌,丢球时的无奈沮丧,输掉比赛时的郁闷,甚至难过落泪,我们常为一个美妙的进球欢呼雀跃,紧紧拥抱在一起,也会因为一个低级的失误互相指责,总会有人站出来激励大家向前,所有一切都记录在这块小小的球场上,破旧的球门里,和我们每一个人的心里。不管你平时活跃或沉默,无论你球踢的好坏与否,足球给我们提供了一个舞台,宣泄心情的舞台,它实实在在给我们的生活带来了快乐。前段时间回学校去看看时,我特意拍了好多球场的照片,虽然看来有些单调,但何尝不是我们梦中的恋之风景呢!
球场的南边,是体育馆,建成后学校很多活动都在这里举行,女排比赛,冰红茶的校园歌手大赛,很多学术报告等,对了,健美课也是在这里上的,据说好像很好玩。西边是 8 号楼,00 届男生和 01 届成教先后住过,最早时候是租给外面的,后来学校人员紧张才收回的,这里一大好处是白天可以在窗口看不少比赛,坏处是晚上球场尽是情侣,对单身汉是极大的打击。北边,是原来的杭电俱乐部,早就出租给了证券公司做营业大厅,学校只有在大型活动时才租回来用,我印象里有两次,一次是我们入学时迎新大会,还有一次是杭电另一牛人——马云(可是上过 newsweekly 的主啊)回校作报告时,坐的很满,也很热情,不过报告完了提的问题实在有些逊,那天我是在第一排,是负责录音的,现在马云报告的磁带还在杭电广播台,只是不知搬迁后尚还在否?说起来,还可以自我吹捧一些,杭电我们所知两大风云人物郑钧是国贸的学长,而马 ceo 是国贸的老师,不知杭电下一个风云人物还会出在国贸吗?愿上苍保佑郑钧马云的后辈们,玩且笑之,拭目以待!
在球场东南角,是浴室、理发室、水房。水房门口总是 n 多水瓶子,“勤劳的处女们——f4 美作语” 总是会打很多水,开始男生总在猜测哪里需要那么多水,据说有 n 多版本,后来传出来是因为女生不喜欢在浴室洗澡,呵呵,扯远了。她们(他们)为了防止被懒人顺手牵羊,于是形成了独特的热水瓶文化,瓶子上用记号笔写了许多让人忍俊不禁的话,来告诫偷水贼们,有的直接:住手,小偷;有的善良:水,你拿走,瓶子,请留下,谢谢;还有的恶毒,写满了诅咒。这大概也算是环境造就文化吧。理发室从没去过,只知道剪出来都差不多,不过理发室有个小孩子非常可爱,发型常变,世界杯时候留的就是 R9 的阿福头,呵呵,很想看他什么时候留上贝壳当年的莫西干 :-)浴室,应该每个经历过大学的人都知道,水龙头总是稀缺产品,都有过几个人共用一个的时候,相应的,就有人提早在开门前排队来抢得位置,也有人专门挑关门前半小时去洗,结果常常在本该拥挤的中间时间会很空,然后循环,颇有些敌进我退,敌退我进的游击战道理。而在这时候,人生一大乐事就不是什么金榜题名,洞房花烛,而是浴室逢战友了,碰到熟人,就意味着可以挤上一个水龙头了,哪管这个人也许才认识一天。浴室水很充足,这一点比在家里洗还舒服,但却要时刻忍受冷热交替的煎熬,时不时会听到一声惨叫,不是被烫着了就是冻着了。浴室最大的痛苦还不在于此,空气很不流通,经常会洗到一半觉得气闷,忍不住要晕倒,不过也有狠角色,我们寝室的毛比就经常一个澡能洗上两个小时,美其名曰效益最大化。
离浴室最近的就是杭电的核心所在,1 号楼。之所以说是核心,当然不是指地理位置,这里僻居西北一隅,核心在于此乃女生楼所在,理工科学校的人该都知道学校里女生的珍贵程度,看看每天 1 号楼前络绎不绝的男生就有数了。1 号楼好像是 6 层吧,97 届在 2 层,99 届在 1 层、3 层和 5 层,其他不详。朝北寝室的特点是一年四季永远挂着的窗帘,还有每张床上的床罩,双重保护,好像也是挡不住对面 3 号楼如狼似虎的男生们,百多号望远镜天天盯着,总会有漏网之鱼的,于是…… 就在男生中传播。我也有个望远镜,当然地理位置始终不好,所以前三年不是外借就是闲置,直到大四搬到 4 号楼,才可以拿来看对面工程的模特美女们,可惜抢的人太多,哎,这班兄弟!记得以前我们杭电论坛 hder 上的一个兄弟拿数码相机从窗口拍对面一号楼的 mm 们然后发到网上,供饿狼们流口水,当然照片很健康,只是走在路上的场景而已,我还记得有一张是我们认为的杭电校花吴 mm 的,然后对她的讨论是论坛上最热的帖子,好像回复有几百条。呵呵,说起来,这位 mm 我们很多人曾经也是动过心的,不过据说她和男朋友是青梅竹马的,最后也就是曾经想想而已(其实最主要的原因是我和她们班一个 mm 谈过恋爱,所以…… 槽糕,暴露自己的本来面目了:-))。望远镜好像女生也有,只不过男生看女生,对方是躲,而女生看男生,是悉听尊便,男生总是要大方些的嘛。女生终究还是含蓄些的,不至于明目张胆的去偷窥,不过私底下的议论总是少不了的,我曾经听过一个女孩子录的她们寝室卧谈的内容,呵呵,很是有些…… 涉及隐私,我不该多说,女生们心里自然有数的。因为男生禁止入内,所以对大部分男生来说,他们大学里很大的一个遗憾,就是没进过女生楼。很不幸的,我进去过了,其实看门的大妈也是有人情味的,找好理由就成功了一半。我那时是放暑假时候,我 mm 和大妈很熟,跟她说我要帮她搬电视机,所以我就得到特许,抱着电视机上了二楼,当然时间不久,呵呵,那时候单纯,就是有不该看的也没有去看,其实也没什么特别的,只不过女生楼三个字特别而已。1 号楼,最热的当然是一楼口的传达室,这里的大妈也是杭电最忙碌的人们。楼前小小一块地方,不知留下了多少人的多少回忆,见面,接吻,吵架,哭泣,一幕幕似是而非的场景天天在上演,有的始终是两个人的游戏,有的年年岁岁花相近,岁岁年年人不同,或男主角,或女主角,变幻不停,爱情的故事永远不会终结。熄灯后,1 号楼前的小亭子里,总是会有人在,小两口恋恋不舍相伴到天明也是常有的事,然而我始终记得的是半夜独自哭泣的女孩,是为她的爱情,还是那曾经年轻的岁月。——思往事,惜流芳,易成伤,拟歌先敛,欲笑还颦,最断人肠。
说到女生楼,自然离不开浪漫的爱情。记得以前在 hder,常有人问起杭电的浪漫经典,我所知道的不多,那时就发了个帖子——情已逝,杭电的风花雪月。提到有两件比较近的。一是我确实知道的,97 国贸的一个学姐,好像是国贸的系花哦,和男朋友闹别扭了,那位 gg 买了 999 朵玫瑰(那时侯可是 99 年),把整个寝室都堆满了;还有一件好像是有些久了,毕业的时候,好多的学长通宵坐在 1 号楼下,弹起吉他,为心中的那个她放声高歌,楼上的 jj 们也点歌、合唱,好多暗恋了四年的人们在那么多人前大声的说出了心中憋了四年的话,那一夜,杭电无眠。还有一件我无法证实,好像是有位 gg 用玫瑰在 1 号楼前摆出了 i love you,那位 jj 在全楼女生的鼓励下勇敢的冲出了 1 号楼,天下有情人终……。
抛开风月,回到杭电。1 号楼对面是 3 号楼,以望远镜出名,而且好像卫生也是最差的,3 号楼的兄弟不要砍我。这里还有学校的西门,常年不开。1 号楼隔壁就是 2 号楼,这楼最出名的是有位自动化的 98 级兄弟从五楼跳下来,不过运气很差,挂住了树枝受了伤,后来如何就不得而知了。在 3 号楼隔壁是 4 号楼,我在那里住了一年,还是很有感情的。两楼中间是小超市,东西不多,我们常来买些瓜子、花生、锅巴、牛肉什么的,有时候有茶叶蛋,这里的肉粽不要买,因为要自己剥的,很粘手。超市门口有电话亭,好像是打 ic 的吧,大一时要排队,手机多了后就很空了。回过来讲 4 号楼,1 楼是宿管中心所在,我们闹保险丝的事情就是在这里,楼里的管理员陈老师很有意思,挺热情的,大四下整天在寝室里就混的熟了,经常喜欢和我们聊几句。我们是在 3 楼,我开始住南面,斜对面就看得到女生楼,所以寝室很热闹,没几天我就逃到对面住了,正对工程学院大门,每天见的最多的就是工程的美女和门口停的 benz、bmw,刺激加打击,经常有兄弟拿我望远镜看完美女们一个个上车后,大吼一声:“xxx,n 年后老子开劳斯莱斯在工程门口晃。” 后来窗前的树砍了,失去了掩护,经常有 mm 发现我们的偷窥行为,记得有次我和一个美女对视了足有一分钟,最后还是我落荒而逃,惭愧惭愧!临离校前几天,一个晚上,看到我们经常看到的一个模特美女,和另个 mm 在工程门口的角落里失声痛哭,虽然我们并不知她哭是为毕业抑或是为爱情,却不由得触动了我们脆弱的神经。此后几天,我和寝室的哥们每天都坐在窗台上,把自己电脑里的 mp3 全放了一遍,音量放到最大,我们也高声唱和,唱那逝去的年华。看着楼下来来往往的人们,思绪也特别的伤感。走了,都要走了,再回来不知是何时了,有些人也许此生都不会再见了,那些一起踢球的兄弟,那些一起上课的同学,那些一起生活四年的哥们。离别,在这个非典之后的夏天,预料中而又猝不及防的击中了我们,却又无药可医。散伙的饭一顿顿的吃,告别的酒一杯杯的喝,每天都有喝的烂醉抬回来的人,我也真切的看过有个哥们喝醉后泪流满面,听过他一遍遍的我舍不得兄弟们啊! 泪水,就在此刻肆虐,无可救药。也许真的是我太脆弱,曾经我那么的盼望工作,我喜欢工作。可现在真要离开了,这却是怎样的一种告别啊!!也许你并不怀念具体的某个人,可是这段生活,这段岁月,是你所能忘怀的吗?这是我们最最美好的一段年华啊,你年少的笑的痛的泪的爱都留在了这里,这个也许并不出名并不韶华的地方,这是怎样的一种特别。离开了学校,也就告别了自己真正的青春年华,告别了年少的自己,不管还有多少眷恋。
我写的太过伤感了,还是继续回忆我们的杭电吧。和 4 号楼毗邻的两层小楼,楼上是卫生所,非典时要到这里量体温。楼下也是个小超市,不过这里东西要多些,肉粽也都是剥好的,我们的早饭大都在这里解决。大四下的时候很懒,经常睡到 10 点钟起来,胡乱套上衣服,跑超市买瓶百事,对面报亭买上体坛,再爬回床上,躺着看完报纸,12 点广播台音乐响起,几个人起床洗梳,然后排着队去超市买泡面,在店里泡好(寝室没打水),回去打开电脑开始挑英雄无敌。还有一点,超市里收音机整天开 103.2,也就是后来 96.8 音乐调频,我们也就顺便听会歌。说到这里,想起来在学校时,每天晚上熄灯后躺被窝里大家就等着听 amy 的节目,我爱 qq 和 amy.com, 特别是周四吧,讲鬼故事啦,虽然很多故事并不可怕,可是大家似乎都乐此不疲,好怀念 amy 和可乐的声音。再过去就是 5 号楼,以前是住千届男生的,再早是 98 的,我一位好兄弟住过,现在他已经在遥远的加拿大了。在 5 号楼和工字楼中间,是电教楼,语音教室,电脑课都在这里上,杭电好像 edi、eci 什么的比较好,所以很多老师都做电脑教材的。大一入校英语摸底考试是在这里,大学里第一次考试;教我们计算机的老师是省二级出卷的,所以每次课我们班都会多几个人,最后讲重点时候,教室里挤满 n 多人,看看四周都是自己不认识的,我也带过好几个杭商的朋友来听课的;电教 408 以前是杭电最大的活动场地,经常举办活动,那个叫万峰的鸟人就在这里高谈过性,新生华能杯特长赛、辩论赛什么的都在这里,好像还办过圣诞晚会,印象不深了,后来改了机房;三楼定期放电影,我们外教上课也常放电影给我们看。
转了一大圈,回到工字楼附近。斜对面靠 2 号楼一边是老食堂,我们进校时就有的,老生比较喜欢在这边吃饭。1 楼是小锅菜,非常贵,开始吃还好,后来吃多了也腻了,不觉得比大锅好吃多少,可惜我们太懒,下来吃饭很晚,所以只有这里还有;吃饭腻了就只有吃面,刀削面还有拉面,当然是说的好听,其实就是开水里烫烫,很难吃,可是却吃了四年,每次外面吃完回来,头几天完全没有胃口;2 楼云梦楼是可以点菜的,有包厢,服务还可以,但上菜实在是太慢了;外面食堂基本上都是 99 的人在吃,好像感觉比较干净,这层还是存饭卡的地方,中午都要排队的,不过好像有人一次会存 2000 的,晕!像我一个学期在学校里也吃不到 500 块;三楼好像面积是最大的,以前点菜是在这里的,12 点后非常的挤,吃的多了和师傅熟了,我好像买红烧肉都比别人大块的多的,对了,这里的红烧肉非常好吃,是杭电食堂第一美食。
三楼正对楼梯的地方,就是广播台。门口贴几个小字:杭电广播台,里面家当不多,一个大间隔成三间加个走廊。外间摆几台电脑,大都是学生自己的,既是广播台的会议室、资料库,又是红色家园的工作间。红色家园当年就是在这里诞生的,说起来我还是红色家园的元老了,虽然我其实也没干什么事,可是一批批人都是我招进来的,还记得那时和小余同志一起做红色家园创建的策划方案,讨论组织框架,安排人事计划什么的,好像还是在昨天,可他现在已经在中海油奋斗了,呵呵,纳斯达克啊!后来我还是挂了一年的红色家园学生站长,实在是很惭愧,真正劳苦功高的是 mr fu,miss nie 几位。里间是编辑室,一张桌子,几张椅子,一个抽屉架而已,很简单,也很温馨。最喜欢夏天站在窗口看远处的蓝天,似乎特别的纯净,夜里看则是繁星点点,银汉皎皎。冬天的时候,经常一大帮人做完中午节目,聚在这里晒晒太阳,聊聊天,何等的惬意啊!再里间就是播音间,一整套设备都是当年我和老台长一起去挑的,旧是旧的,我们也没舍得扔,教下届机务的时候我还像个宝贝似的拿了出来,台里的音像资料很多也都是我们台员自己贡献出来的。很多时候,我喜欢一个人待在台里,拉掉闸刀,放上自己喜欢的音乐,静静得抽支烟,什么也不想…… 说起广播台来,好像就没个完,毕竟自己大学里最大的兴趣就放在了这里,而且这里也承载了我太多的回忆留恋。台里的东西很多都是我亲手买回来的,一件件好像都和自己有些关系;台里的规章制度,人员节目安排一样样都是自己制定的,看到这些,好像就看到了当初的自己。而且,在这里还有我大学里的第一段感情,很多往事啊!
对应老食堂的,是新食堂,1 楼 2 楼是食堂,千届好像比较多在这边吃饭,冬天的时候这里有现烧的热汤卖,还可以;世界杯的时候这里放过中国队三场比赛,真是万人空巷啊;每年 2 月时候这里会办招聘会,一批批毕业生从这里踏上了工作道路;三楼是学工处、团委和社团活动中心,对了,这里也有个机房;四楼是活动中心,是杭电后期举办活动的主力场地,我在这里做过 n 次的 dj,不过因为空间太大,音响效果很差,主要是搞一些校内部的活动,校际的大都放体育馆,广播台也在这里搞过建台二十周年纪念晚会的,记得送出了三个很大的玩偶,好像是我去庆春路买的。我们的毕业典礼也是在这里,完了后,我们就算是离开了杭电。
最后一个地方,是真正中心的中心花坛,一棵树,还有四时换的花,好像经常会种那种看上去像卷心菜的花。这个花坛,也是杭电的中轴所在,是集合的好地方。
本来,杭电是已经讲完了,可是我还是要在这里再罗嗦几句。校外宿舍,西溪的最幸福,98 届的时候是党员响应号召为解决学校困难搬出去住的,没想那里最快乐,通宵供电,楼上就是大专的女生宿舍,进出自由;喜苑的就在喜园酒店对面,好像房间很大,住的是四个人吧,和现在下沙差不多的;湖墅那边的最辛苦,好像条件很差,因为是女生宿舍,所以没进去过,不得而知。
下沙,因为大三的时候筹建下沙的广播台,我跑的很多,基本上一周一次,328 都坐的我头痛,当然大部分时候能赶的上校车,但交通实在太差了,夜里打车过来,再要回去的时候,根本拦不到车,我有次一直走到下沙镇,才坐三轮机动车到东站再换的 taxi。记得我第一次去的时候,还是很荒凉,根本就还没有杭电的样子呢,我们是从大门东侧现在有几家店的那个位置拐进去,直接走工地的。再后来到无锡接过新生,再过去的时候已经有点样子了,不过大门那里还完全是烂泥堆。后来去的多了,才慢慢看着学校建立起来。后门的门卫总是要查我们校徽学生证,不习惯的我们可是只有身份证的;活动中心,我们在门口摆过招聘摊子,里面上次财经院队过来和 0102 财经踢球时我们在里面换过衣服,楼上的广播台我和主管老师讨论过以后的布局;教学楼很奇怪,居然是 u 型的,里面路很不好找,里面的天井也很奇怪;前面的大草坪看的我就有踢球的冲动,不知道是不是禁止踩踏的,我就看过 n 多人穿越,包括老师;现代教学楼什么的,我记不清名字了,经常在年级主任办公室活动的,还有后来二楼的财经分院办公室,视野很好,好几次能和他们聊上一个下午的;食堂,那时只有一栋楼,还记得有人嘲笑我吃土豆都要剥皮,我现在还有多余的下沙饭票;宿舍楼间很空旷,应该回声很大的,冬天实在是太冷了;我们的广播台居然设在女生楼里,我好像还领了个通行证;宿舍区的超市我特意买了东西带回去,告诉他们这可是下沙的东西,这帮家伙一边抢着吃一边叫:向下沙的弟兄们问好!
套用现在流行的一句话:我们的青春泪流满面!
罗里罗嗦终于一大篇文字打完,手里的一包 esse 早已成了灰烬,在缭绕的烟雾中,我的思绪被越飘越远,慢慢回到了 1999 年 9 月 14,那个阳光明媚的上午,我第一天来到杭电,我的回忆从此开始……
以我的学长郑钧这段话开始我对杭电特别是文一路校区一些琐碎的回忆,权当给后来者更或说是自己留些关于老校区的声色影像吧!
杭州,西湖区,文一路,杭州电子工业学院。
文一路相当繁华,特别是对学生来说,有数不尽的小店。吃的比较出名的有:知味观——杭电校外食堂;太一——以廉价出名,是男人拼酒的首选;一品——冬天最爱,骨头砂锅相当不错;此外,教工路上雅兔对面的衢州三头够辣,财经东方分部门口的山东升风水饺很不错,上过快报推荐的。买衣服的话,以我爱我家为界,西边都是专卖店,东边则都是个性小店,大都挂外贸服饰的招牌。其他店家,东方对面有个藏银,据说东西都是西藏过来的,是否真实就不得而知了;杭师门口的游戏人间总卖一些新奇玩意,仔细淘的话,有不少好东东;再过去快乐小站,是个水吧,也是我们的一大据点,东西一般,但正对杭师院大门,坐靠窗位置,可阅美女无数;网吧,教工路上风影看电影的多,雅兔则是游戏天堂,杭电人相当的多;书店则首推江郎,很多学生自己去那里买教材的,可以打折的。
杭电两侧各有一个大超市,翠苑的物美感觉东西多些,而且楼上就是翠苑电影大世界,外围全是座位,可以在物美或者 kfc 买好吃的,在楼上和 mm 坐一个下午,风景不错;东边的华润是今年新开的,去的不多,总感觉东西偏少,而且学生顾客没有物美多;说到超市,其实最喜欢的还是黄龙好又多,很多人宁可多跑路,也要去好又多的,虽然只有一层,但感觉就比较好,而且很多人喜欢那里的电炉烤鸡。小超市,最爱是可的,以前是在杭电诊所楼下的,经常有人半夜跑出去买东西吃,后来文一路整顿,搬了,附近就没什么好的地方了,现在的店都是后来开的。
学校四周,比较有印象的还有:京典——洗头比理发好,和 11 路站的阿俊、杭师对面的千丝万缕算是这一带比较出名的;一家小店,在工程门口,现在叫不上名字了,家常菜做的不错,关键是价廉量足,非典过后那段时间每天吃饭都要排队的;天马——以前是网吧老大,后来没落,主要经营歌厅和陶吧;撞击酒吧——学校外教最喜欢去的地方,经常有这方面的活动;文二夜市——杭电人逛的最多的地方,淘碟、点卡、明星壁画还有其他小玩意;文二电影院——平时几乎都是情侣专场,只有有球赛的日子才有上座率,00 欧锦赛,02 世界杯,大家很多时候在这里度过。
公交车,最熟悉的自然是 11 路,到保俶路站转 328 去下沙,到武林广场站去银泰,终点站城站回家,到现在我到杭州也总是要坐坐 11 路;58 路,是个环线,可以到杭大、湖墅电子市场、武林广场等,我在杭大学法语时天天坐这班;155 是到延安路的,25 到吴山,816 到舟山东路,等等。其他就记不大清了,不过有一点特别,杭电门口的站叫省委党校,而文一和教工路口站才叫电子学院。
外面绕了一大圈,回到杭电。学校大门最大的特点就是那棵老树,估计也是有点年头的,正说明了杭电校园的特点:绿化好;校名写的很小,不注意还发现不了,经常有女生整个寝室来拍照片的;门口门卫都很年轻,离校前在草坪聚会时,找他们吹过瓶子;旁边是邮递房,新生时信很多,越大就越懒了,记得我们的邮箱是 233;进校后的大草坪,是杭电一大聚会地点,迎新晚会、班级聚会、老乡聚会很多在这里,右边有几棵树,形成一个隐秘的小空间,情侣很多,记得吃完散伙饭那天我们霸占了草坪,赶走了 n 多情侣,呵呵。
正对校门是实验楼,也是杭电最宏伟的建筑,好象是 9 层还是 10 层吧。每两个分院共用一层,各分院的办公室、实验室还有机房。我们财经是和管理一起在四层,以前三个分院的时候我们是一起的,叫二分院(工商管理),辩论超强,出过不少牛人。现在的财经是杭电第一大院,以人数称雄,体育也很强。实验楼有个很大的好处,夏天很凉快,所以不少学生晚上睡这里的,理由当然很多,有的是做实验,有的是看门,不一而足。这里的另一特色是电梯超慢,人狂多,经常等电梯还没走路快。二楼是文理分院,可能是大家除了本分院外最常去的地方,因为英语和数学教研室在这里,所以大英、高数、线代这些公共基础课的重修补考费就是这里缴的,心痛啊。里面有个老师是杭电第一名捕,常监考数学,捕人无数,我亲眼看到过的就不下二十来人。
实验楼东侧,是图书馆,藏书好像还可以吧,算不上多,也不会太少,就是旧了点。借书很有意思,正门很少人走的,都从旁边楼梯直接上三楼侧门,脱鞋进去后,里面就是夏天也是感觉冷的。借书是有限制的,我记得是文艺类一次只能借两本吧,超期要罚的,我弄丢了《欧美音乐赏析》,超期加赔花了 100 多。一楼自由阅览室是考研基地,位置永远紧俏的,经常晚上和早上晨跑时就有人抢了。在这里你看到的是杭电最努力最用功的一批人,至于成效如何,我们不得而知。而杭电最精英的人,此刻要么在实验室里埋头研究,要么就在寝室里砍 cs、星际,再或者就是觥筹交措、呼朋唤友中,总之,各有各的精彩。二楼和四楼记得都有期刊阅览室,这里是写论文的好地方,所以大四学生居多。
图书馆后是花圃,还有一条越来越干净的西溪河,这里也是情侣的天堂,美中不足的是这里有住校工,而且有养狗。河拐出去的地方是东门,是后来新开的,这里管的比较松,24 小时可以进出。东门进来是杭电招待所,比较一般,好像是 150 标间,非典时外地回来学生在这里隔离,只要交每天 10 块钱伙食费就可以待一周,吃的不错,还有冠军杯看,羡慕。学校的宴席大都在这里摆,我吃过几次,味道一般。对面是以前的研究生楼,房间不大,两人一间吧,带厨房,感觉太暗,有些潮。一栋新楼是教师宿舍,学校老师基本上是住翠苑和文二的,这里住的大都是单身的还有外教,我们外教还养过小狗,非常可爱。楼侧面的墙是练习网球的好地方。
这一段绿化特别的好,绿树丛中,还有个小院,不大容易发现,这里是 6 号楼,最早是诊所兼教师宿舍,后来住成教,再后来人越来越多了,就住的是千届学生,我记得是 2000 工管的,回字型的房子,男女生分住两头,我们曾经猜测过是否有人可以趁管理员不注意溜到对面去,无从考究。这里管理员好像对外来的不大友善,很多人就在院子里直接叫人,不过容易暴露身份。反正我是很不喜欢这个楼,从来只打电话的。
再往里走,是两栋教学楼,一曰东教,一曰新东教,这也是杭电一大笑话,造新东教时学校还征集过楼名,记得还有不少人投了,结果最后命名新东教,举校倾倒。先说东教,杭电老建筑,也算得上雕梁画栋了,以前杭电的明信片上有图。木地板、木桌、铁椅,大而空,感觉就是两个字:阴冷,据说闹鬼,以前有人上吊过的,晚上很少人自习的。这里每层都有大教室,所以很多哲学这种公共课在这里上的,很多辩论赛也是在这里搞的,我在这里作过很多次的 dj。杭电的大教室有个共同点,就是后面的高一级,所以在两段高低上的位置是考试最佳,你视力好的话前面一览无余。顺便说一句,东教对面是杭商的女生宿舍,有时候能和她们聊上几句。
说起新东教,话题可就多了。这里最早是杭电花圃,有一条长廊,据说是爬满葡萄藤的,是杭电最出名的情人长廊,一到了晚上,非常……,演绎了很多杭电浪漫经典。好像说有些坏小子会偷偷跑去捣乱,呵呵,杭电毕竟还是光棍多啊。很可惜我们 99 届进校后,花圃就拆了盖楼,不过到现在我还记得那时我们的美女辅导员带我们去看长廊,讲过去的往事。变成新东教后,学校还很搞笑的在旁边原地建了一个亭子,种上了竹子什么的,可是亭子正对教室,恐怕没有人愿意在众目睽睽下卿卿我我吧。新东的教室,4 和 6 是大教室,很大的那种,好像所有分院都是在这里开年级会议的,我们 99 财经都是在 306。这里还是考试集中的地方,凡是两个班以上的大课,大都在这里上和考试,四级也是在这里,六级则在新教。新东最好的一点是阳光充足,特别是 4 和 6 的大教室,坐在后排都沐浴在和煦的阳光中,所以,嘿嘿,这里也是要抢的,好像有很多次前面五六排几乎没人。新东一个特点是逃课很难,想偷偷溜走不被发现,非常难,不过高手还是很多,羡慕!在这里上过的课中印象最深的是大二时候的美术欣赏,上的人很多,而且以男生居多,知道原因了吧,因为传说中教这门的 miss l 是个美女,所以很多可以说是完全没有艺术细胞的人也挤来凑热闹。你如果要问,传说是否属实,那就到杭电上美术欣赏吧,不过她挺凶的。新东四楼比较偏僻,上课自习的人不多,也是个小小的禁区吧,因为经常一个教室里就两个人,*#•¥ (以下省略 100 字),顶上好像是个天台,以前情侣很多的,不过出过一次事后就人少了,去年吧,有人从上面摔下来死了,原因众说不一。
新东前面,是杭电一个好地方,是一大块草坪,完全沐浴在阳光中,冬天在这里聊天嗑瓜子打牌的很多,看报纸睡觉的也很多,我经常和同学在这里背书,要考试了。旁边则是中心花园,杭电喜欢用方位来命名地点。这个花园两部分,靠行政楼是草坪,也是冬天背书的好地方,不过我们一直奢望有天能在上面踢场球,这里最有特色是有棵孤零零的小树,什么树我忘了,可能是柳树一类吧,就这么一棵,非常滑稽,这边也是拍照好地方。靠实验楼一边比较乱,有亭子假山,这里是我们背书打牌的地方;有小林子,情侣的地盘;有一排石桌,这里是早上起来背英语人的。这个花园里好像很多乱七八糟的植物,名字都怪怪的,记得我们英语听说课,有次老外就要我们写中心花园,晕,那些名字看了就头痛哦。
花园旁一条路兼是停车场,好车不多,都是老师的吧。学生,我知道的不多,只是听说自动化有个家伙开别克赛欧的。
路的尽头是工字楼,我以前文章提过,是我们的老巢,住了三年。结果大家都住出感情来了,好处很多:位置优越,四通八达,吃饭打水踢球上课出门都很近;独门独院,没有人和声音骚扰;有自己独立的停车棚和晒衣场,不用和别人挤;房间小些,但只住六人,舒服多了;没有公共电扇,可以通宵供电…… 所以大四搬走后,经常夜里和凌晨会听到有人高叫:“打回工字楼,还我工字楼”可见怀念之深,后来这里成了研究生楼,郁闷!工字楼晚是要锁门的,以前还可以翻窗,后来封了,就只能叫门了,看夜的是个老头子,反应比较慢,有次我们一个兄弟喝高了,等久了,叫把玻璃给砸了,生猛啊,结果后来入不成党,可惜啊!杭电规矩是男生不得入女生楼,女生进男生宿舍呢,则要登记,还隔会就打传呼来催,工字楼好的是后门比较好进,所以常常有人在门口掩护,女生悄悄溜进来。不过有几个家伙和管理员混熟了,女朋友可以随意进出的,所以有时会遇上尴尬,大家都知道男生在寝室穿着是怎么的咯!我们班住在二楼的最东头,也是最热闹的地段,晚上熄灯后好多人聚在门口,打双扣的,看小说的,聊天的,吃东西的,比熄灯前还热闹,经常还有人会提议去 “调戏” 某人,于是,惨叫声起,一出悲剧在上演。
从我们工南窗口望出去,是新教,这是在新东教之前东西教之后盖的,故曰新教楼。不过也很有点旧了,前年考试前,我们在寝室里复习时,就是轰的一声,四楼的楼梯断了,幸好无人受伤。新教是老生最爱,98 和 99 的喜欢在这里自习,千届则大都在新东教,还有些勇敢的则在东西教。新教单数是大教室,逃课最好的战场,双数是小教室,大部分小课在这里上。这里给我印象最深的是徐旭初老师的课,可能很多杭电人都应该知道的,他也算是杭电最受欢迎的老师吧,据说是出身于高官家庭。本人很有些才华,颇有些读书人的风骨。他的讲座在杭电也是很受欢迎的,我还曾经请他到学校电台作过几次节目,聊的还是很投机的。此外,出身社科部的几位如钱升老师、郑建功老师等在学校也是比较受欢迎的,或严谨,或风趣,个人认为是为杭电读书人之望。还有一位老夫子,是曾经教我们英语的,张唯新老师,一看就是那种老底子的人,每次看到他我就会想到上海的老克腊,虽然和他打的交道不多,但心里还是很敬佩他的。
新教两侧就是前面提到过的行政楼和西教楼。行政楼和我们学生联系不大,我只是因为广播台的关系常去宣传部,也没什么特别的地方,顶楼会议室常有些招聘会什么的,学校派出所也在这里,打证明什么的要到这里。西教故事和东教差不多。过去一点还有学校的印刷厂,服务态度不好。
再过去就是南门了,也就是学校的后门,好像很多人把学校后门外叫堕落街,可能也是有点吧。南门出去,有几栋居民楼,好像有些房子出租的,不过很紧俏,很难租到;临街门口有几个小摊,卖煎饼的,卖水果的,卖烧烤的,还有糯米团子,课间很多人出来解决早饭问题。往外走,是杭商东院,以前是中专,杭电没抢过杭商,结果让他们把学校伸到教工路东面来了,课间时候很多人进出,有不少美女。靠着杭商的门,一排依次是杭商招待所,房间很小,好像也是 150;速食店,开始主卖珍珠奶茶,后来卖炒粉、炒饭什么的,生意极其火暴,这里做的最好的是卤肉饭,5 元一份,浇上汤非常好吃,光我们班就平均每天有六七个人吃的,常年如此;一家超市,门口卖台湾香肠和肉饼,味道还可以;网吧,换了好几个老板,最后开网吧才稳定下来;街口是烧烤店,生意也很好,吃鱿鱼的人很多,卖的甘蔗不错。再出去就是文二路了,短短一段街上好几家中专大专学校,所以相应的店也很多,不过和文一路不同,这里大都是小店。
再回到学校,南门口是跃进楼,成教的地盘,也是校学生会驻地,这里印象不深,很少有这里的课。靠着跃进楼是篮球场,自然是灌蓝高手们的天堂了,据我所知,杭电好像只有一个人能扣篮,是个体育老师。篮球场有三块,八个篮筐,杭电篮球自 98 届之后,就很少高手了,98 届打篮球很有几个出名人物的,不知道下沙那边水平如何?我只知道财经 01 届有几个特招生,所以女篮还打进过 cuba。旁边是排球场,学校打排球风气还是比较盛的,好像 97 时候水平不错,到了 99 就不行了,上次在杭电举行的浙江省大运会女排比赛,我们就打的很烂。说到排球,我们班排球很强,几次校内比赛,都成绩很好,也拿过冠军的。不过排球场和篮球场都经常被我抢来踢球的,永远怀念那些小场地比赛,衣服脱下一摆门,几个人一凑就开始了,还经常喜欢下雨天冒雨踢,真的特别痛快,那些岁月……
再西边,就是足球场了。说实话,场地很烂,基本上没什么草,可每天都挤满了人,有时候抢场地还会吵起来甚至打架。通常整个场地里同时可以进行 10 几场比赛,纵横交错,合理利用每一寸土地,大概杭电的男生工程制图都学的很好吧,划分的很有效率。这里,是我们的 easy band,凝聚着我们太多的回忆,进球后的疯狂庆祝,赢球时的痛快高歌,丢球时的无奈沮丧,输掉比赛时的郁闷,甚至难过落泪,我们常为一个美妙的进球欢呼雀跃,紧紧拥抱在一起,也会因为一个低级的失误互相指责,总会有人站出来激励大家向前,所有一切都记录在这块小小的球场上,破旧的球门里,和我们每一个人的心里。不管你平时活跃或沉默,无论你球踢的好坏与否,足球给我们提供了一个舞台,宣泄心情的舞台,它实实在在给我们的生活带来了快乐。前段时间回学校去看看时,我特意拍了好多球场的照片,虽然看来有些单调,但何尝不是我们梦中的恋之风景呢!
球场的南边,是体育馆,建成后学校很多活动都在这里举行,女排比赛,冰红茶的校园歌手大赛,很多学术报告等,对了,健美课也是在这里上的,据说好像很好玩。西边是 8 号楼,00 届男生和 01 届成教先后住过,最早时候是租给外面的,后来学校人员紧张才收回的,这里一大好处是白天可以在窗口看不少比赛,坏处是晚上球场尽是情侣,对单身汉是极大的打击。北边,是原来的杭电俱乐部,早就出租给了证券公司做营业大厅,学校只有在大型活动时才租回来用,我印象里有两次,一次是我们入学时迎新大会,还有一次是杭电另一牛人——马云(可是上过 newsweekly 的主啊)回校作报告时,坐的很满,也很热情,不过报告完了提的问题实在有些逊,那天我是在第一排,是负责录音的,现在马云报告的磁带还在杭电广播台,只是不知搬迁后尚还在否?说起来,还可以自我吹捧一些,杭电我们所知两大风云人物郑钧是国贸的学长,而马 ceo 是国贸的老师,不知杭电下一个风云人物还会出在国贸吗?愿上苍保佑郑钧马云的后辈们,玩且笑之,拭目以待!
在球场东南角,是浴室、理发室、水房。水房门口总是 n 多水瓶子,“勤劳的处女们——f4 美作语” 总是会打很多水,开始男生总在猜测哪里需要那么多水,据说有 n 多版本,后来传出来是因为女生不喜欢在浴室洗澡,呵呵,扯远了。她们(他们)为了防止被懒人顺手牵羊,于是形成了独特的热水瓶文化,瓶子上用记号笔写了许多让人忍俊不禁的话,来告诫偷水贼们,有的直接:住手,小偷;有的善良:水,你拿走,瓶子,请留下,谢谢;还有的恶毒,写满了诅咒。这大概也算是环境造就文化吧。理发室从没去过,只知道剪出来都差不多,不过理发室有个小孩子非常可爱,发型常变,世界杯时候留的就是 R9 的阿福头,呵呵,很想看他什么时候留上贝壳当年的莫西干 :-)浴室,应该每个经历过大学的人都知道,水龙头总是稀缺产品,都有过几个人共用一个的时候,相应的,就有人提早在开门前排队来抢得位置,也有人专门挑关门前半小时去洗,结果常常在本该拥挤的中间时间会很空,然后循环,颇有些敌进我退,敌退我进的游击战道理。而在这时候,人生一大乐事就不是什么金榜题名,洞房花烛,而是浴室逢战友了,碰到熟人,就意味着可以挤上一个水龙头了,哪管这个人也许才认识一天。浴室水很充足,这一点比在家里洗还舒服,但却要时刻忍受冷热交替的煎熬,时不时会听到一声惨叫,不是被烫着了就是冻着了。浴室最大的痛苦还不在于此,空气很不流通,经常会洗到一半觉得气闷,忍不住要晕倒,不过也有狠角色,我们寝室的毛比就经常一个澡能洗上两个小时,美其名曰效益最大化。
离浴室最近的就是杭电的核心所在,1 号楼。之所以说是核心,当然不是指地理位置,这里僻居西北一隅,核心在于此乃女生楼所在,理工科学校的人该都知道学校里女生的珍贵程度,看看每天 1 号楼前络绎不绝的男生就有数了。1 号楼好像是 6 层吧,97 届在 2 层,99 届在 1 层、3 层和 5 层,其他不详。朝北寝室的特点是一年四季永远挂着的窗帘,还有每张床上的床罩,双重保护,好像也是挡不住对面 3 号楼如狼似虎的男生们,百多号望远镜天天盯着,总会有漏网之鱼的,于是…… 就在男生中传播。我也有个望远镜,当然地理位置始终不好,所以前三年不是外借就是闲置,直到大四搬到 4 号楼,才可以拿来看对面工程的模特美女们,可惜抢的人太多,哎,这班兄弟!记得以前我们杭电论坛 hder 上的一个兄弟拿数码相机从窗口拍对面一号楼的 mm 们然后发到网上,供饿狼们流口水,当然照片很健康,只是走在路上的场景而已,我还记得有一张是我们认为的杭电校花吴 mm 的,然后对她的讨论是论坛上最热的帖子,好像回复有几百条。呵呵,说起来,这位 mm 我们很多人曾经也是动过心的,不过据说她和男朋友是青梅竹马的,最后也就是曾经想想而已(其实最主要的原因是我和她们班一个 mm 谈过恋爱,所以…… 槽糕,暴露自己的本来面目了:-))。望远镜好像女生也有,只不过男生看女生,对方是躲,而女生看男生,是悉听尊便,男生总是要大方些的嘛。女生终究还是含蓄些的,不至于明目张胆的去偷窥,不过私底下的议论总是少不了的,我曾经听过一个女孩子录的她们寝室卧谈的内容,呵呵,很是有些…… 涉及隐私,我不该多说,女生们心里自然有数的。因为男生禁止入内,所以对大部分男生来说,他们大学里很大的一个遗憾,就是没进过女生楼。很不幸的,我进去过了,其实看门的大妈也是有人情味的,找好理由就成功了一半。我那时是放暑假时候,我 mm 和大妈很熟,跟她说我要帮她搬电视机,所以我就得到特许,抱着电视机上了二楼,当然时间不久,呵呵,那时候单纯,就是有不该看的也没有去看,其实也没什么特别的,只不过女生楼三个字特别而已。1 号楼,最热的当然是一楼口的传达室,这里的大妈也是杭电最忙碌的人们。楼前小小一块地方,不知留下了多少人的多少回忆,见面,接吻,吵架,哭泣,一幕幕似是而非的场景天天在上演,有的始终是两个人的游戏,有的年年岁岁花相近,岁岁年年人不同,或男主角,或女主角,变幻不停,爱情的故事永远不会终结。熄灯后,1 号楼前的小亭子里,总是会有人在,小两口恋恋不舍相伴到天明也是常有的事,然而我始终记得的是半夜独自哭泣的女孩,是为她的爱情,还是那曾经年轻的岁月。——思往事,惜流芳,易成伤,拟歌先敛,欲笑还颦,最断人肠。
说到女生楼,自然离不开浪漫的爱情。记得以前在 hder,常有人问起杭电的浪漫经典,我所知道的不多,那时就发了个帖子——情已逝,杭电的风花雪月。提到有两件比较近的。一是我确实知道的,97 国贸的一个学姐,好像是国贸的系花哦,和男朋友闹别扭了,那位 gg 买了 999 朵玫瑰(那时侯可是 99 年),把整个寝室都堆满了;还有一件好像是有些久了,毕业的时候,好多的学长通宵坐在 1 号楼下,弹起吉他,为心中的那个她放声高歌,楼上的 jj 们也点歌、合唱,好多暗恋了四年的人们在那么多人前大声的说出了心中憋了四年的话,那一夜,杭电无眠。还有一件我无法证实,好像是有位 gg 用玫瑰在 1 号楼前摆出了 i love you,那位 jj 在全楼女生的鼓励下勇敢的冲出了 1 号楼,天下有情人终……。
抛开风月,回到杭电。1 号楼对面是 3 号楼,以望远镜出名,而且好像卫生也是最差的,3 号楼的兄弟不要砍我。这里还有学校的西门,常年不开。1 号楼隔壁就是 2 号楼,这楼最出名的是有位自动化的 98 级兄弟从五楼跳下来,不过运气很差,挂住了树枝受了伤,后来如何就不得而知了。在 3 号楼隔壁是 4 号楼,我在那里住了一年,还是很有感情的。两楼中间是小超市,东西不多,我们常来买些瓜子、花生、锅巴、牛肉什么的,有时候有茶叶蛋,这里的肉粽不要买,因为要自己剥的,很粘手。超市门口有电话亭,好像是打 ic 的吧,大一时要排队,手机多了后就很空了。回过来讲 4 号楼,1 楼是宿管中心所在,我们闹保险丝的事情就是在这里,楼里的管理员陈老师很有意思,挺热情的,大四下整天在寝室里就混的熟了,经常喜欢和我们聊几句。我们是在 3 楼,我开始住南面,斜对面就看得到女生楼,所以寝室很热闹,没几天我就逃到对面住了,正对工程学院大门,每天见的最多的就是工程的美女和门口停的 benz、bmw,刺激加打击,经常有兄弟拿我望远镜看完美女们一个个上车后,大吼一声:“xxx,n 年后老子开劳斯莱斯在工程门口晃。” 后来窗前的树砍了,失去了掩护,经常有 mm 发现我们的偷窥行为,记得有次我和一个美女对视了足有一分钟,最后还是我落荒而逃,惭愧惭愧!临离校前几天,一个晚上,看到我们经常看到的一个模特美女,和另个 mm 在工程门口的角落里失声痛哭,虽然我们并不知她哭是为毕业抑或是为爱情,却不由得触动了我们脆弱的神经。此后几天,我和寝室的哥们每天都坐在窗台上,把自己电脑里的 mp3 全放了一遍,音量放到最大,我们也高声唱和,唱那逝去的年华。看着楼下来来往往的人们,思绪也特别的伤感。走了,都要走了,再回来不知是何时了,有些人也许此生都不会再见了,那些一起踢球的兄弟,那些一起上课的同学,那些一起生活四年的哥们。离别,在这个非典之后的夏天,预料中而又猝不及防的击中了我们,却又无药可医。散伙的饭一顿顿的吃,告别的酒一杯杯的喝,每天都有喝的烂醉抬回来的人,我也真切的看过有个哥们喝醉后泪流满面,听过他一遍遍的我舍不得兄弟们啊! 泪水,就在此刻肆虐,无可救药。也许真的是我太脆弱,曾经我那么的盼望工作,我喜欢工作。可现在真要离开了,这却是怎样的一种告别啊!!也许你并不怀念具体的某个人,可是这段生活,这段岁月,是你所能忘怀的吗?这是我们最最美好的一段年华啊,你年少的笑的痛的泪的爱都留在了这里,这个也许并不出名并不韶华的地方,这是怎样的一种特别。离开了学校,也就告别了自己真正的青春年华,告别了年少的自己,不管还有多少眷恋。
我写的太过伤感了,还是继续回忆我们的杭电吧。和 4 号楼毗邻的两层小楼,楼上是卫生所,非典时要到这里量体温。楼下也是个小超市,不过这里东西要多些,肉粽也都是剥好的,我们的早饭大都在这里解决。大四下的时候很懒,经常睡到 10 点钟起来,胡乱套上衣服,跑超市买瓶百事,对面报亭买上体坛,再爬回床上,躺着看完报纸,12 点广播台音乐响起,几个人起床洗梳,然后排着队去超市买泡面,在店里泡好(寝室没打水),回去打开电脑开始挑英雄无敌。还有一点,超市里收音机整天开 103.2,也就是后来 96.8 音乐调频,我们也就顺便听会歌。说到这里,想起来在学校时,每天晚上熄灯后躺被窝里大家就等着听 amy 的节目,我爱 qq 和 amy.com, 特别是周四吧,讲鬼故事啦,虽然很多故事并不可怕,可是大家似乎都乐此不疲,好怀念 amy 和可乐的声音。再过去就是 5 号楼,以前是住千届男生的,再早是 98 的,我一位好兄弟住过,现在他已经在遥远的加拿大了。在 5 号楼和工字楼中间,是电教楼,语音教室,电脑课都在这里上,杭电好像 edi、eci 什么的比较好,所以很多老师都做电脑教材的。大一入校英语摸底考试是在这里,大学里第一次考试;教我们计算机的老师是省二级出卷的,所以每次课我们班都会多几个人,最后讲重点时候,教室里挤满 n 多人,看看四周都是自己不认识的,我也带过好几个杭商的朋友来听课的;电教 408 以前是杭电最大的活动场地,经常举办活动,那个叫万峰的鸟人就在这里高谈过性,新生华能杯特长赛、辩论赛什么的都在这里,好像还办过圣诞晚会,印象不深了,后来改了机房;三楼定期放电影,我们外教上课也常放电影给我们看。
转了一大圈,回到工字楼附近。斜对面靠 2 号楼一边是老食堂,我们进校时就有的,老生比较喜欢在这边吃饭。1 楼是小锅菜,非常贵,开始吃还好,后来吃多了也腻了,不觉得比大锅好吃多少,可惜我们太懒,下来吃饭很晚,所以只有这里还有;吃饭腻了就只有吃面,刀削面还有拉面,当然是说的好听,其实就是开水里烫烫,很难吃,可是却吃了四年,每次外面吃完回来,头几天完全没有胃口;2 楼云梦楼是可以点菜的,有包厢,服务还可以,但上菜实在是太慢了;外面食堂基本上都是 99 的人在吃,好像感觉比较干净,这层还是存饭卡的地方,中午都要排队的,不过好像有人一次会存 2000 的,晕!像我一个学期在学校里也吃不到 500 块;三楼好像面积是最大的,以前点菜是在这里的,12 点后非常的挤,吃的多了和师傅熟了,我好像买红烧肉都比别人大块的多的,对了,这里的红烧肉非常好吃,是杭电食堂第一美食。
三楼正对楼梯的地方,就是广播台。门口贴几个小字:杭电广播台,里面家当不多,一个大间隔成三间加个走廊。外间摆几台电脑,大都是学生自己的,既是广播台的会议室、资料库,又是红色家园的工作间。红色家园当年就是在这里诞生的,说起来我还是红色家园的元老了,虽然我其实也没干什么事,可是一批批人都是我招进来的,还记得那时和小余同志一起做红色家园创建的策划方案,讨论组织框架,安排人事计划什么的,好像还是在昨天,可他现在已经在中海油奋斗了,呵呵,纳斯达克啊!后来我还是挂了一年的红色家园学生站长,实在是很惭愧,真正劳苦功高的是 mr fu,miss nie 几位。里间是编辑室,一张桌子,几张椅子,一个抽屉架而已,很简单,也很温馨。最喜欢夏天站在窗口看远处的蓝天,似乎特别的纯净,夜里看则是繁星点点,银汉皎皎。冬天的时候,经常一大帮人做完中午节目,聚在这里晒晒太阳,聊聊天,何等的惬意啊!再里间就是播音间,一整套设备都是当年我和老台长一起去挑的,旧是旧的,我们也没舍得扔,教下届机务的时候我还像个宝贝似的拿了出来,台里的音像资料很多也都是我们台员自己贡献出来的。很多时候,我喜欢一个人待在台里,拉掉闸刀,放上自己喜欢的音乐,静静得抽支烟,什么也不想…… 说起广播台来,好像就没个完,毕竟自己大学里最大的兴趣就放在了这里,而且这里也承载了我太多的回忆留恋。台里的东西很多都是我亲手买回来的,一件件好像都和自己有些关系;台里的规章制度,人员节目安排一样样都是自己制定的,看到这些,好像就看到了当初的自己。而且,在这里还有我大学里的第一段感情,很多往事啊!
对应老食堂的,是新食堂,1 楼 2 楼是食堂,千届好像比较多在这边吃饭,冬天的时候这里有现烧的热汤卖,还可以;世界杯的时候这里放过中国队三场比赛,真是万人空巷啊;每年 2 月时候这里会办招聘会,一批批毕业生从这里踏上了工作道路;三楼是学工处、团委和社团活动中心,对了,这里也有个机房;四楼是活动中心,是杭电后期举办活动的主力场地,我在这里做过 n 次的 dj,不过因为空间太大,音响效果很差,主要是搞一些校内部的活动,校际的大都放体育馆,广播台也在这里搞过建台二十周年纪念晚会的,记得送出了三个很大的玩偶,好像是我去庆春路买的。我们的毕业典礼也是在这里,完了后,我们就算是离开了杭电。
最后一个地方,是真正中心的中心花坛,一棵树,还有四时换的花,好像经常会种那种看上去像卷心菜的花。这个花坛,也是杭电的中轴所在,是集合的好地方。
本来,杭电是已经讲完了,可是我还是要在这里再罗嗦几句。校外宿舍,西溪的最幸福,98 届的时候是党员响应号召为解决学校困难搬出去住的,没想那里最快乐,通宵供电,楼上就是大专的女生宿舍,进出自由;喜苑的就在喜园酒店对面,好像房间很大,住的是四个人吧,和现在下沙差不多的;湖墅那边的最辛苦,好像条件很差,因为是女生宿舍,所以没进去过,不得而知。
下沙,因为大三的时候筹建下沙的广播台,我跑的很多,基本上一周一次,328 都坐的我头痛,当然大部分时候能赶的上校车,但交通实在太差了,夜里打车过来,再要回去的时候,根本拦不到车,我有次一直走到下沙镇,才坐三轮机动车到东站再换的 taxi。记得我第一次去的时候,还是很荒凉,根本就还没有杭电的样子呢,我们是从大门东侧现在有几家店的那个位置拐进去,直接走工地的。再后来到无锡接过新生,再过去的时候已经有点样子了,不过大门那里还完全是烂泥堆。后来去的多了,才慢慢看着学校建立起来。后门的门卫总是要查我们校徽学生证,不习惯的我们可是只有身份证的;活动中心,我们在门口摆过招聘摊子,里面上次财经院队过来和 0102 财经踢球时我们在里面换过衣服,楼上的广播台我和主管老师讨论过以后的布局;教学楼很奇怪,居然是 u 型的,里面路很不好找,里面的天井也很奇怪;前面的大草坪看的我就有踢球的冲动,不知道是不是禁止踩踏的,我就看过 n 多人穿越,包括老师;现代教学楼什么的,我记不清名字了,经常在年级主任办公室活动的,还有后来二楼的财经分院办公室,视野很好,好几次能和他们聊上一个下午的;食堂,那时只有一栋楼,还记得有人嘲笑我吃土豆都要剥皮,我现在还有多余的下沙饭票;宿舍楼间很空旷,应该回声很大的,冬天实在是太冷了;我们的广播台居然设在女生楼里,我好像还领了个通行证;宿舍区的超市我特意买了东西带回去,告诉他们这可是下沙的东西,这帮家伙一边抢着吃一边叫:向下沙的弟兄们问好!
套用现在流行的一句话:我们的青春泪流满面!
罗里罗嗦终于一大篇文字打完,手里的一包 esse 早已成了灰烬,在缭绕的烟雾中,我的思绪被越飘越远,慢慢回到了 1999 年 9 月 14,那个阳光明媚的上午,我第一天来到杭电,我的回忆从此开始……
]]>将每个HTTP 请求封装在一个数据库事务中
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': '',
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
'ATOMIC_REQUESTS': True,
}
}
如果需要禁用Django 的事务管理并自己实现,设置它为False, 但不建议这样做。
无视全局事务设置,恢复Django默认的自动提交模式
单个方法事务控制
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic():
# This code executes inside a transaction.
do_more_stuff()
设置保存点
from django.db import transaction
# open a transaction
@transaction.atomic
def viewfunc(request):
a.save()
# transaction now contains a.save()
sid = transaction.savepoint()
b.save()
# transaction now contains a.save() and b.save()
if want_to_keep_b:
# 释放保存点
transaction.savepoint_commit(sid)
# open transaction still contains a.save() and b.save()
else:
transaction.savepoint_rollback(sid)
# open transaction now contains only a.save()
如果b.save()
抛出异常,所有未提交操作都会回滚。
调用 transaction.rollback() 回滚整个事物。任何未提交的数据库操作都会丢失。在此例中, 由 a.save()所保存的变更将会丢失,即使这个操作自身没有产生错误。
a.save() # Succeeds, but may be undone by transaction rollback
try:
b.save() # Could throw exception
except IntegrityError:
transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone
如果b.save()
抛出异常,a.save()
后的未提交操作都会被回滚
a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
b.save() # Could throw exception
# 释放保存点
transaction.savepoint_commit(sid)
except IntegrityError:
transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone
transaction.savepoint()
之前的代码永不会被回滚
2016-07-06 初稿
显示npm版号
npm -v
# 或
npm version
安装模块
# 带-g为全局安装
# 本地安装:package会被下载到当前所在目录,也只能在当前目录下使用。
# 全局安装:package会被下载到到特定的系统目录下,安装的package能够在所有目录下使用。
npm install <package> -g
# 简写
npm i <package> -g
升级全局安装的指定模块
npm update <package> -g
升级当前目录下的指定模块
npm update <package>
升级当前目录下全部模块
npm update
升级node自身
# 安装一个叫n的模块
npm install -g n
# 升级到最新稳定版
n stable
# 升级到指定版本
n v0.10.26
卸载移除指定模块
npm uninstall <package>
# 别名:remove, rm, r, un, unlink
显示已安装模块
npm list
显示模块详细信息
npm show <package>
查看全局包安装路径
npm root -g
查看当前包安装路径
npm root
查看npm配置
npm config list
查看帮助
npm help
查看相关命令的帮助文档
npm help <command>
]]>Checker Plus for Gmail™
Gmail/Inbox插件,不用打开网页处理邮件。
Wappalyzer
探测当前网页正在使用的开源软件或者js类库,web开发者必备神器。
crxMouse Chrome Gestures
鼠标手势。
Ghostery
了解谁在跟踪您的网页浏览操作,并可禁用跟踪行为。
Google Keep Chrome 扩展程序
将网页快速的保存到Google Keep中。
Google翻译
支付在网页中划词翻译。
划词翻译
划词翻译,支持谷歌、百度、有道、必应四大翻译和朗读引擎,访问Google比较因难的的可以用这个。
LastPass
免费的密码管理管理器。
MindMap Tab
在新标签页中快速编辑思维导图。
Octotree
在浏览器左侧树形展示Github代码。
Prism Pretty
美化代码(html、css、js、json……)
Proxy SwitchyOmega
你懂的。
Pushbullet
快速的往其它设备发送消息。
Save to Pocket
快速保存网页到Pocket。
Search by Image (by Google)
浏览器右键增加Google的以图搜图功能。
v2ex plus
优雅便捷的 V2EX 扩展。
Web Timer
每个网站停留时间统计。
为知笔记网页剪辑器
配合为知笔记,快速保存网页内容到为知笔记。
二维码(生成及识别)
生成或识别二维码。
惠惠购物助手
在主流电商网站页面上提供商品的历史价格,及在同款商品在其它平台的价格比较。
新浪微博图床
微博是个好图床。
网页截图:注释&批注
捕获整个页面或任何部分,矩形,圆形,箭头,线条和文字,模糊敏感信息,一键上传分享。
Postman
功能强大的接口调试工具。
Google Keep - 记事和清单
不解释。
Pocket
不解释。
JSON Editor
图形化json编辑工具。
admin
from django.contrib import admin
from myproject.myapp.models import Author
admin.site.register(Author)
或是使用装饰器
from django.contrib import admin
from myproject.myqll.models import Author
@admin.register(Author[, Reader, Editor])
class PersonAdmin(admn.ModelAdmin):
pass
# 或
@admin.register
class PersonAdmin(admn.ModelAdmin):
model = Author
list_display
控制在管理的列表页面显示的字段
fields
控制在表单页面显示的字段、字段顺序及行内分组,如果字段在model
里设置了editable=False
,则只有当此字段在readonly_fields
中时,才能包含在fields
中;
exclude
控制在表单页面不需要显示的字段
在apps.py
里
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class AuthorConfig(AppConfig):
name = 'myapp'
verbose_name = u'管理后台显示的名称'
```
在`__init__.py`加入
```python
default_app_config = 'myapp.apps.AuthorConfig'
ModelAdmin options
ModelAdmin.actions
列表, 列表页操作,详见下章
ModelAdmin.actions_on_top
ModelAdmin.actions_on_bottom
布尔值,列表页操作控件显示的位置,上面可同时显示
ModelAdmin.actions_selection_counter
布尔值,是否在列表页操作控件显示选中数量
ModelAdmin.date_hierarchy
时间类型字段名字符,列表页顶部可根据指定日期字段生成一个智能过滤条
ModelAdmin.empty_value_display
当字段值为None
、空字符时显示的替代内容,默认为 -
ModelAdmin.exclude
哪些字段在列表页中不显示
ModelAdmin.view_on_site
在站点上查看当前编辑的对象
默认使用get_absolute_url()
或可重写view_on_site(self, obj)
方法返回url
列表页批量操作
from django.contrib import admin
from myapp.models import Article
def make_published(modeladmin, request, queryset):
# queryset为用户所选的对象集合
queryset.update(status='p')
make_published.short_description = u'发布文章'
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'status']
ordering = ['title']
# 使用actions注册操作
actions = [make_published]
admin.site.register(Article, ArticleAdmin)
因为make_published
方法和Article
模型是紧密耦合的,所以为了更好的设计,应该把make_published
绑定到ArticleAdmin
里面去。
class ArticleAdmin(admin.ModelAdmin):
...
def make_published(self, request, queryset):
rows_updated = queryset.update(status='p')
if rows_updated == 1:
message_bit = "1 story was"
else:
message_bit = "%s stories were" % rows_updated
self.message_user(request, "%s successfully marked as published." % message_bit)
操作的中间页面
只要从你的操作返回HttpResponse(或其子类)就可以了。
from django.http import HttpResponse
from django.core import serializers
def export_as_json(modeladmin, request, queryset):
response = HttpResponse(content_type="application/json")
serializers.serialize("json", queryset, stream=response)
return response
from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
def export_selected_objects(modeladmin, request, queryset):
selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
ct = ContentType.objects.get_for_model(queryset.model)
return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected)))
全站操作
from django.contrib import admin
# 第二个参数为禁用时使用的名字,如果方法没有short_description,也会使用这个名字
admin.site.add_action(export_selected_objects,u'导出')
export_selected_objects
方法同前面相同
禁用全站操作
from django.contrib import admin
# 禁用时使用add_action时的name,如果没有,直接使用方法名字字符串
admin.site.disable_action(u'导出')
如果想要局部启用被全局禁用的操作可以显示的放到ModelAdmin.actions
列表中就可以
如果想要在某个ModelAdmin
上禁用所有操作,可以把ModelAdmin.actions
设置为None
,这会所有列表上的批量操作全部禁用,包括django
自带的操作,设置ModelAdmin.actions
为空list
并不能禁用全站级操作
根据每个请求来动态的开启或禁用操作
class MyModelAdmin(admin.ModelAdmin):
...
def get_actions(self, request):
actions = super(MyModelAdmin, self).get_actions(request)
if request.user.username[0].upper() != 'J':
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
ModelAdmin.get_actions()
会返回当前ModelAdmin
上的所有操作的有序字典,key
为action
的name
(不是short_description
),value
为(function, name, short_description)
元组
short_description
只是用来给人类看的,所以不太会用作参数
2016-04-04 初稿
2016-06-30 更新内容
限制普通字段的选择范围
choices
,value-text,
显示get_foo_display()
模型继承
多重继承主要用于mix-in
类
多表继承时使用parent_link=True
显示指定OneToOne字段
与尚未定义的模型关联使用模型名字(字符串)而非本身(类)
关联自己使用self
related_name
relate_query_name
外键关联到特定字段
to_field
限制外键的选择范围(可以是一个字典、一个Q 对象或者一个返回字典或Q对象的可调用对象)
limit_choices_to
外键关联对象删除行为
on_delete
1.8以后保存模型时,未保存的外键对象将被忽略,除非设置allow_unsaved_instance_assignment=True
关联自身的多对多关系默认对称,取消对称设置symmetrical=False
ImageField
中的height_field
和width_field
是用来存储存入图片的高度和宽度值的
##执行查询
可自定义查询(高级查找)
exclude
多条件查询时是用or关系而不是and关系
F()
用于模型内部字段间的比较支持加法、减法、乘法、除法、取模以及幂计算等算术操作
支持.bitand() 和.bitor()位操作,update()
也可以使用F()
但有限制(在update 中你不可以使用F() 对象引入join —— 你只可以引用正在更新的模型的字段)
查询集缓存
当只对查询集的部分进行求值时会检查缓存, 但是如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
>>> queryset = Entry.objects.all()
>>> print queryset[5] # Queries the database
>>> print queryset[5] # Queries the database again
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print queryset[5] # Uses cache
>>> print queryset[5] # Uses cache
Q()
可使用Q对象进行复杂查询
判断两相模型实例是否相同,直接使用==
比较即可
默认批量删除对象时不会调用实例的delete
方法
拷贝实例,把pk
设置为None
再save
即可(如果是继承的,则pk
和id
都需要设置为None
)
update()
方法也不会调用模型的save()
方法,不会发出pre_save
和post_save
信号,字段的auto_now
也不会起作用
一对多关联对象访问会缓存
>>> e = Entry.objects.get(id=2)
>>> print(e.blog) # Hits the database to retrieve the associated Blog.
>>> print(e.blog) # Doesn't hit the database; uses cached version.
自定义反向管理器1.7+
from django.db import models
class Entry(models.Model):
#...
objects = models.Manager() # Default Manager
entries = EntryManager() # Custom Manager
b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
一次创建多条数据(只有一条sql)
bulk_create
根据提供的一组pk
查询出所有对应的对象
in_bulk
在查作者列表时要查每个作者有几篇博文
>>> from django.db.models import Count
>>> authors = Author.object.all().annotate(Count('blog'))
# authors[0]作者的博文数
>>> authors[0].blog__count
3
# 或
>>> authors = Author.object.all().annotate(number_of_blog=Count('blog'))
>>> authors[0].number_of_blog
3
算出所有作者的年龄总合(不需要其它数据)
>>> ageAuthor.objects.all().aggregate(Sum('age'))
{'age__sum': 26}
annotate
和aggregate
都可写入多个注解表达式
annotate
和aggregate
可聚合关联对象
对注解进行过滤
# 查询出作者数大于1的书本
# 只有一条sql
Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
但顺序不一样,结果也不同,如:
Publisher.objects.annotate(num_books=Count('book')).filter(book__rating__gt=3.0)
Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
对注解项进行排序
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
values()
使用注解时要小心,如果values()
在注解之前,会对结果进行分组,注解会作用在分组上而不是整个查询集上
与默认排序交换或order_by()¶
在查询集中的order_by() 部分(或是在模型中默认定义的排序项) 会在选择输出数据时被用到,即使这些字段没有在 values() 调用中被指定。这些额外的字段可以将相似的数据行分在一起,也可以让相同的数据行相分离。在做计数时, 就会表现地格外明显:
通过例子中的方法,假设有一个这样的模型:
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
class Meta:
ordering = ["name"]
关键的部分就是在模型默认排序项中设置的name字段。如果你想知道每个非重复的data值出现的次数,可以这样写:
# Warning: not quite correct!
Item.objects.values("data").annotate(Count("id"))
...这部分代码想通过使用它们公共的 data 值来分组 Item对象,然后在每个分组中得到 id 值的总数。但是上面那样做是行不通的。这是因为默认排序项中的 name也是一个分组项,所以这个查询会根据非重复的 (data, name) 进行分组,而这并不是你本来想要的结果。所以,你应该这样改写:
Item.objects.values("data").annotate(Count("id")).order_by()
...这样就清空了查询中的所有排序项。 你也可以在其中使用 data ,这样并不会有副作用,这是因为查询分组中只有这么一个角色了。
这个行为与查询集文档中提到的 distinct() 一样,而且生成规则也一样:一般情况下,你不想在结果中由额外的字段扮演这个角色,那就清空排序项,或是至少保证它仅能访问 values()中的字段。
http://python.usyiyi.cn/django/intro/tutorial06.html
http://python.usyiyi.cn/django/ref/templates/builtins.html
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static user_stylesheet %}" />
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
<link rel="stylesheet" type="text/css" href="{% get_static_prefix %}pools/style.css" />
{% get_static_prefix as STATIC_PREFIX %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_PREFIX }}pools/style.css" />
{% static "images/hi.jpg" as myphoto %}
<img src="{{ myphoto }}"></img>
还有get_media_prefix
从数据库中重新加载值
Model.refresh_from_db(using=None, fields=None, **kwargs)
返回模型中当前所有延迟字段的属性名称
Model.get_deferred_fields()
验证对象
字段的基本验证会最先跑,但不管前面运行是否通过,对于每个字段,如果Field.clean() 方法抛出 ValidationError,那么将不会调用该字段对应的clean_
()方法。 但是,剩余的字段的验证方法仍然会执行。
先跑form
里验证,再跑modle
验证
先跑验证器,再跑clean
先跑单个字段验证,再跑整体验证
Model.clean_field()
会覆盖Model
里所有字段的验证器,但不会对Form
里的验证器产生影响
验证模型的字段Model.clean_fields(exclude=None)
验证模型的完整性Model.clean()
验证模型的唯一性Model.validate_unique(exclude=None)
调用full_clean()
时,上面三个方法都会执行(执行顺序即上面的书写顺序),ModelForm
的is_valid()
也会执行上所有验证
Model.full_clean(exclude=None, validate_unique=True)
save()
时,full_clean()
不会被调用,如果想验证数据,可手动调用
Model.clean()
时,引发特定字段的异常
使用一个字典实例化ValidationError
即可或使用add_error(field, msg)
方法
在数据库字段值的基础上进行简单的算法操作,应该尽量使用F()
表达式,避免问题竞态条件
指定要保存的字段
如果传递给save() 的update_fields 关键字参数一个字段名称列表,那么将只有该列表中的字段会被更新。如果你想更新对象的一个或几个字段,这可能是你想要的。不让模型的所有字段都更新将会带来一些轻微的性能提升。例如:
product.name = 'Name changed again'
product.save(update_fields=['name'])
update_fields
参数可以是任何包含字符串的可迭代对象。空的update_fields
可迭代对象将会忽略保存。如果为None
值,将执行所有字段上的更新。
指定
update_fields
将强制使用更新操作。
当保存通过延迟模型加载(
only()
或defer()
)进行访问的模型时,只有从数据库中加载的字段才会得到更新。这种情况下,有个自动的update_fields
。如果你赋值或者改变延迟字段的值,该字段将会添加到更新的字段中。
new in 1.9
使用Model.delete()
删除多表继承的子表数据时,使用``keep_parents=True可以保留上级数据,默认为
False`
返回值为删除数据的条数
DateField
和DateTimeField
字段如果null=False
则支持下面两个方法
Model.get_next_by_FOO(**kwargs)¶
Model.get_previous_by_FOO(**kwargs)
django遇到的第一个管理器为默认管理器
如果需要访问关联对象调用关联对象的默认管理器,需要在管理器中加use_for_related_fields=True
,不然会调用朴素管理器
# -*- coding: utf-8 -*-
from django.db import models
class DefaultManager(models.Manager):
def get_queryset(self):
queryset = super(DefaultManager, self).get_quertset().filter(is_delete=False)
return queryset
class Author(models.Model):
name = models.CharField(max_length=100)
is_delete = models.BooleanField()
objects = DefaultManager()
class Post(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
content = models.TextField()
is_delete = models.BooleanField()
objects = DefaultManager()
author = Author.objects.get(pk=1)
post = Post.objects.get(pk=2)
# 调用DefaultManager管理器
author.post_set.all()
# 调用朴素管理器,如果要调用DefaultManager管理器,需要设置DefaultManager管理器的类变量use_for_related_fields=True
post.author
注:朴素管理器里找不到的方法会在默认管理器里查找
Manager.raw(raw_query, params=None, translations=None)
django.db.connection对象提供了常规数据库连接的方式。为了使用数据库连接,先要调用connection.cursor()方法来获取一个游标对象之后,调用cursor.execute(sql, [params])来执行sql语句,调用cursor.fetchone()或者cursor.fetchall()来返回结果行。
详细笔记见django1.8事务.md
将每个HTTP请求封装在一个数据库事务中
settings中设置ATOMIC_REQUESTS=True
单独给一个方法加上数据库事务控制使用atomic
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
或
from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic():
# This code executes inside a transaction.
do_more_stuff()
避免在 atomic里捕获异常!
使用数据库的方法
from django.db.models import Func, F
queryset.annotate(field_lower=Func(F('field'), function='LOWER'))
或
class Lower(Func):
function = 'LOWER'
queryset.annotate(field_lower=Lower(F('field')))
高级用法查看在线版
When
Case
Coalesce
接收一个含有至少两个字段名称或表达式的列表,返回第一个非空的值(空字符串不认为是一个空值)
根据遗留数据库生成models
python manage.py inspectdb > models.py
使用fixtures
[
{
"model": "myapp.person",
"pk": 1,
"fields": {
"first_name": "John",
"last_name": "Lennon"
}
},
{
"model": "myapp.person",
"pk": 2,
"fields": {
"first_name": "Paul",
"last_name": "McCartney"
}
}
]
导入数据命令
python manage.py loaddata <fixturename>
添加索引,比任何查询语法优化都来的重要
理解查询集
QuerySets是延迟的。
什么时候它们被计算出来。
数据在内存中如何存储。
使用cached_property
装饰器,只要是同一个实例,一个方法就只会执行一次
使用with
模版标签
使用iterator
迭代器
在数据库中而不是python中做数据库工作
使用过滤器和反射过滤器对数据进行过滤
使用F()
表达式
使用注解和聚合
使用原始SQL
用唯一的或被索引的列来检索独立对象
在不同位置多次访问数据库,每次获取一个数据集,不如在一次查询中获取它们。比如循环的时候。
使用select_related()
和prefetch_related()
不检索你不需要的信息
使用QuerySet.values()
和QuerySet.values_list()
使用QuerySet.defer()
和QuerySet.only()
计算数量不要使用len(queryset)
而是使用QuerySet.count()
判断是否存在结果使用QuerySet.exists()
而不是用if queryset
但不要过度使用count()
和exists()
,如果你本来就需要里面的数据,那就不要使用
使用QuerySet.update()
和QuerySet.delete()
批量操作数据
直接使用外键的值
entry.blog_id
# 而不是
entry.blog.id
如果你并在意结果集的顺序,不要进行排序,移除Meta.ordering
创建对象时尽可能使用bulk_create()
来减少sql查询数量
这也适用于ManyToManyFields
的情况,一起add
而不是一个一个add
my_band.members.add(me, my_friend)
#更优于
my_band.members.add(me)
my_band.members.add(my_friend)
url捕获的参数永远是字符串
在根url上获取的参数不影响参数传递
# In settings/urls/main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
]
# In foo/urls/blog.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.blog.index),
url(r'^archive/$', views.blog.archive),
]
在上面的例子中,捕获的"username"变量将被如期传递给include()指向的URLconf。
可嵌套
from django.conf.urls import url
urlpatterns = [
url(r'blog/(page-(\d+)/)?$', blog_articles), # bad
url(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good
]
传递额外的参数
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]
当url捕获的参数和字典中传递的参数同名时,将忽略url捕获的参数而使用字典里的参数值
传递额外的参数给include()
# main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^blog/', include('inner'), {'blogid': 3}),
]
# inner.py
from django.conf.urls import url
from mysite import views
urlpatterns = [
url(r'^archive/$', views.archive),
url(r'^about/$', views.about),
]
效果等同
# main.py
from django.conf.urls import include, url
from mysite import views
urlpatterns = [
url(r'^blog/', include('inner')),
]
# inner.py
from django.conf.urls import url
urlpatterns = [
url(r'^archive/$', views.archive, {'blogid': 3}),
url(r'^about/$', views.about, {'blogid': 3}),
]
HttpResponse
子类,状态码
HttpResponseRedirect
临时重定向,302HttpResponsePermanentRedirect
永久重定向,301HttpResponseNotModified
没有任何修改,304HttpResponseBadRequest
语义有误码,当前请求不被服务器理解,400HttpResponseNotFound
页面没找到,404HttpResponseForbidden
服务器理解请求,但拒绝执行,403HttpResponseNotAllowed
请求中指定的请求方式不能用于请求相应资源,405HttpResponseGone
请求的资源在服务器上已经不可用,而且没有已知的转发地址,410HttpResponseServerError
服务器遇到了一个意外的错误,导致无法完成对请求的处理,500HttpResponse(status=201)
自定义返回状态码重写错误视图(在url中)
handler404 = 'mysite.views.my_custom_page_not_found_view'
handler500 = 'mysite.views.my_custom_error_view'
handler403 = 'mysite.views.my_custom_permission_denied_view'
handler400 = 'mysite.views.my_custom_bad_request_view'
template_name
可传一个模版序列,django将使用存在的第一个模版
redirect(to, [permanent=False, ]*args, **kwargs)[source]
为传递进来的参数返回HttpResponseRedirect 给正确的URL 。
参数可以是:一个模型:将调用模型的get_absolute_url() 函数 一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称 一个绝对的或相对的URL,将原样作为重定向的位置。
默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。
get_object_or_404(klass, *args, **kwargs)
可以传Model也可以传QuerySet实例和关联的管理器
get_list_or_404(klass, *args, **kwargs)
可以传Model也可以传QuerySet实例和关联的管理器
按需内容处理
django.views.decorators.http
包里的装饰器可以基于请求的方法来限制对视图的访问。若条件不满足会返回 django.http.HttpResponseNotAllowed。
require_http_methods(request_method_list)
限制视图只能服务于规定的http方法(需要大写)
require_GET()
require_POST()
require_safe()
只允许视图接受GET和HEAD方法的装饰器。
@condition(etag_func=None, last_modified_func=None)
@last_modified(last_modified_func)
根据最后修改时间来决定是否运行视图,可减少流量
@etag(etag_func)
etag
(版本?)和last_modified
不能同时使用
GZip
对内容进行压缩,节省流量,但增加处理时间
vary_on_cookie
vary_on_headers
基于特定的请求头部来控制缓存
never_cache
HttpRequest
对象(除非特殊说明,所有属性都是只读,session
属性是个例外)
HttpRequest.scheme
请求方案(通常为http或https)
HttpRequest.body
字节字符串,表示原始http请求正文
HttpRequest.path
字符串,表示请求的页面的完整路径,不包含域名
HttpRequest.path_info
在某些Web 服务器配置下,主机名后的URL 部分被分成脚本前缀部分和路径信息部分。path_info 属性将始终包含路径信息部分,不论使用的Web 服务器是什么。使用它代替path 可以让代码在测试和开发环境中更容易地切换。
例如,如果应用的WSGIScriptAlias 设置为"/minfo",那么当path 是"/minfo/music/bands/the_beatles/" 时path_info 将是"/music/bands/the_beatles/"。
HttpRequest.method
请求使用的http方法,大写
HttpRequest.encoding
表示提交的数据的编码方式,可写
HttpRequest.GET
HttpRequest.POST
HttpRequest.REQUEST
不建议使用,使用GET
和POST
代替
HttpRequest.COOKIES
字典,键和值都是字符串
HttpRequest.FILES
类似字典的对象,包含所有的上传文件,
django-filter
提供一种简单的方式为用户提供的参数(url传参)来过滤queryset
model
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField()
description = models.TextField()
release_date = models.DateField()
manufacturer = models.ForeignKey(Manufacturer)
filter
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['price', 'release_date']
url参数:price
release_date
匹配模式:完全匹配
重写某个字段的过滤
import django_filters
class ProductFilter(django_filters.FilterSet):
price = django_filters.NumberFilter(lookup_type='lt')
class Meta:
model = Product
fields = ['price', 'release_date']
url参数:price
release_date
匹配模式:price
小于输入值,release_date
精确匹配
附:
lt
小于
lte
小于等于
gt
大于
gte
大于等于
重写一个CharField
类型字段的过滤
class ProductFilter(django_filters.FilterSet):
filter_overrides = {
models.CharField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_type': 'icontains',
}
}
}
class Meta:
model = Product
fields = ['name']
url参数:name
匹配模式:完全匹配
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = {'price': ['lt', 'gt'],
'release_date': ['exact'],
}
url参数:price__lt
price__gt
release_date
exact
是默认的
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['manufacturer__country']
类视图
# urls.py
from django.conf.urls import patterns, url
from django_filters.views import FilterView
from myapp.models import Product
urlpatterns = patterns('',
(r'^list/$', FilterView.as_view(model=Product)),
)
需要提供一个模版名字为<app>/<model>_filter.html
,在模版里可以获取名为object_list
的Product
QuerySet
方法视图
虽然现在建议使用,但对针方法视图也有对应的方法
# urls.py
from django.conf.urls import patterns, url
from myapp.models import Product
urlpatterns = patterns('',
(r'^list/$', 'django_filters.views.object_filter', {'model': Product}),
)
http://django-filter.readthedocs.org/en/latest/index.html
]]>drf默认是关闭版本控制功能,如需要开启,可在settings.py
里添加对应的设置
REST_FRAMEWORK = {
……
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
# 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
# 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
# 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.HostNameVersioning',
# 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning',
……
}
当然,你也可以为每个视图单独添加,不过不建议这么做
class ProfileList(APIView):
versioning_class = versioning.QueryParameterVersioning
开启版本控制之后,就可以从request
取得版本号request.version
(当然你settings.py
里配置的是什么方式,就用什么方式传版本号,这样就才可以从request
里获取到版本号)
def get_serializer_class(self):
if self.request.version == 'v1':
return AccountSerializerVersion1
return AccountSerializer
启动版本控制后,url逆向解析方法需要传入request
参数
from rest_framework.reverse import reverse
reverse('bookings-list', request=request)
如果是使用Namespace时的版本控制,因为配置了DEFAULT_VERSIONING_CLASS
,所以设置view_name时不需要添加v1:
前缀,见django rest framework入门笔记.md
最后在设置里添加以下全局设置来控制能访问的版本
'DEFAULT_VERSION': None, #默认版本,request里没有版本信息时,使用的版本,默认为None
'ALLOWED_VERSIONS': [None, 'v1', 'v2'], #允许访问的版本,如果访问的版本不在列表中,则会抛出异常
也可以为每个视图单独设置
from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView
class ExampleVersioning(URLPathVersioning):
default_version = ...
allowed_versions = ...
version_param = ...
class ExampleView(APIVIew):
versioning_class = ExampleVersioning
]]>curl http://www.example.com
默认curl使用GET方式请求数据,这种方式下直接通过URL传递数据
可以通过 --data/-d 方式指定使用POST方式传递数据
# GET
curl -u username https://api.github.com/user?access_token=XXXXXXXXXX
# POST
curl -u username -d "param1=value1¶m2=value" https://api.github.com
# 也可以指定一个文件,将该文件中的内容当作数据传递给服务器端
curl -d @filename https://github.api.com/authorizations
注:默认情况下,通过POST方式传递过去的数据中若有特殊字符,首先需要将特殊字符转义在传递给服务器端,如value值中包含有空格,则需要先将空格转换成%20,如:
curl -d "value%201" http://hostname.com
在新版本的CURL中,提供了新的选项 --data-urlencode,通过该选项提供的参数会自动转义特殊字符。
curl --data-urlencode "value 1" http://hostname.com
除了使用GET和POST协议外,还可以通过 -X 选项指定其它协议,如:
curl -I -X DELETE https://api.github.com
curl -H 'Accept-Language: zh' http://cnn.com
-H
或被多次指定
curl -H 'Host: 157.166.226.25'-H 'Accept-Language: zh'-H 'Cookie: ID=1234' http://cnn.com
对于"User-Agent", "Cookie", "Host"这类标准的HTTP头部字段,通常会有另外一种设置方法。curl命令提供了特定的选项来对这些头部字段进行设置:
-A (or --user-agent): 设置 "User-Agent" 字段.
-b (or --cookie): 设置 "Cookie" 字段.
-e (or --referer): 设置 "Referer" 字段.
curl -H "User-Agent: my browser" http://cnn.com
curl -A "my browser" http://cnn.com
curl -I http://www.baidu.com
curl --form "fileupload=@filename.txt" http://hostname/resource
-o
: 将文件保存为命令行中指定的文件名到本地
-O
: 使用url中默认的文件名保存文件到本地
curl -o index.html http://www.example.com
# 或
curl http://www.baidu.com > index.html
# 在windows上没成功
curl -O http://www.example.com
工作中,经常需要用自签的假证书搭建开发环境。cURL在遇到证书错误时罢工,使用 -k 参数就可以让它不做证书校验。
curl -k https://www.example.com
如果url重定向的话,curl默认是不会去获取重定向后的url页面的,使用-L
可进行强制重定向
curl -L http://www.example.com
cURL提供了一个 –compress 参数,可以用来发送支持压缩的请求。但使用了–compress之后,虽然传输过程是压缩的,cURL的输出还是解压之后的,难以看到效果。
自己写一个 Accept-Encoding 字段在头信息中。
curl -H "Accept-Encoding: gzip" http://www.kuqin.com/
如果直接运行上面的命令,会得到一堆乱码,因为cURL输出的内容,是压缩后的数据。不妨在后面接一个gunzip试试。
# 使用gunzip解压
curl -H "Accept-Encoding: gzip" http://www.kuqin.com/ | gunzip
使用gunzip解压之后,信息又被还原了。
通过使用-C选项可对大文件使用断点续传功能
# 未下载完成即中断该进程
curl -o a.zip http://www.example.com/bigfile.zip
# 后面可以通过-C来继续下载
curl -C -o a.html http://www.example.com/bigfile.zip
使用-limit-rate进行限速
# 限速为100k/s
curl --limit-rate 1000k -o a.zip http://www.example.com/bigfile.zip
# 若文件的修改时间在2011/12/11之后,则下载
curl -z 21-Dec-11 http://www.example.com/bigfile.zip
在访问需要授权的页面时,可通过-u
来提供用户名和密码进行授权
curl -u username:password http://www.example.com
# 列出指定目录下的所有文件
curl -u ftpuser:ftppw -O ftp://ftp_server/public_html/
# 下载文件
curl -u ftpuser:ftppw -O
ftp://ftp_server/public_hmtl/bigfile.zip
# 上传文件
curl -u ftpuser:ftppw -T myfile.txt ftp://ftp_server/public_html/
# 上传多个文件
curl -u ftpuser:ftppw -T "{myfile1.txt, myfile2.txt}" ftp://ftp_server/public_html/
# 从标准输入获取内容保存到服务器的指定文件中
curl -u ftpuser:ftppw -T - ftp://ftp_server/public_html/1.txt
curl -x proxyserver.com:1080 http://www.example.com
# 将网站的cookies信息保存到example_cookies文件中
curl -D example_cookies http://www.example.com
# 使用cookies信息访问url
curl -b example_cookies http://www.example.com/user/
]]>ForeignKey
多对一关系class ForeignKey(othermodel[, **options])
如果关联模型还没有创建,可以使用该模型的名字而不是模型本身来指定
options
limit_chices_to
对关联对象进行过滤,可以是一个字典, 一个 Q 对象, 或者一个可调用的返回字典或者Q对象的对象
related_name
在关联对象上返回这个对象的名称,替代默认的xxx_set
django字段选项related_name和related_query_name.md
related_query_name
在关联对象上进行查询时使用的名称
to_field
指定关联到关联对象的字段名称,默认使用关联对象主键
db_constraint
是否在数据库中为这个外键创建约束的控制。默认值为 True, 而且这几乎一定是你想要的效果。如果设置成 False 对数据集成来说是很糟糕的。即便如此,有一些场景你也许想要这么设置:
on_delete
当此外键引用的对象被删除时,这个字段的行为
swappable
allow_unsaved_instance_assignment
ManyToManyField
多对多关系class ManyToManyField(othermodel[, **options])
related_name
同ForeignKey.related_name
related_query_name
同ForeignKey.related_query_name
limit_choices_to
同ForeignKey.limit_choices_to
symmetrical
只有在关联自身情况下使用,默认是对称的,即如果a是b的好友,默认b也是a的好友,要取消对称设置symmetrical为False
from django.db import models
class Person(models.Model):
friends = models.ManyToManyField("self")
through
djagno默认会自动创建一个表来管理多对多的关系,但是你可以自己建立中介表,然后来手动指定。详细
注意:手动指定中间表后,关联表的add
create
remove
将不可用,但clear
还是可以用的
(http://python.usyiyi.cn/django/ref/models/fields.html#django.db.models.ManyToManyField.through)
through_fields
在中间表中对应的字段名,只能在自定义中间表时使用[详细]
db_table
指定中间表名称
db_constraint
是否为外键创建约束,默认为True
swappable
allow_unsaved_instance_assignment
OneToOneField
一对一关系这个是一个对一关联关系。总体上,这个字段类很像是ForeignKey设置了unique=True, 不同的是它会直接返回关系的另一边的对象。
from django.conf import settings
from django.db import models
class MySpecialUser(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
supervisor = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='supervisor_of')
>>> user = User.objects.get(pk=1)
>>> hasattr(user, 'myspecialuser')
True
>>> hasattr(user, 'supervisor_of')
True
import datetime
from django.db import models
class Blog(models.Model):
...
class Entry(models.Model):
blog = models.ForeignKey(Blog)
published = models.DateTimeField(default=datetime.datetime.now)
...
有以下方法
blog.entry_set
entry.blog
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)
有以下方法
topping.pizza_set
pizza.toppings
把指定的模型对象添加到关联对象集中。
>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.add(e) # Associates Entry e with Blog b.
不需要调用save()方法
创建一个新的对象,保存对象,并将它添加到关联对象集之中。返回新创建的对象:
>>> b = Blog.objects.get(id=1)
>>> e = b.entry_set.create(
... headline='Hello',
... body_text='Hi',
... pub_date=datetime.date(2005, 1, 1)
... )
# No need to call e.save() at this point -- it's already been saved.
这完全等价于(不过更加简洁于):
>>> b = Blog.objects.get(id=1)
>>> e = Entry(
... blog=b,
... headline='Hello',
... body_text='Hi',
... pub_date=datetime.date(2005, 1, 1)
... )
>>> e.save(force_insert=True)
要注意我们并不需要指定模型中用于定义关系的关键词参数。在上面的例子中,我们并没有传入blog参数给create()。Django会明白新的 Entry对象blog 应该添加到b中。
从关联对象集中移除执行的模型对象(不是删除)
>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.remove(e) # Disassociates Entry e from Blog b.
和add()相似,上面的例子中,e.save()可会执行更新操作。但是,多对多关系上的remove(),会使用QuerySet.delete()删除关系,意思是并不会有任何模型调用save()方法:如果你想在一个关系被删除时执行自定义的代码,请监听m2m_changed信号。
对于ForeignKey对象,这个方法仅在null=True时存在。如果关联的字段不能设置为None (NULL),则这个对象在添加到另一个关联之前不能移除关联。在上面的例子中,从b.entry_set()移除e等价于让e.blog = None,由于blog的ForeignKey没有设置null=True,这个操作是无效的。
对于ForeignKey对象,该方法接受一个bulk参数来控制它如果执行操作。如果为True(默认值),QuerySet.update()会被使用。而如果bulk=False,会在每个单独的模型实例上调用save()方法。这会触发pre_save和post_save,它们会消耗一定的性能。
从关联对象集中移除一切对象(不是删除)
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.clear()
注意这样不会删除对象 —— 只会删除他们之间的关联。
就像 remove() 方法一样,clear()只能在 null=True的ForeignKey上被调用,也可以接受bulk关键词参数。
注意
注意对于所有类型的关联字段,add()、create()、remove()和clear()都会马上更新数据库。换句话说,在关联的任何
端,都不需要再调用save()方法。
同样,如果你再多对多关系中使用了中间模型,一些关联管理的方法会被禁用。
通过赋值一个新的可迭代的对象,关联对象集可以被整体替换掉。
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set = new_list
如果外键关系满足null=True,关联管理器会在添加new_list中的内容之前,首先调用clear()方法来解除关联集中一切已存在对象的关联。否则, new_list中的对象会在已存在的关联的基础上被添加。
]]>data
sqlite> select * from author;
id name age
1 jim 12
2 tom 11
sqlite> select * from book;
id name author_id
1 learn java 1
2 learn python 1
3 learn c++ 2
]]>data
sqlite> select * from author;
id name age
1 jim 12
2 tom 11
sqlite> select * from book;
id name author_id
1 learn java 1
2 learn python 1
3 learn c++ 2
models.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(verbose_name='姓名', max_length=50)
age = models.IntegerField(verbose_name='年龄')
class Book(models.Model):
name = models.CharField(verbose_name='书名', max_length=100)
author = models.ForeignKey(Author, verbose_name='作者')
执行语句
>>> Author.objects.filter(book__name='learn java')
[<Author: jim>]
>>> author = Author.objects.get(pk=1)
>>> author.book_set.all()
[<Book: learn java>, <Book: learn python>]
假如把类Book
改成这样
class Book(models.Model):
name = models.CharField(verbose_name='书名', max_length=100)
author = models.ForeignKey(Author, verbose_name='作者', related_name='bs', related_query_name='b')
那么上面查询代码就应该写成这样
>>> Author.objects.filter(b__name='learn java')
[<Author: jim>]
>>> author = Author.objects.get(pk=1)
>>> author.bs.all()
[<Book: learn java>, <Book: learn python>]
]]>如果
book
表里有两个字段都外键关联author
表,这时related_name
就非常有用了。
git stash
命令。
]]>git stash
命令。
git stash
命令可将工作区的改动存储git栈,运行git stash
之后,可以再运行git status -s
验证下发现目录和上交commit时是一致的,没有任何修改,这时你就可以切换到其它分支进行工作,当你完成工作后,再切换回来,使用git stash pop
可以从Git栈中读取最近一次保存的内容,恢复到工作区。
git stash: 备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到Git栈中。
git stash save "message": 备份当前的工作区的内容,并添加备注信息
git stash list: 显示git栈内的所有备份,可以利用这个列表来决定从那个地方恢复。
git stash pop stash@{0}: 从git栈中读取并恢复工作区,然后删除对应的记录,默认恢复最新的(stash@{0}为最新)
git stash apply stash@{0}: 同git stash pop,但不会删除对应的记录
git stash drop: 删除最新的一个备份
git stash clear: 清空git栈。此时使用gitg等图形化工具会发现,原来stash的哪些节点都消失了。
http://www.tuicool.com/articles/rUBNBvI
及git stash --help
往前n个提前内容的patch
git format-patch -n
某个commit(含)的及之前的n-1次提交的patch
git format-patch -n SHA
]]>往前n个提前内容的patch
git format-patch -n
某个commit(含)的及之前的n-1次提交的patch
git format-patch -n SHA
某个commit的patch
git format-patch -1 SHA
当前分支所有超前master提交的patch
git format-patch -M master
两个commit之间的所有patch(不包含较早SHA1提交的内容)
git format-patch SHA1...SHA1
某个commit之后的所有patch
git format-patch -s SHA
检查patch
git apply --stat xxx.patch
检查能否应用成功
git apply --check xxx.patch
打补丁
git am -s xxx.patch
如果有冲突,整个PATCH都不会被集成,接来来解决冲突问题
# 把没有冲突的文件先合并了,剩下有冲突的作了标记
git apply PATCH --reject
# 这里手动解决冲突
# 把解决冲突的和PATCH里新加的文件全部add进来,因为git am并不会改变index
git add FIXED_FILES
git am --resolved
http://blog.csdn.net/daydring/article/details/42676987
]]>2015-10-15 初稿
Material Design 中文版(官网翻译而来)
阅读: http://wiki.jikexueyuan.com/project/material-design/
GitHub: https://github.com/1sters/material_design_zh_2
PDF: http://pan.baidu.com/s/1fr1gi
Material Design 中文版(官网翻译而来)
阅读: http://design.1sters.com/ (已关停)
GitHub: https://github.com/1sters/material_design_zh
Google Material Design 正體中文版(官网翻译而来)
阅读: https://www.gitbook.com/book/wcc723/google_design_translate/details
GitHub: https://github.com/Wcc723/google_design_translate
PDF: http://pan.baidu.com/s/1dD8X1Zj
Material Design非官方中文指导手册
PDF: http://pan.baidu.com/s/1mg3P45i
资源大合集
https://github.com/lightSky/Awesome-MaterialDesign
十大Material Design开源项目
http://www.csdn.net/article/2014-11-21/2822753-material-design-libs/1
Material icons guide
主页: http://google.github.io/material-design-icons/
GitHub: https://github.com/google/material-design-icons
Angular Material(控件最全,样式不错,交互动态效果多)
主页: https://material.angularjs.org/
GitHub: https://material.angularjs.org/
material ui(控件较多,样式不错,交互动态效果多)
主页: http://material-ui.com/
GitHub: https://github.com/callemall/material-ui
bootstrap-material-design(控件数量还行,样式较好,交互动态效果还行,Demo里部分样式有错位问题)
主页: http://fezvrasta.github.io/bootstrap-material-design/
GitHub: http://fezvrasta.github.io/bootstrap-material-design/
muicss(控件较少,样式普通,交互动态效果少)(体积小,css+js 11.4k)
主页: https://www.muicss.com/
GitHub: https://github.com/muicss/mui
MaterializeCss(控件数量还行,样式较好,交互动态效果还行)
主页: http://materializecss.com/
GitHub: https://github.com/Dogfalo/materialize
bootswatch里的parper主题
主页: http://bootswatch.com/paper/
GitHub: https://github.com/thomaspark/bootswatch
jQuery Mobile Material Theme(控件较少,样式普通,无交互动态效果)
]]>主页: http://store.ququplay.com/
# 查看文件修改/变动情况
git status -s
# 把所有已跟踪的文件添加到暂存区
git add -u
# 把所有已跟踪并有更新的文件提交到本地仓库
git commit -am "update message"
# 列出本地分支
git branch
或
git branch -v
# 列出本地和远程所有分支
git branch -a
或
git branch -va
# 删除本地分支
git branck -D/-d <branch>
# 删除远程分支,注意冒号前有空格
git push origin :<分支名>
# 等价于
git push origin -d <branch>
# 推送当前分支到默认remote上,remote上没有对应分支则自动创建
git push
# 推送当前分支到指定remote,remote上没有对应分支则自动创建
git push <remote>
# 推送到指定分支到remote的指定分支上
git push <remote> <remote_branch>:<loclal_branch>
git branch (-m | -M) [<old-branch>] <new-branch>
git checkout -b <branch> <remote>/<branch>
或
git checkout --track <remote>/<branch>
push
情况下)git reset --mixed <SHA1> # 此SHA1之后的commit全部撤消,并回退index,工作空间代码不变,--mixed可省略
git reset --soft <SHA1> # 此SHA1之后的commit全部撤消,工作空间代码和index不变
git reset --hard <SHA1> # 此SHA1之后的commit全部撤消,工作空间代码和index全部退回
commit
记录git log --oneline -n # 单行显示最后n个commit的记录
git branch --set-upstream-to=<remote>/<remote_branch> <loclal_branch>
git push -u origin <branch_name>
git tag -l
# 删除本地tag
git tag -d <tag_name>
# 删除远程tag
git push origin :refs/tags/<tag_name>
# 推送单个tag
git push origin <tag_name>
# 推送所有tag
git push origin --tags
# 当前分支日志
git log
# 所有本地分支日志
git log --all
# 指定本地分支日志
git log <branch_name>
# 指定远程分支日志
git log origin/<branch_name>
# 所有远程分支日志
git log --all origin
]]>如果要为这类页面展示建立多个数据表,显然是很浪费的行为,但如果直接写成静态页面文件,更新又比较很麻烦,这时候就可以使用flatpages
来解决这类问题了。
如果要为这类页面展示建立多个数据表,显然是很浪费的行为,但如果直接写成静态页面文件,更新又比较很麻烦,这时候就可以使用flatpages
来解决这类问题了。
django.contrib.flatpages
是Django
的内置app,用于添加更新的一些简单的页面,具体设置,请继续查看以下步骤。
首先确保INSTALLED_APPS
中已经存在django.contrib.sites
,因为django.contrib.flatpages
依赖于此包。
settings.py
INSTALLED_APPS = (
# ...
'django.contrib.sites',
'django.contrib.flatpages',
)
# ...
# 如果没有设置`SITE_ID`值,则需要设置,这里直接设置为1
SITE_ID = 1
执行python manage.py migrate
建表
路由配置可先以有多种形式
urls.py
第一种(需放在最后,推荐)
from django.contrib.flatpages import views
urlpatterns += [
url(r'^(?P<url>.*/)$', views.flatpage),
]
第二种(每个页面都需要写一个url,推荐)
from django.contrib.flatpages import views
urlpatterns = [
url(r'^about-us/$', views.flatpage, {'url': '/about-us/'}, name='about'),
url(r'^license/$', views.flatpage, {'url': '/license/'}, name='license'),
]
或者,如果你不想配置路由,还有一种更简单的方法,直接在settings.py
的里添加中间件
MIDDLEWARE_CLASSES = (
# ...
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
)
为确保配置生效,保险的方法是把django.contrib.flatpages.middleware.FlatpageFallbackMiddleware
放在最后一行
flatpages
默认的你可以登录超级管理员后台(如果开启),找到Flat pages
,进去点击添加,可以看到可配置的选项有,URL
Title
Content
Site
Enable comments
Registration required
Template name
URL
: 页面所处的 URL,不包括域名,但是包含前导斜杠 (例如 /about/contact/ )Title
: 页面的标题,框架不对它作任何特殊处理。由你通过模板来显示它Content
: 页面的内容 (即 HTML 页面),框架不会对它作任何特别处理。由你负责使用模板来显示Site
: 页面放置的站点,该项设置集成了 Django 多站点框架Enable comments
: 是否允许该简单页面使用评论,框架不对此做任何特别处理。你可在模板中检查该值并根据需要显示评论窗体Registration required
: 是否注册用户才能查看此简单页面,该设置项集成了 Djangos 验证/用户框架,该框架于第十二章详述。Template name
: 用来解析该简单页面的模板名称,这是一个可选项,如果未指定模板或该模板不存在,系统会退而使用默认模板 flatpages/default.html
(我在Django1.8.4
里死活没找到,只好自己写好一个扔进去)当添加相应的数据后,剩下工作就交给flatpages
吧,如果你是使用中间件形式的,则flatpages
会在配置完所有urls.py
后,没有找到配置到对应的URL
,才会到flatpages
中查找,如果还是找不到,则会引发Http404
异常,即FlatpageFallbackMiddleware
只在404
时会被激活,而不会在500
或其它错误响应时被激活。
如果你需要自己定制,则可以针对django/contrib/flatpages/models.py
自己写增删改方法就可以。
models.py
class FlatPage(models.Model):
url = models.CharField(_('URL'), max_length=100, db_index=True)
title = models.CharField(_('title'), max_length=200)
content = models.TextField(_('content'), blank=True)
enable_comments = models.BooleanField(_('enable comments'), default=False)
template_name = models.CharField(_('template name'), max_length=70, blank=True,
help_text=_(
"Example: 'flatpages/contact_page.html'. If this isn't provided, "
"the system will use 'flatpages/default.html'."
),
)
registration_required = models.BooleanField(_('registration required'),
help_text=_("If this is checked, only logged-in users will be able to view the page."),
default=False)
sites = models.ManyToManyField(Site)
class Meta:
db_table = 'django_flatpage'
verbose_name = _('flat page')
verbose_name_plural = _('flat pages')
ordering = ('url',)
def __str__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
# Handle script prefix manually because we bypass reverse()
return iri_to_uri(get_script_prefix().rstrip('/') + self.url)
默认模板路径为flatpages/default.html
<!DOCTYPE html>
<html>
<head>
<title>{{ flatpage.title }}</title>
</head>
<body>
{{ flatpage.content }}
</body>
</html>
在实际应用中,我们不太可能会使用默认的模板,你可能需要自己写一个漂亮模板,比如有一个头部和底部,头部可能还需要添加
requeset.user
显示用户信息等。
获取flatpages
实例列表
{% load flatpages %}
{% get_flatpages as flatpages %}
获取当前用户能打开的flatpages
实例列表
{% load flatpages %}
{% get_flatpages for request.user as about_pages %}
获取链接以/about/
为开头的flatpages
实例列表
{% load flatpages %}
{% get_flatpages '/about/' as about_pages %}
上面两种也可以组合使用
{% load flatpages %}
{% get_flatpages '/about/' for someuser as about_pages %}
sitemaps.xml
from django.conf.urls import url
from django.contrib.flatpages.sitemaps import FlatPageSitemap
from django.contrib.sitemaps.views import sitemap
urlpatterns = [
# ...
# the sitemap
url(r'^sitemap\.xml$', sitemap,
{'sitemaps': {'flatpages': FlatPageSitemap}},
name='django.contrib.sitemaps.views.sitemap'),
]
最好把settings.py
里的APPEND_SLASH
设置为Ture
, 这样不管是/about-us
还是/about-us/
都可以访问到。
Windows10
为日常使用,笔记本Ubuntu&Windows7
,以前一直在Windows
上敲代码,现在正转向Ubuntu
,但家里桌子上摆放两台电脑已经有点挤了,如果再来两套键鼠那就成二手电脑配件甩卖铺了,所以上网查了下看看有没有软件能实现两台电脑共享一套键盘的,还真找到了一款叫Synergy
的软件,支持Windows
, Mac OS X
, Linux
三大系统,软件是下载收费,使用使用免费,官网上标明基础版$10,高级版$29。下面我说说我自己的配置过程。
]]>Windows10
为日常使用,笔记本Ubuntu&Windows7
,以前一直在Windows
上敲代码,现在正转向Ubuntu
,但家里桌子上摆放两台电脑已经有点挤了,如果再来两套键鼠那就成二手电脑配件甩卖铺了,所以上网查了下看看有没有软件能实现两台电脑共享一套键盘的,还真找到了一款叫Synergy
的软件,支持Windows
, Mac OS X
, Linux
三大系统,软件是下载收费,使用使用免费,官网上标明基础版$10,高级版$29。下面我说说我自己的配置过程。
在Ubuntu
里打开终端,输入以下命令进行安装
sudo apt-get install synergy
在Windwos
上双击安装。
Ubuntu
我安装的是1.6.2,Windwos
上是1.7.4 x64
synergy
需要一台电脑做为服务端,其它电脑做为客户端来连接服务端。
本来是我想选择Ubuntu
做为服务端的,但设置好后链接失败提示为WARNING: failed to connect to server: incompatible client 1.5
,似乎是不兼容,但我的客户端版本是1.6.2
不是1.5
啊,所以作罢,只得选用Windwos
来做服务端。
我们选用Windwos
来做服务端,在Windwos
打开软件,选择「server」;
Synergy-Windows-1.png)
点击「设置服务端」进行添加客户端操作;
Synergy-Windows-3.png)
从右上角手动电脑图标到下方的格子里,这里的格式位置对应你当前几台电脑的实际的以我把电脑图标拖到中间左侧的格子里;
Synergy-Windows-4.png)
双击电脑图标进行编辑,这里我们只需要输入客户端电脑的计算机名,其它都默认;
Synergy-Windows-5.png)
设置好后点击再次ok,回到设置首页,点击「开始」启动服务端;
Synergy-Windows-2.png)
Ubuntu
上打开Synergy
,选择「Client」,在Server IP
里输入服务端的IP,点击「Start」即可;要在Ubuntu
开机在登录界面前启动synergy
,编辑/etc/lightdm/lightdm.conf
文件添加display-setup-script=/usr/bin/synergyc 192.168.9.102
,把192.168.9.102
换成你自己的synergy
服务端IP。
到这里你就会发现你可鼠标可以在两个电脑屏幕上移动了,像我刚才配置的是在Windows
左侧添加了Ubuntu
,所以当我在Windows
上把鼠标向左移动,并移到边界,再继续左移时,鼠标就会出现在Ubuntu
屏幕上,键盘的行为跟随鼠标的,即鼠标在哪个屏幕,键盘输入就对应哪个屏幕的系统。
网上说
Synergy
支持在不同电脑间复制粘贴,目前我自己没有试成功,有知道朋友可以和说。
项目主页:https://github.com/synergy/synergy
2015-08-29 初稿
2015-09-03 补充Ubuntu
客户端自启明说
先检查系统更新,如果有则进行更新,在终端中输入以下命令
sudo apt-get update && sudo apt-get dist-upgrade
然后检查是否有可用的版本
sudo update-manager -d
回车后会弹出软件更新器,等检查完成后点击升级,后面还会出来很多个确认对话框,一路确定下去就好。
最后点击开始升级,然后就等着更新自动下载更新吧。
ubuntu-14.10-15.04-01.png)
更新完后重启系统即可完成升级。
ubuntu-15.04-info.png)
对于“XY问题”,不同的人有不同的解释:
你想做X,但你认为Y是实现X最好的方法。你不问关于X的事,反而问起Y的事。
— 来自 Re: sequencial file naming by Abigail
你尝试去做X,但你想起了Y方案。于是,你开始问关于Y方案的事,完全不提X。问题是,也许会有更好的方案,但如果你不描述X是什么,我们根本无法出谋划策。
— 来自 Re: How do I keep the command line from eating the backslashes? by revdiablo
有些人问如何去做Y,但他们实际是想做X。他们之所以问如何做Y,因为他们相信Y是实现X最好的方法。人们用各种的“试试这个”来给予帮助,而结果往往是“这不行,因为….”。这给我们提示,依赖环境的不同,你的问题可能会有其它更好的方案。
— 来自 Re: Re: Re: Re: regex to validate e-mail addresses and phone numbers by Limbic~Region
在不理解更大的问题(上下文)X的情况下,去回答问题Y,往往完全无助于解决问题X。
— 来自 m18zt5muq9.fsf_-_@halfdome.holdit.com by merlyn
也叫做“过早下结论”:有疑问的人希望能解决一些阐述的并不清楚的问题X,他们断定Y是解决方案的一个要素,于是他们就询问如何实现Y。
— from Pine.GHP.4.21.0009061210570.8800-100000@hpplus03.cern.ch by Alan J. Flavell
XY问题是指,当你需要做X时,你认为可以用Y来实现X,于是你问如何做Y,而你实际应该做的是说明你的X问题是什么。也许会有一个比Y更好的Z解决方案,但如果X没有被说出来,没有人能提出更好的建议。
— 来自 slrn89um8j.5g9.tadmc@magna.metronet.com by Tad McClellan
当有人来问如何做一些傻事时,我真的不知道如何去做。我只能照实回答,说我可不要告诉别人如何做傻事…..
但是,一旦我这样做了,人们就会蹦到我面前自作聪明。这种事情经常发生。(“别呀,帮帮这个可怜的人吧,如果你知道他们真的需要知道如何做,你干嘛不告诉他们呢?”)
. . .
另一方面,我可以从另一个层面上回答他们,给他们一个更好的方案,但这种执教也许会让他们脸上不好看。如果他们接受倒好,如果不接受,你会很伤心看到自己的努力和好建议被忽略。同样,人们会蹦到你面前指责你没有直接回答他们的问题。(“谁要你告诉他该怎么做了,你只要回答他的问题就行了。”)
. . .
我想这种两种回答方式生活中都经常会有。但也许没有一种回答会得到好结果。
— 来自 6lnb70$lct$1@monet.op.net by MJD
部分内容引用自外刊IT评论
]]>]]>通用唯一识别码(英语:Universally Unique Identifier,简称UUID)是一种软件建构的标准,亦为开放软件基金会组织在分散式计算环境领域的一部份。
UUID的目的,是让分散式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的UUID。在这样的情况下,就不需考虑资料库建立时的名称重复问题。目前最广泛应用的UUID,是微软公司的全局唯一标识符(GUID),而其他重要的应用,则有Linux ext2/ext3档案系统、LUKS加密分区、GNOME、KDE、Mac OS X等等。另外我们也可以在e2fsprogs套件中的UUID函式库找到实现。[[3]][1]
通用唯一识别码(英语:Universally Unique Identifier,简称UUID)是一种软件建构的标准,亦为开放软件基金会组织在分散式计算环境领域的一部份。
UUID的目的,是让分散式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的UUID。在这样的情况下,就不需考虑资料库建立时的名称重复问题。目前最广泛应用的UUID,是微软公司的全局唯一标识符(GUID),而其他重要的应用,则有Linux ext2/ext3档案系统、LUKS加密分区、GNOME、KDE、Mac OS X等等。另外我们也可以在e2fsprogs套件中的UUID函式库找到实现。[3]
UUID是由一组32位数的16进位数字所构成,是故UUID理论上的总数为1632=2128,约等于3.4 x 1038。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完,,它保证对在同一时空中的所有机器都是唯一的(重复机率请参考随机UUID的重复机率)。
python
中在生成UUIDimport uuid
后即可使用
示例代码
import uuid
uuid.uuid1()
uuid.uuid3(namespace, name)
uuid.uuid4()
uuid.uuid5(namespace, name)
]]>http://zh.wikipedia.org/zh-hans/%E9%80%9A%E7%94%A8%E5%94%AF%E4%B8%80%E8%AF%86%E5%88%AB%E7%A0%81
https://docs.python.org/2/library/uuid.html
Apache的rewrite模块,提供了一个基于规则的重写(rewrite,也许译为重构更为合适)引擎,来实时重写发送到Apache的请求URL。因功能极其强大,被称为URL重写的“瑞士军刀”。
这个模块使用一个基于正则表达式解析器开发的重写引擎,根据web管理员定义的规则来实时(on the fly)重写请求URL。它支持任意数目的重写规则,以及附加到一条规则上的任意数目的规则条件,从而提供了一套非常灵活和功能强大的URL处理机制。 URL处理操作的实施与否,依赖于各种各样的条件检查,如检查服务器变量、环境变量、HTTP头字段、时间戳的值,甚至外部数据库的检索结果。这个模块可 以在服务器范围内(http.conf)、目录范围内(.htaccess)或请求串(query-string)的一部分处理有关的URL。重写的结果 URL,可以指向一个站内的处理程序、指向站外的重定向或者一个站内的代理。与灵活和功能强大相随的是设置的复杂。
]]>Apache的rewrite模块,提供了一个基于规则的重写(rewrite,也许译为重构更为合适)引擎,来实时重写发送到Apache的请求URL。因功能极其强大,被称为URL重写的“瑞士军刀”。
这个模块使用一个基于正则表达式解析器开发的重写引擎,根据web管理员定义的规则来实时(on the fly)重写请求URL。它支持任意数目的重写规则,以及附加到一条规则上的任意数目的规则条件,从而提供了一套非常灵活和功能强大的URL处理机制。 URL处理操作的实施与否,依赖于各种各样的条件检查,如检查服务器变量、环境变量、HTTP头字段、时间戳的值,甚至外部数据库的检索结果。这个模块可 以在服务器范围内(http.conf)、目录范围内(.htaccess)或请求串(query-string)的一部分处理有关的URL。重写的结果 URL,可以指向一个站内的处理程序、指向站外的重定向或者一个站内的代理。与灵活和功能强大相随的是设置的复杂。
2015年05月18日 - 初稿
在http.conf
中找到
# LoadModule rewrite_module modules/mod_rewrite.so
取消注释
在http.conf
中加入下列代码(如果启用了httpd-vhosts.conf
,请在httpd-vhosts.conf
里做配置)
<IfModule rewrite_module>
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www.a.com [NC]
RewriteRule ^/(.*) http://www.b.com/$1 [R=301,l]
<IfModule>
RewriteCond
义重写发生的条件,在一条RewriteRule指令前面可能会有一条或多条RewriteCond指令,只有当自身的模板(pattern)匹配成功且这些条件也满足时规则才被应用于当前URL处理,上面代码的
NC
:不区分大小写
RewriteRule
满足^/(.*)
此规则的所有URL都重定向到http://www.b.com/$1
,$1
使用前面(.*)
匹配后的字符填充
所以前面的规则就是的最终效果是访问www.a.com
的所以页面都会被重定向到www.b.com
相应路径下的页面
]]>http://blog.chinaunix.net/uid-20639775-id-154471.html
http://man.lupaworld.com/content/manage/Apache2.2_chinese_manual/mod/mod_rewrite.html
http://httpd.apache.org/docs/current/mod/mod_rewrite.html
在网站根目录添加manifest.json
,并进行相应配置
{
"name": "iblogc",
"icons": [
{
"src": "launcher-icon-0-75x.png",
"sizes": "36x36",
"type": "image/png",
"density": "0.75"
},
{
"src": "launcher-icon-1x.png",
"sizes": "48x48",
"type": "image/png",
"density": "1.0"
},
{
"src": "launcher-icon-1-5x.png",
"sizes": "72x72",
"type": "image/png",
"density": "1.5"
},
{
"src": "launcher-icon-2x.png",
"sizes": "96x96",
"type": "image/png",
"density": "2.0"
},
{
"src": "launcher-icon-3x.png",
"sizes": "144x144",
"type": "image/png",
"density": "3.0"
},
{
"src": "launcher-icon-4x.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0"
}
],
"start_url": "index.html",
"display": "standalone",
"orientation": "portrait"
}
<link rel="manifest" href="manifest.json">
在Android使用Chrome打开网站,点击memu,选择“添加到主屏幕”选项,点击就可以添加到主屏幕了,步骤及显示效果截图如下:
PS:地址栏是不是不见了,看着像app而不是网页
ios的safari也有此功能,因手头无ios设备测试不了,所以内容不写了,大家可以参考此文章http://www.prower.cn/technic/2314
]]>https://developer.chrome.com/multidevice/android/installtohomescreen
]]>META是HTML语言中的一个标签,也称作元标记。 元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。 标签位于文档的头部,不包含任何内容, 标签的属性定义了与文档相关联的名称/值对。
META是HTML语言中的一个标签,也称作元标记。 元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。 标签位于文档的头部,不包含任何内容, 标签的属性定义了与文档相关联的名称/值对。
强制横屏/强制竖屏
<meta name="screen-orientation" content="landscape/portrait">
<meta name="full-screen" content="yes">
默认值为uc-fitscreen=no,即不启用此功能,此时浏览器的缩放行为与标准一致。当设置为uc-fitscreen=yes,则当进行缩放操作时,仅放大图片和文字等页面元素,但不放大屏幕宽度,从而避免了左右滚动条的产生。
<meta name="viewport" content="uc-fitscreen=yes"/>
Uc浏览器提供两种排版模式,分别是适屏模式及标准模式,其中适屏模式简化了一些页面的处理,使得页面内容更适合进行页面阅读、节省流量及响应更快,而标准模式则能按照标准规范对页面进行排版及渲染。通过新定义的标签及js api接口,可以让网页设计者执行决定采用何种排版方式向用户展现页面。
<meta name="layoutmode" content="fitscreen/standard" />
允许进入夜间模式/禁止进入夜间模式
<meta name="nightmode" content="enable/disable"/>
<meta name="imagemode" content="force"/>
默认将全屏,禁止长按菜单,禁止手势,标准排版
<meta name="browsermode" content="application"/>
强制横屏/强制竖屏/自动(默认)
<meta name="x5-orientation" content="landscape/portrait/auto"/>
强制全屏/跟随浏览器(默认)
<meta name="x5-fullscreen" content="true/auto"/>
普通浏览模式(默认)/网页应用模式(定制工具栏,全屏显示)
<meta name="x5-page-mode" content="default/app"/>
]]>http://www.uc.cn/business/developer/
http://open.mb.qq.com/doc?id=1201#_1
unable to run mksdcard sdk tool
sudo apt-get install lib32z1 lib32ncurses5 lib32bz2-1.0 lib32stdc++6
]]>到oracle网站下载JDK8
]]>到oracle网站下载JDK8
sudo tar xzvf jdk-8u40-linux-x64.tar.gz
mkdir -p /usr/lib/jvm
sudo mv /usr/lib/jvm jdk1.8.0_40 /usr/lib/jvm
cd /usr/lib/jvm
sudo ln -s jdk1.8.0_40 java-8
添加PATH,CLASSPATH,JAVA_HOME环境变量
gedit ~/.bashrc
在打开的窗口里添加以下内容
export JAVA_HOME=/usr/lib/jvm/java-8
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
保存退出,执行命令使配置生效
source ~/.bashrc
在有的系统中会预装OpenJDK,系统默认使用的是这个,而不是刚才装的。所以这一步是通知系统使用Oracle的JDK,非OpenJDK。
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-8/bin/java 300
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-8/bin/javac 300
sudo update-alternatives --config java
sudo update-alternatives --config javac
java -version
sudo apt-get install python-pip python-m2crypto
sudo pip install shadowsocks
sudo gedit /etc/shadowsocks/config.json
{
"server":"remote-shadowsocks-server-ip-addr",
"server_port":8883,
"local_address":"127.0.0.1",
"local_port":1080,
"password":"abcdef",
"timeout":300,
"method":"aes-256-cfb",
"fast_open":false,
"workers":1
}
请根据实际情况配置
sslocal -c /etc/shadowsocks.json
Firefox可使用FoxyProxy Standard
Chrome可使用Proxy SwitchOmega
配置请自行Google/百度
首页在http://www.geforce.cn/drivers选择相应的显卡型号下载对应的驱动,下载完成后重命令为NVIDIA.run
输入sudo /etc/init.d/gdm stop
或sudo /etc/init.d/lightdm stop
停止X server,这时桌面会消失,按Ctrl+Alt+F1进入文本模式
进入驱动所在文件夹,执行sudo sh NVIDIA.run
,安装驱动过程中会有几次对话框需要确认。
sudo /etc/init.d/gdm start
或sudo /etc/init.d/lightdm start
sudo reboot
这样NV的驱动就安装好了。
]]>2016-03-04 更新教程
2015-09-16 添加旧版软件下载
2015-02-04 初稿
鉴于国内大部分ADSL屏蔽80端口,而微信公众号开发只支持80端口,所以在本地开发测试微信公众号就是一个问题了,这里我们可以使用软件ngrok来解决这个问题。
注册成功后拿到授权码auth token
,使用ngrok时并不强制用户注册,但注册后会附加更多功能(如自定义二级域名);
让本地的‘http://127.0.0.1:80’ 可以让外网访问
ngrok http 80
ngrok会随机分配一个二级域名,可直接通过外网可通过http://xxxx.tunnel.mobi
来访问本机的http://127.0.0.1:80
网站
在ngrok.exe
目录下执行命令(不带尖括号),生成配置文件(配置文件会在C:\Users\用户名/.ngrok2/ngrok.yml
下「windows」)
ngrok authtoken <you authtoken>
修改配置文件,可配置多个tunnel(注意,配置文件是yaml格式,冒号后面如果还有内容需要加空格)
authtoken:<you authtoken>
tunnels:
# 自定义隧道名
iblogc:
#本地服务端口
addr: 4000
# 用于http/https里的身份认证
#auth: "username:password"
proto: http
# 二级域名,如果运行提示重复,换一个就行
subdomain: iblogc
django:
addr: 8000
auth: "abc:123456"
proto: http
subdomain: django
weixin:
addr: 80
proto: http
subdomain: weixin
现在执行
ngrok start iblogc
试试,如果你设置的的二级域名没有被占用的话,那么就会启动成功,否则请更换一个二级域重试。
http://iblogc.ngrok.io
https://iblogc.ngrok.io
协议均可以访问。
ngrok-start-iblogc.png)
你也可以同时启动两个tunnel
ngrok start iblogc django weixin
ngrok-start-iblogc-django-weixin.png)
因为我的django tunnel配置文件里添加了auth
配置所以访问http://django.ngrok.io
需要输入用户名密码。
ngrok-auth.png)
假设weixin
就是我本地跑在80端口的微信项目,现在就可以在微信公众平台「开发者中心」可以使用weixin.ngrok.com
进行配置了,所有发向此域名的请求都会转发到你的本地127.0.0.1:80
上。
http://127.0.0.1:4040
查看详细信息nrok-web-interface.png)
官方文档:https://ngrok.com/docs
]]>]]>Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。
Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。
git config --global user.name 'Your Name'
设置git提交显示的名字
git config --global user.email your_email@example.com
设置git提交显示的邮箱
git config --global alias.unstage "reset HEAD"
替换命令 git reset HEAD
命令改为 git unstage
ssh-keygen -t rsa -C your_email@example.com
生成SSH Key
git config --global core.editor emacs
设置文件编辑器
git config --global merge.tool vimdiff
设置差异分析工具
git config --list
查看配置信息
初始化仓库
git init
添加远程仓库
git remote add <自定义名字> <远程仓库url>
给某个仓库名再添加另一个远程仓库url(可实现一次提交到两个远程仓库)
git remote set-url --add <自定义名字> <远程仓库url>
更新项目
git pull
合并分支到当前分支
git merge <分支名>
创建标签
git tag <标签名字> <提交id前10位字符>
可通过git log
获取
获取log
git log
切换分支
git checkout <分支名>
创建分支并切换过去
git checkout -b <分支名>
删除分支
git branch -D <分支名>
推送
git push origin <分支名/标签名>
强制推送更新
git push -f origin <分支名/标签名>
推送所有分支
git push origin --all
推送所有标签
git push origin --tags
撤消本地改动(新文件和提交到缓存区的改动,不受影响)
git checkout -- <目录><文件名>
撤消本地所有提交与改动
假如你想要丢弃你所有的本地改动与提交,可以到服务器上获取最新的版本并将你本地主分支指向到它
git fetch origin
git reset --hard origin/master
其它命令
gitk
获取当前分支图形个界面
<分支名>
: 获取某分支图形界面=--all
: 获取所有分支图形个界面cat <目录><文件名>
查看文件内容初始
git init
初始化仓库
ls
显示目录下文件及文件夹(不包含隐藏文件即名字前带点的)
-a
显示目录下所有文件及文件夹git clone <url>
克隆项目
提交
git add <目录><文件名>
添加文件到版本库,可以多个文件一起添加,中间用空格隔开
git add *
或 git add .
添加所有文件到版本库
git status
查看项目当前状态,详细信息
-s
: 显示简洁版绿色表示已经提交的缓存区,红色表示在工作区未提交到缓存区的
A新增 M修改 D删除 U冲突 R重命名?
push会把绿色部分提交,红色部分不提交
已有记录文件做过改动和新文件,需要git add
git diff
查看整个项目里的文件改动情况(工作区和缓存区比较)
<目录><文件名>
: 查看单个文件改动情况(工作区和缓存区比较)<标签名>
: 查看自当前标签发布之后项目的改动情况--cached
: 查看整个项目里的文件改动情况(缓存区和本地仓库比较)HEAD
: 查看整个项目里的文件改动情况(工作区和本地仓库比较)--stat
: 显示摘要,而非完整diffgit commit
: 提交到缓存
-m
: 后面空格接提交信息-a
: 为所有已有记录文件执行git add
(新添加文件还是需要手动git add
)git reset HEAD
取消缓存已缓存的内容
<目录><文件名>
: 单个文件取消缓存已缓存内容git rm <目录><文件名>
: 将文件从缓存区和硬盘上移除
--cached
: 删除缓存中的文件,保留硬盘上的文件git mv
不推荐用
git log
显示当前分支提交记录
--author=<authorname>
: 只寻找某个特定作者的提交--oneline
: 显示简洁版
--oneline -<数字N>
: 显示简洁版,显示最近N次提交的记录--graph
: 显示拓扑图(查看历史中什么时候出现了分支、合并)--grep=<关键字>
: 根据提交注释关键字过滤提交记录Git 会对所有的 --grep 和 --author 参数作逻辑或。 如果你用 --grep 和 --author 时,想看的是某人写作的并且有某个特殊的注释内容的提交记录, 你需要加上 --all-match 选项。 在这些例子中,我会用上 --format 选项,这样我们就可以看到每个提交的作者是谁了。详细参考:Git参考手册:检查与比较
<分支名>
:显示指定分支“可及”的提交记录<分支名1> ^<分支名1>
: 查看在分支1不在分支2中的提交记录分支可以是本地的也可以是远端的
--decorate
: 显示带tag的记录-p
: 显示每个提交引入的补丁--stat
: 显示每个提交引入的差值统计--since
--before
--until
--after
git log --since --before 根据日期过滤提交记录
如果你要指定一个你感兴趣的日期范围以过滤你的提交,可以执行几个选项 —— 我用 --since 和 --before,但是你也可以用 --until 和 --after。 例如,如果我要看 Git 项目中三周前且在四月十八日之后的所有提交,我可以执行这个(我还用了 --no-merges 选项以隐藏合并提交)Git参考手册:检查与比较:
$ git log --oneline --before={3.weeks.ago} --after={2010-04-18} --no-merges
5469e2d Git 1.7.1-rc2
d43427d Documentation/remote-helpers: Fix typos and improve language
272a36b Fixup: Second argument may be any arbitrary string
b6c8d2d Documentation/remote-helpers: Add invocation section
5ce4f4e Documentation/urls: Rewrite to accomodate transport::address
00b84e9 Documentation/remote-helpers: Rewrite description
03aa87e Documentation: Describe other situations where -z affects git diff
77bc694 rebase-interactive: silence warning when no commits rewritten
636db2c t3301: add tests to use --format="%N"
分支
git branch
列出当前项目的可用分支,并显示当前工作目录当前分支
参数<分支名>
: 创建分支
git checkout <分支名>
切换到对应分支
-b
创建分支并立即切换到新分支git merge <分支名>
合并指定分支到当前分支
标签
git tag
显示当前项目的标签
<标签名>
给某个历史记录打标签-a
: 添加注解<SHA>
: 提交id前n位字符,可通过git log
获取,n位基于SHA唯一就行(建议5~7位)远程
git remote
列出远端别名
-参数-v
: 列出远端别名及链接
一般一个别名会看到两个相同的链接(fetch和push)分别是获取和推送的链接
-add <仓库别名> <仓库链接>
: 为项目添加一个新的远端仓库
rm <仓库别名>
: 为项目删除一个远端仓库只是本地删掉和远端仓库的链接,不会对远端仓库造成影响
git fetch
从远端仓库下载最新的分支与数据
git pull
从远端仓库下载最新数据,并尝试合并到当前分支
<仓库别名>
: 从哪个仓库拉取更新,默认为origin
git pull
实际是先git fetch
后git merge
git push
推送更新
<仓库别名> <分支名>
: 推送新分支与数据到某个远端仓库<仓库别名> --all
: 推送所有分支<仓库别名> --tagsl
: 推送所有标签]]>
pip freeze
or pip list
pip freeze
or pip list
pip freeze > <目录>/requirements.txt
pip install <包名>
或 pip install -r requirements.txt
# 安装1.9版本的django
pip install django==1.9
# 安装版本号大于1.9的django,注意有引号
pip install "django>1.9"
pip install "django>=1.9"
pip install "django<1.9"
pip install "django<=1.9"
pip install "django><1.9"
requirements.txt内容格式为:
APScheduler==2.1.2
Django==1.5.4
MySQL-Connector-Python==2.0.1
MySQL-python==1.2.3
PIL==1.1.7
South==1.0.2
django-grappelli==2.6.3
django-pagination==1.0.7
pip install <目录>/<文件名>
或 pip install --use-wheel --no-index --find-links=wheelhouse/ <包名>
<包名>前有空格
可简写为
pip install --no-index -f=<目录>/ <包名>
pip uninstall <包名>
或 pip uninstall -r requirements.txt
pip install -U <包名>
pip install -U pip
pip show -f <包名>
pip search <搜索关键字>
pip list -o
pip install <包名> -d <目录>
或 pip install -d <目录> -r requirements.txt
pip wheel <包名>
pip install <包名> -i http://pypi.v2ex.com/simple
在unix和macos,配置文件为:$HOME/.pip/pip.conf
在windows上,配置文件为:%HOME%\pip\pip.ini
[global]
timeout = 6000
index-url = http://pypi.douban.com/simple
]]>
pip install virtualenv
或
pip install https://github.com/pypa/virtualenv/tarball/develop
]]>pip install virtualenv
或
pip install https://github.com/pypa/virtualenv/tarball/develop
virtualenv myVE
指定python解释器
-p PYTHON_EXE, --python=PYTHON_EXE
创建虚拟环境时默认会自动安装setuptools和pip
不安装setuptool
--no--setuptools
不安装pip
--no--pip
更多Options请参考官方文档
Mac OS
cd myVE
source ./bin/activate
Windows
cd myVE
scripts\activate
启动成功后可以在开头显示"(myVE)",说明已经进入刚刚创建的虚拟环境了
deactivate
Virtaulenvwrapper是virtualenv的扩展包,用于更方便管理虚拟环境,它可以做:
- 将所有虚拟环境整合在一个目录下
- 管理(新增,删除,复制)虚拟环境
- 切换虚拟环境
pip install virtualenvwrapper
Windows下还需额外安装virtualenvwrapper-win
pip install virtualenvwrapper-win
ubuntu需要将下面这句加入到~/.bashrc
里面
if [ -f /usr/local/bin/virtualenvwrapper.sh ]; then
source /usr/local/bin/virtualenvwrapper.sh
fi
加入后需要重启才能生效,如果想要立即生效,输入命令
source ~/.bashrc
部分命令在windows下无效
workon myEnv
: 切换虚拟环境mkvirtualenv
: 新建工作环境rmvirtualenv
: 删除工作环境cdproject
: 切换到工程目录workon
/lsvirtualenv
: 列出所有虚拟环境deactivate
: 退出虚拟环境cpvirtualenv [source] [dest]
复制一份虚拟环境。cdvirtualenv [subdir]
把当前工作目录设置为所在的环境目录。cdsitepackages [subdir]
把当前工作目录设置为所在环境的sitepackages路径。add2virtualenv [dir] [dir]
把指定的目录加入当前使用的环境的path中,这常使用于在多个project里面同时使用一个较大的库的情况。toggleglobalsitepackages -q
控制当前的环境是否使用全局的sitepackages目录。https://virtualenv.pypa.io/en/latest/
http://virtualenvwrapper.readthedocs.org/en/latest/
https://github.com/davidmarble/virtualenvwrapper-win
]]>导演: 凯尔·巴尔达 / 皮埃尔·科芬
编剧: 布莱恩·林奇
主演: 迈克尔·基顿 / 桑德拉·布洛克 / 乔恩·哈姆 / 凯蒂·米克松 / 真田广之 / 珍妮弗·桑德斯 / 皮埃尔·科芬 / 克里斯·雷纳德 / Dave Rosenbaum
类型: 喜剧 / 动画 / 家庭
制片国家/地区: 美国
语言: 英语
上映日期: 2015-07-10(美国)
又名: 小黄人大电影
豆瓣链接: 小黄人
预告版:
2015-01-01 更新 GitCafe-Page IP地址
2014-11-09 初稿
虽然GitHub Pages和GitCafe Pages默认为每个用户分配了一个二级域名(GitHub为username.github.io
或username.github.com
,GitCafe为username.gitcafe.com
),但如果你对这个二级域名不满意也可以申请一个自己的域名进行绑定。下面就说说GitHub和GitCafe的绑定过程。
2015-01-01 更新 GitCafe-Page IP地址
2014-11-09 初稿
虽然GitHub Pages和GitCafe Pages默认为每个用户分配了一个二级域名(GitHub为username.github.io
或username.github.com
,GitCafe为username.gitcafe.com
),但如果你对这个二级域名不满意也可以申请一个自己的域名进行绑定。下面就说说GitHub和GitCafe的绑定过程。
CNAME
的文件(无后缀)$ hexo new "My New Post"
More info: Writing
$ hexo server
More info: Server
$ hexo generate
More info: Generating
$ hexo deploy
More info: Deployment
]]>