顯示具有 django 標籤的文章。 顯示所有文章
顯示具有 django 標籤的文章。 顯示所有文章

星期三, 8月 19, 2020

Django site framework

 之前專案都是使用 cookiecutter + cookiecutter-django 來產生的,前一陣子在它產生出來的設定裡看到 SITE_ID 以及註解,就發現了 Django site framework

Django site framework 裡的 site ,就有點像是 virtual host 裡的 host,簡單的說,可以做到以下事情:

  • Virtual host:Django 可以有能力去判斷網址裡的網域,來決定如何處理。
  • 不同網站入口,但共用資料庫

啟用方法

基本上 site framework 已經內建在 Django 裡了,所以只要啟用就可以

  1. 在 settings 的 INSTALLED_APPS 加入 “django.contrib.sites”
  2. 增加 SITE_ID  (選填,可以加這個設定,也可以不加)

執行 migrate python manage.py migrate

接著利用 admin 後台或者是 shell 去新增 site。

功能

有提供以下功能

  • 物件 (model) 跟指定的 site 關連
  • View 可以依據 site 來做出不同的處理跟回應
  • 多個 site,但只使用指定的 site
  • 自動依照網址內的網域來判斷,並將 site 放到 request 裡
物件 (model) 跟指定的 site 關連

site framework 本身有建立 model ,也就是說有建立資料表格,所以其實是可以讓物件模型跟 site 做關連,那麼之後在處理時,就可以依據這個關係,只顯示出跟指定 site 相關連的物件。

# 引用自 django 文件
from django.contrib.sites.models import Site
from django.db import models

class Article(models.Model):
    headline = models.CharField(max_length=200)
    # ...
    sites = models.ManyToManyField(Site)

上面的程式碼,就是表示 Article 跟 Site 做多對多的關聯。之後在 View 裡就可以用下面的程式碼撈出跟指定站點相關連的 Article

# 引用自 Django 文件
from django.contrib.sites.shortcuts import get_current_site

def article_detail(request, article_id):
    try:
        a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id)
    except Article.DoesNotExist:
        raise Http404("站點裡沒有文章")
    # ...
View 可以依據 site 來做出不同的處理跟回應

其實上面的程式碼已經就是了,這邊再舉個例子

# 引用自 Django 文件
from django.contrib.sites.shortcuts import get_current_site

def my_view(request):
    current_site = get_current_site(request)
    if current_site.domain == 'foo.com':
        # Do something
        pass
    else:
        # Do something else.
        pass

這邊使用 site framework 提供的 shortcut – get_current_site 來取得目前的 site,然後用 if – else 來做判斷,執行不同的邏輯

多個 site,但只使用指定的 site

那程式裡已經有多個 site,但是想拆分出來,只服務單一個 site 時,可以在 settings 裡指定 SITE_ID,程式裡可以直接引用,例如

from django.conf import settings

def my_view(request):
    if settings.SITE_ID == 3:
        # Do something.
        pass
    else:
        # Do something else.
        pass

這個蠻適合應用在 docker 上,我只要打包好 docker image,之後就可以再利用指定環境變數的方式去讓這個 container 能處理指定的 site

自動依照網址內的網域來判斷,並將 site 放到 request 裡

要完成這個功能,需要在 settings 裡的 MIDDLEWARE 裡,加入 “django.contrib.sites.middleware.CurrentSiteMiddleware”

加入以後,View 裡的 request 就會多出一個 site 的屬性,那在 View 裡,就可以直接使用 request.site

Site framework 內部的運作

依據上面的說明,除了資料庫以外,get_current_site 好像…挺重要的,下面就來繼續挖掘。

我們先從 middleware 開始,CurrentSiteMiddleware 裡面蠻簡單的,只有 override process_request(),裡面只有一行:request.site = get_current_site(request)

!! 又是 get_current_site()

那 get_current_site() 又做了什麼事情?get_current_site() 只判斷 django.contrib.sites 有沒有在 settings INSTALLED_APPS 裡,有的話,就使用 Site.objects.get_current() 來取得目前的 site;沒有的話,改用 RequestSite 類別來判斷,RequestSite 只是一個封裝,封裝出類似 Site 的物件,讓你後續存取可以跟原來的 site 物件一樣。

    if apps.is_installed('django.contrib.sites'):
        from .models import Site
        return Site.objects.get_current(request)
    else:
        from .requests import RequestSite
        return RequestSite(request)

