Admin InterfaceのTextareaを広くする

jQueryを使ってTextareaの高さを自在に変更できると入力しやすく便利です。
適応前は表示範囲は固定されていて見辛いですが、
AdminExtendTextarea01
posted by (C)常山
適応後広げて使ってみると快適です。
AdminExtendTextarea02
posted by (C)常山


変更箇所は1ヶ所のみ。
"/admin/base_site.html"ですが、"MEDIA_ROOT"のディレクトリ直下の"/admin/base_site.html"に追加修正します。

{% block extrahead %}
<style type="text/css">
    div.grippie {
        background:#EEEEEE url({{ MEDIA_URL }}js/textarea_resize/grippie.png) no-repeat scroll center 2px; 
        border-color:#DDDDDD;
        border-style:solid;
        border-width:0pt 1px 1px;
        cursor:s-resize;
        height:9px;
        overflow:hidden;
        margin-left: 106px;
    }
    textarea {
        display:block;
        margin-bottom:0pt;
        width:95%;
        height: 20%;
    }
</style>
<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/textarea_resize/jquery.textarearesizer.js"></script>
<script type="text/javascript">
    $(document).ready(function() {
         $('textarea:not(.processed)').TextAreaResizer();
	});
</script>
{% endblock %}

たったこれだけで、Textareaの入力が快適になります。


元ネタはExpandable Textareas in Django

ManyToManyFieldをAdmin Interfaceで使うときに付けると便利なオプション

2日連続のAdmin Interfaceネタ


まずは、ManyToManyFieldを使ったmodelを作成します。

class Lang(models.Model):
    name = models.CharField(max_length = 20)

    def __unicode__(self):
        return self.name

class Project(models.Model):
    name = models.CharField(max_length = 100)
    lang = models.ManyToManyField(Lang, null = True)

    def __unicode__(self):
        return self.name

次に、Admin Interfaceで使える最低限のadmin.pyを作成

from django.contrib import admin
from models import Lang, Project

class ProjectAdmin(admin.ModelAdmin):
    pass

admin.site.register(Project, ProjectAdmin)
admin.site.register(Lang)

Projectにアクセスしてみると以下のようになります。
AdminManyToMany01
posted by (C)常山
Langの項目が少ない場合は、Ctrlを使って選択すればこと足りましたが、多くなるとそうはいきません。
admin.pyを以下のようにすると、かなり便利になります。

from django.contrib import admin
from models import Lang, Project

class ProjectAdmin(admin.ModelAdmin):
    filter_horizontal = ['lang']
    #filter_vertical = ['lang']

admin.site.register(Project, ProjectAdmin)
admin.site.register(Lang)

AdminManyToMany02
posted by (C)常山


見てみるとわかるのですが、いつ変更があったのかわかりませんが絞込みの機能が付いています。
使ってみました。
AdminManyToMany03
posted by (C)常山


ManyToManyFildを使用したModelをAdmin Interfaceで利用するなら、
filter_horizontal, filter_verticalオプションを使用することをお薦めします。


今回のネタ元は、Personnalisation de la page d'admin Django pour les ManyToManyFieldからです。

Admin Interfaceのカスタマイズ

Admin Interfaceのカスタマイズは、大まかに分けて3種類あります。

  • テンプレートのカスタマイズ
  • admin.pyを使用したカスタマイズ
  • フォームをメインとしたカスタマイズ

です。


フォームをメインとしたカスタマイズはcanadian-payroll-calculatorのpayrollアプリのソースを読めばわかると思います。
勉強用にUserモデルを継承したProfileアプリを作成したいと思います。


注意

  • Djangoの開発版で作成しています。
  • settings.py, urls.pyの設定は省略します。

まずはmodels.pyから。

from django.db import models
from django.contrib.auth import models as authModel

class Profile(authModel.User):
    mobile_mail = models.CharField(max_length = 100, blank = True, null = True)
    zip = models.CharField(max_length = 20, blank = True, null = True)
    address1 = models.CharField(max_length = 100, blank = True, null = True)
    address2 = models.CharField(max_length = 100, blank = True, null = True)
    address3 = models.CharField(max_length = 100, blank = True, null = True)

    def __unicode__(self):
        return "%s %s" % (self.first_name, self.last_name)

これに対してsyncdbを実行します。
次にAdmin Interfaceで使えるようにadmin.pyを作成します。

from django.contrib import admin
from models import Profile

class ProfileAdmin(admin.ModelAdmin):
    pass

admin.site.register(Profile, ProfileAdmin)

これでAdmin InterfaceからProfileを見ることができます。
AdminCustomize01
posted by (C)常山
必要な項目のみ表示するようにこのページをカスタマイズしていきます。
表示項目が

  • user_name
  • first_name
  • last_name
  • mail_address
  • mobile_email
  • zip
  • address1
  • address2
  • address3

のforms.pyを作成します。

