accrete 0.0.54__py3-none-any.whl → 0.0.56__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,28 +3,19 @@
3
3
 
4
4
  {% block content %}
5
5
  <div class="columns is-desktop m-0">
6
- <div class="column p-0 is-8-desktop">
6
+ <div id="form-content" class="column p-0 is-8-desktop">
7
7
  <div class="box mt-2">
8
- {% include 'ui/partials/form_errors.html' with show_field_errors=True %}
8
+ {% if form.is_saved is False %}
9
+ {% include 'ui/partials/form_errors.html' with show_field_errors=True %}
10
+ {% endif %}
9
11
  {% block form %}{% endblock %}
10
12
  </div>
11
13
  </div>
12
- <div class="column is-4">
13
- <nav class="">
14
+ <div id="form-info" class="column is-4">
15
+ <nav>
14
16
  {% block info_panel %}
15
17
  {% endblock %}
16
18
  </nav>
17
19
  </div>
18
20
  </div>
19
-
20
-
21
- {# <div class="columns is-desktop m-0">#}
22
- {# <div class="column p-0 is-8-desktop">#}
23
- {# <div>#}
24
- {# {% include 'ui/partials/form_errors.html' with show_field_errors=True %}#}
25
- {# {% block form %}{% endblock %}#}
26
- {# </div>#}
27
- {# </div>#}
28
- {# <div class="column is-4"></div>#}
29
- {# </div>#}
30
21
  {% endblock %}
@@ -18,6 +18,8 @@
18
18
  {% block htmx %}
19
19
  <script src="{% static "js/htmx.min.js" %}" defer type="text/javascript"></script>
20
20
  {% endblock %}
21
+ <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/sort@3.x.x/dist/cdn.min.js"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js" defer type="text/javascript"></script>
21
23
  {% block script %}
22
24
  <script src="{% static "js/ui.js" %}" defer type="text/javascript"></script>
23
25
  {% endblock %}
@@ -62,7 +64,7 @@
62
64
  <div class="navbar-item has-dropdown is-hoverable" onclick="this.classList.toggle('is-active');">
63
65
  <a class="navbar-link is-arrowless {% if request.member.name %}is-size-7 has-text-centered-desktop{% endif %}">
64
66
  {% if request.member.name %}
65
- {{ request.member }}<br>{{ user }}
67
+ {{ request.member }}
66
68
  {% else %}
67
69
  {{ user }}
68
70
  {% endif %}
@@ -56,8 +56,8 @@
56
56
  </div>
57
57
 
58
58
  <div id="list-customization" style="display: flex; flex-direction: column; flex-wrap: nowrap">
59
- <div class="is-flex-grow-1 mt-2">
60
- <div class="field has-addons is-fullwidth">
59
+ <div class="mt-2">
60
+ <div class="field has-addons">
61
61
  {% if field_selection %}
62
62
  <div class="control is-expanded">
63
63
  <button id="list-customization-fields-trigger"
@@ -1,23 +1,36 @@
1
- {% if form.non_field_errors or form.save_error or form.errors and show_field_errors %}
2
- <div class="columns py-0">
3
- <div class="column is-12">
4
- <div class="notification is-danger is-light">
5
- <p>{{ form.non_field_errors }}</p>
6
- {% if form.save_error %}
7
- <p>{{ form.save_error|safe }}</p>
8
- <p>Error ID: {{ form.save_error_id }}</p>
9
- {% endif %}
10
- {% if show_field_errors %}
11
- {% for f in form %}
12
- {% if f.errors %}
13
- <label class="label has-text-danger">
14
- <span class="is-underlined">{{ f.label_tag }}</span>
15
- <span class="is-size-7">{{ f.errors }}</span>
16
- </label>
17
- {% endif %}
1
+ <div class="columns py-0">
2
+ <div class="column is-12">
3
+ <div class="notification is-danger is-light">
4
+ <p>{{ form.non_field_errors }}</p>
5
+ {% for inline_form in form.inline_forms %}
6
+ <p>{{ inline_form.non_field_errors }}</p>
7
+ {% endfor %}
8
+ {% if form.save_error %}
9
+ <p>{{ form.save_error|safe }}</p>
10
+ <p>Error ID: {{ form.save_error_id }}</p>
11
+ {% endif %}
12
+ {% if show_field_errors %}
13
+ {% for f in form %}
14
+ {% if f.errors %}
15
+ <label>
16
+ <span class="is-underlined">{{ f.label_tag }}</span>
17
+ <span class="is-size-7">{{ f.errors }}</span>
18
+ </label>
19
+ {% endif %}
20
+ {% endfor %}
21
+ {% for formset in form.inline_forms %}
22
+ {% for inline_form in formset %}
23
+ {% for field in inline_form %}
24
+ {% if field.errors %}
25
+ <label>
26
+ <span class="is-underlined">{{ field.label_tag }}</span>
27
+ <span class="is-size-7">{{ field.errors }}</span>
28
+ </label>
29
+ {% endif %}
30
+ {% endfor %}
18
31
  {% endfor %}
19
- {% endif %}
20
- </div>
32
+ {% endfor %}
33
+ {% endif %}
21
34
  </div>
22
35
  </div>
23
- {% endif %}
36
+ </div>
@@ -29,7 +29,7 @@
29
29
  {% endif %}
30
30
  </div>
31
31
 
32
- <div class="is-flex is-hidden-fullhd is-justify-content-space-between mb-2 pt-3">
32
+ <div class="is-flex is-hidden-fullhd is-justify-content-space-between mb-2">
33
33
  <div id="header-actions" class="is-flex py-1 px-1 ml-2" style="overflow-x: auto">
34
34
  {% for action in actions %}
35
35
  {% if action.actions %}
accrete/forms.py CHANGED
@@ -1,250 +1,12 @@
1
1
  import logging
2
- from uuid import uuid4
3
- from functools import partial
4
-
5
2
  from django import forms
6
3
  from django.contrib.auth import get_user_model
7
- from django.db import transaction
8
4
  from accrete.tenant import get_tenant
9
5
 
10
6
  _logger = logging.getLogger(__name__)
11
7
  User = get_user_model()
12
8
 
13
9
 
14
- class One2ManyModelForm(forms.ModelForm):
15
- """
16
- Template Access:
17
- In Templates/Forms the generated fields can be accessed via
18
- form.get_>>related_name<< e.g. form.get_items returns
19
- a list of dictionaries containing the fields.
20
-
21
- Cleaning:
22
- For every One2Many field, clean_>>related_name<< is called.
23
- To clean all entries together, self.cleaned_data[>>related_name<<] is
24
- available in clean() containing a list of all entries
25
-
26
- Initial data can be supplied by creating a form method called
27
- get_initial_>>related_name<<_>>field_name<< which takes the
28
- related instance as a parameter
29
- or by setting an attribute on the model which can be a callable
30
-
31
-
32
- One2Many field configuration:
33
- o2m_fields = {
34
- # related_name
35
- 'items': {
36
- # field that holds the key to self.model
37
- 'fk_field': 'pricelist',
38
- # Order by the given fields value, can be prefixed by '-' to
39
- # control ascending/descending order
40
- 'order': '-pk'
41
- # fields of the related model that are needed in the form
42
- 'fields': {
43
- # pk field must be present for updating existing rows.
44
- # Otherwise, all related objects will be deleted and recreated
45
- # upon saving.
46
- 'pk': {
47
- 'field_class': partial(
48
- forms.IntegerField,
49
- required=False,
50
- widget=forms.HiddenInput
51
- ),
52
- # if only data is sent with fields that have
53
- # "count_as_empty" set to true, the whole fieldset is
54
- # ignored and won't be added to self.fields
55
- 'count_as_empty': True
56
- },
57
- 'product': {
58
- 'field_class': partial(
59
- forms.ModelChoiceField,
60
- Product.objects.all(),
61
- label=_('Product')
62
- )
63
- },
64
- 'price': {
65
- 'field_class': partial(
66
- forms.FloatField,
67
- label=_('Price')
68
- )
69
- }
70
- }
71
- }
72
- }
73
- """
74
- o2m_fields = {}
75
-
76
- def __init__(self, *args, **kwargs):
77
- super().__init__(*args, **kwargs)
78
- self.o2m_keys = None
79
- if self.instance and self.instance.pk and not kwargs.get('data'):
80
- self.add_instance_o2m_objects()
81
- else:
82
- self.add_o2m_fields(**kwargs)
83
- self.add_o2m_getter()
84
-
85
- def add_instance_o2m_objects(self):
86
- self.o2m_keys = set()
87
- for field in self.o2m_fields.keys():
88
- related_objects = getattr(self.instance, field)
89
- for o in related_objects.all():
90
- field_key = f'{field}_{str(uuid4())[:8]}'
91
- for k, v in self.o2m_fields[field]['fields'].items():
92
- func_name = f'get_initial_{field}_{k}'
93
- field_name = f'{field_key}-{k}'
94
- self.fields[field_name] = v['field_class']()
95
- if hasattr(self, func_name):
96
- initial = getattr(self, func_name)(o)
97
- else:
98
- initial = getattr(o, k) if hasattr(o, k) else False
99
- if callable(self.fields[field_name].initial):
100
- initial = initial()
101
- self.fields[field_name].initial = initial
102
- self.o2m_keys.add(field_key)
103
-
104
- def add_o2m_fields(self, **kwargs):
105
- self.o2m_keys = set()
106
- data = kwargs.get('data', {})
107
- for field, config in self.o2m_fields.items():
108
- fields = config['fields']
109
- field_keys = set(
110
- k.split('-')[0] for k in data.keys() if k.startswith(field)
111
- )
112
- if not field_keys:
113
- field_keys = set(
114
- f'{k}_1' for k in self.o2m_fields.keys()
115
- )
116
- for field_key in field_keys:
117
- to_add = any([
118
- data.get(f'{field_key}-{name}')
119
- for name in list(fields.keys())
120
- if not fields.get(f'{name}', {}).get('count_as_empty')
121
- ])
122
- if to_add:
123
- for k, v in fields.items():
124
- fname = f'{field_key}-{k}'
125
- self.fields[fname] = v['field_class']()
126
- self.fields[fname].initial = kwargs.get(fname)
127
- self.o2m_keys.add(field_key)
128
-
129
- def add_o2m_getter(self):
130
- for k in self.o2m_fields.keys():
131
- func_name = f'get_{k}'
132
- setattr(self, func_name, partial(self.get_o2m_fields, k))
133
-
134
- def get_o2m_fields(self, prefix):
135
- fields = []
136
- for k in self.o2m_keys:
137
- if k.startswith(prefix):
138
- fnames = list(
139
- self.o2m_fields.get(prefix, {}).get('fields', [])
140
- )
141
- fields.append({
142
- fname: self[f'{k}-{fname}']
143
- for fname in fnames
144
- })
145
-
146
- order = self.o2m_fields.get(prefix, {}).get('order')
147
- reverse = False
148
- if order and order.startswith('-'):
149
- order = order[1:]
150
- reverse = True
151
- if order and fields:
152
- fields = sorted(
153
- fields, key=lambda x: x[order].value(), reverse=reverse
154
- )
155
- return fields
156
-
157
- def _clean_fields(self):
158
- super()._clean_fields()
159
- self._clean_o2m_fields()
160
-
161
- def _clean_o2m_fields(self):
162
- for related_name, related_field_config in self.o2m_fields.items():
163
- self.cleaned_data[related_name] = []
164
- fields = related_field_config['fields'].keys()
165
- entries = filter(
166
- lambda x: x.startswith(related_name), self.o2m_keys
167
- )
168
- for entry in entries:
169
- value = {
170
- fname: self.cleaned_data.get(f'{entry}-{fname}')
171
- for fname in fields
172
- }
173
- try:
174
- self.cleaned_data[related_name].append(
175
- getattr(self, f'clean_{related_name}')(value)
176
- )
177
- except AttributeError:
178
- self.cleaned_data[related_name].append(value)
179
- except forms.ValidationError as error:
180
- self.add_error(entry, error)
181
-
182
- def save_o2m(self):
183
- for field in self.o2m_fields.keys():
184
- fk_field = self.o2m_fields[field].get('fk_field')
185
- if not fk_field:
186
- raise KeyError(
187
- 'One2Many Form definition is missing the '
188
- f'"fk_field" attribute on field {field}'
189
- )
190
-
191
- related_objects = getattr(self.instance, field)
192
- if not self.cleaned_data.get(field, []):
193
- related_objects.all().delete()
194
-
195
- related_object_fields = [
196
- field.name for field
197
- in related_objects.model._meta.get_fields()
198
- ]
199
- update_pks, to_update, to_create = [], [], []
200
-
201
- for item in self.cleaned_data.get(field, []):
202
- item.update({fk_field: self.instance})
203
- if pk := item.get('pk'):
204
- update_pks.append(pk)
205
- to_update.append(item)
206
- else:
207
- to_create.append(item)
208
-
209
- qs = related_objects.filter(pk__in=update_pks).order_by('pk')
210
- related_objects.exclude(pk__in=update_pks).delete()
211
- to_update = sorted(to_update, key=lambda x: x['pk'])
212
- if qs.count() != len(to_update):
213
- raise ValueError(
214
- 'Mismatching length of found objects and update data'
215
- )
216
- updated = []
217
- for obj, update in zip(qs, to_update):
218
- for k, v in update.items():
219
- if k not in related_object_fields:
220
- continue
221
- setattr(obj, k, v)
222
- updated.append(obj)
223
- objects_to_create = []
224
- for c in to_create:
225
- values = {}
226
- for k, v in c.items():
227
- if k in related_object_fields:
228
- values.update({k: v})
229
- objects_to_create.append(related_objects.model(**values))
230
- if updated:
231
- field_list = [
232
- f for f in to_update[0].keys()
233
- if f in related_object_fields
234
- ]
235
- related_objects.bulk_update(
236
- updated, fields=field_list
237
- )
238
- if to_create:
239
- related_objects.bulk_create(objects_to_create)
240
-
241
- def save(self, commit=True):
242
- super().save(commit=commit)
243
- if commit:
244
- self.save_o2m()
245
- return self.instance
246
-
247
-
248
10
  class TenantForm(forms.Form):
249
11
 
250
12
  def __init__(self, *args, **kwargs):
@@ -259,7 +21,7 @@ class TenantForm(forms.Form):
259
21
  field.queryset = field.queryset.filter(tenant=self.tenant)
260
22
 
261
23
 
262
- class TenantModelForm(One2ManyModelForm):
24
+ class TenantModelForm(forms.ModelForm):
263
25
 
264
26
  def __init__(self, *args, **kwargs):
265
27
  self.tenant = get_tenant()
@@ -272,13 +34,6 @@ class TenantModelForm(One2ManyModelForm):
272
34
  for field in fields_to_filter:
273
35
  field.queryset = field.queryset.filter(tenant=self.tenant)
274
36
 
275
- def save_o2m(self):
276
- o2m_fields = self.o2m_fields.keys()
277
- for field in o2m_fields:
278
- for item in self.cleaned_data.get(field, []):
279
- item.update({'tenant': self.tenant})
280
- return super().save_o2m()
281
-
282
37
  def save(self, commit=True):
283
38
  super().save(commit=False)
284
39
  if not self.instance.pk or not self.instance.tenant:
@@ -287,6 +42,4 @@ class TenantModelForm(One2ManyModelForm):
287
42
  if commit:
288
43
  self.instance.save()
289
44
  self.save_m2m()
290
- self.save_o2m()
291
-
292
45
  return self.instance
accrete/managers.py CHANGED
@@ -7,8 +7,7 @@ class TenantManager(models.Manager, AnnotationManagerMixin):
7
7
 
8
8
  def get_queryset(self):
9
9
  queryset = super().get_queryset()
10
- tenant = get_tenant()
11
- if tenant:
10
+ if tenant := get_tenant():
12
11
  queryset = queryset.filter(tenant=tenant)
13
12
  return queryset.annotate(**self.get_annotations())
14
13
 
accrete/utils/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  from . import dates
2
- from .forms import save_form
3
- from .http import filter_from_querystring
2
+ from .forms import save_form, save_forms, inline_vals_from_post, extend_formset
3
+ from .http import filter_from_querystring, cast_param
4
4
  from .models import get_related_model
accrete/utils/forms.py CHANGED
@@ -1,6 +1,9 @@
1
+ import re
1
2
  import logging
2
3
  from uuid import uuid4
4
+ from typing import Type
3
5
  from django.db import transaction
6
+ from django.forms import BaseFormSet
4
7
 
5
8
  _logger = logging.getLogger(__name__)
6
9
 
@@ -22,3 +25,79 @@ def save_form(form, reraise=False):
22
25
  if reraise:
23
26
  raise e
24
27
  return form
28
+
29
+
30
+ def save_forms(form, inline_formsets: list = None, reraise: bool = False):
31
+
32
+ def handle_error(error):
33
+ form.save_error = repr(error)
34
+ error_id = str(uuid4())[:8]
35
+ _logger.exception(f'{error_id}: {error}')
36
+ form.save_error_id = error_id
37
+
38
+ form.is_saved = False
39
+ form.save_error = None
40
+ form.save_error_id = None
41
+ form.inline_forms = inline_formsets
42
+
43
+ try:
44
+ form.is_valid()
45
+ inlines_valid = all([
46
+ inline_formset.is_valid() for inline_formset in inline_formsets
47
+ ])
48
+ except Exception as e:
49
+ handle_error(e)
50
+ if reraise:
51
+ raise e
52
+ return form
53
+
54
+ if not form.is_valid() or not inlines_valid:
55
+ return form
56
+
57
+ try:
58
+ with transaction.atomic():
59
+ form.save()
60
+ for inline_formset in inline_formsets:
61
+ inline_formset.save()
62
+ except Exception as e:
63
+ handle_error(e)
64
+ if reraise:
65
+ raise e
66
+ return form
67
+
68
+ form.is_saved = True
69
+ return form
70
+
71
+
72
+ def inline_vals_from_post(post: dict, prefix: str) -> list[dict]:
73
+ post_keys = set(re.findall(f'{prefix}-[0-9]+', ', '.join(post.keys())))
74
+ initial_data = {
75
+ post_key: {}
76
+ for post_key in post_keys if not post.get(f'{post_key}-DELETE')
77
+ }
78
+ for key, val in post.items():
79
+ post_key = '-'.join(key.split('-')[:-1])
80
+ if post_key not in initial_data:
81
+ continue
82
+ field_name = key.split('-')[-1]
83
+ initial_data[post_key].update({field_name: val})
84
+ return [val for val in initial_data.values()]
85
+
86
+
87
+ def extend_formset(formset_class, post: dict, data: list[dict]|dict, **formset_kwargs) -> Type[BaseFormSet]:
88
+ formset = formset_class(post, **formset_kwargs)
89
+ if not formset.is_valid():
90
+ return formset
91
+ form_data = post.copy()
92
+ if isinstance(data, dict):
93
+ data = [data]
94
+ prefix = formset_kwargs.get('prefix', 'form')
95
+ total = int(form_data[f'{prefix}-TOTAL_FORMS']) - 1
96
+ for item in data:
97
+ total += 1
98
+ form_data.update({f'{prefix}-{total}-{key}': value for key, value in item.items()})
99
+ form_data[f'{prefix}-TOTAL_FORMS'] = total + 1
100
+ formset = formset_class(form_data, **formset_kwargs)
101
+ for form in formset:
102
+ form._errors = {}
103
+ return formset
accrete/utils/http.py CHANGED
@@ -1,13 +1,14 @@
1
1
  import logging
2
2
  import json
3
3
  import operator
4
+ from typing import Callable
4
5
  from django.db.models import Model, Q, QuerySet
5
6
  from accrete.utils.models import get_related_model
6
7
  from accrete.annotation import Annotation
7
8
 
8
9
  _logger = logging.getLogger(__name__)
9
10
 
10
- Querystring_KEY_MAP = {
11
+ QUERYSTRING_KEY_MAP = {
11
12
  'querystring': 'q',
12
13
  'order': 'order',
13
14
  'paginate_by': 'paginate_by',
@@ -19,7 +20,7 @@ def filter_from_querystring(
19
20
  model: type[Model], get_params: dict, key_map: dict = None
20
21
  ) -> QuerySet:
21
22
 
22
- key_map = key_map or Querystring_KEY_MAP
23
+ key_map = key_map or QUERYSTRING_KEY_MAP
23
24
  querystring = get_params.get(key_map['querystring'], '[]')
24
25
  order = get_params.get(key_map['order']) or model._meta.ordering
25
26
 
@@ -96,4 +97,14 @@ def parse_querystring(model: type[Model], query_string: str) -> Q:
96
97
 
97
98
  ops = {'&': operator.and_, '|': operator.or_, '^': operator.xor}
98
99
  query = parse_query_block(query_data)
99
- return query
100
+ return query
101
+
102
+
103
+ def cast_param(params: dict, param: str, cast_to: Callable, default):
104
+ if param not in params:
105
+ return default
106
+ try:
107
+ return cast_to(params.get(param, default))
108
+ except Exception as e:
109
+ _logger.exception(e)
110
+ return default
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: accrete
3
- Version: 0.0.54
3
+ Version: 0.0.56
4
4
  Summary: Django Shared Schema Multi Tenant
5
5
  Author-email: Benedikt Jilek <benedikt.jilek@pm.me>
6
6
  License: Copyright (c) 2023 Benedikt Jilek
@@ -3,8 +3,8 @@ accrete/admin.py,sha256=MUYUmCFlGYPowiXTbwl4_Q6Cq0-neiL53WW4P76JCLs,1174
3
3
  accrete/annotation.py,sha256=P85kNgf_ka3U8i5cwaiKaAiSm21U-xY9PKmXMZR2ulU,1160
4
4
  accrete/apps.py,sha256=F7ynMLHJr_6bRujWtZVUzCliY2CGKiDvyUmL4F68L2E,146
5
5
  accrete/config.py,sha256=eJUbvyBO3DvAD6xkVKjTAzlXy7V7EK9bVyb91girfUs,299
6
- accrete/forms.py,sha256=2LobDn8EjHIaqBcgE4_Xk-nbfN6CPQasey-l22cQcT0,10740
7
- accrete/managers.py,sha256=EvWURMnGadnH3d-wXwv0-sLJKD6uGY03tSCiRSGsKbo,1170
6
+ accrete/forms.py,sha256=2vUh80qNvPDD8Zl3agKBSJEQeY7bXVLOx_SAB34wf8E,1359
7
+ accrete/managers.py,sha256=CaIJLeBry4NYIXaVUrdUjp7zx4sEWgv-1-ssI1m-EOs,1156
8
8
  accrete/middleware.py,sha256=bUsvhdVdUlbqB-Hd5Y5w6WL8rO8It1VSGA5EZaEPA3o,3266
9
9
  accrete/models.py,sha256=grvRNXg0ZYAJU3KAIX-svuZXeXlfqP4qEJ00nlbV594,5145
10
10
  accrete/tenant.py,sha256=g3ZuTrQr2zqmIopNBRQeCmHEK2R3dlUme_hOV765J6U,1778
@@ -32,12 +32,12 @@ accrete/contrib/system_mail/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2
32
32
  accrete/contrib/system_mail/views.py,sha256=xc1IQHrsij7j33TUbo-_oewy3vs03pw_etpBWaMYJl0,63
33
33
  accrete/contrib/system_mail/migrations/0001_initial.py,sha256=6cwkkRXGjXvwXoMjjgmWmcPyXSTlUbhW1vMiHObk9MQ,1074
34
34
  accrete/contrib/system_mail/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- accrete/contrib/ui/__init__.py,sha256=gnU3YLT6kHCxrUm1c-Bsvh8-hpdLGbGYxdNGcNGsXlg,477
35
+ accrete/contrib/ui/__init__.py,sha256=aeRSerct2JWpztNoxWDZXi7FzJhfxptVMVAZl4Sdzqs,504
36
36
  accrete/contrib/ui/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,63
37
37
  accrete/contrib/ui/apps.py,sha256=E0ao2ox6PQ3ldfeR17FXJUUJuGiWjm2DPCxHbPXGzls,152
38
- accrete/contrib/ui/context.py,sha256=Nyrv9EqdadUxZVGw2Eq--Rmpc6TtqvxJGBVR3pDp6bA,7080
39
- accrete/contrib/ui/elements.py,sha256=mxhNC-29YqkPei4bQB6fHkBuOc-mv5WWN7JiUJ_ys0o,1856
40
- accrete/contrib/ui/filter.py,sha256=PnBDVwl2iV3xBg5cphYiib5dUx-0wKHGtwS-FaBDbkA,12188
38
+ accrete/contrib/ui/context.py,sha256=jVD7w9QIIA2qh04UrO9rYDDrY-0Osr6FLggMlGxvztI,8364
39
+ accrete/contrib/ui/elements.py,sha256=0F5q0-XLdAWQjdYLMQt6RXqz4xPD_ECrhs0kcBfYkvo,1856
40
+ accrete/contrib/ui/filter.py,sha256=L7sBpmk454kaSZIQXe9hNj1Xbna8hJ2P-YTvmM7T5FE,12243
41
41
  accrete/contrib/ui/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
42
42
  accrete/contrib/ui/urls.py,sha256=TUBlz_CGs9InTZoxM78GSnucA73I8knoh_obt12RUHM,186
43
43
  accrete/contrib/ui/views.py,sha256=WpBKMsxFFG8eG4IN7TW_TPE6i3OFF7gnLDTK7JMKti8,191
@@ -135,9 +135,9 @@ accrete/contrib/ui/static/bulma/versions/bulma-no-dark-mode.scss,sha256=w6Q80mCV
135
135
  accrete/contrib/ui/static/bulma/versions/bulma-no-helpers-prefixed.scss,sha256=6HCUc4hxyaj4_rHqUnoy7aMuFZECHDYh5jvp-1CEtfE,344
136
136
  accrete/contrib/ui/static/bulma/versions/bulma-no-helpers.scss,sha256=5dzAXSgReWUO8GXLbYTpOclPPD0xqvvBiCCX_GOR_5U,313
137
137
  accrete/contrib/ui/static/bulma/versions/bulma-prefixed.scss,sha256=Yj7oEO00jy_G_L32y6rwzp2P5p2YtQ2Pvq4aZhvBSE8,138
138
- accrete/contrib/ui/static/css/accrete.css,sha256=gly-wbcFOb6Hp_W3Mn1Cqs3e-0x8c1YeOVrBg79ez6I,604755
139
- accrete/contrib/ui/static/css/accrete.css.map,sha256=eo0HelugPMu2qS1b3iC6LA-gLB7b7TwqtmqMt8rYTyU,94336
140
- accrete/contrib/ui/static/css/accrete.scss,sha256=gZnT9Quqku1IZ8zVTPCNs9WJcGjm2QsIJF1QDBFowJ0,9959
138
+ accrete/contrib/ui/static/css/accrete.css,sha256=pgbEKJKk3C0KXn_Vn43V90DGSC-J3Geg-imyTNuT4jk,604948
139
+ accrete/contrib/ui/static/css/accrete.css.map,sha256=g0TsYsK_LT_sa79uPNMFbzAyiUkaPwrUTw4jrE-g0Cg,94375
140
+ accrete/contrib/ui/static/css/accrete.scss,sha256=UHa62b7J14bEoiKYAmxm1dMlnb0lnpLDXWNbKT8kxX0,10385
141
141
  accrete/contrib/ui/static/css/fa.css,sha256=wiz7ZSCn_btzhjKDQBms9Hx4sSeUYsDrTLg7roPstac,102641
142
142
  accrete/contrib/ui/static/css/icons.css,sha256=5550KHsaayeEtRaUdf0h7esQhyec-_5ZfecZ_sOC6v0,6334
143
143
  accrete/contrib/ui/static/icons/Logo.svg,sha256=hGZuxrAa-LRpFavFiF8Lnc7X9OQcqmb6Xl_dxx-27hM,1861
@@ -160,15 +160,16 @@ accrete/contrib/ui/templates/django/forms/widgets/input.html,sha256=CRu81kTsbPwi
160
160
  accrete/contrib/ui/templates/django/forms/widgets/select.html,sha256=jT_UnHizHfdWTdJoSxjcIqTiR7NbVBA4bBSvkuDPRtw,394
161
161
  accrete/contrib/ui/templates/django/forms/widgets/text.html,sha256=MSmLlQc7PsPoDLVtTOOiWNprrsPriNr712yFxaHyDIo,47
162
162
  accrete/contrib/ui/templates/django/forms/widgets/textarea.html,sha256=c9BTedqb3IkXLyVYd0p9pR8DFnsXCNGoxVBWZTk_Fic,278
163
- accrete/contrib/ui/templates/ui/detail.html,sha256=23daOnFjl1rx32zJJ7MtZtRur1Z4-KeHNrbpNPJyliU,5181
164
- accrete/contrib/ui/templates/ui/form.html,sha256=R5digymvYfefbT4QmwzMzWm41GS-JA3CZU6zDTDWr9k,915
165
- accrete/contrib/ui/templates/ui/layout.html,sha256=yZ4ItF8fXah76EltFsb_vb5RpcCPkx_4gc-RBp_ShMM,14587
163
+ accrete/contrib/ui/templates/ui/dashboard.html,sha256=udnwiSJEcn2wMaJfTs4P0Y20FU79VguK_9Lq4K2BqtM,160
164
+ accrete/contrib/ui/templates/ui/detail.html,sha256=b1HC1QCGooo6tLh7fLhEDPau1tIOzscLXP6R_PN758I,1093
165
+ accrete/contrib/ui/templates/ui/form.html,sha256=cd6VUNzfIZH2_iudQa_EkqsPaBjyTam4Fm-Kgh8YpkY,655
166
+ accrete/contrib/ui/templates/ui/layout.html,sha256=f8K0KDlh_eDz0UKH0IB34dBjPtDiNgsuPgysK-DYLgE,14792
166
167
  accrete/contrib/ui/templates/ui/list.html,sha256=6jmChhCpj6iuSqAG7X_eD5ZjVvwU4cyynmvoH0-juTk,1740
167
168
  accrete/contrib/ui/templates/ui/table.html,sha256=bdPN2F7e7i3FHcQ18e0HJKkYT64PpxPRALRjcirJKGQ,4243
168
- accrete/contrib/ui/templates/ui/partials/filter.html,sha256=ypmjni56II9s1j3PuxDutuieR7wU-NFCZlSnSfWPGc4,7212
169
- accrete/contrib/ui/templates/ui/partials/form_errors.html,sha256=1_TQvTdiejsn-43YSyp2YfnP52P-MFYb-HGY0DLm4oA,991
169
+ accrete/contrib/ui/templates/ui/partials/filter.html,sha256=2vmeL3980rMmkRnmVtZh9mBHe6S0PTMjaGIN1J6SpNM,7184
170
+ accrete/contrib/ui/templates/ui/partials/form_errors.html,sha256=QjfRriD9Z5SlhIFX__nVXqB3rPg4icbG4G02kML8IcQ,1529
170
171
  accrete/contrib/ui/templates/ui/partials/form_modal.html,sha256=TQVkLypx8y1inZbvUYKtSNHrDXJM1xvYl8IsDDwQwkE,1093
171
- accrete/contrib/ui/templates/ui/partials/header.html,sha256=8pGO-0eO44BPz12sDWq2GOfri8JOh1cMs39eramXLr8,6921
172
+ accrete/contrib/ui/templates/ui/partials/header.html,sha256=5ER9E6c-vwDxuIuMSXL4F_fq2zYY17R_0A0Ao--H4Us,6916
172
173
  accrete/contrib/ui/templates/ui/partials/onchange_form.html,sha256=K5twTGqRUW1iM2dGtdWntjsJvJVo5EIzKxX2HK-H1yw,160
173
174
  accrete/contrib/ui/templates/ui/partials/pagination_detail.html,sha256=58nA3X7Il0FAD4VcYyr7tTGWRiVf_FN1TkImmKEpKHU,1014
174
175
  accrete/contrib/ui/templates/ui/partials/pagination_list.html,sha256=Eyx1lsk9UIFFYPICL7RuYeUFaKVlmvWVXnFCGR-II7k,1324
@@ -213,12 +214,12 @@ accrete/contrib/user_registration/templates/user_registration/mail_templates/con
213
214
  accrete/migrations/0001_initial.py,sha256=azThbc8otEhxJwo8BIgOt5eC30mxXhKJLBAazZFe3BA,4166
214
215
  accrete/migrations/0002_initial.py,sha256=dFOM7kdHlx7pVAh8cTDlZMtciN4O9Z547HAzEKnygZE,1628
215
216
  accrete/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
216
- accrete/utils/__init__.py,sha256=BGXx2NwF4Sc6uaenKNvPZEmHuHVY1u97PPECrd1seA8,129
217
+ accrete/utils/__init__.py,sha256=YwEzwjz-E92LHhqeLsQ4167zXHHY1PFG6xcsAofnmBU,192
217
218
  accrete/utils/dates.py,sha256=apM6kt6JhGrKgoT0jfav1W-8AUVTxNc9xt3fJQ2n0JI,1492
218
- accrete/utils/forms.py,sha256=FHR9IP76dkULCtX4H68xbQG5uvkBQZnDEXSlHyz34iU,606
219
- accrete/utils/http.py,sha256=dR4p-Q8xoTlrjfx0sN--vkY4ZGtYUsqqquAfPNxsXx8,3249
219
+ accrete/utils/forms.py,sha256=UP6vCCTtXD5MqU2LWbNXtk2ZMMEmoty_tjLCbJlqYsY,2984
220
+ accrete/utils/http.py,sha256=mAtQRgADv7zu1_j7A-EKVyb-oqa5a21i4Gd0QfjzGV0,3540
220
221
  accrete/utils/models.py,sha256=EEhv7-sQVtQD24PEb3XcDUAh3VVhVFoMMLyFrDjGEaI,706
221
- accrete-0.0.54.dist-info/METADATA,sha256=Ko3L--FycMyXqzIrefGNJnrK_vVfvTn-LCiFo3Di-LM,4892
222
- accrete-0.0.54.dist-info/WHEEL,sha256=xl5aZkiJYVTjhVaiADvIe6UeUVylGNomrxKZ0Zda1CE,87
223
- accrete-0.0.54.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
224
- accrete-0.0.54.dist-info/RECORD,,
222
+ accrete-0.0.56.dist-info/METADATA,sha256=B_wB5SK-frLdSHpern7BWrGfaHDlycd0A34UGnkXFY0,4892
223
+ accrete-0.0.56.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
224
+ accrete-0.0.56.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
225
+ accrete-0.0.56.dist-info/RECORD,,