design.rst 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. .. _design:
  2. Design Overview
  3. ===============
  4. Introduction
  5. ------------
  6. Distro Tracker is implemented as a Python application using the
  7. `Django framework <https://www.djangoproject.com>`_. It aims to support both
  8. Python2.7 and Python3.
  9. An important goal of the project is to implement a system which is easily
  10. customizable so it could serve Debian derivatives too (vendors).
  11. This document will present an overview of the high-level design choices which
  12. were made.
  13. Note that a previous version of the service pre-existed (it was known
  14. as the Package Tracking System), but which operated over completely
  15. different technology (statically generated documents, etc.). Some
  16. features have been kept identical over the rewrite.
  17. .. _email_design:
  18. Email Interface
  19. ---------------
  20. There are three aspects to the email interface: the control message
  21. processing, dispatching received package messages to the correct
  22. subscribers and creating news items based on received emails.
  23. This is implemented in the :mod:`distro_tracker.mail` app. The three mentioned
  24. functionalities are found in the following subpackages and modules of this app:
  25. - :mod:`distro_tracker.mail.control.control`
  26. - :mod:`distro_tracker.mail.dispatch`
  27. - :mod:`distro_tracker.mail.mail_news`
  28. .. _control_email_design:
  29. Email Control Messages
  30. ++++++++++++++++++++++
  31. Distro Tracker expects the system's MTA to pipe any received control emails to the
  32. :mod:`distro_tracker.mail.management.commands.tracker_control` Django management
  33. command. For information how to set this up, refer to the
  34. :ref:`mailbot setup <mailbot>`.
  35. The actual processing of the received command email message is implemented in
  36. :func:`distro_tracker.mail.control.process.process`. It does this by retrieving the message's
  37. payload and feeding it into an instance of
  38. :class:`distro_tracker.mail.control.commands.CommandProcessor`.
  39. The :class:`CommandProcessor <distro_tracker.mail.control.commands.CommandProcessor>` takes
  40. care of parsing and executing all given commands.
  41. All available commands are implemented in the :mod:`distro_tracker.mail.control.commands`
  42. module. Each command must be a subclass of the
  43. :mod:`distro_tracker.mail.control.commands.base.Command` class. There are three attributes of the
  44. class that subclasses must override:
  45. - :attr:`META <distro_tracker.mail.control.commands.base.Command.META>` - most importantly
  46. provides the command name
  47. - :attr:`REGEX_LIST <distro_tracker.mail.control.commands.base.Command.REGEX_LIST>` - allows
  48. matching a string to the command
  49. - :meth:`handle() <distro_tracker.mail.control.commands.base.Command.handle>` - implements the command
  50. processing
  51. The class :class:`distro_tracker.mail.control.commands.CommandFactory` produces instances of
  52. the correct :class:`Command <distro_tracker.mail.control.commands.base.Command>` subclasses
  53. based on a given line.
  54. Commands which require confirmation are easily implemented by decorating the
  55. class with the :func:`distro_tracker.mail.control.commands.confirmation.needs_confirmation`
  56. class decorator. In addition to that, two more methods can be implemented, but
  57. are not mandatory:
  58. - ``pre_confirm`` - for actions which should come before asking for
  59. confirmation for the command. If this method does not return an
  60. object which evalutes as a True Boolean, no confirmation is sent.
  61. It should also make sure to add appropriate status messages to the
  62. response.
  63. If the method is not provided, then a default response indicating that
  64. a confirmation is required is output.
  65. - ``get_confirmation_message`` - Method which should return a string
  66. containing an additional message to be included in the confirmation
  67. email.
  68. .. _dispatch_email_design:
  69. Email Dispatch
  70. ++++++++++++++
  71. As is the case for control message processing, Distro Tracker expects the system's MTA
  72. to pipe any received package emails to a management command -
  73. :mod:`distro_tracker.mail.management.commands.tracker_dispatch`. For information how to set
  74. this up, refer to the :ref:`mailbot setup <mailbot>`.
  75. The function that performs the processing of a received package message is
  76. :func:`distro_tracker.mail.dispatch.process`. In order to tag the received message
  77. with a keyword, it uses a vendor-provided function
  78. :func:`get_keyword <distro_tracker.vendor.skeleton.rules.get_keyword>`. In case a vendor
  79. has not implemented this function, the message is tagged as ``default``.
  80. News from Email Messages
  81. ++++++++++++++++++++++++
  82. Distro Tracker allows for automatic news creation based on received emails. It is necessary
  83. to set up the MTA so it pipes received emails which should potentially be turned into
  84. news items, to the management command
  85. :mod:`distro_tracker.mail.management.commands.tracker_receive_news`.
  86. News are created as :class:`distro_tracker.core.models.News` objects and each of the
  87. model's instances associated with a particular package is displayed in the
  88. :class:`NewsPanel <distro_tracker.core.panels.NewsPanel>`.
  89. By default, any messages given to the management command which contains the
  90. ``X-Distro-Tracker-Package`` header are turned into news items with the content type of
  91. the news item being ``message/rfc822`` and the content the entire message.
  92. However, it is also possible to implement a vendor-specific function
  93. :func:`distro_tracker.vendor.skeleton.rules.create_news_from_email_message` which will be
  94. given the received email message object and can create custom news items based
  95. on vendor-specific rules.
  96. .. _tasks_design:
  97. Tasks Framework
  98. ---------------
  99. Since Distro Tracker aggregates information based on many different sources,
  100. a way to perform incremental updates is necessary. This means that if an update
  101. from one source causes such changes which could have an effect on some other
  102. information, this information needs to be updated, as well. In order to avoid
  103. recalculating everything after each update, a framework for executing such
  104. tasks is implemented in :mod:`distro_tracker.core.tasks`.
  105. Each task defines a list of "events" which it produces and a list of "events"
  106. it depends on. An event is any change of shared information or anything else
  107. a task would like to inform other tasks of happening. Knowing this, the
  108. framework can build a graph of dependencies between tasks.
  109. When running a single task, all other tasks which are dependent on that one
  110. are automatically run afterwards, in the correct order and ensuring a task runs
  111. only once all the tasks it depends on are completed. It also makes sure not to
  112. initiate any task for which no events were raised.
  113. In order to implement a task, the :class:`distro_tracker.core.tasks.BaseTask` class should
  114. be subclassed. Its attributes
  115. :attr:`PRODUCES_EVENTS <distro_tracker.core.tasks.BaseTask.PRODUCES_EVENTS>` and
  116. :attr:`DEPENDS_ON_EVENTS <distro_tracker.core.tasks.BaseTask.DEPENDS_ON_EVENTS>` are lists
  117. of strings giving names of events which the task produces and depends on,
  118. respectively. The :meth:`execute() <distro_tracker.core.tasks.BaseTask.execute>` method
  119. implements the task's functionality.
  120. .. note::
  121. All task classes should be placed in a module called ``tracker_tasks`` found at
  122. the top level of an installed Django app. Tasks in apps which are not
  123. installed will never be run.
  124. When running a task, a :class:`distro_tracker.core.tasks.Job` instance is created which
  125. keeps track of raised events, completed tasks and the order in which the tasks
  126. should run. It stores its state using the :class:`distro_tracker.core.tasks.JobState`
  127. class which is in charge of making sure the job state is persistent, so that
  128. even if a job were to fail, it is still possible to reconstruct it and continue
  129. its execution.
  130. .. note::
  131. Each task's operation must be idempotent to ensure that if an error does occur
  132. before being able to save the state of the job, rerunning the task will not
  133. cause any inconsistencies.
  134. A task has access to the :class:`Job <distro_tracker.core.tasks.Job>` instance it is a
  135. part of and can access all events raised during its processing. A convenience
  136. method :meth:`get_all_events <distro_tracker.core.tasks.BaseTask.get_all_events>` is
  137. provided which returns only the events the class has indicated in the
  138. :attr:`DEPENDS_ON_EVENTS <distro_tracker.core.tasks.BaseTask.DEPENDS_ON_EVENTS>` list.
  139. For more information see the documentation on the :mod:`distro_tracker.core.tasks` module.
  140. .. _vendor_design:
  141. Vendor-specific Rules
  142. ---------------------
  143. Since Distro Tracker aims to be extensible, it allows a simple way for vendors to
  144. implement functions which are plugged in by core code when necessary.
  145. Vendor-provided functions can be called using the :func:`distro_tracker.vendor.common.call`
  146. function. The function object itself can be retrieved by using the
  147. lower-level :func:`distro_tracker.vendor.common.get_callable` function, but this should
  148. be avoided.
  149. All vendor-provided functions must be found in the module given by the
  150. ``DISTRO_TRACKER_VENDOR_RULES`` settings value.
  151. .. _packageinfo_design:
  152. Package Information
  153. -------------------
  154. Distro Tracker retrieves package information from a set of user-defined repositories.
  155. Admin users can add new :class:`distro_tracker.core.models.Repository` instances through
  156. the admin panel. Information from repositories is updated by the task
  157. :class:`distro_tracker.core.retrieve_data.UpdateRepositoriesTask` and it emits events
  158. based on changes found in the repositories.
  159. Additional tasks are implemented in :class:`distro_tracker.core.retrieve_data` which
  160. use those events to store pre-calculated (extracted) information ready
  161. to be rendered in a variety of contexts (webpage, REST, RDF, etc.).
  162. Distro Tracker also updates the list of existing pseudo packages by using the
  163. vendor-provided function
  164. :func:`get_pseudo_package_list <distro_tracker.vendor.skeleton.rules.get_pseudo_package_list>`.
  165. All retrieved data can be accessed by using the models found in
  166. :mod:`distro_tracker.core.models`. Refer to that module's documentation for convenient
  167. APIs for interacting with the extracted information.
  168. Data model
  169. ++++++++++
  170. You may wish to check the data model. This can be done for instance
  171. with the following command after having installed 'django_extensions'
  172. in INSTALLED_APPS (see distro_tracker.project.setup.locals.py)::
  173. $ ./manage.py graph_models core | dot -Tpng >graph.png
  174. .. _web_design:
  175. Web Interface
  176. -------------
  177. .. _panels_web_design:
  178. Panels Framework
  179. ++++++++++++++++
  180. Distro Tracker allows an easy way to embed new information on a package Web page.
  181. It consists of implementing a subclass of the :class:`distro_tracker.core.panels.BasePanel`
  182. class. Panels can provide the HTML directly or, alternatively, the name of the
  183. template which should be included. This template then has to render the panel's
  184. information to the page.
  185. It is recommended that the panel inherits from the ``core/panels/panel.html``
  186. template and fills in its contents to the blocks defined in the template, so
  187. that the page remains visually consistent. This is not mandatory, however.
  188. .. note::
  189. All panel classes should be placed in a module called ``tracker_panels`` found at
  190. the top level of an installed Django app. Panels from apps which are not
  191. installed will never appear on a package page.
  192. Distro Tracker implements some general panels which could be used by any vendor.
  193. Refer to the documentation of each panel in :mod:`distro_tracker.core.panels` to see
  194. any possible ways of augmenting their information by implementing
  195. vendor-specific functions.
  196. .. _views_web_design:
  197. Views and Templates
  198. +++++++++++++++++++
  199. The core views are found in :mod:`distro_tracker.core.views` and are extremely thin.
  200. The package page view only finds the correct package model instance and
  201. passes it on to available panels. It renders a template which includes each
  202. panel within the skeleton of the page.
  203. Other core views are in charge of a redirect of legacy package URLs, package
  204. search and package autocomplete.
  205. .. _clientside_web_design:
  206. Client-side Functionality
  207. +++++++++++++++++++++++++
  208. The client-side implements a simple autocomplete form for searching packages.
  209. It uses Javascript to call an HTTP endpoint implemented by one of the views.
  210. The HTML of the pages uses the HTML5 standard.
  211. The `Bootstrap <http://twitter.github.io/bootstrap/>`_ front-end framework is
  212. used for the GUI.