FrontPage



tips

misc

runfcgi

python manage.py runfcgi \
    method=prefork \
    host=127.0.0.1 \
    port=8000 \
    workdir=/opt/venvs/hoge-prj \
    outlog=hoge.log \
    errlog=hoge.err \
    daemonize=true \
    maxrequests=4 \
    --settings=settings_production

appを別ディレクトリに分ける

bad wayな気がするので推奨しません!

manage.pyに以下を足す。

import os
import sys

PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(1, os.path.join(PROJECT_DIR, 'apps'))

static / mediaの使い分けと注意事項

static静的ファイル
media動的生成ファイル、ユーザのアップロードコンテンツなど

ファイルシステムにユーザのアップロードコンテンツを保存する場合、 セキュリティの観点からMEDIA_ROOTディレクトリ以下に保存する必要がある。 もしMEDIA_ROOT以外に保存しようとすると、 djangoはシステムへの攻撃とみなして例外SuspiciousOperation?を送出する。

コンテキストプロセッサを追加する

global_settingsに含まれるTEMPLATE_CONTEXT_PROCESSORSに追加する。

from django.conf import global_settings

TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
    'core.context_processors.static_url',
)

静的ファイルをdjangoでホストする

標準ビューに静的ファイルを取り扱える関数があるのでそれを使う(django.views.static.serve)。 遅いのでDEBUG用というかんじ。

ENABLE_STATIC_SERVEというパラメタを作って、この値で切り替えるようにしている。

settings.py::

import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

ENABLE_STATIC_SERVE = True
STATIC_ROOT = os.path.join(BASE_DIR, 'statics')

urls.py::

from django.conf import settings

if settings.ENABLE_STATIC_SERVE:
    urlpatterns += patterns('',
                            (r'^statics/(?P<path>.*)$', 'django.views.static.serve',
                             {'document_root': settings.STATIC_ROOT}),
                            )

フック

signalを使う。 http://djangoproject.jp/doc/ja/1.0/topics/signals.html

コマンド拡張

djangoアプリの中にmanagement/commandsというディレクトリを掘ってモジュールにする。

$ mkdir -p module/management/commands/
$ touch module/management/__init__.py
$ touch module/management/commands/__init__.py

explodeという拡張を作りたければ、explode.pyを置く。

$ vi module/management/commands/explode.py

django.core.management.base.BaseCommand?を継承したCommandというクラスを作る。 handleメソッドを生やすとそれが実行される。

import os, glob

from django.core.management.base import BaseCommand

class Command(BaseCommand):
  def handle(self, *args, **options):
    pass

dev serverのbindアドレス変更

runserverの引数にIPアドレスとポートを着ける。

全部のインターフェスにbindするには以下のようにする。

$ python manage.py runserver 0.0.0.0:8000

debug_toolbar

django-debug-toolbarがすごく便利。

  • INSTALLED_APPS に debug_toolbar を加える
  • MIDDLEWARE_CLASSES に debug_toolbar.middleware.DebugToolbarMiddleware? を加える
  • INTERNAL_IPS に適当なIPアドレスを加える('127.0.0.1'とか)

以下のように、DEBUG_TOOLBARフラグで切り替えておくと便利。

DEBUG_TOOLBAR = True

INTERNAL_IPS=('127.0.0.1')

if DEBUG_TOOLBAR:
    INSTALLED_APPS += ('debug_toolbar',)
    MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
    TEMPLATE_DIRS += ('/some/where/debug_toolbar/templates',)
    DEBUG_TOOLBAR_PANELS = (
        'debug_toolbar.panels.version.VersionDebugPanel',
        'debug_toolbar.panels.timer.TimerDebugPanel',
        'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
        'debug_toolbar.panels.headers.HeaderDebugPanel',
        'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
        'debug_toolbar.panels.template.TemplateDebugPanel',
        'debug_toolbar.panels.sql.SQLDebugPanel',
        'debug_toolbar.panels.signals.SignalDebugPanel',
        'debug_toolbar.panels.logger.LoggingPanel',
        )
    DEBUG_TOOLBAR_CONFIG={
        'INTERCEPT_REDIRECTS':False, # dont trap redirect
        }

