Jet fork des offiziellen jet-admin projekts. Geupdated für Django4.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

469 lines
16KB

  1. import datetime
  2. import json
  3. from django.template import Context
  4. from django.utils import translation
  5. from jet import settings
  6. from jet.models import PinnedApplication
  7. try:
  8. from django.apps.registry import apps
  9. except ImportError:
  10. try:
  11. from django.apps import apps # Fix Django 1.7 import issue
  12. except ImportError:
  13. pass
  14. from django.core.serializers.json import DjangoJSONEncoder
  15. from django.http import HttpResponse
  16. try:
  17. from django.core.urlresolvers import reverse, resolve, NoReverseMatch
  18. except ImportError: # Django 1.11
  19. from django.urls import reverse, resolve, NoReverseMatch
  20. from django.contrib.admin import AdminSite
  21. from django.utils.encoding import smart_str as smart_text
  22. from django.utils.text import capfirst
  23. from django.contrib import messages
  24. from django.utils.encoding import force_str as force_text
  25. from django.utils.functional import Promise
  26. from django.contrib.admin.options import IncorrectLookupParameters
  27. from django.contrib import admin
  28. from django.utils.translation import gettext_lazy as _
  29. from django.utils.text import slugify
  30. try:
  31. from collections import OrderedDict
  32. except ImportError:
  33. from ordereddict import OrderedDict # Python 2.6
  34. class JsonResponse(HttpResponse):
  35. """
  36. An HTTP response class that consumes data to be serialized to JSON.
  37. :param data: Data to be dumped into json. By default only ``dict`` objects
  38. are allowed to be passed due to a security flaw before EcmaScript 5. See
  39. the ``safe`` parameter for more information.
  40. :param encoder: Should be an json encoder class. Defaults to
  41. ``django.core.serializers.json.DjangoJSONEncoder``.
  42. :param safe: Controls if only ``dict`` objects may be serialized. Defaults
  43. to ``True``.
  44. """
  45. def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs):
  46. if safe and not isinstance(data, dict):
  47. raise TypeError('In order to allow non-dict objects to be '
  48. 'serialized set the safe parameter to False')
  49. kwargs.setdefault('content_type', 'application/json')
  50. data = json.dumps(data, cls=encoder)
  51. super(JsonResponse, self).__init__(content=data, **kwargs)
  52. def get_app_list(context, order=True):
  53. admin_site = get_admin_site(context)
  54. request = context['request']
  55. app_dict = {}
  56. for model, model_admin in admin_site._registry.items():
  57. app_label = model._meta.app_label
  58. try:
  59. has_module_perms = model_admin.has_module_permission(request)
  60. except AttributeError:
  61. has_module_perms = request.user.has_module_perms(app_label) # Fix Django < 1.8 issue
  62. if has_module_perms:
  63. perms = model_admin.get_model_perms(request)
  64. # Check whether user has any perm for this module.
  65. # If so, add the module to the model_list.
  66. if True in perms.values():
  67. info = (app_label, model._meta.model_name)
  68. model_dict = {
  69. 'name': capfirst(model._meta.verbose_name_plural),
  70. 'object_name': model._meta.object_name,
  71. 'perms': perms,
  72. 'model_name': model._meta.model_name
  73. }
  74. if perms.get('change', False):
  75. try:
  76. model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=admin_site.name)
  77. except NoReverseMatch:
  78. pass
  79. if perms.get('add', False):
  80. try:
  81. model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=admin_site.name)
  82. except NoReverseMatch:
  83. pass
  84. if app_label in app_dict:
  85. app_dict[app_label]['models'].append(model_dict)
  86. else:
  87. try:
  88. name = apps.get_app_config(app_label).verbose_name
  89. except NameError:
  90. name = app_label.title()
  91. app_dict[app_label] = {
  92. 'name': name,
  93. 'app_label': app_label,
  94. 'app_url': reverse(
  95. 'admin:app_list',
  96. kwargs={'app_label': app_label},
  97. current_app=admin_site.name,
  98. ),
  99. 'has_module_perms': has_module_perms,
  100. 'models': [model_dict],
  101. }
  102. # Sort the apps alphabetically.
  103. app_list = list(app_dict.values())
  104. if order:
  105. app_list.sort(key=lambda x: x['name'].lower())
  106. # Sort the models alphabetically within each app.
  107. for app in app_list:
  108. app['models'].sort(key=lambda x: x['name'])
  109. return app_list
  110. def get_admin_site(context):
  111. try:
  112. current_resolver = resolve(context.get('request').path)
  113. index_resolver = resolve(reverse('%s:index' % current_resolver.namespaces[0]))
  114. if hasattr(index_resolver.func, 'admin_site'):
  115. return index_resolver.func.admin_site
  116. for func_closure in index_resolver.func.__closure__:
  117. if isinstance(func_closure.cell_contents, AdminSite):
  118. return func_closure.cell_contents
  119. except:
  120. pass
  121. return admin.site
  122. def get_admin_site_name(context):
  123. return get_admin_site(context).name
  124. class LazyDateTimeEncoder(json.JSONEncoder):
  125. def default(self, obj):
  126. if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date):
  127. return obj.isoformat()
  128. elif isinstance(obj, Promise):
  129. return force_text(obj)
  130. return self.encode(obj)
  131. def get_model_instance_label(instance):
  132. if getattr(instance, "related_label", None):
  133. return instance.related_label()
  134. return smart_text(instance)
  135. class SuccessMessageMixin(object):
  136. """
  137. Adds a success message on successful form submission.
  138. """
  139. success_message = ''
  140. def form_valid(self, form):
  141. response = super(SuccessMessageMixin, self).form_valid(form)
  142. success_message = self.get_success_message(form.cleaned_data)
  143. if success_message:
  144. messages.success(self.request, success_message)
  145. return response
  146. def get_success_message(self, cleaned_data):
  147. return self.success_message % cleaned_data
  148. def get_model_queryset(admin_site, model, request, preserved_filters=None):
  149. model_admin = admin_site._registry.get(model)
  150. if model_admin is None:
  151. return
  152. try:
  153. changelist_url = reverse('%s:%s_%s_changelist' % (
  154. admin_site.name,
  155. model._meta.app_label,
  156. model._meta.model_name
  157. ))
  158. except NoReverseMatch:
  159. return
  160. changelist_filters = None
  161. if preserved_filters:
  162. changelist_filters = preserved_filters.get('_changelist_filters')
  163. if changelist_filters:
  164. changelist_url += '?' + changelist_filters
  165. if model_admin:
  166. queryset = model_admin.get_queryset(request)
  167. else:
  168. queryset = model.objects
  169. list_display = model_admin.get_list_display(request)
  170. list_display_links = model_admin.get_list_display_links(request, list_display)
  171. list_filter = model_admin.get_list_filter(request)
  172. search_fields = model_admin.get_search_fields(request) \
  173. if hasattr(model_admin, 'get_search_fields') else model_admin.search_fields
  174. list_select_related = model_admin.get_list_select_related(request) \
  175. if hasattr(model_admin, 'get_list_select_related') else model_admin.list_select_related
  176. actions = model_admin.get_actions(request)
  177. if actions:
  178. list_display = ['action_checkbox'] + list(list_display)
  179. ChangeList = model_admin.get_changelist(request)
  180. change_list_args = [
  181. request, model, list_display, list_display_links, list_filter,
  182. model_admin.date_hierarchy, search_fields, list_select_related,
  183. model_admin.list_per_page, model_admin.list_max_show_all,
  184. model_admin.list_editable, model_admin]
  185. try:
  186. sortable_by = model_admin.get_sortable_by(request)
  187. change_list_args.append(sortable_by)
  188. except AttributeError:
  189. # django version < 2.1
  190. pass
  191. try:
  192. cl = ChangeList(*change_list_args)
  193. queryset = cl.get_queryset(request)
  194. except IncorrectLookupParameters:
  195. pass
  196. return queryset
  197. def get_possible_language_codes():
  198. language_code = translation.get_language()
  199. language_code = language_code.replace('_', '-').lower()
  200. language_codes = []
  201. # making dialect part uppercase
  202. split = language_code.split('-', 2)
  203. if len(split) == 2:
  204. language_code = '%s-%s' % (split[0].lower(), split[1].upper()) if split[0] != split[1] else split[0]
  205. language_codes.append(language_code)
  206. # adding language code without dialect part
  207. if len(split) == 2:
  208. language_codes.append(split[0].lower())
  209. return language_codes
  210. def get_original_menu_items(context):
  211. if context.get('user') and user_is_authenticated(context['user']):
  212. pinned_apps = PinnedApplication.objects.filter(user=context['user'].pk).values_list('app_label', flat=True)
  213. else:
  214. pinned_apps = []
  215. original_app_list = get_app_list(context)
  216. return map(lambda app: {
  217. 'app_label': app['app_label'],
  218. 'url': app['app_url'],
  219. 'url_blank': False,
  220. 'label': app.get('name', capfirst(_(app['app_label']))),
  221. 'has_perms': app.get('has_module_perms', False),
  222. 'models': list(map(lambda model: {
  223. 'url': model.get('admin_url'),
  224. 'url_blank': False,
  225. 'name': model['model_name'],
  226. 'object_name': model['object_name'],
  227. 'label': model.get('name', model['object_name']),
  228. 'has_perms': any(model.get('perms', {}).values()),
  229. }, app['models'])),
  230. 'pinned': app['app_label'] in pinned_apps,
  231. 'custom': False
  232. }, original_app_list)
  233. def get_menu_item_url(url, original_app_list):
  234. if isinstance(url, dict):
  235. url_type = url.get('type')
  236. if url_type == 'app':
  237. return original_app_list[url['app_label']]['url']
  238. elif url_type == 'model':
  239. models = dict(map(
  240. lambda x: (x['name'], x['url']),
  241. original_app_list[url['app_label']]['models']
  242. ))
  243. return models[url['model']]
  244. elif url_type == 'reverse':
  245. return reverse(url['name'], args=url.get('args'), kwargs=url.get('kwargs'))
  246. elif isinstance(url, str):
  247. return url
  248. def get_menu_items(context):
  249. pinned_apps = PinnedApplication.objects.filter(user=context['user'].pk).values_list('app_label', flat=True)
  250. original_app_list = OrderedDict(map(lambda app: (app['app_label'], app), get_original_menu_items(context)))
  251. custom_app_list = settings.JET_SIDE_MENU_ITEMS
  252. custom_app_list_deprecated = settings.JET_SIDE_MENU_CUSTOM_APPS
  253. if custom_app_list not in (None, False):
  254. if isinstance(custom_app_list, dict):
  255. admin_site = get_admin_site(context)
  256. custom_app_list = custom_app_list.get(admin_site.name, [])
  257. app_list = []
  258. def get_menu_item_app_model(app_label, data):
  259. item = {'has_perms': True}
  260. if 'name' in data:
  261. parts = data['name'].split('.', 2)
  262. if len(parts) > 1:
  263. app_label, name = parts
  264. else:
  265. name = data['name']
  266. if app_label in original_app_list:
  267. models = dict(map(
  268. lambda x: (x['name'], x),
  269. original_app_list[app_label]['models']
  270. ))
  271. if name in models:
  272. item = models[name].copy()
  273. if 'label' in data:
  274. item['label'] = data['label']
  275. if 'url' in data:
  276. item['url'] = get_menu_item_url(data['url'], original_app_list)
  277. if 'url_blank' in data:
  278. item['url_blank'] = data['url_blank']
  279. if 'permissions' in data:
  280. item['has_perms'] = item.get('has_perms', True) and context['user'].has_perms(data['permissions'])
  281. return item
  282. def get_menu_item_app(data):
  283. app_label = data.get('app_label')
  284. if not app_label:
  285. if 'label' not in data:
  286. raise Exception('Custom menu items should at least have \'label\' or \'app_label\' key')
  287. app_label = 'custom_%s' % slugify(data['label'], allow_unicode=True)
  288. if app_label in original_app_list:
  289. item = original_app_list[app_label].copy()
  290. else:
  291. item = {'app_label': app_label, 'has_perms': True}
  292. if 'label' in data:
  293. item['label'] = data['label']
  294. if 'items' in data:
  295. item['items'] = list(map(lambda x: get_menu_item_app_model(app_label, x), data['items']))
  296. if 'url' in data:
  297. item['url'] = get_menu_item_url(data['url'], original_app_list)
  298. if 'url_blank' in data:
  299. item['url_blank'] = data['url_blank']
  300. if 'permissions' in data:
  301. item['has_perms'] = item.get('has_perms', True) and context['user'].has_perms(data['permissions'])
  302. item['pinned'] = item['app_label'] in pinned_apps
  303. return item
  304. for data in custom_app_list:
  305. item = get_menu_item_app(data)
  306. app_list.append(item)
  307. elif custom_app_list_deprecated not in (None, False):
  308. app_dict = {}
  309. models_dict = {}
  310. for app in original_app_list.values():
  311. app_label = app['app_label']
  312. app_dict[app_label] = app
  313. for model in app['models']:
  314. if app_label not in models_dict:
  315. models_dict[app_label] = {}
  316. models_dict[app_label][model['object_name']] = model
  317. app['items'] = []
  318. app_list = []
  319. if isinstance(custom_app_list_deprecated, dict):
  320. admin_site = get_admin_site(context)
  321. custom_app_list_deprecated = custom_app_list_deprecated.get(admin_site.name, [])
  322. for item in custom_app_list_deprecated:
  323. app_label, models = item
  324. if app_label in app_dict:
  325. app = app_dict[app_label]
  326. for model_label in models:
  327. if model_label == '__all__':
  328. app['items'] = models_dict[app_label].values()
  329. break
  330. elif model_label in models_dict[app_label]:
  331. model = models_dict[app_label][model_label]
  332. app['items'].append(model)
  333. app_list.append(app)
  334. else:
  335. def map_item(item):
  336. item['items'] = item['models']
  337. return item
  338. app_list = list(map(map_item, original_app_list.values()))
  339. current_found = False
  340. for app in app_list:
  341. if not current_found:
  342. for model in app['items']:
  343. if not current_found and model.get('url') and context['request'].path.startswith(model['url']):
  344. model['current'] = True
  345. current_found = True
  346. else:
  347. model['current'] = False
  348. if not current_found and app.get('url') and context['request'].path.startswith(app['url']):
  349. app['current'] = True
  350. current_found = True
  351. else:
  352. app['current'] = False
  353. return app_list
  354. def context_to_dict(context):
  355. if isinstance(context, Context):
  356. flat = {}
  357. for d in context.dicts:
  358. flat.update(d)
  359. context = flat
  360. return context
  361. def user_is_authenticated(user):
  362. if not hasattr(user.is_authenticated, '__call__'):
  363. return user.is_authenticated
  364. else:
  365. return user.is_authenticated()