偏った言語信者の垂れ流し

2014-09-22

[][]Django1.7の管理サイトでアプリケーション名を変更する

Django1.6以前のバージョンでは、管理サイトで表示されるアプリケーション名を変更する方法は通常の使い方では提供されていませんでした。

変えたいのはこの「Myapp」という部分。

f:id:nullpobug:20140922221155p:image

Django1.7ではアプリケーションごとの設定が抽象化、再利用可能な仕組みが導入され、アプリケーション名を設定できるようになりました。

ソースコード

django.apps.AppConfigクラスを継承して、アプリケーション設定のクラスを作成し、アプリケーション名を設定します。

作成したアプリケーション設定のクラスを、アプリケーションデフォルトにします。

試したバージョンはPython3.4, Django1.7です。

myapp/models.py

モデルはいつも通り。

from django.db import models


class Item(models.Model):
    name = models.CharField(max_length=50)
    price = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = verbose_name_plural = "商品"
myapp/apps.py

Django1.7で追加されたAppConfigを使います。nameには他のアプリケーションと被らないようにアプリケーションの識別名を代入します。とりあえずはモジュール名で大丈夫です。

verbose_nameにアプリケーションの表示名を代入します。これが管理サイトで表示されます。

from django.apps import AppConfig


class MyAppConfig(AppConfig):
    name = 'myapp'
    verbose_name = "私のアプリケーション"
myapp/__init__.py

default_app_config変数文字列アプリケーション設定のクラスを指定します。

default_app_config = 'myapp.apps.MyAppConfig'

実行結果

「Myapp」の部分を「私のアプリケーション」という表示に変更できました。

f:id:nullpobug:20140922221156p:image

参考

Applications | Django documentation | Django

Django 1.7で追加されるAppConfigの紹介 - 偏った言語信者の垂れ流し

2014-08-31

[][]DjangoでDEBUG=Falseの際にSQLのログを出力する

Djangoクエリのログを出力したい場合、ロギングの設定でdjango.db.backendsのロガーを設定すればできます。

ただし、これはsettings.DEBUG=Trueのときにしか出力されないようになっています。

これはドキュメントにも書かれています。

ロギング — Django 1.4 documentation

Logging | Django documentation | Django

DEBUG=Falseの際でもSQLのログを記録したい場合があり、方法を調べてました。

試したのは、Python 2.7, Django 1.6.6

注意

Djangoデバッグ時にしかクエリログを出力しないようにしているのは、パフォーマンスに問題がある等の理由で意図的なものです。

この記事の方法でログを出力すると、パフォーマンスに問題が出る可能性があることに注意してください。

内部の動き

内部の仕組みとしては、django/db/__init__.pyのBaseDatabaseWrapperクラス、cursorメソッドでCursorクラスをデバッグ用と通常用を切り替えています。

    def cursor(self):
        """
        Creates a cursor, opening a connection if necessary.
        """
        self.validate_thread_sharing()
        if (self.use_debug_cursor or
            (self.use_debug_cursor is None and settings.DEBUG)):
            cursor = self.make_debug_cursor(self._cursor())
        else:
            cursor = util.CursorWrapper(self._cursor(), self)
        return cursor

self.use_debug_cursorの値がTrueであればデバッグ用のCursorクラスが使われるようです。

use_debug_cursorの値を変更するミドルウェアを作成する

connectionオブジェクトのuse_debug_cursorをTrueに設定するミドルウェアを作成します。

myproject/middleware.py
# coding: utf-8
class UseDebugCursorMiddleware(object):
    def process_request(self, request):
        # SQLのログを出力するようにデバッグ用のCursorクラスを使用
        from django.db import connection
        connection.use_debug_cursor = True

settings.pyでミドルウェアを使用する

作成したミドルウェアは、DB接続の前に実行されていないとダメなので、MIDDLEWARE_CLASSESの先頭に設定します。

myproject/settings.py

LOGGINGの設定は通常のクエリログを出力するためのものと同じです。

# 中略

DEBUG = False

# 中略

MIDDLEWARE_CLASSES = (
    'myproject.middleware.UseDebugCursorMiddleware',  # 一番最初にミドルウェアを適用します
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

# 中略

LOGGING = {
    'version': 1,
    'handlers': {
        'logfile': {
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR, 'query.log'),
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['logfile'],
            'level': 'DEBUG',
        }
    },
}