ログ出力

loggerモジュールを使う。

import logging

dev view(request):
    logger = logging.getLogger()
    logger.debug(msg)

contrib.contenttypes

マニュアルに書いてあるんだけど、スニペットがないと忘れるのでメオ。

http://djangoproject.jp/doc/ja/1.0/ref/contrib/contenttypes.html

ContentType?で参照しているモデルから検索

ややこしいな。。

get_for_model()の使い方がキモ。

from django.contrib.contenttypes.models import ContentType
from bike.models import Bike

ctype = ContentType.objects.get_for_model(Bike)
obj = models.RideSummary.objects.get(content_type__pk=ctype.pk, object_id=bike.pk)

ContentType?を使ったモデルを作る

content_object引数がキモ。

from django.contrib.contenttypes.models import ContentType
from bike.models import Bike

bike = Bike.objects.get(pk=1) # 自転車を取得する

obj = models.RideSummary.objects.create(
    content_object=bike,
    distance=distance,
    ridetime=ridetime
    )

contrib.admin

特定のカラムを編集不可能にする

readonly_fieldsプロパティを使う。

from django.contrib import admin
import models

class CardAdmin(admin.ModelAdmin):
    readonly_fields = ('user', )
    list_display = ('user', 'email', 'token', 'created_at')

モデルのセーブをフックする

ブログのポストを保存するとき、自動的にログインユーザのインスタンスをauthorに保存する。

ModelAdmin?にsave_model()というのがあって、requestオブジェクトを取り扱うことが出来る。 ここに処理を書くと、フォームバリデーション→save_model()の処理→実際の保存とすることが出来る。

class EntryAdmin(admin.ModelAdmin):
    form = EntryAdminForm

    def save_model(self, request, obj, form, change):
        obj.author = request.user
        obj.save()

カラムのフィルタ

list_filterプロパティを使う。

class EntryAdminForm(forms.ModelForm):
  list_filter=('category', )

フォームに表示するカラムを指定する

ModelForm?のメタクラスにfieldsプロパティを追加して、 編集したいカラムを列挙する(リスト)。

class EntryAdminForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = [
            'publish_at',
            'is_publish',
            'markup',
            'category',
            'slug',
            'title',
            'text',
            'tag',
            ]

カラムの検索を行う

Adminクラスにsearch_fieldsプロパティを加える。

class HogeAdmin(admin.ModelAdmin):
  search_fields = ['first_name', 'last_name']

一覧するカラムを定義する

Adminクラスにlist_displayプロパティを加える。

class HogeAdmin(admin.ModelAdmin):
  list_display = ('first_name', 'last_name', 'age', 'ctime', 'utime')

データ一覧で編集可能なカラムを定義する

list_editableを使う。

from django.contrib import admin

import models

class CardAdmin(admin.ModelAdmin):
    list_editable = ('email', 'token', )

フォーム定義のオーバーライド

Adminクラスのプロパティ(?)を書き換える。

from forms import HogeAdminForm

class HogeAdmin(admin.ModelAdmin):
  form = HogeAdminForm

contrib.auth

プロフィールの拡張

settings.AUTH_PROFILE_MODULEで設定することにより拡張できる。

http://www.b-list.org/weblog/2006/jun/06/django-tips-extending-user-model/

settings.pyにAUTH_PROFILE_MODULEを書く。書き方はapp名とmodel名をピリオドでつなげる。

AUTH_PROFILE_MODULE = 'myapp.UserProfile'

models.pyにdjango.contrib.auth.models.Userへ外部キーを持つモデル追加(継承じゃダメなんか?)。

from django.db import models
from django.contrib.auth.models import User
     
class UserProfile(models.Model):
    """ユーザのプロフィールを格納する"""
    user = models.ForeignKey(User, unique=True)
    url = models.URLField()
    home_address = models.TextField()
    phone_numer = models.PhoneNumberField()

