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