modules.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. import json
  2. from django import forms
  3. from django.contrib.admin.models import LogEntry
  4. from django.db.models import Q
  5. from django.template.loader import render_to_string
  6. from django.utils.translation import ugettext_lazy as _
  7. from jet.utils import get_app_list, LazyDateTimeEncoder
  8. import datetime
  9. class DashboardModule(object):
  10. """
  11. Base dashboard module class. All dashboard modules (widgets) should inherit it.
  12. """
  13. #: Path to widget's template. There is no need to extend such templates from any base templates.
  14. template = 'jet.dashboard/module.html'
  15. enabled = True
  16. draggable = True
  17. collapsible = True
  18. deletable = True
  19. show_title = True
  20. #: Default widget title that will be displayed for widget in the dashboard. User can change it later
  21. #: for every widget.
  22. title = ''
  23. title_url = None
  24. css_classes = None
  25. #: HTML content that will be displayed before widget content.
  26. pre_content = None
  27. #: HTML content that will be displayed after widget content.
  28. post_content = None
  29. children = None
  30. #: A ``django.forms.Form`` class which may contain custom widget settings. Not required.
  31. settings_form = None
  32. #: A ``django.forms.Form`` class which may contain custom widget child settings, if it has any. Not required.
  33. child_form = None
  34. child_name = None
  35. child_name_plural = None
  36. settings = None
  37. column = None
  38. order = None
  39. #: A boolean field which specify if widget should be rendered on dashboard page load or fetched
  40. #: later via AJAX.
  41. ajax_load = False
  42. #: A boolean field which makes widget ui color contrast.
  43. contrast = False
  44. #: Optional style attributes which will be applied to widget content container.
  45. style = False
  46. class Media:
  47. css = ()
  48. js = ()
  49. def __init__(self, title=None, model=None, context=None, **kwargs):
  50. if title is not None:
  51. self.title = title
  52. self.model = model
  53. self.context = context or {}
  54. for key in kwargs:
  55. if hasattr(self.__class__, key):
  56. setattr(self, key, kwargs[key])
  57. self.children = self.children or []
  58. if self.model:
  59. self.load_from_model()
  60. def fullname(self):
  61. return self.__module__ + "." + self.__class__.__name__
  62. def load_settings(self, settings):
  63. pass
  64. def load_children(self, children):
  65. self.children = children
  66. def store_children(self):
  67. return False
  68. def settings_dict(self):
  69. pass
  70. def dump_settings(self, settings=None):
  71. settings = settings or self.settings_dict()
  72. if settings:
  73. return json.dumps(settings, cls=LazyDateTimeEncoder)
  74. else:
  75. return ''
  76. def dump_children(self):
  77. if self.store_children():
  78. return json.dumps(self.children, cls=LazyDateTimeEncoder)
  79. else:
  80. return ''
  81. def load_from_model(self):
  82. self.title = self.model.title
  83. if self.model.settings:
  84. try:
  85. self.settings = json.loads(self.model.settings)
  86. self.load_settings(self.settings)
  87. except ValueError:
  88. pass
  89. if self.store_children() and self.model.children:
  90. try:
  91. children = json.loads(self.model.children)
  92. self.load_children(children)
  93. except ValueError:
  94. pass
  95. def init_with_context(self, context):
  96. pass
  97. def get_context_data(self):
  98. context = self.context
  99. context.update({
  100. 'module': self
  101. })
  102. return context
  103. def render(self):
  104. self.init_with_context(self.context)
  105. return render_to_string(self.template, self.get_context_data())
  106. class LinkListItemForm(forms.Form):
  107. url = forms.CharField(label=_('URL'))
  108. title = forms.CharField(label=_('Title'))
  109. external = forms.BooleanField(label=_('External link'), required=False)
  110. class LinkListSettingsForm(forms.Form):
  111. layout = forms.ChoiceField(label=_('Layout'), choices=(('stacked', _('Stacked')), ('inline', _('Inline'))))
  112. class LinkList(DashboardModule):
  113. """
  114. List of links widget.
  115. Usage example:
  116. .. code-block:: python
  117. from django.utils.translation import ugettext_lazy as _
  118. from jet.dashboard import modules
  119. from jet.dashboard.dashboard import Dashboard, AppIndexDashboard
  120. class CustomIndexDashboard(Dashboard):
  121. columns = 3
  122. def init_with_context(self, context):
  123. self.available_children.append(modules.LinkList)
  124. self.children.append(modules.LinkList(
  125. _('Support'),
  126. children=[
  127. {
  128. 'title': _('Django documentation'),
  129. 'url': 'http://docs.djangoproject.com/',
  130. 'external': True,
  131. },
  132. {
  133. 'title': _('Django "django-users" mailing list'),
  134. 'url': 'http://groups.google.com/group/django-users',
  135. 'external': True,
  136. },
  137. {
  138. 'title': _('Django irc channel'),
  139. 'url': 'irc://irc.freenode.net/django',
  140. 'external': True,
  141. },
  142. ],
  143. column=0,
  144. order=0
  145. ))
  146. """
  147. title = _('Links')
  148. template = 'jet.dashboard/modules/link_list.html'
  149. #: Specify widget layout.
  150. #: Allowed values ``stacked`` and ``inline``.
  151. layout = 'stacked'
  152. #: Links are contained in ``children`` attribute which you can pass as constructor parameter
  153. #: to make your own preinstalled link lists.
  154. #:
  155. #: ``children`` is an array of dictinaries::
  156. #:
  157. #: [
  158. #: {
  159. #: 'title': _('Django documentation'),
  160. #: 'url': 'http://docs.djangoproject.com/',
  161. #: 'external': True,
  162. #: },
  163. #: ...
  164. #: ]
  165. children = []
  166. settings_form = LinkListSettingsForm
  167. child_form = LinkListItemForm
  168. child_name = _('Link')
  169. child_name_plural = _('Links')
  170. def __init__(self, title=None, children=list(), **kwargs):
  171. children = list(map(self.parse_link, children))
  172. kwargs.update({'children': children})
  173. super(LinkList, self).__init__(title, **kwargs)
  174. def settings_dict(self):
  175. return {
  176. 'layout': self.layout
  177. }
  178. def load_settings(self, settings):
  179. self.layout = settings.get('layout', self.layout)
  180. def store_children(self):
  181. return True
  182. def parse_link(self, link):
  183. if isinstance(link, (tuple, list)):
  184. link_dict = {'title': link[0], 'url': link[1]}
  185. if len(link) >= 3:
  186. link_dict['external'] = link[2]
  187. return link_dict
  188. elif isinstance(link, (dict,)):
  189. return link
  190. class AppList(DashboardModule):
  191. """
  192. Shows applications and containing models links. For each model "created" and "change" links are displayed.
  193. Usage example:
  194. .. code-block:: python
  195. from django.utils.translation import ugettext_lazy as _
  196. from jet.dashboard import modules
  197. from jet.dashboard.dashboard import Dashboard, AppIndexDashboard
  198. class CustomIndexDashboard(Dashboard):
  199. columns = 3
  200. def init_with_context(self, context):
  201. self.children.append(modules.AppList(
  202. _('Applications'),
  203. exclude=('auth.*',),
  204. column=0,
  205. order=0
  206. ))
  207. """
  208. title = _('Applications')
  209. template = 'jet.dashboard/modules/app_list.html'
  210. #: Specify models which should be displayed. ``models`` is an array of string formatted as ``app_label.model``.
  211. #: Also its possible to specify all application models with * sign (e.g. ``auth.*``).
  212. models = None
  213. #: Specify models which should NOT be displayed. ``exclude`` is an array of string formatted as ``app_label.model``.
  214. #: Also its possible to specify all application models with * sign (e.g. ``auth.*``).
  215. exclude = None
  216. hide_empty = True
  217. def settings_dict(self):
  218. return {
  219. 'models': self.models,
  220. 'exclude': self.exclude
  221. }
  222. def load_settings(self, settings):
  223. self.models = settings.get('models')
  224. self.exclude = settings.get('exclude')
  225. def init_with_context(self, context):
  226. app_list = get_app_list(context)
  227. app_to_remove = []
  228. for app in app_list:
  229. app['models'] = filter(
  230. lambda model: self.models is None or model['object_name'] in self.models or app.get('app_label', app.get('name')) + '.*' in self.models,
  231. app['models']
  232. )
  233. app['models'] = filter(
  234. lambda model: self.exclude is None or model['object_name'] not in self.exclude and app.get('app_label', app.get('name')) + '.*' not in self.exclude,
  235. app['models']
  236. )
  237. app['models'] = list(app['models'])
  238. if self.hide_empty and len(list(app['models'])) == 0:
  239. app_to_remove.append(app)
  240. for app in app_to_remove:
  241. app_list.remove(app)
  242. self.children = app_list
  243. class ModelList(DashboardModule):
  244. """
  245. Shows models links. For each model "created" and "change" links are displayed.
  246. Usage example:
  247. .. code-block:: python
  248. from django.utils.translation import ugettext_lazy as _
  249. from jet.dashboard import modules
  250. from jet.dashboard.dashboard import Dashboard, AppIndexDashboard
  251. class CustomIndexDashboard(Dashboard):
  252. columns = 3
  253. def init_with_context(self, context):
  254. self.children.append(modules.ModelList(
  255. _('Models'),
  256. exclude=('auth.*',),
  257. column=0,
  258. order=0
  259. ))
  260. """
  261. title = _('Models')
  262. template = 'jet.dashboard/modules/model_list.html'
  263. #: Specify models which should be displayed. ``models`` is an array of string formatted as ``app_label.model``.
  264. #: Also its possible to specify all application models with * sign (e.g. ``auth.*``).
  265. models = None
  266. #: Specify models which should NOT be displayed. ``exclude`` is an array of string formatted as ``app_label.model``.
  267. #: Also its possible to specify all application models with * sign (e.g. ``auth.*``).
  268. exclude = None
  269. hide_empty = True
  270. def settings_dict(self):
  271. return {
  272. 'models': self.models,
  273. 'exclude': self.exclude
  274. }
  275. def load_settings(self, settings):
  276. self.models = settings.get('models')
  277. self.exclude = settings.get('exclude')
  278. def init_with_context(self, context):
  279. app_list = get_app_list(context)
  280. models = []
  281. for app in app_list:
  282. app['models'] = filter(
  283. lambda model: self.models is None or model['object_name'] in self.models or app.get('app_label', app.get('name')) + '.*' in self.models,
  284. app['models']
  285. )
  286. app['models'] = filter(
  287. lambda model: self.exclude is None or model['object_name'] not in self.exclude and app.get('app_label', app.get('name')) + '.*' not in self.exclude,
  288. app['models']
  289. )
  290. app['models'] = list(app['models'])
  291. models.extend(app['models'])
  292. self.children = models
  293. class RecentActionsSettingsForm(forms.Form):
  294. limit = forms.IntegerField(label=_('Items limit'), min_value=1)
  295. class RecentActions(DashboardModule):
  296. """
  297. Display list of most recent admin actions with following information:
  298. entity name, type of action, author, date
  299. Usage example:
  300. .. code-block:: python
  301. from django.utils.translation import ugettext_lazy as _
  302. from jet.dashboard import modules
  303. from jet.dashboard.dashboard import Dashboard, AppIndexDashboard
  304. class CustomIndexDashboard(Dashboard):
  305. columns = 3
  306. def init_with_context(self, context):
  307. self.children.append(modules.RecentActions(
  308. _('Recent Actions'),
  309. 10,
  310. column=0,
  311. order=0
  312. ))
  313. """
  314. title = _('Recent Actions')
  315. template = 'jet.dashboard/modules/recent_actions.html'
  316. #: Number if entries to be shown (may be changed by each user personally).
  317. limit = 10
  318. #: Specify actions of which models should be displayed. ``include_list`` is an array of string
  319. #: formatted as ``app_label.model``. Also its possible to specify all application models
  320. #: with * sign (e.g. ``auth.*``).
  321. include_list = None
  322. #: Specify actions of which models should NOT be displayed. ``exclude_list`` is an array of string
  323. #: formatted as ``app_label.model``. Also its possible to specify all application models
  324. #: with * sign (e.g. ``auth.*``).
  325. exclude_list = None
  326. settings_form = RecentActionsSettingsForm
  327. user = None
  328. def __init__(self, title=None, limit=10, **kwargs):
  329. kwargs.update({'limit': limit})
  330. super(RecentActions, self).__init__(title, **kwargs)
  331. def settings_dict(self):
  332. return {
  333. 'limit': self.limit,
  334. 'include_list': self.include_list,
  335. 'exclude_list': self.exclude_list,
  336. 'user': self.user
  337. }
  338. def load_settings(self, settings):
  339. self.limit = settings.get('limit', self.limit)
  340. self.include_list = settings.get('include_list')
  341. self.exclude_list = settings.get('exclude_list')
  342. self.user = settings.get('user', None)
  343. def init_with_context(self, context):
  344. def get_qset(list):
  345. qset = None
  346. for contenttype in list:
  347. try:
  348. app_label, model = contenttype.split('.')
  349. if model == '*':
  350. current_qset = Q(
  351. content_type__app_label=app_label
  352. )
  353. else:
  354. current_qset = Q(
  355. content_type__app_label=app_label,
  356. content_type__model=model
  357. )
  358. except:
  359. raise ValueError('Invalid contenttype: "%s"' % contenttype)
  360. if qset is None:
  361. qset = current_qset
  362. else:
  363. qset = qset | current_qset
  364. return qset
  365. qs = LogEntry.objects
  366. if self.user:
  367. qs = qs.filter(
  368. user__pk=int(self.user)
  369. )
  370. if self.include_list:
  371. qs = qs.filter(get_qset(self.include_list))
  372. if self.exclude_list:
  373. qs = qs.exclude(get_qset(self.exclude_list))
  374. self.children = qs.select_related('content_type', 'user')[:int(self.limit)]
  375. class FeedSettingsForm(forms.Form):
  376. limit = forms.IntegerField(label=_('Items limit'), min_value=1)
  377. feed_url = forms.URLField(label=_('Feed URL'))
  378. class Feed(DashboardModule):
  379. """
  380. Display RSS Feed entries with following information:
  381. entry title, date and link to the full version
  382. Usage example:
  383. .. code-block:: python
  384. from django.utils.translation import ugettext_lazy as _
  385. from jet.dashboard import modules
  386. from jet.dashboard.dashboard import Dashboard, AppIndexDashboard
  387. class CustomIndexDashboard(Dashboard):
  388. columns = 3
  389. def init_with_context(self, context):
  390. self.children.append(modules.Feed(
  391. _('Latest Django News'),
  392. feed_url='http://www.djangoproject.com/rss/weblog/',
  393. limit=5,
  394. column=0,
  395. order=0
  396. ))
  397. """
  398. title = _('RSS Feed')
  399. template = 'jet.dashboard/modules/feed.html'
  400. #: URL of the RSS feed (may be changed by each user personally).
  401. feed_url = None
  402. #: Number if entries to be shown (may be changed by each user personally).
  403. limit = None
  404. settings_form = FeedSettingsForm
  405. ajax_load = True
  406. def __init__(self, title=None, feed_url=None, limit=None, **kwargs):
  407. kwargs.update({'feed_url': feed_url, 'limit': limit})
  408. super(Feed, self).__init__(title, **kwargs)
  409. def settings_dict(self):
  410. return {
  411. 'feed_url': self.feed_url,
  412. 'limit': self.limit
  413. }
  414. def load_settings(self, settings):
  415. self.feed_url = settings.get('feed_url')
  416. self.limit = settings.get('limit')
  417. def init_with_context(self, context):
  418. if self.feed_url is not None:
  419. try:
  420. import feedparser
  421. feed = feedparser.parse(self.feed_url)
  422. if self.limit is not None:
  423. entries = feed['entries'][:self.limit]
  424. else:
  425. entries = feed['entries']
  426. for entry in entries:
  427. try:
  428. entry.date = datetime.date(*entry.published_parsed[0:3])
  429. except:
  430. pass
  431. self.children.append(entry)
  432. except ImportError:
  433. self.children.append({
  434. 'title': _('You must install the FeedParser python module'),
  435. 'warning': True,
  436. })
  437. else:
  438. self.children.append({
  439. 'title': _('You must provide a valid feed URL'),
  440. 'warning': True,
  441. })