def create_user_profile(sender, instance, **kwargs):
    """UserProfileを生成する"""
    try:
        instance.get_profile()
    except ObjectDoesNotExist:
        UserProfile.objects.create(user=instance)

post_save.connect(create_user_profile, sender=User)

※django.contrib.auth.models.User.create_user()でやってくれヨ

呼び出し方。

from django.contrib.auth.models import User
u = User.objects.get(pk=1) # Get the first user in the system
user_address = u.get_profile().home_address

モデルでプロパティを拡張しておくとちょっと便利。

class UserProfile(models.Model):
(省略)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

プロパティとして呼び出すと勝手にprofileを取得してくれる(無ければ作ってくれる)。

from django.contrib.auth.models import User
user = User.objects.get(pk=1)
user.profile.additional_info_field

参考::

シェルからパスワード変更

$ python manage.py changepassword someuser
Changing password for user 'someuser'
Password: 
Password (again): 
Password changed successfully for user 'someuser'

ユーザオブジェクトのパスワード変更

$ python manage.py shell
> from django.contrib.auth.models import User
> users=User.objects.get(pk=1)
> users.set_password('hogehoge')
> users.save()

test

フォームにファイルをPOST

f = open(settings.MEDIA_ROOT + '/tests/evolution.jpg', 'rb')
buf = f.read()
f.seek(0)

requset = HttpRequest()
request.FILES['photo'] = UploadedFile(f, content_type='image/jpeg', size=len(buf))

form = HogeForm(request.POST, request.FILES)

※検証してません

これでテストで使えるはず。

ビューにファイルをPOST

    def test_register_bike_image(self):
        """登録された自転車に対して画像をつける"""
        self._login()
        f = open(settings.MEDIA_ROOT + '/tests/evolution.jpg')
        response = self.client.post(reverse('bike-edit', kwargs={'bike_id':self.frame.pk}), {
                'number':self.frame.number,
                'manufacturer':self.frame.manufacturer.id,
                'model':self.frame.model,
                'picture':f,
                })

テストの高速化

testを実行するとCREATE TABLEが走ってしまって遅いので、 固有のデータベース機能(関数とか)を使っていないならSQLite3 + in-memoryデータベースを使うと高速になる。

test用のsettingsを作成して以下のコンフィグを書く。

DATABASE_NAME = ':memory:'
DATABASE_ENGINE = 'sqlite3'

IntegrityError?の捕捉

unique制約などに引っかかったときに送出される IntegrityError?はdjango.dbの中に入ってます。

from django.db import IntegrityError

class HogeTest(TestCase):
    def testEntryPost(self):
       self.assertRaises(IntegrityError, Entry.objects.entry, (date=date, title=title))

ClientでPOSTする

from django.test.client import Client
client = Client()
response = c.post('/login/', {'username': 'john', 'password': 'smith'})

Clientで環境変数を渡す

特定のブラウザのふりをする、 特別なヘッダを送るなどの場合、**{'key':val}として辞書を指定することでヘッダを指定できる。

from django.test.client import Client

class HogeTest(TestCase):
  def reqeust_test(self):
    client = Client()

    response = client.get(
           '/some/where/',
           **{'HTTP_USER_AGENT':'DoCoMo/2.0 SH06A3(c500;TB;W24H12)',
              'HTTP_X_DCMGUID':'0123456789'})

models

継承とOneToOneField?の違い

継承の場合はParentとChildの関係性が1:Nとなる。

# 継承
class Parent(Model):
    pass

class Child(Parent):
    pass

# _setを利用して取得
parent = Parent.objects.create()
parent.child_set.all()

OneToOneField?の場合はParentとChildの関係性が1:1となる。内部的には主キーが同じ。

# OneToOneField
class Parent(Model):
    child = OneToOneField('Child')

class Child(Model):
    pass

# OneToOneFieldのプロパティを利用してアクセス
parent = Parent.objects.create()
parent.child

GROUP BY

values()で表示カラムを指定、annotate()で絞り込み条件を指定。

from core.models import Frame
from django.db.models import Count

Frame.objects.values('manufacturer').annotate(count=Count('manufacturer')).order_by('-count')