from django import forms
from models import Profile

class ProfileForm(forms.Form):
    user_name = forms.CharField()
    first_name = forms.CharField()
    last_name = forms.CharField()
    mail_address = forms.CharField()
    mobile_email = forms.CharField(required = False)
    zip = forms.CharField(required = False)
    address1 = forms.CharField(required = False)
    address2 = forms.CharField(required = False)
    address3 = forms.CharField(required = False)

次にadmin.pyを編集します。

from django.contrib import admin
from models import Profile
from forms import ProfileForm
from django.shortcuts import render_to_response
from django.template import RequestContext

class ProfileAdmin(admin.ModelAdmin):
    def add_view(self, request, form_url = '', extra_context = None):
        model = self.model
        opts = model._meta

        if request.method == 'GET':
            initial = None
            form = ProfileForm(initial = initial)
        return render_to_response('admin/testapp/add_form.html',
                                    {
                                        "title": "TestForm",
                                        "form": form,
                                        "is_popup": request.REQUEST.has_key('_popup'),
                                        "add": True,
                                        "change": True,
                                        "has_add_permission": True,
                                        "has_delete_permission": True,
                                        "has_change_permission": True,
                                        "has_file_field": False,
                                        "has_absolute_uri": False,
                                        "auto_populated_fields": (),
                                        "opts": opts,
                                        "save_as": False,
                                        "root_path": self.admin_site.root_path,
                                        "app_label": self.model._meta.app_label,
                                    }, context_instance = RequestContext(request)
    )

admin.site.register(Profile, ProfileAdmin)

次にrender_to_responseで指定したPath(admin/testapp/add_form.html)に以下のテンプレートを置きます。

{% extends "admin/change_form.html" %}
{% load i18n %}

{% block after_field_sets %}
<fieldset class="module aligned">
    <table>
        <tbody>
            {{ form.as_table }}
        </tbody>
    </table>
</fieldset>
{% endblock %}

そしてアクセスしてみると以下のような画面になります。
AdminCustomize02
posted by (C)常山
上記のソースはフォームを表示させるだけの不完全なものです。
canadian-payroll-calculatorを参考にすればAdmin Interfaceに作成したアプリケーションを埋め込むことが可能ではないでしょうか。


間違い等ございましたら指摘してください。

repsheetを見てみる

GeoDjangoのチュートリアルも一応終え、GeoDjangoを使ったrepsheetのソースを見てみます。
稼動しているサイトはhttp://repsheet.comです。


まずはmodels.pyを見てみる(一部抜粋)。

from django.contrib.gis.db import models
from django.contrib.localflavor.us.models import PhoneNumberField, USStateField


class Boundary(models.Model):
    CLASS_CHOICES=(
        ('stbr', 'StateBoundary'),
        ('ctbr', 'CityBoundary'),
        ('nycb', 'NYCityBoundary'))
    
    class_type = models.CharField(max_length=4, choices=CLASS_CHOICES, db_index=True)
    BTYPE_CHOICES = ()
    bnd_type = models.CharField(max_length=3, choices=BTYPE_CHOICES, db_index=True)
    district = models.PositiveIntegerField()
    state = USStateField()
    geom = models.GeometryField()
    
    def get_pboundary(self):
        if self.class_type == 'stbr':
            return self.stateboundary
        elif self.class_type == 'ctbr':
            return self.cityboundary
        elif self.class_type == 'nycb':
            return self.cityboundary.nycityboundary
    
    def save(self, force_insert=False, force_update=False):
        self.class_type = self.get_class_type()
        super(Boundary, self).save(force_insert, force_update)

class StateBoundary(Boundary):
    BTYPE_CHOICES = (
        ('sen', 'senate'),
        ('hou', 'house'),
        ('cng', 'congress'))
    
    def get_class_type(self):
        return 'stbr'

class CityBoundary(Boundary):
    BTYPE_CHOICES = (('wrd', 'Ward'),)
    
    city = models.CharField(max_length=200,)
    
    def get_ward_label(self):
        return 'Ward'
    
    def get_class_type(self):
        return 'ctbr'

class NYCityBoundary(CityBoundary):
    BTYPE_CHOICES = CityBoundary.BTYPE_CHOICES + (
        ('bor', 'borough'),
        ('com', 'community'))
    
    def get_ward_label(self):
        return 'City Council'
    
    def get_class_type(self):
        return 'nycb'

継承が参考になる。
うーん、Adminで見たとき"bnd_type"がコンボボックスになると思っていたんだけどならないのは何故?
おれの思い違い?写経ミスかな?

こういう使い方初めて見た

文字列フォーマット操作
今まで使っていたのは、

