Jet fork des offiziellen jet-admin projekts. Geupdated für Django4.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

474 рядки
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('view', 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('change', False):
  80. try:
  81. model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=admin_site.name)
  82. except NoReverseMatch:
  83. pass
  84. if perms.get('add', False):
  85. try:
  86. model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=admin_site.name)
  87. except NoReverseMatch:
  88. pass
  89. if app_label in app_dict:
  90. app_dict[app_label]['models'].append(model_dict)
  91. else:
  92. try:
  93. name = apps.get_app_config(app_label).verbose_name
  94. except NameError:
  95. name = app_label.title()
  96. app_dict[app_label] = {
  97. 'name': name,
  98. 'app_label': app_label,
  99. 'app_url': reverse(
  100. 'admin:app_list',
  101. kwargs={'app_label': app_label},
  102. current_app=admin_site.name,
  103. ),
  104. 'has_module_perms': has_module_perms,
  105. 'models': [model_dict],
  106. }
  107. # Sort the apps alphabetically.
  108. app_list = list(app_dict.values())
  109. if order:
  110. app_list.sort(key=lambda x: x['name'].lower())
  111. # Sort the models alphabetically within each app.
  112. for app in app_list:
  113. app['models'].sort(key=lambda x: x['name'])
  114. return app_list
  115. def get_admin_site(context):
  116. try:
  117. current_resolver = resolve(context.get('request').path)
  118. index_resolver = resolve(reverse('%s:index' % current_resolver.namespaces[0]))
  119. if hasattr(index_resolver.func, 'admin_site'):
  120. return index_resolver.func.admin_site
  121. for func_closure in index_resolver.func.__closure__:
  122. if isinstance(func_closure.cell_contents, AdminSite):
  123. return func_closure.cell_contents
  124. except:
  125. pass
  126. return admin.site
  127. def get_admin_site_name(context):
  128. return get_admin_site(context).name
  129. class LazyDateTimeEncoder(json.JSONEncoder):
  130. def default(self, obj):
  131. if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date):
  132. return obj.isoformat()
  133. elif isinstance(obj, Promise):
  134. return force_text(obj)
  135. return self.encode(obj)
  136. def get_model_instance_label(instance):
  137. if getattr(instance, "related_label", None):
  138. return instance.related_label()
  139. return smart_text(instance)
  140. class SuccessMessageMixin(object):
  141. """
  142. Adds a success message on successful form submission.
  143. """
  144. success_message = ''
  145. def form_valid(self, form):
  146. response = super(SuccessMessageMixin, self).form_valid(form)
  147. success_message = self.get_success_message(form.cleaned_data)
  148. if success_message:
  149. messages.success(self.request, success_message)
  150. return response
  151. def get_success_message(self, cleaned_data):
  152. return self.success_message % cleaned_data
  153. def get_model_queryset(admin_site, model, request, preserved_filters=None):
  154. model_admin = admin_site._registry.get(model)
  155. if model_admin is None:
  156. return
  157. try:
  158. changelist_url = reverse('%s:%s_%s_changelist' % (
  159. admin_site.name,
  160. model._meta.app_label,
  161. model._meta.model_name
  162. ))
  163. except NoReverseMatch:
  164. return
  165. changelist_filters = None
  166. if preserved_filters:
  167. changelist_filters = preserved_filters.get('_changelist_filters')
  168. if changelist_filters:
  169. changelist_url += '?' + changelist_filters
  170. if model_admin:
  171. queryset = model_admin.get_queryset(request)
  172. else:
  173. queryset = model.objects
  174. list_display = model_admin.get_list_display(request)
  175. list_display_links = model_admin.get_list_display_links(request, list_display)
  176. list_filter = model_admin.get_list_filter(request)
  177. search_fields = model_admin.get_search_fields(request) \
  178. if hasattr(model_admin, 'get_search_fields') else model_admin.search_fields
  179. list_select_related = model_admin.get_list_select_related(request) \
  180. if hasattr(model_admin, 'get_list_select_related') else model_admin.list_select_related
  181. actions = model_admin.get_actions(request)
  182. if actions:
  183. list_display = ['action_checkbox'] + list(list_display)
  184. ChangeList = model_admin.get_changelist(request)
  185. change_list_args = [
  186. request, model, list_display, list_display_links, list_filter,
  187. model_admin.date_hierarchy, search_fields, list_select_related,
  188. model_admin.list_per_page, model_admin.list_max_show_all,
  189. model_admin.list_editable, model_admin]
  190. try:
  191. sortable_by = model_admin.get_sortable_by(request)
  192. change_list_args.append(sortable_by)
  193. except AttributeError:
  194. # django version < 2.1
  195. pass
  196. try:
  197. cl = ChangeList(*change_list_args)
  198. queryset = cl.get_queryset(request)
  199. except IncorrectLookupParameters:
  200. pass
  201. return queryset
  202. def get_possible_language_codes():
  203. language_code = translation.get_language()
  204. language_code = language_code.replace('_', '-').lower()
  205. language_codes = []
  206. # making dialect part uppercase
  207. split = language_code.split('-', 2)
  208. if len(split) == 2:
  209. language_code = '%s-%s' % (split[0].lower(), split[1].upper()) if split[0] != split[1] else split[0]
  210. language_codes.append(language_code)
  211. # adding language code without dialect part
  212. if len(split) == 2:
  213. language_codes.append(split[0].lower())
  214. return language_codes
  215. def get_original_menu_items(context):
  216. if context.get('user') and user_is_authenticated(context['user']):
  217. pinned_apps = PinnedApplication.objects.filter(user=context['user'].pk).values_list('app_label', flat=True)
  218. else:
  219. pinned_apps = []
  220. original_app_list = get_app_list(context)
  221. return map(lambda app: {
  222. 'app_label': app['app_label'],
  223. 'url': app['app_url'],
  224. 'url_blank': False,
  225. 'label': app.get('name', capfirst(_(app['app_label']))),
  226. 'has_perms': app.get('has_module_perms', False),
  227. 'models': list(map(lambda model: {
  228. 'url': model.get('admin_url'),
  229. 'url_blank': False,
  230. 'name': model['model_name'],
  231. 'object_name': model['object_name'],
  232. 'label': model.get('name', model['object_name']),
  233. 'has_perms': any(model.get('perms', {}).values()),
  234. }, app['models'])),
  235. 'pinned': app['app_label'] in pinned_apps,
  236. 'custom': False
  237. }, original_app_list)
  238. def get_menu_item_url(url, original_app_list):
  239. if isinstance(url, dict):
  240. url_type = url.get('type')
  241. if url_type == 'app':
  242. return original_app_list[url['app_label']]['url']
  243. elif url_type == 'model':
  244. models = dict(map(
  245. lambda x: (x['name'], x['url']),
  246. original_app_list[url['app_label']]['models']
  247. ))
  248. return models[url['model']]
  249. elif url_type == 'reverse':
  250. return reverse(url['name'], args=url.get('args'), kwargs=url.get('kwargs'))
  251. elif isinstance(url, str):
  252. return url
  253. def get_menu_items(context):
  254. pinned_apps = PinnedApplication.objects.filter(user=context['user'].pk).values_list('app_label', flat=True)
  255. original_app_list = OrderedDict(map(lambda app: (app['app_label'], app), get_original_menu_items(context)))
  256. custom_app_list = settings.JET_SIDE_MENU_ITEMS
  257. custom_app_list_deprecated = settings.JET_SIDE_MENU_CUSTOM_APPS
  258. if custom_app_list not in (None, False):
  259. if isinstance(custom_app_list, dict):
  260. admin_site = get_admin_site(context)
  261. custom_app_list = custom_app_list.get(admin_site.name, [])
  262. app_list = []
  263. def get_menu_item_app_model(app_label, data):
  264. item = {'has_perms': True}
  265. if 'name' in data:
  266. parts = data['name'].split('.', 2)
  267. if len(parts) > 1:
  268. app_label, name = parts
  269. else:
  270. name = data['name']
  271. if app_label in original_app_list:
  272. models = dict(map(
  273. lambda x: (x['name'], x),
  274. original_app_list[app_label]['models']
  275. ))
  276. if name in models:
  277. item = models[name].copy()
  278. if 'label' in data:
  279. item['label'] = data['label']
  280. if 'url' in data:
  281. item['url'] = get_menu_item_url(data['url'], original_app_list)
  282. if 'url_blank' in data:
  283. item['url_blank'] = data['url_blank']
  284. if 'permissions' in data:
  285. item['has_perms'] = item.get('has_perms', True) and context['user'].has_perms(data['permissions'])
  286. return item
  287. def get_menu_item_app(data):
  288. app_label = data.get('app_label')
  289. if not app_label:
  290. if 'label' not in data:
  291. raise Exception('Custom menu items should at least have \'label\' or \'app_label\' key')
  292. app_label = 'custom_%s' % slugify(data['label'], allow_unicode=True)
  293. if app_label in original_app_list:
  294. item = original_app_list[app_label].copy()
  295. else:
  296. item = {'app_label': app_label, 'has_perms': True}
  297. if 'label' in data:
  298. item['label'] = data['label']
  299. if 'items' in data:
  300. item['items'] = list(map(lambda x: get_menu_item_app_model(app_label, x), data['items']))
  301. if 'url' in data:
  302. item['url'] = get_menu_item_url(data['url'], original_app_list)
  303. if 'url_blank' in data:
  304. item['url_blank'] = data['url_blank']
  305. if 'permissions' in data:
  306. item['has_perms'] = item.get('has_perms', True) and context['user'].has_perms(data['permissions'])
  307. item['pinned'] = item['app_label'] in pinned_apps
  308. return item
  309. for data in custom_app_list:
  310. item = get_menu_item_app(data)
  311. app_list.append(item)
  312. elif custom_app_list_deprecated not in (None, False):
  313. app_dict = {}
  314. models_dict = {}
  315. for app in original_app_list.values():
  316. app_label = app['app_label']
  317. app_dict[app_label] = app
  318. for model in app['models']:
  319. if app_label not in models_dict:
  320. models_dict[app_label] = {}
  321. models_dict[app_label][model['object_name']] = model
  322. app['items'] = []
  323. app_list = []
  324. if isinstance(custom_app_list_deprecated, dict):
  325. admin_site = get_admin_site(context)
  326. custom_app_list_deprecated = custom_app_list_deprecated.get(admin_site.name, [])
  327. for item in custom_app_list_deprecated:
  328. app_label, models = item
  329. if app_label in app_dict:
  330. app = app_dict[app_label]
  331. for model_label in models:
  332. if model_label == '__all__':
  333. app['items'] = models_dict[app_label].values()
  334. break
  335. elif model_label in models_dict[app_label]:
  336. model = models_dict[app_label][model_label]
  337. app['items'].append(model)
  338. app_list.append(app)
  339. else:
  340. def map_item(item):
  341. item['items'] = item['models']
  342. return item
  343. app_list = list(map(map_item, original_app_list.values()))
  344. current_found = False
  345. for app in app_list:
  346. if not current_found:
  347. for model in app['items']:
  348. if not current_found and model.get('url') and context['request'].path.startswith(model['url']):
  349. model['current'] = True
  350. current_found = True
  351. else:
  352. model['current'] = False
  353. if not current_found and app.get('url') and context['request'].path.startswith(app['url']):
  354. app['current'] = True
  355. current_found = True
  356. else:
  357. app['current'] = False
  358. return app_list
  359. def context_to_dict(context):
  360. if isinstance(context, Context):
  361. flat = {}
  362. for d in context.dicts:
  363. flat.update(d)
  364. context = flat
  365. return context
  366. def user_is_authenticated(user):
  367. if not hasattr(user.is_authenticated, '__call__'):
  368. return user.is_authenticated
  369. else:
  370. return user.is_authenticated()