出力されるSQLは、GROUP BYが冗長…なんだこりゃ。

str(Frame.objects.values('manufacturer').annotate(count=Count('manufacturer')).order_by('-count').query
SELECT "core_frame"."manufacturer_id", COUNT("core_frame"."manufacturer_id") AS "count"
 FROM "core_frame"
GROUP BY "core_frame"."manufacturer_id", "core_frame"."manufacturer_id"
ORDER BY count DESC

OR, ANDなど複雑な検索条件

Qオブジェクトを利用する。

from django.db.models import Q
user = HogeUser.objects.filter(Q(username__startswith=value) | Q(email__startswith=value))

ファイル保存先を動的に設定する

models.FileField?()のupload_toパラメタにパスを渡す。 upload_toには、instance, filenameという二つの引数を持つ呼び出し可能オブジェクト(コールバック関数ってこと?)を指定することができ、関数を利用することで柔軟にファイル保存先を変更できる。

class HogeModel(models.Model):
    image = models.FileField(u'画像', upload_to=lambda instance, filename: '%d/%s' % (instance.pk, filename))

モデルの名前

admin.siteで出てくる名前を漢字にしたりとか。verbose_nameが単数形、verbose_name_pluralが複数形の指定。

class Person(models.Model):
    class Meta:
        verbose_name = u'ひと'
        verbose_name_plural = u'ひとたち'

モデルの名前(外部キー編)

外部キーにもわかり易い名前をつけられる。verbose_nameを使う。

class Person(models.Model):
    parent = models.ForeignKey('self', verbose_name=u'親')

親オブジェクト→子オブジェクトの呼び出し

親オブジェクト→子オブジェクトという呼び出し。child_set(childの部分は子オブジェクトのクラス名ね)というプロパティから呼び出せる。

p = ParentModel.objects.get(pk=1)
p.child_set.all()

カスタムマネージャ

最初に書いたマネージャが有効。

class MaleManager(models.Manager):
  def get_query_set(self):
    return super(MaleManager, self).get_query_set().filter(sex='m')

class Person(models.Model):
  objects=models.Manager() # これがデフォルトマネージャになる
  men=MaleManager()
  women=WomenManager()

これだとMaleManager?がデフォルトマネージャとなる。 つまり、Person.objects.filter()などとすると、MaleManager?が使われるので注意する。

マネージャが設定されてない場合、Djangoモデルはobjectsという名称を使う。

SQLを表示する

QuerySet?オブジェクトから取得することが出来る。

以下はDjango 1.2の例。

(Pdb) print User.objects.order_by('-ctime').query
SELECT "user"."id", "user"."ctime", "user"."utime", "user"."pub_flg","user"."email", "user"."name",  FROM "user" ORDER BY "user"."ctime" DESC

モデル取得を試みて、無ければ404を返す

get_object_or_404(), get_list_or_404()という関数を使う。 特定のモデルから条件指定してオブジェクトそのもの、あるいは一覧を取得する。 取得に失敗するとHttp404例外を送出するので、ビュー内で使用する想定。

from django.shortcuts import get_object_or_404

def index(request):
  user=get_object_or_404(User, status='valid')

オブジェクトを指定するところは、クエリセットを指定しても良い。

def index(request):
  user=get_object_or_404(User.objects.validated())

モデル取得を試みて、無ければ作る

Managerにget_or_create()があるのでこれを使う。

class HogeManager(models.Manager):
  def increment(self, id):
    obj = Hoge.objects.get_or_create(id=id) # idで検索してあれば取得、無ければ作成
    return obj

戻り値はtupleなので、uniqueカラムなどでひとつだけ欲しい場合にはget_or_create(id=id)[0]としてとると楽ちん。

マネージャでの処理の共通化

Managerクラスに共通の処理、ビジネスロジックを放り込んで使う。

models.py

class HogeManager(models.Manager):
  def validated(self):
    """status='valid'のオブジェクトを取得する
    """
    return self.filter(status='valid')

class Hoge(models.Model):
  """Hogeクラス
  """
  ...
  status=models.CharField(default='valid')
  objects=HogeManager()

views.py

from models import Hoge
def index(request):
  hoges = Hoge.objects.validated()

COUNT(*)

count()メソッドというそのものがある。

public_count = Hoge.objects.filter(pub_flag=True).count()

外部参照しているテーブルを取得する

いわゆるJOIN…なのかな?select_related()を使用する。

for e in Entry.objects.select_related().all():
  title = e.Blog.title

depth引数でJOINの段数を制限することも出来る。

Entry.objects.select_related(depth=1).all()

テーブル物理名を定める

Metaクラスに書く。

class Hoge(models.Model):
    class Meta:
        db_table = 'hoge'

カラムの指定

values_listメソッドを使う。 idとnameカラムが欲しかったら次のようにする。

Hoge.objects.values_list('id', 'name')

fixtureのロード

loaddataコマンドを使う。

$ python manage.py loaddata some/where/hoge.json

シリアライズ形式はJSONが使える。その他は調べてない。

必須可否の設定

フィールドクラスにblank引数を渡す。TrueだとNULLでいける、Falseならダメ。

class Hoge(models.Model):
    tag=models.ManyToManyField(Tag, blank=True)

シリアライズ

serializersモジュールを使えばOK。xml, json, python, yamlがサポートされている。

from django.core.serializers import serialize
json=serialize('json', Blog.objects.all())

特定のカラムのみシリアライズ

serialize関数にfields引数を渡す。

from django.core.serializers import serialize
json=serialize('json', Blog.objects.all(), fields=('title', 'body'))

FileField?使ったアップロード

1対多のモデルとモデルフォームを作る。

Fileモデルのsaveメソッドがキモ。

class FileType(models.Model):
    extension=models.CharField(max_length=8)
    description=models.TextField(max_length=512)

class File(models.Model):
    file=models.FileField(upload_to='upload')
    filetype=models.ForeignKey(FileType, blank=True, editable=False)

    def save(self, force_insert=False, force_update=False):
        '''
        Fileモデルセーブ前に拡張子を取得する。
        取得した拡張子をFileモデルに含める。
        '''
        fname=str(self.file)
        i=fname.rfind('.')
        ext=fname[i+1:]
        filetype=FileType.objects.get(extension=ext)
       
        self.filetype=filetype
       
        super(File, self).save(force_insert, force_update)

class FileUploadForm(ModelForm):
    class Meta:
        model=File

ビューはこんなかんじ。

def upload(request):
    form=FileUploadForm(request.POST, request.FILES)
    fileobj=form.save()

実ファイルの所在は、MEDIA_ROOT + upload_toで指定したディレクトリとなる。

DateTimeField?にxml schema datetime型を放り込む

TCXファイルから時刻引っ張って入れようとしたらはまった。

DateTimeField?は yyyy-mm-dd hh:mm:ss フォーマット(ISOフォーマット?)じゃないとダメなので、元の時刻文字列から変換する必要がある。

具体的に次のようにした。

timeモジュール呼び出す。

>>> import time

xml schemaのデータ型からタプルに変換する。strptimeのフォーマット引数は合ってるか不安。タイムゾーンがすっ飛んでるかも…。

>>> xmltime='2009-01-01T12:51:45Z'
>>> xmltime
'2009-01-01T12:51:45Z'
>>> timetup=time.strptime(xmltime, '%Y-%m-%dT%H:%M:%SZ')
>>> timetup
(2009, 1, 1, 12, 51, 45, 3, 1, -1)

で、タプルをベースにstrftimeでISOフォーマットに変換する。

>>> newtime=time.strftime('%Y-%m-%d %H:%M:%S', timetup)
>>> newtime
'2009-01-01 12:51:45'

この文字列ならモデルに放り込むことが出来る。

デフォルトのソート条件

デフォルトのソート条件はMetaクラスにorderingプロパティをセットする。

class Hoge(models.Model):
    length = models.IntegerField()

    class Meta:
        ordering = ["length"]

インデックス

インデックスを張るにはフィールド指定時にdb_index=Trueをつける。

from django.db import models

class Mail(models.Model):
  name = models.CharField(db_index=True)
  address = models.EMailField()

ユニーク

ユニーク制約をつけるにはフィールド指定時にunique=Trueをつける。

from django.db import models

class Carrier(models.Model):
    carrier_name=models.CharField(max_length=32, unique=True)

マルチカラムユニーク

複数カラムでユニーク条件をつけるにはMetaクラスに書き込む。

from django.db import models

class Hoge(models.Model):
        field_a=models.CharField(max_length=32)
        field_b=models.CharField(max_length=32)

        class Meta:
                unique_together = (('field_a', 'field_b'),)

オブジェクトの保存をフック

publish_atというDateTimeField?に加え、publish_at_dateというDateField?を作る。publish_at_dateをユーザに入力させるのは無駄なので、publish_atの値から生成し、オブジェクトを保存するようにフックを書く。

import datetime

class Journal(models.Model):
    title=models.CharField(default='', max_length=200)
    text=models.TextField(default='')
    publish_at=models.DateTimeField(default=None)
    publish_at_date=models.DateField(default=None, editable=False)
 
    def save(self, *args, **kwargs):
        # publish_at_dateを生成して埋める
        self.publish_at_date=datetime.date(self.publish_at.year, self.publish_at.month, self.publish_at.day)
        super(Journal, self).save(*args, **kwargs)

forms

フォームの初期値指定

フォームクラスのコンストラクタのinitialに辞書を渡す。

form=HogeForm(initial={'name':u'名前', 'address':u'東京'})

モデルフォームの初期値指定

モデルフォームクラスのコンストラクタのinstanceにモデルのインスタンスを渡す。

owner = User.objects.get(pk=1)
form = HogeForm(instance=owner)

更新時にもインスタンスを指定する必要があります。

def update_account(request):
    if request.method == 'POST':
        form = HogeForm(request.POST, instance=request.user)

        if form.is_valid():
            form.save()
    else:
        form = HogeForm(instance=request.user)
    return direct_to_template(request, 'form.html', {'form':form})

ウィジェットの指定(プロパティ編)

文字列なんだけどtype="text"だったりtextareaだったりすることがある。 その辺りの指定。

属性(attribute)はコンストラクタにattrsという引数を渡すことで任意の値をセットできる。

from django import forms

class SomeForm(forms.Form):
  guide_text=forms.CharField(
    max_length=128,
    label=u"案内文",
    widget=forms.Textarea(attrs={'size':2})
  )))

