實用 Linux 小工具

找出 File System 中最大的檔案

1
2
3
4
5
# Type the following command at the shell prompt to find out top 10 largest file/directories:
du -a /var | sort -n -r | head -n 10
# If you want more human readable output try (GNU user only):
du -hsx * | sort -rh | head -10

Python 時間/時區處理

Python 時間/時區處理筆記

很常用到的 function,可是三不五時都要去尻以前的 Code 來用有點麻煩
乾脆直接寫在這,以後來這邊尻!

取得 timestamp 與轉換

1
2
3
4
5
6
7
8
9
10
11
12
import time
import datetime
# 取得 timestamp
ts = time.time()
ts_str = datetime.strftime('%s')
# 用 datetime 物件轉成 timestamp
time.mktime(datetime.now().timetuple())
# 轉成 datetime 物件
dt_obj = datetime.datetime.fromtimestamp(ts)

用字串轉成 datetime 物件

1
dt_obj = datetime.datetime.strptime('2016-08-20 11:21:00','%Y-%m-%d %H:%M:%S')

datetime object 變換/加減

1
2
3
4
5
6
from datetime import datetime, timedelta
new_date = datetime.now().replace(days=1, hours=2, minutes=3)
offset_dt = datetime.now() + timedelta(days=2, hours=5, minutes=30)
offset_dt = datetime.now() - timedelta(days=0, hours=5, minutes=30)

用 pytz 做時區轉換

1
2
3
4
5
6
7
8
9
10
11
import pytz
from datetime import datetime
datetime_obj = datetime.strptime('2016-08-20 11:21:00', '%Y-%m-%d %H:%M:%S')
# 先將 naive datetime datetime_obj 掛上 tzinfo
# 並取得巴西聖保羅時間 2016-08-20 11:21:00 GMT-3
br_tz = pytz.timezone('America/Sao_Paulo')
br_local_date = br_tz.localize(datetime_obj, is_dst=None)
# datetime.datetime(2016, 8, 20, 11, 21, tzinfo=<DstTzInfo 'America/Sao_Paulo' BRT-1 day, 21:00:00 STD>)

要注意 python 的 datetime.timetuple 物件並沒有包含 timezone 資訊,
所以如果想把帶有 tzinfo 的 datetime 物件轉出 utc 會有一些問題。

看看以下例子。

1
2
3
4
5
6
br_local_date.timetuple()
# time.struct_time(tm_year=2016, tm_mon=8, tm_mday=20, tm_hour=11, tm_min=21, tm_sec=0, tm_wday=5, tm_yday=233, tm_isdst=0)
br_local_date.utctimetuple()
# time.struct_time(tm_year=2016, tm_mon=8, tm_mday=20, tm_hour=14, tm_min=21, tm_sec=0, tm_wday=5, tm_yday=233, tm_isdst=0)
# 要使用 utctimetuple 才能知道這個 datetime 物件的 utc 時間

所以我們來做個實驗

1
2
3
4
5
6
7
test_date = datetime.strptime('2016-08-20 11:21:00', '%Y-%m-%d %H:%M:%S')
time.mktime(test_date.timetuple())
# 1471663260
time.mktime(br_local_date.timetuple())
# 1471663260

疑?兩個一樣捏,我不是已經把 br_local_date 掛上 tzinfo 了嗎?
我不是應該拿到台灣當地時間 2016-08-20 11:21:00 的 utc
跟巴西聖保羅當地時間 2016-08-20 11:21:00 的 utc 嗎?

因為 time.mktime 會拿你本地系統時區去轉 timestamp,不是看 tzinfo。
而 calendar.timegm 不會自動幫你加時區。
所以做法就是要先把帶有 tzinfo 的 datetime 物件轉到 utc timezone,
再透過 calendar + datetime.utctimetuple() 把正確的 timestamp 轉出來!
這個雷踩過幾次,要特別小心。

想把巴西當地時間的 2016-08-20 11:21:00 轉成 utc 的完整範例:

1
2
3
4
5
6
7
8
9
10
11
12
import calendar
import pytz
from datetime import datetime
datetime_obj = datetime.strptime('2016-08-20 11:21:00', '%Y-%m-%d %H:%M:%S')
br_tz = pytz.timezone('America/Sao_Paulo')
br_local_date = br_tz.localize(datetime_obj, is_dst=None)
br_utc = br_local_date.astimezone(pytz.timezone('utc'))
calendar.timegm(br_utc.utctimetuple())
# 1471702860 = GMT: Sat, 20 Aug 2016 14:21:00 GMT

Refs

卡西哥的文章

客製化 Django Admin Layout

最近收到了一些 Request 要把之前用 Django Admin 搞的後台做一些 improve

雖然用了 Django 一陣子可是覺得自己還是只懂皮毛而已,還有蠻多奇奇怪怪的功能都沒用過

之前就聽說 Admin 的 layout 除了常見的那些欄位跟 filter 顯示,

其他都可以客製化,可是改起來頗麻煩,剛好趁這次機會紀錄一下改法。

其實官網文件都有寫,不過我還是先從思考方向先記錄起。

從何下手

首先我從 source code 下手想看看 Admin 裡面到底是怎麼把各 page render 出來,

到底哪些東西是可以調哪些不能調,個人覺得這招搭配看文件很有用。

所以就直接殺到的 django/contrib/admin/templates/

哦!沒錯就是這邊!

Admin 的各種 template 全都放在這,看來就是從這下手了!

以我這次要改的部分叫做 submit_line.html 為例,

我想透過參數去控制 submit row 在某些 model 下會出現新的客製 action。

所以我就看到 django/contrib/admin/templatetags/admin_modify.py

發現裡面有一個 function 是來控制這件事,

先附上 code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@register.inclusion_tag('admin/submit_line.html', takes_context=True)
def submit_row(context):
"""
Displays the row of buttons for delete and save.
"""
opts = context['opts']
change = context['change']
is_popup = context['is_popup']
save_as = context['save_as']
show_save = context.get('show_save', True)
show_save_and_continue = context.get('show_save_and_continue', True)
ctx = {
'opts': opts,
'show_delete_link': (
not is_popup and context['has_delete_permission'] and
change and context.get('show_delete', True)
),
'show_save_as_new': not is_popup and change and save_as,
'show_save_and_add_another': (
context['has_add_permission'] and not is_popup and
(not save_as or context['add'])
),
'show_save_and_continue': not is_popup and context['has_change_permission'] and show_save_and_continue,
'is_popup': is_popup,
'show_save': show_save,
'preserved_filters': context.get('preserved_filters'),
}
if context.get('original') is not None:
ctx['original'] = context['original']
return ctx

看到這邊就得到我的結論:Submit row 的按鈕都可以透過 admin.py 的參數設定去做開關,但是只限於原本就有的那些 action,如果你要新增 action button 或是某些特殊邏輯才 show 的話還是要透過 overwrite 的方式去做才可以!

Overwrite admin template

從剛剛的 source code 看到我要做兩件事,自訂 template 跟 自訂 tag

自訂 template

官網文件 The Django admin site

開一個資料夾 MYAPP/templates/admin 把要 overwrite 的 template 丟進去

change_form.html

1
2
3
4
5
{% extends "admin/change_form.html" %}
{% load custom_submit_line %}
{% block submit_buttons_bottom %}{% custom_submit_line %}{% endblock %}

這邊可以發現我定義了一個 tag 叫做 custom_submit_line

並且把原本在 admin/change_form.html 內的

1
{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}

submit_row overwrite 成 custom_submit_line

custom_submit_line.html