先看 Site.objecs.get_current() ,這函式的實作是在 django/contrib/sites/models.py 裡。裡面也很簡單,先去看 settings 裡有沒有 SITE_ID,有的話,就用 SITE_ID 去查詢資料表格,取出對應的 site;沒有 SITE_ID 或者是 SITE_ID 被判定為 False (‘’, 0 都算是 False),就改使用 _get_site_by_request() 從 request 去做判讀。

_get_site_by_request() 使用 request.get_host() 取出 host,然後解析出 domain/port,再使用 domain 去查詢資料表格,找到對應的 site。

總結

以上,就是 site framework ,為了驗證我對 site framework 的認知,製作了一個小的展示專案,放在 github 上:https://github.com/elleryq/site_framework_demo

星期三, 1月 08, 2020

Django開發者常犯的7個錯誤

從這篇 7 common mistakes that django developers make 整理出來的
  • Reinventing the wheel
    • 儘量找人家已經寫好的 package 來用,不要沒事自己在那邊重新刻
  • Monolith application structure
    • 要切 app ,該切就切。
    • 我自己目前有比較 confuse 的是,有用到其他 app 的東西時,該怎麼降低耦合度,又能使用方便,這中間去找到一個平衡點。這邊要等以後再來慢慢想怎麼做會更好了。
  • Writing fat representations and thin models
    • View 不要寫太多邏輯
    • 把邏輯抽到 module 或是 model 裡,讓邏輯統一,才能儘量重複使用。
  • Too many queries per view, or unoptimized queries
    • 一個 view 有太多資料庫查詢或是沒有將查詢最佳化,這個會導致速度變慢。
    • 我建議這邊同時可以考慮儘量使用 cache ,來減輕資料庫負擔。
  • Redundant model fields
    • 思考是不是要有真實的資料庫欄位,思考是不是可以使用 property 來做到同樣的事情。
  • Not adding indexes on models
    • 常用來查詢的欄位 (放在 filter 裡的) 記得加 index :db_index=True,該加就加。
  • Inconsistent data validation
    • model 的檢查跟 form 的檢查要一致。

星期四, 10月 31, 2019

從 IP 取得 Country / City / 經緯度

使用的環境是 Ubuntu 16.04 + Django 2.2,先處理 geoip 資料庫。
sudo apt-get install geoipupdate
安裝完以後先填設定,編輯 /etc/GeoIP.conf ,這邊要注意 geoipupdate 版本,2.5 之前的話,AccountID 要改成 UserId,EditionIDs 要改成 ProductIds ,Ubuntu 16.04 用的是 2.5 之前的版本:
# The following AccountID and LicenseKey are required placeholders.
# For geoipupdate versions earlier than 2.5.0, use UserId here instead of AccountID.
# AccountID 0
UserId 0
LicenseKey 000000000000


# Include one or more of the following edition IDs:
# * GeoLite2-City - GeoLite 2 City
# * GeoLite2-Country - GeoLite2 Country
# For geoipupdate versions earlier than 2.5.0, use ProductIds here instead of EditionIDs.
# EditionIDs GeoLite2-City GeoLite2-Country
ProductIds GeoLite2-City GeoLite2-Country
執行 sudo -H geoipupdate,檔案會下載到 /var/lib/GeoIP
接著在 Django 環境下安裝 geoip2
pip install geoip2
然後先在 shell 裡試試看:python manage.py shell
from django.contrib.gis.geoip2 import GeoIP2
g = GeoIP2('/var/lib/GeoIP')  # 將路徑指過去
print(g.country('59.120.21.9'))
print(g.city('59.120.21.9'))
print(g.lat_lon('59.120.21.9'))
一般來說,用 city(“your_ip”) 就可以拿到足夠的資訊了
{'city': 'Taipei',
 'continent_code': 'AS',
 'continent_name': 'Asia',
 'country_code': 'TW',
 'country_name': 'Taiwan',
 'dma_code': None,
 'latitude': 25.0478,
 'longitude': 121.5318,
 'postal_code': None,
 'region': 'TPE',
 'time_zone': 'Asia/Taipei'}
參考資料

星期一, 9月 02, 2019

Using curl to upload file to Django without csrf_exempt

簡單的說就是利用 cookie 來取得必要的 CSRF TOKEN 資訊。
#!/bin/sh