ウィジェットの指定(Metaクラス編)

ウィジェットはMetaクラスからも指定できる。 ModelForm?などで、入力フィールドを再定義する手間を省ける。

class AccountEditForm(forms.ModelForm):
    class Meta:
        model = User
        fields = [
            'username',
            'first_name',
            'last_name',
            ]
        widgets = {
            'username':forms.TextInput(attrs={'class':'input-datum', 'id':'id_username', 'size':35, 'maxlength':150}),
            'last_name':forms.TextInput(attrs={'class':'input-datum', 'id':'id_last_name', 'size':35, 'maxlength':150}),
            'first_name':forms.TextInput(attrs={'class':'input-datum', 'id':'id_first_name', 'size':35, 'maxlength':150}),
            }

モデルのデータを元にSELECTを生成

ChoiceField?使うと範囲にあったバリデーションをしてくれて便利。

from django import forms

class SomeForm(forms.Form):
   notice=forms.ChoiceField(label=u"注意事項",
                            widget=forms.Select,
                            choices=Notice.objects.values_list('id', 'name'))

バリデーションのカスタマイズ(clean編)

clean()はフォーム全体の整合性を確認するためのメソッド(フィールドが全部埋まってるかとか)。

Formのclean()はオーバーライド可能。デフォルトのclean()も実行したい場合には、 親クラスのclean()を呼び出す必要がある。cleaned_dataを返す(でいいのかな?)。