1
2
3
4
5
6
7
8
9
10
11
12
{% load i18n admin_urls %}
<div class="submit-row">
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
{% if show_delete_link %}
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
<p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p>
{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
{% if show_something %}<input type="submit" value="{% trans 'Aloha!' %}" name="_custom" />{% endif %}
</div>

最後別忘了在 settings.py 內設定一下 TEMPLATES 的 DIR 參數

不然會噴 not found 的錯誤!

自訂 tag

官網文件 Custom template tags and filters

開一個資料夾 MYAPP/APP_NAME/templatetags 把要 overwrite 的 tag 丟進去

custom_submit_line.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import os
from django import template
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
SUBMIT_LINE_HTML = os.path.normpath(
os.path.join(CURRENT_PATH, '../../templates/admin/custom_submit_line.html')
)
register = template.Library()
@register.inclusion_tag(SUBMIT_LINE_HTML, takes_context=True)
def custom_submit_line(context):
"""
Displays the row of buttons for delete and save.
"""
opts = context['opts']
change = context['change']
is_popup = context['is_popup']
save_as = context['save_as']
show_save = context.get('show_save', True)
show_save_and_continue = context.get('show_save_and_continue', True)
ctx = {
'opts': opts,
'show_delete_link': (
not is_popup and context['has_delete_permission'] and
change and context.get('show_delete', True)
),
'show_save_as_new': not is_popup and change and save_as,
'show_save_and_add_another': (
context['has_add_permission'] and not is_popup and
(not save_as or context['add'])
),
'show_save_and_continue': not is_popup and context['has_change_permission'] and show_save_and_continue,
'is_popup': is_popup,
'show_save': show_save,
'preserved_filters': context.get('preserved_filters'),
}
if context.get('original') is not None:
ctx['original'] = context['original']
if opts.model_name == 'card':
ctx['show_something'] = True
return ctx

跟原本的長很像我只有在最後做了一些邏輯,

然後把 template 換成 custom_submit_line.html 如此而已!

這樣就完成了客製化工作啦~

後記

Trace 了 source code 之後發現幾乎每一個頁面都可以客製化,真心覺得 django admin 很方便,

幫我處理掉了很多冗事,但是它裡面的邏輯很多都設計好了,要改的話要小心把預設的邏輯改爆。

如果真的要改很大的話,也許自己寫會方便許多,按照專案需求決定會是比較好的選擇!

Reference

  1. 某個 Github Repo

SSH Tunnel 設定

有的時候因為 MySQL, Redis 放在 VPC 內,

會讓 local 開發變得很困難,還要自己裝這些 service,

不然就是要在裡面開機器開發。

不熟 Vim 的話就有頗痛苦!

所以可以透過下面這招,用 ssh tunnel 來做 port forwarding

1
ssh -fNL 6379:<redis_host>:6379 <username>@tunnel.com

這就可以把 Redis default 的 6379 port 轉進來囉!

ES Sharding 問題集

Disk-based Shard Allocation


上週公司的 ES cluster 炸裂,才發現一個有趣的問題。

一度以為是 index 過多所以才掛 shard 緩慢,但我想錯了!

就是當 ES Cluster 在分 shard 的時候,要是 data node 的 disk space 不夠會無法繼續分,

造成 shard 掛不回去。

以下兩個參數是控制關鍵。

cluster.routing.allocation.disk.watermark.low

控制 disk space 的低標。預設 85%,要是超過 85%,則不會再把 shard 分到這個 node 上。

cluster.routing.allocation.disk.watermark.high

控制 disk space 的高標。預設 90%,要是超過 90%,es 會嘗試把 shard 掛到其他 node 上。

ES 官網參考文件

透過 Virtualenv 建立 Python 開發環境

在設定 Python 開發環境的時候,為了避免各專案之間的 module 互相干擾,把系統的 pip 裝了一堆不同版本的 libs,會利用 virtualenv 來打造獨立的專案運行環境。

Mac 的安裝方式

  1. 透過 Brew 安裝 Python2
brew install python```
1
2
3
4
2. 透過 pip 安裝 virtualenv
```$ pip install virtualenv

然後你就得到 virtualenv 了!

  1. 初始化 Virtualenv (通常會在專案目錄內建立)
virtualenv venv ```
1
2
3
4
4. 進入 venv 的環境
```$ source venv/bin/activate

接下來我會習慣在每個 Python 專案內,把他的 pip-requirements.txt 寫好
比如說這樣

1
2
boto==2.13.0
Django==1.9.1

就可以透過下面的指令把專案用到的套件裝起來,
這樣就不會干擾到系統內的 pip 啦!

1
pip install -r pip-requirements.txt