ENDPOINT=http://localhost:8000/uploads/simple/
COOKIE_JAR=cookies.txt
# 是否要讓 curl 顯示詳細的資訊 (含 header 等)
# VERBOSE=-v
VERBOSE=

# Get
# 用 -c 存到 cookies.txt
curl \
    $VERBOSE \
    -c $COOKIE_JAR \
    $ENDPOINT

# Get CSRF token from cookies.txt
# cat $COOKIE_JAR
CSRFTOKEN=$(awk '/csrftoken/{print $7;}' $COOKIE_JAR)
echo "CSRFTOKEN=$CSRFTOKEN"

# POST 時,在標頭 X-CSRFToken 帶入剛剛找到的 CSRF token
curl \
    $VERBOSE \
    -X POST \
    -b $COOKIE_JAR \
    -H "X-CSRFToken: $CSRFTOKEN" \
    -F myfile=@image003.png \
    $ENDPOINT

星期二, 6月 25, 2019

簡略的看 Tastypie

跟 DRF 不一樣,Tastypie 以 ModelResource 為主,埋下 Resource 時,就是完整的 LIST/CREATE/UPDATE/DELETE。
Resource 的 model 不一定要是 django model,也可以是自訂的 resource:https://django-tastypie.readthedocs.io/en/latest/non_orm_data_sources.html
  • Authentication 蠻多的,Basic/ApiKey/Session/Digest/OAuth/Multi 都有,OAuth 有內建。
  • Authorization 是指定允許的動作,像是 read_list / read_detail / create_list …. 等等。
  • Serializer 跟 DRF 有點不一樣,這邊僅指輸出的格式,DRF 主要是指輸出哪些欄位。
  • Throttling 可指定一秒內能呼叫的次數
  • 有支援 Paginator 翻頁。
  • 支援 GeoDjango!! 這倒是很方便,GeoDjango 看來是值得花時間來研究的。
缺點是,github 的活躍度不是太高,上次的更新是4個月前 (2018/9)。
跟 DRF 的比較可以參考這篇:https://stackshare.io/stackups/django-rest-framework-vs-tastypie

星期五, 5月 31, 2019

Django jsonfield

Django 有提供 jsonfield,但只能用在 postgresql 上,有另外一個專案 django-mysql 提供了可以用在 MySQL 上的 jsonfield。是的,就目前來說,並沒有一個通用的 jsonfield。惟一能找到的,就這個 django-jsonfielddjango-jsonfield 的網站上也有特別提到這件事情,並且說 django-jsonfield 只繼續維護,不再開發,因此不建議使用這個專案。但是就跨資料庫來說,目前好像也就這個專案可用。
我有想過要依照 DATABASE 設定來區分要使用哪個 jsonfield,但仔細想想,這最大的問題可能會出在 Migration,因為 Migration 裡會直接引用 jsonfield 。migration 裡沒辦法讀取到 django settings ,也就沒辦法做到動態的處理。不過,可能還是要試試看才知道行不行。

星期五, 4月 12, 2019

django stronghold

網址:https://github.com/mgrouchy/django-stronghold
這個 package 蠻好玩的,django 預設的 view 都是 public 的,得加上 LoginRequired decorator 或繼承 LoginRequiredMixin 才能限制只有使用者能用,但 stronghold 是反過來,在 middleware 加上 LoginRequiredMiddleware,強制所有的 view 都是 LoginRequired,只有加上 public decorator 或繼承 StrongholdPublicMixin 的才是 public。
對一個都需要驗證後才能使用的系統,這倒是方便許多。

星期一, 7月 30, 2018

Celery log 出現 Received and deleted unknown message. Wrong destination

在查 Periodic task 為什麼沒執行,beat 是有發出訊息,但 task 卻沒被執行。在 worker log 裡找到
Received and deleted unknown message. Wrong destination
的訊息,查了以後,找到這些資料:
所有的矛頭都指向 librabbitmq ,所以解法有兩種,一種是移除 librabbitmq,一種則是將 protocol 改為 1
 CELERY_TASK_PROTOCOL = 1

星期四, 5月 10, 2018

Django 節省記憶體的一些紀錄之二

這篇會順帶提一些提高效能的紀錄。

strftime

