text: """
  Module where admin tools dashboard modules classes are defined.
  """
  
  from django.apps import apps as django_apps
  
  try:
      from django.urls import reverse
  except ImportError:
      from django.core.urlresolvers import reverse
  from django.forms.utils import flatatt
  
  from django.utils.itercompat import is_iterable
  from django.utils.text import capfirst
  from django.utils.translation import ugettext_lazy as _
  
  from admin_tools.utils import AppListElementMixin, uniquify
  
  
  class DashboardModule(object):
      """
      Base class for all dashboard modules.
      Dashboard modules have the following properties:
  
      ``enabled``
          Boolean that determines whether the module should be enabled in
          the dashboard by default or not. Default value: ``True``.
  
      ``draggable``
          Boolean that determines whether the module can be draggable or not.
          Draggable modules can be re-arranged by users. Default value: ``True``.
  
      ``collapsible``
          Boolean that determines whether the module is collapsible, this
          allows users to show/hide module content. Default: ``True``.
  
      ``deletable``
          Boolean that determines whether the module can be removed from the
          dashboard by users or not. Default: ``True``.
  
      ``title``
          String that contains the module title, make sure you use the django
          gettext functions if your application is multilingual.
          Default value: ''.
  
      ``title_url``
          String that contains the module title URL. If given the module
          title will be a link to this URL. Default value: ``None``.
  
      ``css_classes``
          A list of css classes to be added to the module ``div`` class
          attribute. Default value: ``None``.
  
      ``pre_content``
          Text or HTML content to display above the module content.
          Default value: ``None``.
  
      ``content``
          The module text or HTML content. Default value: ``None``.
  
      ``post_content``
          Text or HTML content to display under the module content.
          Default value: ``None``.
  
      ``template``
          The template to use to render the module.
          Default value: 'admin_tools/dashboard/module.html'.
      """
  
      template = "admin_tools/dashboard/module.html"
      enabled = True
      draggable = True
      collapsible = True
      deletable = True
      show_title = True
      title = ""
      title_url = None
      css_classes = None
      pre_content = None
      post_content = None
      children = None
      id = None
  
      def __init__(self, title=None, **kwargs):
          if title is not None:
              self.title = title
  
          for key in kwargs:
              if hasattr(self.__class__, key):
                  setattr(self, key, kwargs[key])
  
          self.children = self.children or []
          self.css_classes = self.css_classes or []
          # boolean flag to ensure that the module is initialized only once
          self._initialized = False
  
      def init_with_context(self, context):
          """
          Like for the :class:`~admin_tools.dashboard.Dashboard` class, dashboard
          modules have a ``init_with_context`` method that is called with a
          ``django.template.RequestContext`` instance as unique argument.
  
          This gives you enough flexibility to build complex modules, for
          example, let's build a "history" dashboard module, that will list the
          last ten visited pages::
  
              from admin_tools.dashboard import modules
  
              class HistoryDashboardModule(modules.LinkList):
                  title = 'History'
  
                  def init_with_context(self, context):
                      request = context['request']
                      # we use sessions to store the visited pages stack
                      history = request.session.get('history', [])
                      for item in history:
                          self.children.append(item)
                      # add the current page to the history
                      history.insert(0, {
                          'title': context['title'],
                          'url': request.META['PATH_INFO']
                      })
                      if len(history) > 10:
                          history = history[:10]
                      request.session['history'] = history
  
          Here's a screenshot of our history item:
  
          .. image:: images/history_dashboard_module.png
          """
          pass
  
      def is_empty(self):
          """
          Return True if the module has no content and False otherwise.
  
          >>> mod = DashboardModule()
          >>> mod.is_empty()
          True
          >>> mod.pre_content = 'foo'
          >>> mod.is_empty()
          False
          >>> mod.pre_content = None
          >>> mod.is_empty()
          True
          >>> mod.children.append('foo')
          >>> mod.is_empty()
          False
          >>> mod.children = []
          >>> mod.is_empty()
          True
          """
          return (
              self.pre_content is None
              and self.post_content is None
              and len(self.children) == 0
          )
  
      def render_css_classes(self):
          """
          Return a string containing the css classes for the module.
  
          >>> mod = DashboardModule(enabled=False, draggable=True,
          ...                       collapsible=True, deletable=True)
          >>> mod.render_css_classes()
          'dashboard-module disabled draggable collapsible deletable'
          >>> mod.css_classes.append('foo')
          >>> mod.render_css_classes()
          'dashboard-module disabled draggable collapsible deletable foo'
          >>> mod.enabled = True
          >>> mod.render_css_classes()
          'dashboard-module draggable collapsible deletable foo'
          """
          ret = ["dashboard-module"]
          if not self.enabled:
              ret.append("disabled")
          if self.draggable:
              ret.append("draggable")
          if self.collapsible:
              ret.append("collapsible")
          if self.deletable:
              ret.append("deletable")
          ret += self.css_classes
          return " ".join(ret)
  
      def _prepare_children(self):
          pass
  
  
  class Group(DashboardModule):
      """
      Represents a group of modules, the group can be displayed in tabs,
      accordion, or just stacked (default).
      As well as the :class:`~admin_tools.dashboard.modules.DashboardModule`
      properties, the :class:`~admin_tools.dashboard.modules.Group`
      has two extra properties:
  
      ``display``
          A string determining how the group should be rendered, this can be one
          of the following values: 'tabs' (default), 'accordion' or 'stacked'.
  
      ``force_show_title``
          Default behaviour for Group module is to force children to always show
          the title if Group has ``display`` = ``stacked``. If this flag is set
          to ``False``, children title is shown according to their``show_title``
          property. Note that in this case is children responsibility to have
          meaningful content if no title is shown.
  
      Here's an example of modules group::
  
          from admin_tools.dashboard import modules, Dashboard
  
          class MyDashboard(Dashboard):
              def __init__(self, **kwargs):
                  Dashboard.__init__(self, **kwargs)
                  self.children.append(modules.Group(
                      title="My group",
                      display="tabs",
                      children=[
                          modules.AppList(
                              title='Administration',
                              models=('django.contrib.*',)
                          ),
                          modules.AppList(
                              title='Applications',
                              exclude=('django.contrib.*',)
                          )
                      ]
                  ))
  
      The screenshot of what this code produces:
  
      .. image:: images/dashboard_module_group.png
      """
  
      force_show_title = True
      template = "admin_tools/dashboard/modules/group.html"
      display = "tabs"
  
      def init_with_context(self, context):
          if self._initialized:
              return
          for module in self.children:
              # to simplify the whole stuff, modules have some limitations,
              # they cannot be dragged, collapsed or closed
              module.collapsible = False
              module.draggable = False
              module.deletable = False
              if self.force_show_title:
                  module.show_title = self.display == "stacked"
              module.init_with_context(context)
          self._initialized = True
  
      def is_empty(self):
          """
          A group of modules is considered empty if it has no children or if
          all its children are empty.
  
          >>> from admin_tools.dashboard.modules import DashboardModule, LinkList
          >>> mod = Group()
          >>> mod.is_empty()
          True
          >>> mod.children.append(DashboardModule())
          >>> mod.is_empty()
          True
          >>> mod.children.append(LinkList('links', children=[
          ...    {'title': 'example1', 'url': 'http://example.com'},
          ...    {'title': 'example2', 'url': 'http://example.com'},
          ... ]))
          >>> mod.is_empty()
          False
          """
          if super(Group, self).is_empty():
              return True
          for child in self.children:
              if not child.is_empty():
                  return False
          return True
  
      def _prepare_children(self):
          # computes ids for children: generates them if they are not set
          # and then prepends them with this group's id
          seen = set()
          for id, module in enumerate(self.children):
              proposed_id = "%s_%s" % (self.id, module.id or id + 1)
              module.id = uniquify(proposed_id, seen)
              module._prepare_children()
  
  
  class LinkList(DashboardModule):
      """
      A module that displays a list of links.
      As well as the :class:`~admin_tools.dashboard.modules.DashboardModule`
      properties, the :class:`~admin_tools.dashboard.modules.LinkList` takes
      an extra keyword argument:
  
      ``layout``
          The layout of the list, possible values are ``stacked`` and ``inline``.
          The default value is ``stacked``.
  
      Link list modules children are simple python dictionaries that can have the
      following keys:
  
      ``title``
          The link title.
  
      ``url``
          The link URL.
  
      ``external``
          Boolean that indicates whether the link is an external one or not.
  
      ``description``
          A string describing the link, it will be the ``title`` attribute of
          the html ``a`` tag.
  
      ``attrs``
          Hash comprising attributes of the html ``a`` tag.
  
      Children can also be iterables (lists or tuples) of length 2, 3, 4 or 5.
  
      Here's a small example of building a link list module::
  
          from admin_tools.dashboard import modules, Dashboard
  
          class MyDashboard(Dashboard):
              def __init__(self, **kwargs):
                  Dashboard.__init__(self, **kwargs)
  
                  self.children.append(modules.LinkList(
                      layout='inline',
                      children=(
                          {
                              'title': 'Python website',
                              'url': 'http://www.python.org',
                              'external': True,
                              'description': 'Python language rocks !',
                              'attrs': {'target': '_blank'},
                          },
                          ['Django', 'http://www.djangoproject.com', True],
                          ['Some internal link', '/some/internal/link/'],
                      )
                  ))
  
      The screenshot of what this code produces:
  
      .. image:: images/linklist_dashboard_module.png
      """
  
      title = _("Links")
      template = "admin_tools/dashboard/modules/link_list.html"
      layout = "stacked"
  
      def init_with_context(self, context):
          if self._initialized:
              return
          new_children = []
          for link in self.children:
              if isinstance(link, (tuple, list,)):
                  link_dict = {"title": link[0], "url": link[1]}
                  if len(link) >= 3:
                      link_dict["external"] = link[2]
                  if len(link) >= 4:
                      link_dict["description"] = link[3]
                  if len(link) >= 5:
                      link_dict["attrs"] = link[4]
                  link = link_dict
              if "attrs" not in link:
                  link["attrs"] = {}
              link["attrs"]["href"] = link["url"]
              if link.get("description", ""):
                  link["attrs"]["title"] = link["description"]
              if link.get("external", False):
                  link["attrs"]["class"] = " ".join(
                      ["external-link"] + link["attrs"].get("class", "").split()
                  ).strip()
              link["attrs"] = flatatt(link["attrs"])
              new_children.append(link)
          self.children = new_children
          self._initialized = True
  
  
  class AppList(DashboardModule, AppListElementMixin):
      """
      Module that lists installed apps and their models.
      As well as the :class:`~admin_tools.dashboard.modules.DashboardModule`
      properties, the :class:`~admin_tools.dashboard.modules.AppList`
      has two extra properties:
  
      ``models``
          A list of models to include, only models whose name (e.g.
          "blog.comments.models.Comment") match one of the strings (e.g.
          "blog.*") in the models list will appear in the dashboard module.
  
      ``exclude``
          A list of models to exclude, if a model name (e.g.
          "blog.comments.models.Comment") match an element of this list (e.g.
          "blog.comments.*") it won't appear in the dashboard module.
  
      If no models/exclude list is provided, **all apps** are shown.
  
      Here's a small example of building an app list module::
  
          from admin_tools.dashboard import modules, Dashboard
  
          class MyDashboard(Dashboard):
              def __init__(self, **kwargs):
                  Dashboard.__init__(self, **kwargs)
  
                  # will only list the django.contrib apps
                  self.children.append(modules.AppList(
                      title='Administration',
                      models=('django.contrib.*',)
                  ))
                  # will list all apps except the django.contrib ones
                  self.children.append(modules.AppList(
                      title='Applications',
                      exclude=('django.contrib.*',)
                  ))
  
      The screenshot of what this code produces:
  
      .. image:: images/applist_dashboard_module.png
  
      .. note::
  
          Note that this module takes into account user permissions, for
          example, if a user has no rights to change or add a ``Group``, then
          the django.contrib.auth.Group model line will not be displayed.
      """
  
      title = _("Applications")
      template = "admin_tools/dashboard/modules/app_list.html"
      models = None
      exclude = None
      include_list = None
      exclude_list = None
  
      def __init__(self, title=None, **kwargs):
          self.models = list(kwargs.pop("models", []))
          self.exclude = list(kwargs.pop("exclude", []))
          self.include_list = kwargs.pop("include_list", [])  # deprecated
          self.exclude_list = kwargs.pop("exclude_list", [])  # deprecated
          super(AppList, self).__init__(title, **kwargs)
  
      def init_with_context(self, context):
          if self._initialized:
              return
          items = self._visible_models(context["request"])
          apps = {}
          for model, perms in items:
              app_label = model._meta.app_label
              if app_label not in apps:
                  apps[app_label] = {
                      "title": django_apps.get_app_config(app_label).verbose_name,
                      "url": self._get_admin_app_list_url(model, context),
                      "models": [],
                  }
              model_dict = {}
              model_dict["title"] = model._meta.verbose_name_plural
              if perms["change"] or perms.get("view", False):
                  model_dict["change_url"] = self._get_admin_change_url(model, context)
              if perms["add"]:
                  model_dict["add_url"] = self._get_admin_add_url(model, context)
              apps[app_label]["models"].append(model_dict)
  
          for app in sorted(apps.keys()):
              # sort model list alphabetically
              apps[app]["models"].sort(key=lambda x: x["title"])
              self.children.append(apps[app])
          self._initialized = True
  
  
  class ModelList(DashboardModule, AppListElementMixin):
      """
      Module that lists a set of models.
      As well as the :class:`~admin_tools.dashboard.modules.DashboardModule`
      properties, the :class:`~admin_tools.dashboard.modules.ModelList` takes
      two extra arguments:
  
      ``models``
          A list of models to include, only models whose name (e.g.
          "blog.comments.models.Comment") match one of the strings (e.g.
          "blog.*") in the models list will appear in the dashboard module.
  
      ``exclude``
          A list of models to exclude, if a model name (e.g.
          "blog.comments.models.Comment") match an element of this list (e.g.
          "blog.comments.*") it won't appear in the dashboard module.
  
      Here's a small example of building a model list module::
  
          from admin_tools.dashboard import modules, Dashboard
  
          class MyDashboard(Dashboard):
              def __init__(self, **kwargs):
                  Dashboard.__init__(self, **kwargs)
  
                  # will only list the django.contrib.auth models
                  self.children += [
                      modules.ModelList(
                          title='Authentication',
                          models=['django.contrib.auth.*',]
                      )
                  ]
  
      The screenshot of what this code produces:
  
      .. image:: images/modellist_dashboard_module.png
  
      .. note::
  
          Note that this module takes into account user permissions, for
          example, if a user has no rights to change or add a ``Group``, then
          the django.contrib.auth.Group model line will not be displayed.
      """
  
      template = "admin_tools/dashboard/modules/model_list.html"
      models = None
      exclude = None
      include_list = None
      exclude_list = None
  
      def __init__(self, title=None, models=None, exclude=None, **kwargs):
          self.models = list(models or [])
          self.exclude = list(exclude or [])
          self.include_list = kwargs.pop("include_list", [])  # deprecated
          self.exclude_list = kwargs.pop("exclude_list", [])  # deprecated
          if "extra" in kwargs:
              self.extra = kwargs.pop("extra")
          else:
              self.extra = []
          super(ModelList, self).__init__(title, **kwargs)
  
      def init_with_context(self, context):
          if self._initialized:
              return
          items = self._visible_models(context["request"])
          if not items:
              return
          for model, perms in items:
              model_dict = {}
              model_dict["title"] = model._meta.verbose_name_plural
              if perms["change"] or perms.get("view", False):
                  model_dict["change_url"] = self._get_admin_change_url(model, context)
              if perms["add"]:
                  model_dict["add_url"] = self._get_admin_add_url(model, context)
              self.children.append(model_dict)
          if self.extra:
              # TODO - permissions support
              for extra_url in self.extra:
                  model_dict = {}
                  model_dict["title"] = extra_url["title"]
                  model_dict["change_url"] = extra_url["change_url"]
                  model_dict["add_url"] = extra_url.get("add_url", None)
                  self.children.append(model_dict)
  
          self._initialized = True
  
  
  class RecentActions(DashboardModule):
      """
      Module that lists the recent actions for the current user.
      As well as the :class:`~admin_tools.dashboard.modules.DashboardModule`
      properties, the :class:`~admin_tools.dashboard.modules.RecentActions`
      takes three extra keyword arguments:
  
      ``include_list``
          A list of contenttypes (e.g. "auth.group" or "sites.site") to include,
          only recent actions that match the given contenttypes will be
          displayed.
  
      ``exclude_list``
          A list of contenttypes (e.g. "auth.group" or "sites.site") to exclude,
          recent actions that match the given contenttypes will not be
          displayed.
  
      ``limit``
          The maximum number of children to display. Default value: 10.
  
      Here's a small example of building a recent actions module::
  
          from admin_tools.dashboard import modules, Dashboard
  
          class MyDashboard(Dashboard):
              def __init__(self, **kwargs):
                  Dashboard.__init__(self, **kwargs)
  
                  # will only list the django.contrib apps
                  self.children.append(modules.RecentActions(
                      title='Django CMS recent actions',
                      include_list=('cms.page', 'cms.cmsplugin',)
                  ))
  
      The screenshot of what this code produces:
  
      .. image:: images/recentactions_dashboard_module.png
      """
  
      title = _("Recent Actions")
      template = "admin_tools/dashboard/modules/recent_actions.html"
      limit = 10
      include_list = None
      exclude_list = None
  
      def __init__(
          self, title=None, limit=10, include_list=None, exclude_list=None, **kwargs
      ):
          self.include_list = include_list or []
          self.exclude_list = exclude_list or []
          kwargs.update({"limit": limit})
          super(RecentActions, self).__init__(title, **kwargs)
  
      def init_with_context(self, context):
          if self._initialized:
              return
          from django.db.models import Q
          from django.contrib.admin.models import LogEntry
  
          request = context["request"]
  
          def get_qset(list):
              # Import this here to silence RemovedInDjango19Warning. See #15
              from django.contrib.contenttypes.models import ContentType
  
              qset = None
              for contenttype in list:
                  if isinstance(contenttype, ContentType):
                      current_qset = Q(content_type__id=contenttype.id)
                  else:
                      try:
                          app_label, model = contenttype.split(".")
                      except:
                          raise ValueError('Invalid contenttype: "%s"' % contenttype)
                      current_qset = Q(
                          content_type__app_label=app_label, content_type__model=model
                      )
                  if qset is None:
                      qset = current_qset
                  else:
                      qset = qset | current_qset
              return qset
  
          if request.user is None:
              qs = LogEntry.objects.all()
          else:
              qs = LogEntry.objects.filter(user__pk__exact=request.user.pk)
  
          if self.include_list:
              qs = qs.filter(get_qset(self.include_list))
          if self.exclude_list:
              qs = qs.exclude(get_qset(self.exclude_list))
  
          self.children = qs.select_related("content_type", "user")[: self.limit]
          if not len(self.children):
              self.pre_content = _("No recent actions.")
          self._initialized = True
  
  
  class Feed(DashboardModule):
      """
      Class that represents a feed dashboard module.
  
      .. important::
  
          This class uses the
          `Universal Feed Parser module <http://www.feedparser.org/>`_ to parse
          the feeds, so you'll need to install it, all feeds supported by
          FeedParser are thus supported by the Feed
  
      As well as the :class:`~admin_tools.dashboard.modules.DashboardModule`
      properties, the :class:`~admin_tools.dashboard.modules.Feed` takes two
      extra keyword arguments:
  
      ``feed_url``
          The URL of the feed.
  
      ``limit``
          The maximum number of feed children to display. Default value: None,
          which means that all children are displayed.
  
      Here's a small example of building a recent actions module::
  
          from admin_tools.dashboard import modules, Dashboard
  
          class MyDashboard(Dashboard):
              def __init__(self, **kwargs):
                  Dashboard.__init__(self, **kwargs)
  
                  # will only list the django.contrib apps
                  self.children.append(modules.Feed(
                      title=_('Latest Django News'),
                      feed_url='http://www.djangoproject.com/rss/weblog/',
                      limit=5
                  ))
  
      The screenshot of what this code produces:
  
      .. image:: images/feed_dashboard_module.png
      """
  
      title = _("RSS Feed")
      template = "admin_tools/dashboard/modules/feed.html"
      feed_url = None
      limit = None
  
      def __init__(self, title=None, feed_url=None, limit=None, **kwargs):
          kwargs.update({"feed_url": feed_url, "limit": limit})
          super(Feed, self).__init__(title, **kwargs)
  
      def init_with_context(self, context):
          if self._initialized:
              return
          import datetime
  
          if self.feed_url is None:
              raise ValueError("You must provide a valid feed URL")
          try:
              import feedparser
          except ImportError:
              self.children.append(
                  {
                      "title": ("You must install the FeedParser python module"),
                      "warning": True,
                  }
              )
              return
  
          feed = feedparser.parse(self.feed_url)
          if self.limit is not None:
              entries = feed["entries"][: self.limit]
          else:
              entries = feed["entries"]
          for entry in entries:
              entry.url = entry.link
              try:
                  entry.date = datetime.date(*entry.published_parsed[0:3])
              except:
                  # no date for certain feeds
                  pass
              self.children.append(entry)
          self._initialized = True
  
  
  # Custom created module type for conversation codes
  class RecentConversationCodes(DashboardModule):
      """
      Module that lists the most recent conversation codes with link to their analytics tabs.
      As well as the :class:`~admin_tools.dashboard.modules.DashboardModule`
      properties, the :class:`~admin_tools.dashboard.modules.RecentConversationCodes`
      takes one extra keyword argument:
  
      ``limit``
          The maximum number of children to display. Default value: 10.
  
      Here's a small example of building a recent actions module::
  
          from admin_tools.dashboard import modules, Dashboard
  
          class MyDashboard(Dashboard):
              def __init__(self, **kwargs):
                  Dashboard.__init__(self, **kwargs)
  
                  # will only list the django.contrib apps
                  self.children.append(modules.RecentActions(
                      limit='10',
                  ))
  
      """
  
      title = _("Active Conversation Codes")
      template = "admin_tools/dashboard/modules/conversation_code.html"
      limit = 10
  
      def __init__(self, title=None, limit=10, **kwargs):
          kwargs.update({"limit": limit})
          super(RecentConversationCodes, self).__init__(title, **kwargs)
  
      def init_with_context(self, context):
          if self._initialized:
              return
          from django.db.models import Q
          from knock.models import Conversation_code, Conversations
  
          self.children = Conversation_code.objects.all()[: self.limit]
          if not len(self.children):
              self.pre_content = _("No conversation codes.")
          self._initialized = True
  