class HogeAdminForm(forms.ModelForm):
    def clean(self):
        super(forms.ModelForm, self).clean()
 
        # レコード登録件数を調べて、10件超えたらエラーにする
        cnt = Hogehoge.objects.count()

        # self.instance.idがあれば更新、無ければ新規追加     
        if self.instance.id is None and cnt < 10:
            return self.cleaned_data

        raise forms.ValidationError(u"登録件数は最大%d件までです。" % (10))

※このコード未検証です。

バリデーションのカスタマイズ(clean_field編)

class HogeForm(forms.ModelForm):
  class Meta:
    model = Hoge

  def clean_hoge(self):
    # cleaned_dataから値を取得する
    value = self.cleaned_data.get('hoge', None)
    if value == 'correct':
      return value # 成功したらvalueを返す

    # 失敗したらValidationError例外をスローする
    raise forms.ValidationError('incorrect!')

ファイルアップロード

フォームタグにenctype='multipart/form-data'と書く。これを忘れるとrequest.FILESが取得できない。

<form action='/some/where/do' method='post' enctype='multipart/form-data'>
〜
</form>

forms.pyにフォームクラスを書く。

from django import forms

class SampleForm(forms.ModelForm):
    profile_photo=forms.ImageField(u"顔写真")

views.pyで受け取りの処理を書く。 フォームクラスのコンストラクタにPOSTとFILESの両方を渡すこと。

