1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
import django.forms
import django.forms.widgets
from django.core.validators import MinValueValidator, MaxValueValidator
from django.conf import settings
from postgresqleu.util.forms import ConcurrentProtectedModelForm
from postgresqleu.util.widgets import HtmlDateInput
import copy
import datetime
from functools import reduce
class _NewFormDataField(django.forms.Field):
required = True
widget = django.forms.HiddenInput
class BackendForm(ConcurrentProtectedModelForm):
list_fields = None
list_order_by = None
queryset_select_related = []
queryset_extra_fields = {} # Goes into queryset.extra()
queryset_extra_columns = [] # Just columns included in .only()
selectize_multiple_fields = None
selectize_taglist_fields = None
json_fields = None
json_form_fields = None
markdown_fields = []
dynamic_preview_fields = []
vat_fields = {}
verbose_field_names = {}
exclude_date_validators = []
extra_update_fields = []
form_before_new = None
newformdata = None
_newformdata = _NewFormDataField()
allow_copy_previous = False
copy_transform_form = None
coltypes = {}
filtercolumns = {}
defaultsort = []
readonly_fields = []
nosave_fields = []
linked_objects = {}
auto_cascade_delete_to = []
fieldsets = []
allow_email = False
force_insert = False
verbose_name = None
verbose_name_plural = None
helplink = None
formnote = ''
extrabuttons = []
def __init__(self, request, conference, *args, **kwargs):
self.request = request
self.conference = conference
if 'newformdata' in kwargs:
self.newformdata = kwargs['newformdata']
del kwargs['newformdata']
super(BackendForm, self).__init__(*args, **kwargs)
if hasattr(self.Meta, 'fieldsets'):
# If fieldsets are specified in the Meta model, copy them to our instance, so we can go ahead
# and modify them.
# If they are not in the Meta model, we rely on the one that's in the base model.
self.fieldsets = copy.deepcopy(self.Meta.fieldsets)
if self.newformdata:
self.fields['_newformdata'].initial = self.newformdata
else:
del self.fields['_newformdata']
if self.json_form_fields:
for fn, ffields in self.json_form_fields.items():
if getattr(self.instance, fn):
for fld in ffields:
self.initial[fld] = getattr(self.instance, fn).get(fld, None)
self.update_protected_fields()
self.fix_fields()
self.fix_selectize_fields(**kwargs)
# Runtime validate fieldsets. It's ugly as fsck to do this at runtime,
# but meh, this isn't used that often so...
if self.fieldsets:
all_fields = set([f for f in self.fields if f not in ('_validator', '_newformdata')])
all_fieldsetted_fields = set(reduce(lambda x, y: x + y, [v['fields'] for v in self.fieldsets]))
missing = all_fields.difference(all_fieldsetted_fields)
if missing:
raise Exception("ERROR: fields %s are not in a fieldset (fieldsets have %s)" % (", ".join(missing), ", ".join(all_fieldsetted_fields)))
for k, v in list(self.fields.items()):
# Adjust widgets
if isinstance(v, django.forms.fields.DateField):
v.widget = HtmlDateInput()
# Add CSS classes and help texts to datetime ranges
if isinstance(v, django.contrib.postgres.forms.ranges.DateTimeRangeField):
v.widget.attrs['class'] = 'dtrangefield'
h = getattr(v, 'help_text', '')
if h:
h += ' '
if v.required:
h += 'Enter start and stop timestamp with both date and time.'
else:
h += 'Enter start and stop timestamp with both date and time. Leaving either field empty menas "infinity".'
v.help_text = h
# Any datetime or date fields that are not explicitly excluded will be forced to be within
# the conference dates.
if self.conference:
if isinstance(v, django.forms.fields.DateTimeField) and k not in self.exclude_date_validators:
v.validators.extend([
MinValueValidator(conference.localize_datetime(datetime.datetime.combine(self.conference.startdate, datetime.time(0, 0, 0)))),
MaxValueValidator(conference.localize_datetime(datetime.datetime.combine(self.conference.enddate + datetime.timedelta(days=1), datetime.time(0, 0, 0)))),
])
elif isinstance(v, django.forms.fields.DateField) and k not in self.exclude_date_validators:
v.validators.extend([
MinValueValidator(self.conference.startdate),
MaxValueValidator(self.conference.enddate),
])
if isinstance(v, (django.forms.fields.DateTimeField, django.forms.fields.DateField, django.forms.fields.TimeField, django.contrib.postgres.forms.ranges.DateTimeRangeField)):
t = getattr(v, 'help_text', '')
if t:
if not t.endswith('.'):
t = t + '.'
t = t + ' '
v.help_text = t + 'Time specified in timezone {}.'.format(self.conference.tzname if self.conference else settings.TIME_ZONE)
for field, vattype in list(self.vat_fields.items()):
self.fields[field].widget.attrs['class'] = 'backend-vat-field backend-vat-{0}-field'.format(vattype)
for field in self.readonly_fields:
self.fields[field].widget.attrs['readonly'] = 'true'
def fix_selectize_fields(self, **kwargs):
if not self.selectize_multiple_fields:
return
for field, lookup in list(self.selectize_multiple_fields.items()):
# If this is a postback of a selectize field, it may contain ids that are not currently
# stored in the field. They must still be among the *allowed* values of course, which
# are handled by the existing queryset on the field.
if self.instance.pk:
# If this object isn't created yet, then it by definition has no related
# objects, so just bypass the collection of values since it will cause
# errors.
vals = [o.pk for o in getattr(self.instance, field).all()]
else:
vals = []
if 'data' in kwargs and str(field) in kwargs['data']:
vals.extend([x for x in kwargs['data'].getlist(field)])
self.fields[field].widget.attrs['data-selecturl'] = lookup.url
self.fields[field].queryset = self.fields[field].queryset.filter(pk__in=set(vals))
self.fields[field].label_from_instance = lookup.label_from_instance
def remove_field(self, fieldname):
# Remove base field
del self.fields[fieldname]
# And then remove any references in a fieldset
for fs in self.fieldsets:
if fieldname in fs['fields']:
fs['fields'].remove(fieldname)
def fix_fields(self):
pass
def post_save(self):
pass
@classmethod
def get_column_filters(cls, conference):
return {}
@classmethod
def get_assignable_columns(cls, conference):
return {}
@classmethod
def assign_assignable_column(cls, obj, what, setval):
setattr(obj, what, setval)
def pre_create_item(self):
pass
@property
def get_json_merge_data(self):
pass
@classmethod
def get_initial(self):
return {}
@classmethod
def get_rowclass_and_title(self, obj, cache):
return None, None
@classmethod
def get_field_verbose_name(self, f):
if f in self.verbose_field_names:
return self.verbose_field_names[f]
return self.Meta.model._meta.get_field(f).verbose_name.capitalize()
@classmethod
def _verbose_name(self):
return self.verbose_name or self.Meta.model._meta.verbose_name
@classmethod
def _verbose_name_plural(self):
return self.verbose_name_plural or self.Meta.model._meta.verbose_name_plural
@classmethod
def numeric_defaultsort(cls):
return [[cls.list_fields.index(fn), d] for fn, d in cls.defaultsort]
@property
def validator_field(self):
return self['_validator']
@property
def newformdata_field(self):
if '_newformdata' in self.fields:
return self['_newformdata']
def get(self, name, default=None):
# Implement the get operator, for template functions to get a field
return self[name]
class BackendBeforeNewForm(django.forms.Form):
def __init__(self, conference, *args, **kwargs):
self.conference = conference
super().__init__(*args, **kwargs)
|