これでDEBUG=Falseの場合でも、SQLのログをファイルに出力することができました。

2014-07-19

[]Nexus4のタッチスクリーンディスプレイを交換した

去年の秋ごろからNexus4を使っていますが、先日歩いているときに地面に落としてしまい、タッチパネルのガラスが割れてしまいました。

f:id:nullpobug:20140719150330j:image

サイドのボタンは動作して画面は表示されるのですが、タッチパネルはどこをタップしても完全に反応しない状態になってしまいました。

新品や中古が安いならそれを買えばいいかと思って調べましたが、値段が全然下がってなく高いままだったので、修理するほうが良い様子。

修理方法をウェブ検索で調べたところ、以下のページの見つけたので参考にさせて頂きました。

スマホ Nexus4 タッチパネル ディスプレイ の 交換修理|むるしすてむ

分解、パーツ交換は自己責任

交換用パーツの入手

交換用のパーツは国内ではやはり手に入らないようなので、eBayから購入して輸入する必要があります。各種手続きなど面倒なので、代行サービスを探して利用することにしました。

いくつか見てみたところ、eBayの商品の落札から国内発送まですべてやってくれるセカイモンというサービスがあったので、今回はこれを利用することにしました。

eBay公認日本語オークション セカイモン

「USA LCD Display Touch Screen Digitizer Glass Assembly For LG google nexus 4 E960」で検索すると該当のパーツが出てきます。45ドル程度。

セカイモン経由で購入して手数料、送料すべて込みで、7,717円で入手出来ました。

端末の分解とパーツ交換

分解とパーツ交換の作業については、参考にしたページで紹介されていた動画が参考になりました。

http://www.youtube.com/watch?v=ZrEEVqXg8G8

背面のカバーを外すために、星形(T5)の精密ドライバーが必要です。また、中のパーツなどを外すためには、プラスの精密ドライバーが必要です。

背面のカバーはツメの引っかかりを外すのに少し苦労。マイナスの精密ドライバ2本を使って何とか開けました。うまくやらないと側がボロボロになりますね。

f:id:nullpobug:20140719150331j:image

次にタッチスクリーンディスプレイですが、これは粘着剤(両面テープ?)のようなものでかなり強力にくっついてるので、無理やり剥がすしかなかったです。端の部分はガラスを細かく割りながらマイナスドライバで削り剥がしました。

交換作業で一番時間かかったのがこの工程で、端の取れにくい部分を除去するのに1時間程度かかってしまいました。

f:id:nullpobug:20140719150332j:image

f:id:nullpobug:20140719150333j:image

バラしたパーツとこれから取り付ける購入したパーツです。

f:id:nullpobug:20140719150334j:image

取り付けから蓋を閉じるまでの細かい作業も動画が参考になりました。

タッチパネルを固定するための両面テープは、幅が細く(5mm)て厚さが薄めのものをホームセンターで購入し、大きすぎる場合はハサミで切ってサイズを調整しました。

取り付けが完了した状態のもの。

f:id:nullpobug:20140719150335j:image

電源を入れて問題なく動くことを確認できました。

f:id:nullpobug:20140719150336j:image

作業開始から終了までだいたい3時間ぐらいでした。

2014-07-18

[]Djangoでprefetch_relatedを使ってクエリ数を減らす

prefetch_relatedはDjango 1.4で追加された機能です。

親子関係を表すモデル(多対多になってるものなど)をツリー状に表示する場合、ループ内でクエリを実行しってしまうと、クエリ数が多くて極端に遅くなります(特に2段目とか3段目)。

prefetch_relatedを使うと、事前にリレーション先のデータを取得しておき、ループ内で新たにクエリが実行されないようにできます。

試したバージョンは、Python2.7、Django1.6.5です。

ソースコード

完全なコードはbitbucketに置いてます。

tokibito / sample_nullpobug / source / django / market — Bitbucket

shop/models.py
# coding: utf-8
from django.db import models

class Category(models.Model):
    "カテゴリ"
    name = models.CharField(max_length=40)

    def __unicode__(self):
        return self.name

    class Meta:
        db_table = 'category'

class Item(models.Model):
    "商品"
    name = models.CharField(max_length=40)
    code = models.CharField(max_length=10, unique=True)
    price = models.IntegerField()
    category = models.ForeignKey('Category')

    def __unicode__(self):
        return self.name

    class Meta:
        db_table = 'item'

