LoginSignup
7
7

More than 5 years have passed since last update.

Django: User Agentを記録してAdminで管理

Posted at

Project作成

  • GET /accounts/login でログインフォームを出す
  • POST /accounts/login でログインし、/accounts/profile/ にリダイレクト
  • GET /accounts/profile で個人情報表示

startproject

(v17)hdknr@wzy:~/ve/v17/src$ mkdir -p ua/web
(v17)hdknr@wzy:~/ve/v17/src$ django-admin startproject app ua/web
(v17)hdknr@wzy:~/ve/v17/src$ cd ua/web
(v17)hdknr@wzy:~/ve/v17/src/ua/web$ python manage.py migrate
(v17)hdknr@wzy:~/ve/v17/src/ua/web$ python manage.py createsuperuser

accounts 追加

(v17)hdknr@wzy:~/ve/v17/src/ua/web$ python manage.py startapp accounts

app/settings.py

INSTALLED_APPS += (
    'accounts',
)

app/urls.py

    url(r'^accounts/', include('accounts.urls')),

accounts/urls.py

from django.conf.urls import patterns, url
import views

urlpatterns = patterns(
    '',
    url(r'login/$', 'django.contrib.auth.views.login'),
    url(r'profile/$', views.profile),
)

accounts/views.py

from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse

import models


@login_required
def profile(request):
    ua = None
    return TemplateResponse(
        request,
        'registration/profile.html',
        dict(request=request, ua=ua, ))

accounts/templates/registration

(v17)hdknr@wzy:~/ve/v17/src/ua/web$ mkdir -p accounts/templates/registration
  • login.html
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" />
</form>
  • profile.html
{{ request.user }}
{{ ua }}
  • この時点でログインまで動きます

UserAgentモデル

UA判定(accounts/ua.py)

  • agent_type() : 正規表現で マッチングする agentを見つける
import re


DETECTOR = [
    # Featured Phone
    r'(?P<agent>DoCoMo)',
    r'(?P<agent>SoftBank)',
    r'^(?P<agent>KDDI)',     # if no KDDI, HDML browser.
    r'(?P<agent>Vodafone)',

    ....
]


def agent_type(agent_string):
    for d in DETECTOR:
        m = re.search(d, agent_string, flags=re.IGNORECASE)
        m = m and m.groupdict() or {}
        if m:
            return m['agent'].lower().replace('-', '').replace(' ', '')
    return "generic"

モデル(accounts/models.py)

from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _

from ua import agent_type
import hashlib

class UserAgent(models.Model):
    agent = models.CharField(
        _(u'User Agent'),
        max_length=30, default="PC")
    key = models.CharField(
        _(u'User Agent Header MD5 Hash'),
        max_length=40, unique=True, db_index=True,)
    header = models.CharField(
        _(u'User Agent Header'),  max_length=512, )
    users = models.ManyToManyField(
        User, default=None, null=True, blank=True)


    @classmethod
    def get_ua(cls, ua_header, user=None):
        ua, created = cls.objects.get_or_create(
            key=hashlib.md5(ua_header).hexdigest())

        if created:
            ua.agent = agent_type(ua_header)
            ua.header = ua_header
            ua.save()

        if user:
            ua.users.add(user)

        return ua

    def __unicode__(self):
        return "%s(%s)" % (self.agent, getattr(self, 'id', ''))

/accounts/profileで記録する(acounts/views.py)

  • profile()修正
@login_required
def profile(request):
    ua = models.UserAgent.get_ua(
        request.META.get('HTTP_USER_AGENT', 'N/A'),
        request.user)

    return TemplateResponse(
        request,
        'registration/profile.html',
        dict(request=request, ua=ua, ))

マイグレート

(v17)hdknr@wzy:~/ve/v17/src/ua/web$ python manage.py makemigrations accounts
Migrations for 'accounts':
  0001_initial.py:
    - Create model UserAgent
