| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- import copy
- import functools
- import datetime
- import decimal
- from functools import update_wrapper
- from inspect import getfullargspec
- from django import forms
- from django.apps import apps
- from django.conf import settings
- from django.contrib import messages
- from django.contrib.auth import get_permission_codename
- from django.core.exceptions import ValidationError
- from django.core.serializers.json import DjangoJSONEncoder
- from django.urls.base import reverse
- from django.http import HttpResponse
- from django.template import Context, Template
- from django.template.response import TemplateResponse
- from django.utils import six
- from django.utils.decorators import method_decorator, classonlymethod
- from django.utils.encoding import force_text, smart_text, smart_str
- from django.utils.functional import Promise
- from django.utils.http import urlencode
- from django.utils.itercompat import is_iterable
- from django.utils.safestring import mark_safe
- from django.utils.text import capfirst
- from django.utils.translation import ugettext as _
- from django.views.decorators.csrf import csrf_protect
- from django.views.generic import View
- from collections import OrderedDict
- from xadmin.util import static, json, vendor, sortkeypicker
- from xadmin.models import Log
- csrf_protect_m = method_decorator(csrf_protect)
- class IncorrectPluginArg(Exception):
- pass
- def get_content_type_for_model(obj):
- from django.contrib.contenttypes.models import ContentType
- return ContentType.objects.get_for_model(obj, for_concrete_model=False)
- def filter_chain(filters, token, func, *args, **kwargs):
- if token == -1:
- return func()
- else:
- def _inner_method():
- fm = filters[token]
- fargs = getfullargspec(fm)[0]
- if len(fargs) == 1:
- # Only self arg
- result = func()
- if result is None:
- return fm()
- else:
- raise IncorrectPluginArg(u'Plugin filter method need a arg to receive parent method result.')
- else:
- return fm(func if fargs[1] == '__' else func(), *args, **kwargs)
- return filter_chain(filters, token - 1, _inner_method, *args, **kwargs)
- def filter_hook(func):
- tag = func.__name__
- func.__doc__ = "``filter_hook``\n\n" + (func.__doc__ or "")
- @functools.wraps(func)
- def method(self, *args, **kwargs):
- def _inner_method():
- return func(self, *args, **kwargs)
- if self.plugins:
- filters = [(getattr(getattr(p, tag), 'priority', 10), getattr(p, tag))
- for p in self.plugins if callable(getattr(p, tag, None))]
- filters = [f for p, f in sorted(filters, key=lambda x:x[0])]
- return filter_chain(filters, len(filters) - 1, _inner_method, *args, **kwargs)
- else:
- return _inner_method()
- return method
- def inclusion_tag(file_name, context_class=Context, takes_context=False):
- def wrap(func):
- @functools.wraps(func)
- def method(self, context, nodes, *arg, **kwargs):
- _dict = func(self, context, nodes, *arg, **kwargs)
- from django.template.loader import get_template, select_template
- cls_str = str if six.PY3 else basestring
- if isinstance(file_name, Template):
- t = file_name
- elif not isinstance(file_name, cls_str) and is_iterable(file_name):
- t = select_template(file_name)
- else:
- t = get_template(file_name)
- _dict['autoescape'] = context.autoescape
- _dict['use_l10n'] = context.use_l10n
- _dict['use_tz'] = context.use_tz
- _dict['admin_view'] = context['admin_view']
- csrf_token = context.get('csrf_token', None)
- if csrf_token is not None:
- _dict['csrf_token'] = csrf_token
- nodes.append(t.render(_dict))
- return method
- return wrap
- class JSONEncoder(DjangoJSONEncoder):
- def default(self, o):
- if isinstance(o, datetime.datetime):
- return o.strftime('%Y-%m-%d %H:%M:%S')
- elif isinstance(o, datetime.date):
- return o.strftime('%Y-%m-%d')
- elif isinstance(o, decimal.Decimal):
- return str(o)
- elif isinstance(o, Promise):
- return force_text(o)
- else:
- try:
- return super(JSONEncoder, self).default(o)
- except Exception:
- return smart_text(o)
- class BaseAdminObject(object):
- def get_view(self, view_class, option_class=None, *args, **kwargs):
- opts = kwargs.pop('opts', {})
- return self.admin_site.get_view_class(view_class, option_class, **opts)(self.request, *args, **kwargs)
- def get_model_view(self, view_class, model, *args, **kwargs):
- return self.get_view(view_class, self.admin_site._registry.get(model), *args, **kwargs)
- def get_admin_url(self, name, *args, **kwargs):
- return reverse('%s:%s' % (self.admin_site.app_name, name), args=args, kwargs=kwargs)
- def get_model_url(self, model, name, *args, **kwargs):
- return reverse(
- '%s:%s_%s_%s' % (self.admin_site.app_name, model._meta.app_label,
- model._meta.model_name, name),
- args=args, kwargs=kwargs, current_app=self.admin_site.name)
- def get_model_perm(self, model, name):
- return '%s.%s_%s' % (model._meta.app_label, name, model._meta.model_name)
- def has_model_perm(self, model, name, user=None):
- user = user or self.user
- return user.has_perm(self.get_model_perm(model, name)) or (name == 'view' and self.has_model_perm(model, 'change', user))
- def get_query_string(self, new_params=None, remove=None):
- if new_params is None:
- new_params = {}
- if remove is None:
- remove = []
- p = dict(self.request.GET.items()).copy()
- arr_keys = list(p.keys())
- for r in remove:
- for k in arr_keys:
- if k.startswith(r):
- del p[k]
- for k, v in new_params.items():
- if v is None:
- if k in p:
- del p[k]
- else:
- p[k] = v
- return '?%s' % urlencode(p)
- def get_form_params(self, new_params=None, remove=None):
- if new_params is None:
- new_params = {}
- if remove is None:
- remove = []
- p = dict(self.request.GET.items()).copy()
- arr_keys = list(p.keys())
- for r in remove:
- for k in arr_keys:
- if k.startswith(r):
- del p[k]
- for k, v in new_params.items():
- if v is None:
- if k in p:
- del p[k]
- else:
- p[k] = v
- return mark_safe(''.join(
- '<input type="hidden" name="%s" value="%s"/>' % (k, v) for k, v in p.items() if v))
- def render_response(self, content, response_type='json'):
- if response_type == 'json':
- response = HttpResponse(content_type="application/json; charset=UTF-8")
- response.write(
- json.dumps(content, cls=JSONEncoder, ensure_ascii=False))
- return response
- return HttpResponse(content)
- def template_response(self, template, context):
- return TemplateResponse(self.request, template, context)
- def message_user(self, message, level='info'):
- """
- Send a message to the user. The default implementation
- posts a message using the django.contrib.messages backend.
- """
- if hasattr(messages, level) and callable(getattr(messages, level)):
- getattr(messages, level)(self.request, message)
- def static(self, path):
- return static(path)
- def vendor(self, *tags):
- return vendor(*tags)
- def log(self, flag, message, obj=None):
- log = Log(
- user=self.user,
- ip_addr=self.request.META['REMOTE_ADDR'],
- action_flag=flag,
- message=message
- )
- if obj:
- log.content_type = get_content_type_for_model(obj)
- log.object_id = obj.pk
- log.object_repr = force_text(obj)
- log.save()
- class BaseAdminPlugin(BaseAdminObject):
- def __init__(self, admin_view):
- self.admin_view = admin_view
- self.admin_site = admin_view.admin_site
- if hasattr(admin_view, 'model'):
- self.model = admin_view.model
- self.opts = admin_view.model._meta
- def init_request(self, *args, **kwargs):
- pass
- class BaseAdminView(BaseAdminObject, View):
- """ Base Admin view, support some comm attrs."""
- base_template = 'xadmin/base.html'
- need_site_permission = True
- def __init__(self, request, *args, **kwargs):
- self.request = request
- self.request_method = request.method.lower()
- self.user = request.user
- self.base_plugins = [p(self) for p in getattr(self,
- "plugin_classes", [])]
- self.args = args
- self.kwargs = kwargs
- self.init_plugin(*args, **kwargs)
- self.init_request(*args, **kwargs)
- @classonlymethod
- def as_view(cls):
- def view(request, *args, **kwargs):
- self = cls(request, *args, **kwargs)
- if hasattr(self, 'get') and not hasattr(self, 'head'):
- self.head = self.get
- if self.request_method in self.http_method_names:
- handler = getattr(
- self, self.request_method, self.http_method_not_allowed)
- else:
- handler = self.http_method_not_allowed
- return handler(request, *args, **kwargs)
- # take name and docstring from class
- update_wrapper(view, cls, updated=())
- view.need_site_permission = cls.need_site_permission
- return view
- def init_request(self, *args, **kwargs):
- pass
- def init_plugin(self, *args, **kwargs):
- plugins = []
- for p in self.base_plugins:
- p.request = self.request
- p.user = self.user
- p.args = self.args
- p.kwargs = self.kwargs
- result = p.init_request(*args, **kwargs)
- if result is not False:
- plugins.append(p)
- self.plugins = plugins
- @filter_hook
- def get_context(self):
- return {'admin_view': self, 'media': self.media, 'base_template': self.base_template}
- @property
- def media(self):
- return self.get_media()
- @filter_hook
- def get_media(self):
- return forms.Media()
- class CommAdminView(BaseAdminView):
- base_template = 'xadmin/base_site.html'
- menu_template = 'xadmin/includes/sitemenu_default.html'
- site_title = getattr(settings, "XADMIN_TITLE", _(u"Django Xadmin"))
- site_footer = getattr(settings, "XADMIN_FOOTER_TITLE", _(u"my-company.inc"))
- global_models_icon = {}
- default_model_icon = None
- apps_label_title = {}
- apps_icons = {}
- def get_site_menu(self):
- return None
- @filter_hook
- def get_nav_menu(self):
- site_menu = list(self.get_site_menu() or [])
- had_urls = []
- def get_url(menu, had_urls):
- if 'url' in menu:
- had_urls.append(menu['url'])
- if 'menus' in menu:
- for m in menu['menus']:
- get_url(m, had_urls)
- get_url({'menus': site_menu}, had_urls)
- nav_menu = OrderedDict()
- for model, model_admin in self.admin_site._registry.items():
- if getattr(model_admin, 'hidden_menu', False):
- continue
- app_label = model._meta.app_label
- app_icon = None
- model_dict = {
- 'title': smart_text(capfirst(model._meta.verbose_name_plural)),
- 'url': self.get_model_url(model, "changelist"),
- 'icon': self.get_model_icon(model),
- 'perm': self.get_model_perm(model, 'view'),
- 'order': model_admin.order,
- }
- if model_dict['url'] in had_urls:
- continue
- app_key = "app:%s" % app_label
- if app_key in nav_menu:
- nav_menu[app_key]['menus'].append(model_dict)
- else:
- # Find app title
- app_title = smart_text(app_label.title())
- if app_label.lower() in self.apps_label_title:
- app_title = self.apps_label_title[app_label.lower()]
- else:
- app_title = smart_text(apps.get_app_config(app_label).verbose_name)
- # find app icon
- if app_label.lower() in self.apps_icons:
- app_icon = self.apps_icons[app_label.lower()]
- nav_menu[app_key] = {
- 'title': app_title,
- 'menus': [model_dict],
- }
- app_menu = nav_menu[app_key]
- if app_icon:
- app_menu['first_icon'] = app_icon
- elif ('first_icon' not in app_menu or
- app_menu['first_icon'] == self.default_model_icon) and model_dict.get('icon'):
- app_menu['first_icon'] = model_dict['icon']
- if 'first_url' not in app_menu and model_dict.get('url'):
- app_menu['first_url'] = model_dict['url']
- for menu in nav_menu.values():
- menu['menus'].sort(key=sortkeypicker(['order', 'title']))
- nav_menu = list(nav_menu.values())
- nav_menu.sort(key=lambda x: x['title'])
- site_menu.extend(nav_menu)
- return site_menu
- @filter_hook
- def get_context(self):
- context = super(CommAdminView, self).get_context()
- if not settings.DEBUG and 'nav_menu' in self.request.session:
- nav_menu = json.loads(self.request.session['nav_menu'])
- else:
- menus = copy.copy(self.get_nav_menu())
- def check_menu_permission(item):
- need_perm = item.pop('perm', None)
- if need_perm is None:
- return True
- elif callable(need_perm):
- return need_perm(self.user)
- elif need_perm == 'super':
- return self.user.is_superuser
- else:
- return self.user.has_perm(need_perm)
- def filter_item(item):
- if 'menus' in item:
- before_filter_length = len(item['menus'])
- item['menus'] = [filter_item(
- i) for i in item['menus'] if check_menu_permission(i)]
- after_filter_length = len(item['menus'])
- if after_filter_length == 0 and before_filter_length > 0:
- return None
- return item
- nav_menu = [filter_item(item) for item in menus if check_menu_permission(item)]
- nav_menu = list(filter(lambda x: x, nav_menu))
- if not settings.DEBUG:
- self.request.session['nav_menu'] = json.dumps(nav_menu, cls=JSONEncoder, ensure_ascii=False)
- self.request.session.modified = True
- def check_selected(menu, path):
- selected = False
- if 'url' in menu:
- chop_index = menu['url'].find('?')
- if chop_index == -1:
- selected = path.startswith(menu['url'])
- else:
- selected = path.startswith(menu['url'][:chop_index])
- if 'menus' in menu:
- for m in menu['menus']:
- _s = check_selected(m, path)
- if _s:
- selected = True
- if selected:
- menu['selected'] = True
- return selected
- for menu in nav_menu:
- check_selected(menu, self.request.path)
- context.update({
- 'menu_template': self.menu_template,
- 'nav_menu': nav_menu,
- 'site_title': self.site_title,
- 'site_footer': self.site_footer,
- 'breadcrumbs': self.get_breadcrumb()
- })
- return context
- @filter_hook
- def get_model_icon(self, model):
- icon = self.global_models_icon.get(model)
- if icon is None and model in self.admin_site._registry:
- icon = getattr(self.admin_site._registry[model],
- 'model_icon', self.default_model_icon)
- return icon
- @filter_hook
- def get_breadcrumb(self):
- return [{
- 'url': self.get_admin_url('index'),
- 'title': _('Home')
- }]
- class ModelAdminView(CommAdminView):
- fields = None
- exclude = None
- ordering = None
- model = None
- remove_permissions = []
- def __init__(self, request, *args, **kwargs):
- self.opts = self.model._meta
- self.app_label = self.model._meta.app_label
- self.model_name = self.model._meta.model_name
- self.model_info = (self.app_label, self.model_name)
- super(ModelAdminView, self).__init__(request, *args, **kwargs)
- @filter_hook
- def get_context(self):
- new_context = {
- "opts": self.opts,
- "app_label": self.app_label,
- "model_name": self.model_name,
- "verbose_name": force_text(self.opts.verbose_name),
- 'model_icon': self.get_model_icon(self.model),
- }
- context = super(ModelAdminView, self).get_context()
- context.update(new_context)
- return context
- @filter_hook
- def get_breadcrumb(self):
- bcs = super(ModelAdminView, self).get_breadcrumb()
- item = {'title': self.opts.verbose_name_plural}
- if self.has_view_permission():
- item['url'] = self.model_admin_url('changelist')
- bcs.append(item)
- return bcs
- @filter_hook
- def get_object(self, object_id):
- """
- Get model object instance by object_id, used for change admin view
- """
- # first get base admin view property queryset, return default model queryset
- model = self.model
- try:
- object_id = model._meta.pk.to_python(object_id)
- return model.objects.get(pk=object_id)
- except (model.DoesNotExist, ValidationError):
- return None
- @filter_hook
- def get_object_url(self, obj):
- if self.has_change_permission(obj):
- return self.model_admin_url("change", getattr(obj, self.opts.pk.attname))
- elif self.has_view_permission(obj):
- return self.model_admin_url("detail", getattr(obj, self.opts.pk.attname))
- else:
- return None
- def model_admin_url(self, name, *args, **kwargs):
- return reverse(
- "%s:%s_%s_%s" % (self.admin_site.app_name, self.opts.app_label,
- self.model_name, name), args=args, kwargs=kwargs)
- def get_model_perms(self):
- """
- Returns a dict of all perms for this model. This dict has the keys
- ``add``, ``change``, and ``delete`` mapping to the True/False for each
- of those actions.
- """
- return {
- 'view': self.has_view_permission(),
- 'add': self.has_add_permission(),
- 'change': self.has_change_permission(),
- 'delete': self.has_delete_permission(),
- }
- def get_template_list(self, template_name):
- opts = self.opts
- return (
- "xadmin/%s/%s/%s" % (
- opts.app_label, opts.object_name.lower(), template_name),
- "xadmin/%s/%s" % (opts.app_label, template_name),
- "xadmin/%s" % template_name,
- )
- def get_ordering(self):
- """
- Hook for specifying field ordering.
- """
- return self.ordering or () # otherwise we might try to *None, which is bad ;)
- @filter_hook
- def queryset(self):
- """
- Returns a QuerySet of all model instances that can be edited by the
- admin site. This is used by changelist_view.
- """
- return self.model._default_manager.get_queryset()
- def has_view_permission(self, obj=None):
- view_codename = get_permission_codename('view', self.opts)
- change_codename = get_permission_codename('change', self.opts)
- return ('view' not in self.remove_permissions) and (self.user.has_perm('%s.%s' % (self.app_label, view_codename)) or
- self.user.has_perm('%s.%s' % (self.app_label, change_codename)))
- def has_add_permission(self):
- codename = get_permission_codename('add', self.opts)
- return ('add' not in self.remove_permissions) and self.user.has_perm('%s.%s' % (self.app_label, codename))
- def has_change_permission(self, obj=None):
- codename = get_permission_codename('change', self.opts)
- return ('change' not in self.remove_permissions) and self.user.has_perm('%s.%s' % (self.app_label, codename))
- def has_delete_permission(self, request=None, obj=None):
- codename = get_permission_codename('delete', self.opts)
- return ('delete' not in self.remove_permissions) and self.user.has_perm('%s.%s' % (self.app_label, codename))
|