relate.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. # coding=UTF-8
  2. from itertools import chain
  3. from django.urls.base import reverse
  4. from django.db.models.options import PROXY_PARENTS
  5. from django.utils import six
  6. from django.utils.encoding import force_text
  7. from django.utils.encoding import smart_str
  8. from django.utils.safestring import mark_safe
  9. from django.db.models.sql.query import LOOKUP_SEP
  10. from django.utils.translation import ugettext as _
  11. from django.db import models
  12. from xadmin.sites import site
  13. from xadmin.views import BaseAdminPlugin, ListAdminView, CreateAdminView, UpdateAdminView, DeleteAdminView
  14. from xadmin.util import is_related_field2
  15. RELATE_PREFIX = '_rel_'
  16. class RelateMenuPlugin(BaseAdminPlugin):
  17. related_list = []
  18. use_related_menu = True
  19. def _get_all_related_objects(self, local_only=False, include_hidden=False,
  20. include_proxy_eq=False):
  21. """
  22. Returns a list of related fields (also many to many)
  23. :param local_only:
  24. :param include_hidden:
  25. :return: list
  26. """
  27. include_parents = True if local_only is False else PROXY_PARENTS
  28. fields = self.opts._get_fields(
  29. forward=False, reverse=True,
  30. include_parents=include_parents,
  31. include_hidden=include_hidden
  32. )
  33. if include_proxy_eq:
  34. children = chain.from_iterable(c._relation_tree
  35. for c in self.opts.concrete_model._meta.proxied_children
  36. if c is not self.opts)
  37. relations = (f.remote_field for f in children
  38. if include_hidden or not f.remote_field.field.remote_field.is_hidden())
  39. fields = chain(fields, relations)
  40. return list(fields)
  41. def get_related_list(self):
  42. if hasattr(self, '_related_acts'):
  43. return self._related_acts
  44. _related_acts = []
  45. for rel in self._get_all_related_objects():
  46. if self.related_list and (rel.get_accessor_name() not in self.related_list):
  47. continue
  48. if rel.related_model not in self.admin_site._registry.keys():
  49. continue
  50. has_view_perm = self.has_model_perm(rel.related_model, 'view')
  51. has_add_perm = self.has_model_perm(rel.related_model, 'add')
  52. if not (has_view_perm or has_add_perm):
  53. continue
  54. _related_acts.append((rel, has_view_perm, has_add_perm))
  55. self._related_acts = _related_acts
  56. return self._related_acts
  57. def related_link(self, instance):
  58. links = []
  59. for rel, view_perm, add_perm in self.get_related_list():
  60. opts = rel.related_model._meta
  61. label = opts.app_label
  62. model_name = opts.model_name
  63. field = rel.field
  64. rel_name = rel.get_related_field().name
  65. verbose_name = force_text(opts.verbose_name)
  66. lookup_name = '%s__%s__exact' % (field.name, rel_name)
  67. link = ''.join(('<li class="with_menu_btn">',
  68. '<a href="%s?%s=%s" title="%s"><i class="icon fa fa-th-list"></i> %s</a>' %
  69. (
  70. reverse('%s:%s_%s_changelist' % (
  71. self.admin_site.app_name, label, model_name)),
  72. RELATE_PREFIX + lookup_name, str(instance.pk), verbose_name, verbose_name) if view_perm else
  73. '<a><span class="text-muted"><i class="icon fa fa-blank"></i> %s</span></a>' % verbose_name,
  74. '<a class="add_link dropdown-menu-btn" href="%s?%s=%s"><i class="icon fa fa-plus pull-right"></i></a>' %
  75. (
  76. reverse('%s:%s_%s_add' % (
  77. self.admin_site.app_name, label, model_name)),
  78. RELATE_PREFIX + lookup_name, str(
  79. instance.pk)) if add_perm else "",
  80. '</li>'))
  81. links.append(link)
  82. ul_html = '<ul class="dropdown-menu" role="menu">%s</ul>' % ''.join(
  83. links)
  84. return '<div class="dropdown related_menu pull-right"><a title="%s" class="relate_menu dropdown-toggle" data-toggle="dropdown"><i class="icon fa fa-list"></i></a>%s</div>' % (_('Related Objects'), ul_html)
  85. related_link.short_description = '&nbsp;'
  86. related_link.allow_tags = True
  87. related_link.allow_export = False
  88. related_link.is_column = False
  89. def get_list_display(self, list_display):
  90. if self.use_related_menu and len(self.get_related_list()):
  91. list_display.append('related_link')
  92. self.admin_view.related_link = self.related_link
  93. return list_display
  94. class RelateObject(object):
  95. def __init__(self, admin_view, lookup, value):
  96. self.admin_view = admin_view
  97. self.org_model = admin_view.model
  98. self.opts = admin_view.opts
  99. self.lookup = lookup
  100. self.value = value
  101. parts = lookup.split(LOOKUP_SEP)
  102. field = self.opts.get_field(parts[0])
  103. if not is_related_field2(field):
  104. raise Exception(u'Relate Lookup field must a related field')
  105. self.to_model = field.related_model
  106. self.rel_name = '__'.join(parts[1:])
  107. self.is_m2m = bool(field.many_to_many)
  108. to_qs = self.to_model._default_manager.get_queryset()
  109. self.to_objs = to_qs.filter(**{self.rel_name: value}).all()
  110. self.field = field
  111. def filter(self, queryset):
  112. return queryset.filter(**{self.lookup: self.value})
  113. def get_brand_name(self):
  114. if len(self.to_objs) == 1:
  115. to_model_name = str(self.to_objs[0])
  116. else:
  117. to_model_name = force_text(self.to_model._meta.verbose_name)
  118. return mark_safe(u"<span class='rel-brand'>%s <i class='fa fa-caret-right'></i></span> %s" % (to_model_name, force_text(self.opts.verbose_name_plural)))
  119. class BaseRelateDisplayPlugin(BaseAdminPlugin):
  120. def init_request(self, *args, **kwargs):
  121. self.relate_obj = None
  122. for k, v in self.request.GET.items():
  123. if smart_str(k).startswith(RELATE_PREFIX):
  124. self.relate_obj = RelateObject(
  125. self.admin_view, smart_str(k)[len(RELATE_PREFIX):], v)
  126. break
  127. return bool(self.relate_obj)
  128. def _get_relate_params(self):
  129. return RELATE_PREFIX + self.relate_obj.lookup, self.relate_obj.value
  130. def _get_input(self):
  131. return '<input type="hidden" name="%s" value="%s" />' % self._get_relate_params()
  132. def _get_url(self, url):
  133. return url + ('&' if url.find('?') > 0 else '?') + ('%s=%s' % self._get_relate_params())
  134. class ListRelateDisplayPlugin(BaseRelateDisplayPlugin):
  135. def get_list_queryset(self, queryset):
  136. if self.relate_obj:
  137. queryset = self.relate_obj.filter(queryset)
  138. return queryset
  139. def url_for_result(self, url, result):
  140. return self._get_url(url)
  141. def get_context(self, context):
  142. context['brand_name'] = self.relate_obj.get_brand_name()
  143. context['rel_objs'] = self.relate_obj.to_objs
  144. if len(self.relate_obj.to_objs) == 1:
  145. context['rel_obj'] = self.relate_obj.to_objs[0]
  146. if 'add_url' in context:
  147. context['add_url'] = self._get_url(context['add_url'])
  148. return context
  149. def get_list_display(self, list_display):
  150. if not self.relate_obj.is_m2m:
  151. try:
  152. list_display.remove(self.relate_obj.field.name)
  153. except Exception:
  154. pass
  155. return list_display
  156. class EditRelateDisplayPlugin(BaseRelateDisplayPlugin):
  157. def get_form_datas(self, datas):
  158. if self.admin_view.org_obj is None and self.admin_view.request_method == 'get':
  159. datas['initial'][
  160. self.relate_obj.field.name] = self.relate_obj.value
  161. return datas
  162. def post_response(self, response):
  163. cls_str = str if six.PY3 else basestring
  164. if isinstance(response, cls_str) and response != self.get_admin_url('index'):
  165. return self._get_url(response)
  166. return response
  167. def get_context(self, context):
  168. if 'delete_url' in context:
  169. context['delete_url'] = self._get_url(context['delete_url'])
  170. return context
  171. def block_after_fieldsets(self, context, nodes):
  172. return self._get_input()
  173. class DeleteRelateDisplayPlugin(BaseRelateDisplayPlugin):
  174. def post_response(self, response):
  175. cls_str = str if six.PY3 else basestring
  176. if isinstance(response, cls_str) and response != self.get_admin_url('index'):
  177. return self._get_url(response)
  178. return response
  179. def block_form_fields(self, context, nodes):
  180. return self._get_input()
  181. site.register_plugin(RelateMenuPlugin, ListAdminView)
  182. site.register_plugin(ListRelateDisplayPlugin, ListAdminView)
  183. site.register_plugin(EditRelateDisplayPlugin, CreateAdminView)
  184. site.register_plugin(EditRelateDisplayPlugin, UpdateAdminView)
  185. site.register_plugin(DeleteRelateDisplayPlugin, DeleteAdminView)