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

manage.pyのコマンドを別のプログラムから実行

from django.core.management import call_command
call_command('hoge')

manage.pyのコマンドをプログラム上から呼び出す

何か作業した後に自動でcollectstaticしたいとかそんなこともこれで出来る

フォームのフィールドをグループ化

Django form field grouping

yieldを使うと良い感じだった。

group1 = ["hoge1","fuga1"]
group2 = ["hoge2","fuga2"]
class HogeForm(ModelForm):
...
    def group1(self):
        for name in group1:
            yield self[name]
    def group2(self):
        for name in group2:
            yield self[name]
<h3>group1</h3>
{% for field in object.group1 %}
    <li>{{ field.data}}</li>
{% endfor %}
<h3>group2</h3>
{% for field in object.group2 %}
    <li>{{ field.data}}</li>
{% endfor %}

こんな感じ

django-formtoolsの確認画面でoptionタグのテキストを表示する

テンプレートフィルタを作ると、そこらへんを自由にできる

アプリ\/templatetags\/data_verbose.py

from django import template

register = template.Library()


@register.filter
def data_verbose(boundField):
    """
    Returns field's data or it's verbose version
    for a field with choices defined.

    Usage::

        {% load data_verbose %}
        {{form.some_field|data_verbose}}
    """
    data = boundField.data
    field = boundField.field
    if hasattr(field, 'choices'):                                              
         return {str(k):str(v) for k,v in field.choices}.get(data, '')          
    return data                                                                
{% for field in object %}
    <li>{{ field|data_verbose}}</li>
{% endfor %}

参考にしたHPそのままだとモデルのフィールドがテキスト以外だとエラーが出るのでちょい変更した。

save前後にモデルやフォームのデータを変更する方法

方法は何種類か

  • clean_[フィールド名] メソッドをフォームクラス内に作る
  • saveメソッドをオーバーライド
  • views.pyでrequestをいじる

テンプレート内でモデルのフィールドについてループ

Iterate over model instance field names and values in template

{% for field in object %}
    <li><b>{{ field.label }}:</b> {{ field.data }}</li>
{% endfor %}

て感じ。fieldがBoundFieldクラスで、その中のdataメソッドでデータを取得している。

認証済みチェック

Django1.10以降

views.pyなどで

request.user.is_authenticated

Django1.9以前

request.user.is_authenticated()

テンプレート内

{% if user.is_authenticated %}

参考:How to check if a user is logged in (how to properly use user.is_authenticated)?

GETパラメーターを入れてそのままリダイレクト

response = redirect('namespace:viewname')
get_params = request.GET.urlencode()
response['location'] += '?'+get_params
return response

参考:Django、リダイレクト時にGETパラメータも渡す

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