'%s.%s' % (model_table, qn(model._meta.pk.column)

こんな感じ。
DjangoのContent Typesを使うコードを色々探していたらdjango-taggingでこんなものを見つけた。
http://code.google.com/p/django-tagging/source/browse/trunk/tagging/models.py

query = """
SELECT DISTINCT %(tag)s.id, %(tag)s.name%(count_sql)s
FROM
    %(tag)s
    INNER JOIN %(tagged_item)s
        ON %(tag)s.id = %(tagged_item)s.tag_id
    INNER JOIN %(model)s
        ON %(tagged_item)s.object_id = %(model_pk)s
    %%s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
    %%s
GROUP BY %(tag)s.id, %(tag)s.name
%%s
ORDER BY %(tag)s.name ASC""" % {
    'tag': qn(self.model._meta.db_table),
    'count_sql': counts and (', COUNT(%s)' % model_pk) or '',
    'tagged_item': qn(TaggedItem._meta.db_table),
    'model': model_table,
    'model_pk': model_pk,
    'content_type_id': ContentType.objects.get_for_model(model).pk,
}

Σ(゚д゚;)< 辞書使っとる!

"Spass mit Newforms-Admin - Read-Only Felder"超絶意訳

続きが出たので今回も超絶意訳をやって行きたいと思います。
前提として、
models.pyは前回使っていたものを使います。
新しく作る場合は、Metaクラス部分はなくても大丈夫です。

from django.db import models
from django.contrib.auth.models import User

# Create your models here.

class Entry(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    author = models.ForeignKey(User)
    
    class Meta:
        permissions = (
            ('can_view_all', 'Can view all Entries'),
        )
        
    def __unicode__(self):
        return self.title

では、Spass mit Newforms-Admin - Read-Only Felder超絶意訳スタートです。


Admin Interfaceを使っていて不満なところってありませんか?
モデルフィールドのオプション"editable"を"False"にするとAdmin Interfaceに表示されなくなります。
これをどうにかしたいと思いませんか?
admin.pyをカスタマイズすることで可能になります。


admin.pyを以下のように作成してください。

from django.contrib import admin
from myproject.weblog.models import Entry

class EntryAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        
        if db_field.name == "title":
            field.widget.attrs = {'disabled': 'disabled'}
        return field

admin.site.register(Entry, EntryAdmin)

次にAdmin Interfaceにアクセスし、entryの追加もしくは変更をしてみて下さい。
以下のように"Title"の部分が入力・変更が出来なくなっていると思います。
read-only1
read-only posted by (C)常山


これでもいいのですが、融通が利いてませんよね。
もう少し変更してみましょう。

from django.contrib import admin
from myproject.weblog.models import Entry

class EntryAdmin(admin.ModelAdmin):
    def add_view(self, request, *args, **kwargs):
        self._is_change_mode = False
        return super(EntryAdmin, self).add_view(request, *args, **kwargs)
    
    def change_view(self, request, object_id, *args, **kwargs):
        self._is_change_mode = True
        return super(EntryAdmin, self).change_view(request, object_id, *args, **kwargs)
    
    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        
        if db_field.name == "title" and self._is_change_mode:
            field.widget.attrs = {'disabled': 'disabled'}
        return field

admin.site.register(Entry, EntryAdmin)

どうでしょうか?
追加の場合、以下のように"Title"が入力できるようになり
read-only2
read-only2 posted by (C)常山

変更の場合は、"Title"が入力不可となっています。
read-only3
read-only3 posted by (C)常山


ここまでこだわってくるともう少し見栄えを良くしたいと思いませんか?
もう少し頑張ってコーディングしてみましょう。

from django.contrib import admin
from myproject.weblog.models import Entry
from django.utils.safestring import mark_safe
from django import forms

class ReadOnlyWidget(forms.HiddenInput):
    def __init__(self, append_text, *args, **kwargs):
        self.append_text = append_text
        super(ReadOnlyWidget, self).__init__()
    
    def render(self, *args, **kwargs):
        field_value = super(ReadOnlyWidget, self).render(*args, **kwargs)
        return mark_safe("%s <strong>%s</strong>" % (field_value, self.append_text))
        
class EntryAdmin(admin.ModelAdmin):
    def add_view(self, request, *args, **kwargs):
        self._is_change_mode = False
        return super(EntryAdmin, self).add_view(request, *args, **kwargs)
    
    def change_view(self, request, object_id, *args, **kwargs):
        self._is_change_mode = True
        self._obj = Entry.objects.get(pk = object_id)
        return super(EntryAdmin, self).change_view(request, object_id, *args, **kwargs)
    
    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        
        if db_field.name == "title" and self._is_change_mode:
            #field.widget.attrs = {'disabled': 'disabled'}
            field.widget = ReadOnlyWidget(append_text=self._obj.title)
        return field

admin.site.register(Entry, EntryAdmin)

read-only4
read-only4 posted by (C)常山
このようにすると、"Title"が違和感なく表示されます。


Spass mit Newforms-Admin - Rowlevel-Permissionsの内容と併せると面白い方法が出来るのではないでしょうか:-)