Djangoやってて忘れっぽいところとかをメモ

forms.DateFieldでISO-8601フォーマットの入力を受入れる

html5でdatetime-localとか扱おうとすると、入力値がISO-8601形式になって、そのままだとDateFieldが入力をdatetimeの正しい形式として認識してくれない。

で、input_formatsを設定するといけるらしいんだけど、あまりマニュアルでちゃんと説明してない。Stack Overflowの情報とかをググると設定方法が間違ってるコードとかあって、正しいのが良く分からない。で、結局色々調べたら

# models.py
date = forms.DateField(input_formats=('%Y-%m-%dT%H:%M:%S',))

とタプルで渡す必要があるらしい

参考:うえちょこ@ぼろぐ» [Django]forms.DateFieldのinputs_formatではまった

ModelForm使ってフォーム出力する時にrequeied属性をテンプレートで参照するとか

# models.py
hoge = forms.CharField(max_length=100,widget=TextInput(attrs={"required":"true"}))

こんな感じで定義したフィールドの属性取ってきたい場合、

\{\% for field in form \%\}
  \{\% if field.field.required \%\}<p>必須</p>\{\% endif \%\}
\{\% endfor \%\}

適当だけど、こんな感じでアクセスできる。

ModelFormでcleanを使ってデータの検証する時に、updateしようとしてるかcreateしようとしてるか判断する

createの時だけチェックしたかったので、色々調べてみたらclean(self)内で、self.instanceでチェックすると良いみたい。例えばプライマリーキーでidがある場合こんな感じ

# models.py
class Hoge(forms.ModelForm):
    def clean(self):
        cleaned_data = self.cleaned_data
        if self.instance.id is None:
            #create用の処理
            raise ValidationError('例外出すのはこんな感じ')
        else:
            #update用の処理
        return cleaned_data

instanceの型チェックする方が正攻法かも。例外のメッセージは

# views.py
def test(request):
    form = Hoge(request.POST)
    if form.is_valid():
        #例外が無かった場合
    else:
        for k,v in form.errors.item():
            #kが例外のフィールド。cleanの場合は__all__
            #vが例外のメッセージ

みたいな感じで取り出せる。

runserver実行時にコンソールにスタックとレースを出力する

# settings.py
LOGGING={
    'version':1,
    'handlers':{
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers':{
        'django.request':{
            'handlers':['console'],
            'propagate':True,
            'level':'DEBUG',
        }
    },
}

これを書いておけばコンソールにスタックトレースを出力してくれる。apiのコード書いてる時とかajaxで呼び出してる場合とかに便利。

参考:python - Print a stack trace to stdout on errors in Django while using manage.py runserver - Stack Overflowというかここのコード丸コピー

Gmail送信

# settings.py
EMAIL_USE_TLS=True
EMAIL_HOST='smpt.gmail.com'
EMAIL_HOST_USER='[email protected]'
EMAIL_HOST_PASSWORD='mypassword'
EMAIL_PORT=587
# views.py
from django.core.mail import send_mail

def mail(request):
    send_mail('Subject','message','mymail.gmail.com',['[email protected]'],fail_silently=False)

こんだけ

参考:DjangoでGmailを使ってメール送信 - brainstorm

django+uwsgi+nginxで502エラーが出た原因の1つ

djangoはあまり関係無いけどuwsgiの設定の問題だったので、こちらに。django+uwsgi+nginxで502エラーが出て、runserverでは普通に動いたので、しばらくググって解答が得られたのでメモ

DataTablesを使って動的にテーブルデータをロードしようとしたら、runserverでは上手く行くのにuwsgiだと502エラーが出てロード出来なかった。最初にnginxのログを見てみると、uwsgiのとこでエラーと書いてあったので、uwsgiのログをチェックした。すると

invalid request block size: xxxxx (max 4096)...

みたいなエラーが出ていた。リクエストが大きすぎるのかなと思い、リクエストの一部を削ってみたらエラーが出なかった。んで、対策をググってみたら書いてあったので、そのまま下のコードを実装してみた。

# uwsgi.ini
buffer-size=65535

みたいにバッファーサイズを大きく(例では64k)した。これでエラーが消えたのでめでたしめでたし。

参考:django - Nginx uwsgi (104: Connection reset by peer) while reading response header from upstream - Stack Overflow

運用環境と開発環境で設定を切り分ける

開発環境用にlocalアプリを作って、local/settings.pyを作成し、開発環境での設定を書いて、プロジェクトのsettings.pyの最後に以下を追加すると良い

try:
    from local.settings import *
except ImportError:
    pass

参考:パーフェクトな Django の設定ファイル -- Kosei Kitahara's Blog