datetime.strftime 可以將日期時間格式化為需要的字串,但是,在經過 profiling 以後,我發現呼叫這個函式相當的花時間。在網路上搜尋以後,發現有人在 stackoverflow 上問相似的問題,有人回答說改用 python 的 string format 就可以大大的提高效能。
# 原作法
from datetime import datetime
dt = datetime.now()
dt.strftime("%Y/%m/%d")  # slower
"{:04d}/{:02d}/{:02d}".format(dt.year, dt.month, dt.day)  # Fast!!

JSONEncoder.iterencode

這是在Use StreamingHttpResponse by default for JSON 這個 gist 上看到的,裏面使用了 JSONEncoder.iterencode 搭配 StreamingHTTPResponse 處理。一般來說,在輸出為 JSON 時,都是整個物件或資料丟給 json.dumps(),但這樣在處理大量資料的情況時,其實是有可能佔用大量記憶體的。Python json 模組的 JSONEncoder 提供了 iterencode() 函式,iterncode() 會回傳 generator 回來,之前有提到使用 generator 可以確保在使用到的時候,才將值回傳出來,可以避免佔用過多的記憶體。再加上 StreamingHTTPResponse/HTTPResponse 的 content 都支援使用 generator,這樣就可以節省大量記憶體了。

日期時間時區的轉換

本來我是使用 Arrow 在處理時區的轉換,但是,在 profiling 以後,發現這個步驟會花掉蠻多時間,於是看過 Arrow 的原始碼以後,發現 Arrow 只是使用 python datetime 模組裡面的函式在做,所以將原本時區轉換的部份改寫掉,就大幅提升速度了。

from arrow import Arrow
from dateutil import tz
from django.utils import timezone

dt = timezone.now()  # utc time
new_timezone = tz.gettz('Asia/Taipei')  # get local timezone
new_local_time_1 = Arrow.fromdatetime(dt).to(new_timezone).datetime  # slower
new_local_time_2 = dt.astimezone(new_timezone)  # Fast!!

星期三, 4月 11, 2018

Django 節省記憶體的一些紀錄

前一陣子在改寫程式,避免使用過多記憶體時的一些紀錄。

pympler


pympler 很好用,主要是用來察看記憶體用量。缺點是得自己安插 code 觀看,之後再移掉。文件裡有不少用法,我只用到兩種。
第1種用法是 asizeof.asizeof(obj) ,這可以看物件使用的記憶體用量。

from pympler import asizeof
obj = {'foo': 'bar'}
print(asizeof.asizeof(obj))
第2種用法是 summary/muppy,這可以察看一段程序執行後的記憶體差異。

from pympler import summary, muppy
sum1 = summary.summarize(muppy.get_objects())
# do works
sum2 = summary.summarize(muppy.get_objects())
diff = summary.get_diff(sum1, sum2)
summary.print_(diff)
# 要輸出到 logging 的話,可以用 format_ 或 _format_table
# for line in summary.format_(diff):
#   logger.debug(line)
這段根據文件也可以使用 tracker.SummaryTracker() 來代替。
參考資料:

yield / generator

函式如果要傳回大量的資料,使用 yield ,這可以避免佔用大量記憶體,只有在 iterate 時,才一次一次拿回來。

def foo():
  for i in range(100000000):
    yield i  # 這讓 foo 的傳回結果變成 generator 物件

for x in foo():
  print(x)

Django Queryset

可以的話,儘量使用 values() 跟 values_list() 並指定所需的欄位,除了可以提升速度之外,也減少記憶體用量。
values() 是將記錄作為一個 dict 傳回,而 values_list() 則是將紀錄作為一個 tuple 傳回。這樣可以省去還原為 Django model object 的程序,因此速度會更快,記憶體使用的更少。而使用 values_list() 比 values() 來的更省,這是因為 tuple 所佔用的記憶體比 dict 來的少。

星期三, 3月 28, 2018

Using jinja2 as django template engine

Django 1.8 以後已經可以選擇使用自家的 template engine 或是使用 jina2 template engine 了,而且是可以混用,不限制你只能使用其中一種。
第一步是先安裝 jinja2:
pip install jinja2
第二步是在 settings 裡的 TEMPLATES 裡增加 jinja2 的設定:
TEMPLATES = [
    # 省略 django template 的設定
    {
        'NAME': 'jinja2',
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': [
            # insert more TEMPLATE_DIRS here
            # join(BASE_DIR, 'templates'), 
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'environment': 'your_project.jinja2.environment',
        },
    },
]