from forms import SampleForm

def upload(request):
    form = SampleForm(request.POST, request.FILES)
    if form.is_valid():
        # validなときの処理
    else:
        # invalidなときの処理

views

コンテキスト

コンテキストプロセッサのコンテキストは、render_to_response()などのビュー関数経由に いちいち指定しなければならないので面倒くさい。

from django.template import RequestContext
from django.shortcuts import render_to_response

def index(request):
  return render_to_response('template.html', dic, context_instance=RequestContext(request))

generic viewのdirect_to_templateを使うと明示的に渡さなくても コンテキストプロセッサのコンテキストが使える。

from django.views.generic.simple import direct_to_template

def index(request):
  return direct_to_template(request, 'template.html', dic)

参考: http://it.kndb.jp/entry/show/id/1110 (ほぼほぼ丸パクリです、すいません)

フォームウィジェットを無視する

Ianに教えてもらった。

フォームオブジェクト.フィールド名.data → value属性値、フォームオブジェクト.フィールド名.name →フォームのname属性値が 取れるよ。

<form enctype="multipart/form-data" action="{% url some_action id=obj.id %}" method="POST">
<textarea name="{{ form.profile.name }}">{{ form.profile.data }}</textarea><br />
<input type="file" name="{{ form.photo_profile.name }}" value="{{ form.photo_profile.data }}" /><br />
<input type="submit" value="登録する" />
</form>

json/xml出力など

serializersモジュールでサクッと出来るらしい。

モデルを丸ごとシリアライズできる。これは便利!

from django.core import serializers
json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)

GET/POSTに制限

tokibitoさんに教えてもらった。超便利。

request methodをPOSTのみに制限する。

from django.views.decorators.http import require_POST

@require_POST
def upload(request):
  return HttpResponse('POST only')

request methodをGETのみに制限する。ただし、HEAD, OPTIONSなども同様に拒否してしまうので、コンテンツを返す必要がある場合にのみ使うこと。(Thanks Ian!)

from django.views.decorators.http import require_GET
@require_GET
def index(request):
  return HttpResponse('GET only')

GET, HEADに対応させるにはrequire_http_methodデコレータを使う。

from django.views.decorators.http import require_http_methods

@require_http_methods(['GET', 'HEAD']) 
def index(request):
  return HttpResponse('GET/HEAD only')

view内の更新処理をトランザクションでくくる

transactionデコレータを使う。

from django.db import transaction

@transaction.commit_on_success
def some_view(request, id):
  try:
    obj_a = Hoge.objects.get(pk=id)
    obj_b = Hage.objects.get(pk=id)

    obj_a.name = 'hogehoge'
    obj_a.save()

    obj_b.prop = 'hage'
    obj_b.save()
  except Exception, e:
    raise e

  return HttpResponse('updated')

レスポンスのContent-Typeを指定