テンプレートの読み込まれ順

  • TEMPLATE_DIRSで指定されたディレクトリの順番で、そこを起点にした全てのテンプレート
  • INSTALLED_APPSに指定した順にアプリケーション直下のtemplatesディレクトリを調べる

hogeアプリでテンプレートを指定した場合に、hogeアプリ直下のtemplatesディレクトリを調べてくれる訳ではないので注意が必要。

例えば

TEMPLATE_DIRS = ['/home/hoge/templates',]
INSTALLED_APPS = (
    'hoge',
    'fuga',
)

と書いてあり、プロジェクトが/www/a_project/に置いてある場合、

  • /home/hoge/templates
  • /www/a_project/hoge/templates
  • /www/a_project/fuga/templates

の順でテンプレートの存在を調べる

参考:DjangoのTemplateローダ | tsuyuki.makoto

url末尾に必ずスラッシュを入れる

url末尾にスラッシュが無い場合、スラッシュ有りにリダイレクトする設定

# settings.py
APPEND_SLASH = True

Django rest apiで日時としてタイムスタンプを出力する

参考:django rest framework - serialize a datetime as an integer timestamp - Stack Overflow

どうもDjango rest frameworkのDateTimeFieldで指定するformatパラメータだとタイムスタンプが指定出来ないみたいなのでこんな感じにすると出来る。

# serialize.py
from rest_framework import serializers
import time
class HogeSerializer(serializers.Serializer):
    date = serializers.DateTimeField()
    timestamp = serializers.SerializerMethodField()
    def get_timestamp(self, obj):
        return time.mktime(obj.date.timetuple())

javascriptのタイムスタンプに対応するためにはget_timestampの返り値を1000倍すればおk

Paginatorでcount文を実行させずに指定件数分のページングを行う

分量が多くて、先頭1000件を100件ずつページング表示させるので十分かなという時に、いちいちcount文を発行させたくない時

paginator = Paginator(query, 100)
paginator._count = 1000

queryは適当なQuerySet

参考:django - How to implement a paginator that doesn't call count(*) - Stack Overflow

メソッドの呼び出し元オブジェクトを取得

inspectモジュールってのが使えるらしい

import inspect
...
    (frame, filename, line_number, function_name, lines, index) = inspect.getouterframes(inspect.currentframe())[1]
    print(frame, filename, line_number, function_name, lines, index)

参考:How to use inspect to get the caller's info from callee in Python? - Stack Overflow

プロファイラ使った時に、socket.pyが呼ばれてるのは分かったけど、その呼び出し元がイマイチわからなかった時に使った。

urlsでviewにパラメータを送る

# urls.py
url('^$',views.hoge, {'fuga':'hage'}, name='hoge'),
...
# views.py
def hoge(request,fuga):
    ...

パラメーターに指定する名前(上の場合はfuga)がviewsとurlsで同じ名前になっていることが必要(でないとエラーが出る)

参考:URL dispatcher | Django documentation | Django

Ajaxでpostしたい時

csrfフィルタが初期状態で設定されてるし、csrfフィルタはあった方が良いので、次のようになったら良いかも(Jqueryを使う場合)

$.post(url,{csrfmiddlewaretoken: '{{csrf_token}}'}...);

参考:Django CSRF check failing with an Ajax POST request - Stack Overflow

AjaxのPOSTで配列を受け取りたい時

var arr = ['hoge','fuga'];
$.post(url, {arr:arr}, function(){alert('success');});

該当するビューでこんな風に書く

def hoge(request):
    arr = request.POST.getlist('arr'[]')

ちなみにgetだと[]は要らないらしい。

参考:[http://stackoverflow.com/questions/12101658/how-to-get-an-array-in-django-posted-via-ajax] 該当するビューでこんな風に書く

def hoge(request):
    arr = request.POST.getlist('arr'[]')

ちなみにgetだと[]は要らないらしい。

参考:How to get an array in Django posted via Ajax - Stack Overflow

intcommaフィルタが効かない(django 1.8)

django 1.4からL10Nを見るので、ロケールjaの場合はNUMBER_GROUPINGを明示しないといけないらしい。

# settings.py
NUMBER_GROUPING = 3

これでおk

参考:やまよし@Google大好き: Django1.4 で intcommaが効かない

リダイレクト時にパラメータを渡す

response = redirect('name', param)
response['Location'] += '?hoge=fuga'
return response

でいける

参考:add request.GET variable using django.shortcuts.redirect - Stack Overflow

テンプレートのforループで何個おきで表示

divisiblebyフィルタを使う

{% for fuga in hoge %}
  {% if forloop.counter0|divisibleby:4%}4個おき{%endif%}
    {{fuga.hage}}
{% endfor %}

参考:python - Modulus % in Django template - Stack Overflow