第三步,是在 your_project 下建立 jinja2.py ,內容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import    # 使用 absolute_import 很重要!!!
import logging
from django.utils import translation
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
import jinja2


logger = logging.getLogger(__name__)


def environment(**options):
    env = jinja2.Environment(
        extensions=['jinja2.ext.i18n', 'jinja2.ext.with_'],
        **options)
    env.globals.update({
        'static': staticfiles_storage.url,
        'url': reverse,
    })
    env.install_gettext_translations(translation)    # 使用 django 的 i18n/l10n
    return env
在這裡,我遇到一個奇怪的狀況,python 2 一直告訴我 jinja2 沒有 Environment,後來看了這篇,才知道是 absolute/relative import 問題,使用 absolute_import 以確保 import 的 jinja2 是正確的,而不是 django 的 jinja2。我想,python 3 應該不會有這問題。
django template 是使用 load 載入模組,jinja2 則需要在 Environment 初始化時就定義。
第四步,是在繼承 TemplateView 的類別裡,加入
template_engine = 'jinja2'
,然後在 your_app 下建立 jinja2 路徑,將 template 檔案放在那裡即可。

class Jinja2Test(TemplateView):
    template_name = 'jinja2test.html'  # template file 的名稱是 jinja2test.html ,檔案必須放在 your_app/jinja2 下。
    template_engine = 'jinja2'  # 表示要使用 jinja2 template engine,這個 jinja2 是定義在 settings 裡的 NAME

    def get_context_data(self, **kwargs):
        ctx = super(Jinja2Test, self).get_context_data(**kwargs)
        ctx['foo'] = 'bar'
        ctx['hello'] = 'world'
        return ctx
參考資料:

星期二, 3月 20, 2018

如何客製 Django package 的 locale?

在 Django 裡使用了某個 package,但該 package 沒有自己所用的 locale 時,該怎麼做呢?
以下以 django-mptt 為例:
  1. 先確認 site-packages 下 django-mptt 的目錄名稱,在安裝 django-mptt 以後,實際上是 mptt,所以在專案目錄下建立 mptt/locale 。
  2. 到 site-packages/mptt 下,執行 django-admin makemessages --locale=zh_TW
  3. 將 site-packages/mptt/locale/zh_TW 搬移到專案目錄下的 mptt/locale
  4. 使用 poedit 或其他工具進行翻譯
  5. 調整 django settings 裡的 LOCALE_PATHS,例如:
    from os.path import dirname, join
    BASE_DIR = dirname(dirname(dirname(__file__)))
    # ...略...
    LOCALE_PATHS = [
        join(BASE_DIR, 'locale'),
    
        # customize translations which package is in site-packages
        join(BASE_DIR, 'mptt', 'locale'),
    ]
    
這樣就大功告成了。

星期日, 9月 24, 2017

Python profiling decorator

前幾天想知道我 Django 程式裡某段函式的瓶頸,所以查了 Django 怎麼做 profiling 。是有查到有 jazzband/silk: Silky smooth profiling for Django 這個 package ,但是有點太大。如果是用常找到的
python -m cProfile xxx.py xxx.py
,Django 程式又不太適合。畢竟我只是想查某個函式而已。所以後來查到這兩個 decorator:
使用上很簡單,程式放進去,在想做 profiling 的函式前加上 decorator 就可以了。
我用的是第一個,在加上 decorator,執行過程式(應該說是瀏覽網頁)以後,在程式當前資料夾裡會找到 .profile 的檔案,為了後續方便說明,假設產生出來是 func.profile 。有這個檔案以後,就可以用
python -m pstats func.profile
開啟,開啟以後,是 pstats 的 shell ,一般要查哪個地方花的時間最多,會用
sort time
依照執行花費時間來排序,再用
stats 10
列出前十個花費時間最多的函式。
參考資料:

星期四, 5月 18, 2017

升級 daphne 到 1.2.0 後導致連線卡住

使用 django channels 得使用 daphne 來處理 asgi (websocket/long poll http) 請求,我遇到的情況是從 1.1.0 升級到 1.2.0 以後,整個 django 應用就無法接受服務。
查了很久,看程式也覺得應該沒問題,該處理的 request ,daphne 都有轉送給 channels 去處理,而 channels 也都有處理。心裡想,這應該是個大問題,早該有人回報吧。但用 google 查詢都沒查到,今天心血來潮查了 Daphne 在 github 上的 closed issues,總算是找到了:daphne==1.2.0 hangs forever · Issue #105 · django/daphne
解法也很簡單,升級 asgi_redis/asgi_ipc 到新版就可以了。下次升級 daphne 時,得注意。