render_to_response()にmimetype引数があるのでそれを使う。

def _render_feed(self, entries):
  tmpl_values=Feeds
  return render_to_response('rss2.0.xml', tmpl_values, mimetype='application/xml')

リダイレクト

ビューからHttpResponseRedirect?クラスを返せばよい。

from django.http import HttpResponseRedirect

def testview(request):
    return HttpResponseRedirect('http://example.com/anywhere')

urls

reverse

from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect

def some_view(request):
   # いろんな処理
  return HttpResponseRedirect(reverse('community_detail', args=(community.slug,) ))

templates

メールの送信内容をテンプレートから取り出す

views.py

from django.template import loader, Context

def hogeview(request):
  t = loader.get_template('hogeview.html')
  c = Context({
    'name':'なまえ',
    'gender':'男性',
    })

  send_mail('Subject', t.render(c), 'from@example.com', [new_data['email']], fail_silently=False)

カスタムフィルタ

作るの簡単。

手順としては、

  • フィルタ関数書く
  • テンプレートオブジェクトに登録

[flickr photo='xxx']という文字列を<img src='xxx' />に置き換えるフィルタの例。

# 必要なモジュールを拾う
from django import template
import re

# テンプレートライブラリ(フィルタ、カスタムタグ)のインスタンスを拾う
register = template.Library()

# フィルタ関数
def flickr_tag(buf):
    ro=re.compile(r"\[flickr (?P<type>photo|set)='(?P<id>\d+)'\]")
    m=ro.search(buf)

    if m is not None:
        flickr_type=m.group('type')
        flickr_id=m.group('id')
       
        replace_string="<img src='%s' />" % (flickr_id)
        buf=buf.replace(m.group(0), replace_string)
       
    # フィルタ済みの値を返す
    return buf

# テンプレートライブラリに登録 
register.filter('flickr_tag', flickr_tag)

context_processorの作り方

関数を書いてCONTEXT_PROCESSORSに追加すればOK

settings.py:

from django.conf import global_settings

TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
    'journal.context_processors.journal_context',
    )

コンテキストプロセッサでは辞書を返す。キーがテンプレートのコンテキスト名になるよ。

context_processors.py:

import journal.models import Journal

def journal_context(request):
    return {
        'journal':Journal.objects.get(),
        }

storage

models.FileField?の扱い

models.py

class File(models.Model):
  image_data=models.ImageField()

views.py

from django.core.files.storage import default_storage

def upload(request):
  default_storage.save(filename, file_like_object)
  fileobject=File.objects.get(pk=id) 

  # いろいろ更新
  f=default_storage.open(filename)
  fileobject.photo=f
  fileobject.save()
  default_storage.delete(filename)

バイナリファイルを扱うContentFile?

バイナリファイルはContentFile?オブジェクトとして扱うのが良い。

from django.core.files.base import ContentFile

buf = open('hoge.jpg', 'r').read()
cfile = ContentFile(buf)

バイナリファイルを手動でモデルに保存

from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from core.models import Frame

# モデルを取ってくる
f = Frame.objects.all()[0]

# ファイルを開き、内容をContentFileオブジェクトにコピーする
picbin = ContentFile(default_storage.open('/some/where/binary.jpg').read())

# ストレージに保存
path = default_storage.save('binary.jpg', picbin)

# モデルのImageFileFieldなどはパスを入れれば良い
f.picture = path

# 保存
f.save()

trouble shoot

SuspiciousOperation?: Attempted access to 〜

djangoプロジェクトの配下以外のディレクトリ、ファイルを操作しようとしている。 djangoプロジェクトで読み書きするファイルは、settings.MEDIA_ROOT以下に存在しなければならない。

例えば MEDIA_ROOT = '/django-proj/media/' であれば、/django-proj/uploads/' ディレクトリ以下のファイルは操作することができない。

テストなどでモデル内の画像ファイル等を取り扱う場合は次のようにする。

obj = Photo.objects.create()
obj.picture = ImageFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.jpg'))
obj.save()

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2012-04-13 (金) 15:14:13 (1989d)