(v17)hdknr@wzy:~/ve/v17/src/ua/web$ python manage.py migrate  accounts
Operations to perform:
  Apply all migrations: accounts
Running migrations:
  Applying accounts.0001_initial... OK

accounts/admin.py

from django.contrib import admin
import models


class UserAgentAdmin(admin.ModelAdmin):
    list_display = tuple(
        [f.name for f in models.UserAgent._meta.fields
         if f.name not in ['key']]
    )

admin.site.register(UserAgent, UserAgentAdmin)

  • この時点でUserAgentのAdmin管理できる

Adminカスタマイズ

UserAgentAdmin(accounts/admin.py)

1. agentフィールドでフィルターする

    list_filter = ('agent', )    

2. usersフィールドを編集できないようにする

    add_exclude = ('users',)
    edit_exclude = ('users',)

    def add_view(self, *args, **kwargs):
        self.exclude = getattr(self, 'add_exclude', ())
        return super(UserAgentAdmin, self).add_view(*args, **kwargs)

    def change_view(self, *args, **kwargs):
        self.exclude = getattr(self, 'edit_exclude', ())
        return super(UserAgentAdmin, self).change_view(*args, **kwargs)

3. UserAgentでログインしたユーザーの数を表示させ、一覧にリンクさせる

from django.core.urlresolvers import reverse
    list_display = tuple(
        [f.name for f in models.UserAgent._meta.fields
         if f.name not in ['key']]
    ) + ('users_count',)

    def users_count(self, obj):
        if not obj.users.exists():
            return '0'

        uri = reverse("admin:%s_changelist" % obj.users.model._meta.db_table)
        query = "?useragent__id__exact=%d" % (obj.id)
        return mark_safe(
            u"<a href='%s'>%d Users</a>" % (uri + query, obj.users.count()))

    users_count.allow_tags = True

image

4. usersフィールドを編集させない変わりに、 ログインしたユーザーへのリンクを表示させる

    readonly_fields = ('ua_users', )

    def ua_users(self, instance):
        try:
            return ",".join([
                mark_safe('<a href="%s">%s</a>   ' % (
                    reverse("admin:%s_change" % u._meta.db_table, args=[u.id]),
                    u.__unicode__()))
                for u in instance.users.all()])
        except:
            return "errors"

    ua_users.short_description = "UserAgent Users"
    ua_users.allow_tags = True

image

UserAdmin(accounts/admin.py)

  • UserからUserAgentのAdmin画面に戻れるようにする
from django.db.models.manager import Manager
from django.utils.safestring import mark_safe
from django.contrib.auth.admin import UserAdmin
  • モデルとそのリレーションフールドが指定されたときに、そのフィールドのAdmin画面に戻れるリンクを作るユーティリティ
def link_to_relation(self, obj, field=""):
    fobj = obj and getattr(obj, field, None)

    if fobj is None:
        return "No Link"

    if issubclass(fobj.__class__, Manager):
        fobj = fobj.all()
    else:
        fobj = [fobj, ]

    return mark_safe("<br/>".join([
        '<a href="%s">%s</a>' % (
            reverse("admin:%s_change" % ln._meta.db_table, args=[ln.id]),
            ln.__unicode__()
        ) for ln in fobj]))
  • Userオブジェクトとuseragent_set でバックリンクを作る
useragent_link = lambda self, obj: link_to_relation(self, obj, "useragent_set")
useragent_link.short_description = u"User Agent"
useragent_link.allow_tags = True
  • これをUserAdminに追加してやる
UserAdmin.list_display = tuple(
    set(UserAdmin.list_display + ('useragent_link', )))
UserAdmin.useragent_link = useragent_link
  • さらにUserAdminでUserAgent#agentでフィルタリングできるようにする
UserAdmin.list_filter = UserAdmin.list_filter + ('useragent__agent',)
  • これで戻れる

image

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7