星期二, 4月 18, 2017

Django queryset 對日期時間欄位的額外設定

Django queryset 對日期時間的處理已經很完備了,可以透過使用 __year 或 __month 等方式來找到是某年或某月的紀錄。
這兩天碰到的狀況是,資料是 MySQL 時,日期時間的比對 (__year / __month) 失效了。仔細看過文件以後,才發現 MySQL 需要事先設定,使用 mysql_tzinfo_to_sql 載入時區表格才行。
This function performs time zone conversions directly in the database. As a consequence, your database must be able to interpret the value of tzinfo.tzname(None). This translates into the following requirements:
在終端機 (shell) 裡,輸入下列指令:
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
接著重新啟動 MySQL 伺服器即可。
mysql_tzinfo_to_sql 的用法不只一種,我選擇的是最簡單的用法。

星期三, 1月 13, 2016

Automate Django createsuperuser in Ansible

Ansible 有個 django_manage 模組,可以很方便的執行 django 裡的 manage.py,但是受限於 createsuperuser 的關係,並沒辦法在建立 superuser 的同時,一併設定密碼。
一般網路上的解決方法是自己寫個小 python 腳本 (可以看這篇 How to automate createsuperuser on django? ),丟給 shell 去執行。我是想到可以利用 manage.py 提供的 changepassword 並搭配 expect 來做,大致上是這樣子:

另外也做了避免重複建 superuser 的機制。

星期一, 11月 30, 2015

Django model 的 validate

同事問,為什麼用 model 建立 instance 以後,呼叫 save 沒有觸發 validate?
上網找了一下,大致有兩種方法:
  1. 用 ModelForm 的 is_valid() 做檢查,Correct Way to Validate Django Model Objects?
  2. 覆寫 model 的 save(),Django model mixin to force Django to validate (i.e. call `full_clean`) before `save`
事情沒有這樣結束,因為很好奇 Django 的作法,就去追 Code。
如果是走 is_valid() :
  1. forms/models.py:ModelForm 繼承自 BaseModelForm,而 BaseModelForm 又繼承自 BaseForm,這個檔案裡,沒有看到 is_valid
  2. forms/forms.py:
    1. BaseForm.is_valid() 很簡單的只回傳 self.is_bound and not self.errors ,self.is_bound 跳過不看,self.errors 是一個 property,裏面呼叫了 self.full_clean()
    2. self.full_clean() 呼叫了 self._clean_fields(), self._clean_form() 與 self._post_clean() ,關鍵在 self._post_clean() 。BaseForm 裡的 _post_clean() 是空的,讓繼承 BaseForm 的類別去決定。
  3. forms/models.py: 回頭看 BaseModelForm._post_clean(),這裡呼叫了 self.instance.full_clean() ,並處理 ValidationError 。instance 就是 model 類別所創出的實體,Model 繼承自 ModelBase 所以接著看 ModelBase 類別。
  4. db/models/base.py: ModelBase 的 full_clean() 呼叫了 self.clean_fields(), self.clean() 與 self.validate_unique ,並且在這裡處理了 ValidationError 例外,最後判斷了 errors 裡是否有東西,有的話,丟出 ValidationError。先看 self.clean_fields() ,函式裡去取得定義在類別裡的欄位,並且呼叫每個欄位 (Field) 類別的 clean() 函式。如果有錯誤就丟出 ValidationError()
  5. db/models/fields/__init__.py:Field 類別定義在這個檔案裡,裏面的 clean() 呼叫了 self.validate() 、self.run_validators()
至此,ModelForm 的路徑就確定了。
來看看第二個方法,他是要求 Model 類別除了繼承 Django Model 之外,再繼承 ValidateModelMixin 這個自訂類別以覆寫 save() ,看看裏面的 save(),就只是多呼叫了 full_clean() 來檢查,也就是走上述步驟 4 以後的路線。
Django 官方網站不建議直接去呼叫 full_clean() 這件事,不過找了一下,也沒找到什麼更好的解法~
P.S.

星期三, 7月 04, 2012

django-navbar

