Login

Django admin autoregister

Author:
Mimino
Posted:
November 8, 2013
Language:
Python
Version:
Not specified
Score:
0 (after 0 ratings)

Official GitHub page: https://github.com/Mimino666/django-admin-autoregister

One call to autoregister_admin() automatically creates and registers admin views for all the models in the specified module with intelligent linking between ForeignKey, OneToOneField and ManyToManyField fields.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from types import ModuleType

from django.contrib import admin
from django.contrib.admin.util import quote
from django.core.urlresolvers import reverse, NoReverseMatch
from django.db.models import ForeignKey, OneToOneField, Count
from django.db.models.base import ModelBase


def _get_admin_change_url(field):
    '''Return function to generate admin change view url for a related object.

    @param field: field pointing to a related object
    @type field: ForeignKey or OneToOneField
    '''
    related_model = field.related.parent_model

    def f(self, obj):
        link_args = getattr(obj, field.attname)
        if link_args is None:
            return u'(None)'
        # we could use field.name to output __unicode__() of the related object,
        # but that would require to prefetch related objects, which can be slow
        link_text = getattr(obj, field.attname)

        try:
            url = reverse('admin:%s_%s_change' %
                            (related_model._meta.app_label, related_model._meta.module_name),
                          args=[quote(link_args)])
        except NoReverseMatch:
            return link_text
        return u'<a href="%s">%s</a>' % (url, link_text)
    f.allow_tags = True
    f.short_description = field.name
    return f


def _get_admin_changelist_url(field):
    '''Return function to generate admin changlelist view url for the related
    objects.

    @param field: field pointing to the related objects
    @type field: ManyToManyField
    '''

    related_model = field.related.parent_model

    def f(self, obj):
        link_cond = '%s=%s' % (field.related_query_name(), quote(obj.pk))
        link_text = u'%s (%s)' % (field.name.title(), getattr(obj, '%s__count' % field.name))

        try:
            url = reverse('admin:%s_%s_changelist' %
                            (related_model._meta.app_label, related_model._meta.module_name))
        except NoReverseMatch:
            return link_text
        return u'<a href="%s?%s">%s</a>' % (url, link_cond, link_text)
    f.allow_tags = True
    f.short_description = field.name
    return f


def _get_admin_queryset(admin_class, count_field_names):
    '''Return function to generate queryset to efficiently fetch counts()
    of related objects.
    '''

    counts = map(Count, count_field_names)
    def queryset(self, request):
        qs = super(admin_class, self).queryset(request)
        if counts:
            qs = qs.annotate(*counts)
        return qs
    return queryset


def autoregister_admin(module, exclude=None, model_fields=None,
                          admin_fields=None):
    '''
    @param module: module containing django.db.models classes
    @type module: str or __module__
                  If you are providing str, use absolute path.

    @param exclude: list of classes to exclude from auto-register
    @type exclude: iterable of strings or None

    @param model_fields: dictionary of additional fields for list_display
        {'model name': [field1, field2, ...]}
    @type model_fields: dict or None

    @param admin_fields: dictionary of additional admin fields
        {'model name': {name: value, ...}}
    @type admin_fields: dict or None
    '''

    exclude = exclude or []
    model_fields = model_fields or {}
    admin_fields = admin_fields or {}
    if isinstance(module, basestring):
        module = __import__(module, fromlist=[module.split('.')[-1]])
    elif not isinstance(module, ModuleType):
        raise TypeError('invalid type of argument `module`, expected `str` or '
                        '`ModuleType`, got %s.' % type(module))

    # collect the models to register
    models = []
    for model in module.__dict__.values():
        if (isinstance(model, ModelBase) and
                model.__module__ == module.__name__ and
                not model._meta.abstract and
                model.__name__ not in exclude):
            models.append(model)

    # for each model prepare an admin class `<model_name>Admin`
    for model in models:
        admin_class = type('%sAdmin' % model.__name__, (admin.ModelAdmin,), dict())
        # list pk as the first value
        admin_class.list_display = [model._meta.pk.name]
        # list all the other fields
        for field in model._meta.fields:
            if field == model._meta.pk:
                continue

            admin_field_name = field.name
            # create link for related objects
            if isinstance(field, (ForeignKey, OneToOneField)):
                admin_field_name += '_link'
                setattr(admin_class, admin_field_name, _get_admin_change_url(field))

            admin_class.list_display.append(admin_field_name)

        count_field_names = []
        for field in model._meta.many_to_many:
            count_field_names.append(field.name)
            admin_field_name = field.name + '_link'
            setattr(admin_class, admin_field_name, _get_admin_changelist_url(field))
            admin_class.list_display.append(admin_field_name)

        # add custom model fields
        for name in model_fields.get(model.__name__, []):
            admin_class.list_display.append(name)

        # prefetch related fields
        admin_class.queryset = _get_admin_queryset(admin_class, count_field_names)

        # add custom admin fields
        for (name, value) in admin_fields.get(model.__name__, {}).iteritems():
            setattr(admin_class, name, value)

        try:
            admin.site.register(model, admin_class)
        # pass gracefully on duplicate registration errors
        except admin.sites.AlreadyRegistered:
            pass

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 3 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 3 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 10 months, 1 week ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 11 months ago
  5. Help text hyperlinks by sa2812 11 months, 3 weeks ago

Comments

Please login first before commenting.