1
0

views.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. from itertools import count
  2. from django.core.exceptions import ImproperlyConfigured
  3. from django.views.generic.list import ListView
  4. from . import tables
  5. from .config import RequestConfig
  6. class TableMixinBase:
  7. """
  8. Base mixin for the Single- and MultiTable class based views.
  9. """
  10. context_table_name = "table"
  11. table_pagination = None
  12. def get_context_table_name(self, table):
  13. """
  14. Get the name to use for the table's template variable.
  15. """
  16. return self.context_table_name
  17. def get_table_pagination(self, table):
  18. """
  19. Return pagination options passed to `.RequestConfig`:
  20. - True for standard pagination (default),
  21. - False for no pagination,
  22. - a dictionary for custom pagination.
  23. `ListView`s pagination attributes are taken into account, if `table_pagination` does not
  24. define the corresponding value.
  25. Override this method to further customize pagination for a `View`.
  26. """
  27. paginate = self.table_pagination
  28. if paginate is False:
  29. return False
  30. paginate = {}
  31. if getattr(self, "paginate_by", None) is not None:
  32. paginate["per_page"] = self.paginate_by
  33. if hasattr(self, "paginator_class"):
  34. paginate["paginator_class"] = self.paginator_class
  35. if getattr(self, "paginate_orphans", 0) != 0:
  36. paginate["orphans"] = self.paginate_orphans
  37. # table_pagination overrides any MultipleObjectMixin attributes
  38. if self.table_pagination:
  39. paginate.update(self.table_pagination)
  40. # we have no custom pagination settings, so just use the default.
  41. if not paginate and self.table_pagination is None:
  42. return True
  43. return paginate
  44. class SingleTableMixin(TableMixinBase):
  45. """
  46. Adds a Table object to the context. Typically used with
  47. `.TemplateResponseMixin`.
  48. Attributes:
  49. table_class: subclass of `.Table`
  50. table_data: data used to populate the table, any compatible data source.
  51. context_table_name(str): name of the table's template variable (default:
  52. 'table')
  53. table_pagination (dict): controls table pagination. If a `dict`, passed as
  54. the *paginate* keyword argument to `.RequestConfig`. As such, any
  55. Truthy value enables pagination. (default: enable pagination).
  56. The `dict` can be used to specify values for arguments for the call to
  57. `~.tables.Table.paginate`.
  58. If you want to use a non-standard paginator for example, you can add a key
  59. `paginator_class` to the dict, containing a custom `Paginator` class.
  60. This mixin plays nice with the Django's ``.MultipleObjectMixin`` by using
  61. ``.get_queryset`` as a fall back for the table data source.
  62. """
  63. table_class = None
  64. table_data = None
  65. def get_table_class(self):
  66. """
  67. Return the class to use for the table.
  68. """
  69. if self.table_class:
  70. return self.table_class
  71. if self.model:
  72. return tables.table_factory(self.model)
  73. raise ImproperlyConfigured(
  74. "You must either specify {0}.table_class or {0}.model".format(type(self).__name__)
  75. )
  76. def get_table(self, **kwargs):
  77. """
  78. Return a table object to use. The table has automatic support for
  79. sorting and pagination.
  80. """
  81. table_class = self.get_table_class()
  82. table = table_class(data=self.get_table_data(), **kwargs)
  83. return RequestConfig(self.request, paginate=self.get_table_pagination(table)).configure(
  84. table
  85. )
  86. def get_table_data(self):
  87. """
  88. Return the table data that should be used to populate the rows.
  89. """
  90. if self.table_data is not None:
  91. return self.table_data
  92. elif hasattr(self, "object_list"):
  93. return self.object_list
  94. elif hasattr(self, "get_queryset"):
  95. return self.get_queryset()
  96. klass = type(self).__name__
  97. raise ImproperlyConfigured(
  98. "Table data was not specified. Define {}.table_data".format(klass)
  99. )
  100. def get_table_kwargs(self):
  101. """
  102. Return the keyword arguments for instantiating the table.
  103. Allows passing customized arguments to the table constructor, for example,
  104. to remove the buttons column, you could define this method in your View::
  105. def get_table_kwargs(self):
  106. return {
  107. 'exclude': ('buttons', )
  108. }
  109. """
  110. return {}
  111. def get_context_data(self, **kwargs):
  112. """
  113. Overridden version of `.TemplateResponseMixin` to inject the table into
  114. the template's context.
  115. """
  116. context = super().get_context_data(**kwargs)
  117. table = self.get_table(**self.get_table_kwargs())
  118. context[self.get_context_table_name(table)] = table
  119. return context
  120. class SingleTableView(SingleTableMixin, ListView):
  121. """
  122. Generic view that renders a template and passes in a `.Table` instances.
  123. Mixes ``.SingleTableMixin`` with ``django.views.generic.list.ListView``.
  124. """
  125. class MultiTableMixin(TableMixinBase):
  126. """
  127. Add a list with multiple Table object's to the context. Typically used with
  128. `.TemplateResponseMixin`.
  129. The `tables` attribute must be either a list of `.Table` instances or
  130. classes extended from `.Table` which are not already instantiated. In that
  131. case, `get_tables_data` must be able to return the tables data, either by
  132. having an entry containing the data for each table in `tables`, or by
  133. overriding this method in order to return this data.
  134. Attributes:
  135. tables: list of `.Table` instances or list of `.Table` child objects.
  136. tables_data: if defined, `tables` is assumed to be a list of table
  137. classes which will be instantiated with the corresponding item from
  138. this list of `.TableData` instances.
  139. table_prefix(str): Prefix to be used for each table. The string must
  140. contain one instance of `{}`, which will be replaced by an integer
  141. different for each table in the view. Default is 'table_{}-'.
  142. context_table_name(str): name of the table's template variable (default:
  143. 'tables')
  144. .. versionadded:: 1.2.3
  145. """
  146. tables = None
  147. tables_data = None
  148. table_prefix = "table_{}-"
  149. # override context table name to make sense in a multiple table context
  150. context_table_name = "tables"
  151. def get_tables(self):
  152. """
  153. Return an array of table instances containing data.
  154. """
  155. if self.tables is None:
  156. klass = type(self).__name__
  157. raise ImproperlyConfigured("No tables were specified. Define {}.tables".format(klass))
  158. data = self.get_tables_data()
  159. if data is None:
  160. return self.tables
  161. if len(data) != len(self.tables):
  162. klass = type(self).__name__
  163. raise ImproperlyConfigured("len({}.tables_data) != len({}.tables)".format(klass, klass))
  164. return list(Table(data[i]) for i, Table in enumerate(self.tables))
  165. def get_tables_data(self):
  166. """
  167. Return an array of table_data that should be used to populate each table
  168. """
  169. return self.tables_data
  170. def get_context_data(self, **kwargs):
  171. context = super().get_context_data(**kwargs)
  172. tables = self.get_tables()
  173. # apply prefixes and execute requestConfig for each table
  174. table_counter = count()
  175. for table in tables:
  176. table.prefix = table.prefix or self.table_prefix.format(next(table_counter))
  177. RequestConfig(self.request, paginate=self.get_table_pagination(table)).configure(table)
  178. context[self.get_context_table_name(table)] = list(tables)
  179. return context