django-navbar 簡單說就是提供 navigation 的解決方案,類同於 ASP.Net 裡的 sitemap。
使用方法不難,但也讓我摸索了一陣子,這邊記錄一下:
  1. INSTALLED_APPS 加入 navbar
  2. TEMPLATE_CONTEXT_PROCESSORS 要加入 "navbar.context_processors.crumbs", "navbar.context_processors.navbar", "navbar.context_processors.navtree", "navbar.context_processors.navbars",預設原來的 settings 裡並沒有 TEMPLATE_CONTEXT_PROCESSORS,所以要自己加。但不是只加入這4個就好,而是還要加入原來的幾個
  3. 要 syncdb ,這會加入兩個 table。
  4. admin 裡會多出 NavBarEntry 的項目設置,注意:在 admin 設置完成以後,要重新 runserver,這是因為 navbar 是把資料預先塞到 Context Processor 裡,只有一開始才會去資料庫撈資料。
  5. 樣板有好幾個用法,但都要先 {%load navbar%},這可以參考 navbar/templates/navbar/ 下的檔案來進行使用。
  6. 有提供 breadcrumbs (就類似 "首頁 / 大功能 / 小功能" 這樣),但是沒有提供樣板,你可以自己刻,變數是在 crumbs 裡,用 {{ crumbs }} 就可以看到。

星期二, 6月 26, 2012

django-grid

django 裡面怎麼好像沒有內建的 grid 產生器? 沒有在 admin app 裡找,後來是找到這個 django-grid ,看起來用法很簡單,就用了。一開始是照著用,然後還去改了他原來的 template,後來才想到應該在自己要用到 grid 的 template 裡用 {% include 'grid/object_list.html' %} 才對,這樣就可以不用更動到原來的 template。
grid 裡已經有處理分頁的部分,官方的文件沒提到的幾點:
  1. 自訂 column 的標題:如果你的 model 裡已經有加上去的話,它會自動去抓,但是如果是自訂的欄位,就要這樣用:columns = ( ('field1', '欄位1'), ('field2', '欄位2'), )
  2. 刪除:grid 有定義這個,在定義 columns 的時候,要使用 __delete__,例如: columns = ( ('field1', '欄位1'), ('field2', '欄位2'), ('__delete__', '刪除'), )
  3. 自行定義欄位:要額外增加 custom_columns,其形態是 dict,grid 在遇到裡面指定的元素時,會改用你定義的 template 去顯示,來達到自定義的效果:custom_columns={ 'field1': 'app/custom_field1.html' }
  4. l10n:grid 沒有做這部分,這就要自己去改 template 了。

總結範例:
from grid import Grid
class MyGrid(grid):
  model = your_model
  columns = ( ('field1', '欄位1'), ('field2', '欄位2'), )
  sortable = ('field1',)
  custom_columns={ 'field1': 'app/custom_field1.html' }

星期五, 9月 02, 2011

Windows 上的 python + virtualenv + django

安裝上並不難,只是最後有卡到一個問題,特別提出來分享一下。
  1. 下載python 2.7安裝檔,然後安裝
  2. 下載setuptools安裝檔,然後安裝
  3. 打開命令提示字元,先 cd /d c:\python27\scripts ,再 easy_install virtualenv,你公司有 proxy 的話,記得先 set http_proxy=http://your_proxy:port
  4. 安裝好 virtualenv 以後,就可以去創建環境了,假設你的環境都在 c:\envs,那就先 cd /d c:\envs ,再 virtualenv --no-site-packages myenv ,這樣就建好環境了
  5. 啟動你的環境,要先 cd /d c:\envs\myenv ,再輸入 scripts\activate 。Linux 下的activate 是放在 bin 下,這是比較大的不同。
  6. 安裝 django:scripts\pip install django
  7. 使用 django 指令:scripts\django-admin.py startproject myproject ,嗶嗶嗶,卡住了。
哈,我就卡在這裡。django-admin.py 會說找不到 django.core ,我苦思了好久,終於才想到,Windows 下是不鳥 python script 第1行的 sha-bang 的啊,所以雖然 django-admin.py 的第1行指定了說要用 c:\envs\myenv\scripts\python.exe 來處理,可是實際上卻是 .py 註冊在 registry 裡的 c:\python27\python.exe 在處理。因此會有找不到 django.core 的情況,改用 scripts\python scripts\django-admin.py 就可以順利找到 django.core 了。