class Bundle(models.Model):
    "まとめ売り"
    name = models.CharField(max_length=40)
    price = models.IntegerField()
    items = models.ManyToManyField('Item', through='BundleItem')

    def __unicode__(self):
        return self.name

    class Meta:
        db_table = 'bundle'

class BundleItem(models.Model):
    "まとめ売り商品"
    bundle = models.ForeignKey('Bundle')
    item = models.ForeignKey('Item')

    class Meta:
        db_table = 'bundle_item'

django-extensionsのgraph_modelsコマンドで出力すると、こういう構造になっています。

f:id:nullpobug:20140718224950p:image

shop/templates/index.html

テンプレートファイルは、どちらのビュー関数でも同じものを使います。

<html>
<body>
<ul>
{% for bundle in bundles %}
  <li>{{ bundle.name }}
    <ul>
    {% for item in bundle.items.all %}
      <li>{{ item }} - {{ item.category }}</li>
    {% endfor %}
    </ul>
  </li>
{% endfor %}
</ul>
</body>
</html>

bundle、itemのところで2重のforループになっています。また2つ目のほうは、bundleからitemを、itemからcategoryを取得して使うようなテンプレートになっています。

shop/views.py

prefetch_relatedを使わない場合と使う場合のビュー関数をそれぞれ用意して、urls.pyに設定しておきます。

# coding: utf-8
from django.shortcuts import render
from shop.models import Category, Item, Bundle


def no_prefetch_view(request):
    # prefetch_relatedを使わない場合
    bundles = Bundle.objects.all()
    return render(request, 'index.html', {'bundles': bundles})


def prefetch_view(request):
    # prefetch_relatedを使う場合
    bundles = Bundle.objects.prefetch_related('items__category')
    return render(request, 'index.html', {'bundles': bundles})

prefetch_relatedを使う場合、今回はテンプレート中でbundle→item→categoryの順でツリー状に表示するため、「items__category」までをプリフェッチ対象に指定しています。

実行結果

URLアクセスすると、どちらも同じ表示になります。

f:id:nullpobug:20140718224953p:image

prefetch_relatedを使わない場合

debug-toolbarでSQLをみてみると、クエリ数は9回になっています。

テンプレート中の「bundle.items.all」の部分と、「item.category」の部分でクエリが実行されているためです。

f:id:nullpobug:20140718224951p:image

prefetch_relatedを使う場合

debug-toolbarでSQLをみてみると、クエリ数は4回になっています。

bundle_idをINで指定してitemテーブル、category_idをINで指定してcategoryテーブルのデータをまとめて取得しています。

f:id:nullpobug:20140718224952p:image

このようにprefetch_relatedを使うことで、いくらかクエリ数を減らせます。

参考

2014-06-22

[][]Django 1.5.3以降で変更されたセッションシリアライズについて

Djangoセッションデータは、settings.SESSION_ENGINEで指定されたクラスによって保存されます。

Django 1.5.3より前のバージョンでは、セッションデータのオブジェクトシリアライズ、デシリアライズする処理は、ここで指定されるバックエンドクラスに任されていました。

デフォルトでは、pickleモジュールでダンプして、ハッシュ値+base64エンコードした文字列になっていました。

Django 1.5.3以降では、このpickleモジュールでダンプしていた部分が分離され、新たにsettings.SESSION_SERIALIZERという設定項目が追加されました。

SESSION_SERIALIZER

Django 1.5.3以降の1.5系では、settingsにSESSION_SERIALIZERを指定しない場合は従来通りpickleモジュールが使用されます。

また、Django 1.6では、デフォルトシリアライザが、jsonモジュールを使ってダンプするJSONSerializerに変更されました。

SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'

JSONSerializerの場合、jsonモジュールのdumpsでシリアライズできないオブジェクトセッションに入れると、保存できずエラーになります。

例えば、Pythonのobjectクラスを継承したインスタンスや、その他jsonモジュールが対応していない値の型、データ構造などが該当します。

Django 1.6以降で従来通り、Pythonの任意のオブジェクトを保存したい場合は、settings.SESSION_SERIALIZERにPickleSerializerを指定するとよいでしょう。

SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

Python以外で作られた別のシステムと連携するのであれば、pickleデータは扱いづらい可能性が高いので、JSONSerializerを使うか、その他自前でシリアライザを作成するとよいでしょう。