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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
# from tinymce.widgets import TinyMCE
import re
import django.forms as forms
from django.core.exceptions import ValidationError
from django.forms import ModelForm
from django.forms.models import modelformset_factory
from troggle.core.models.caves import Cave, CaveAndEntrance, Entrance
from troggle.core.views.editor_helpers import HTMLarea
"""These are all the class-based Forms used by troggle.
There are other, simpler, upload forms in view/uploads.py
class-based forms are quicker to set up (for Django experts) but
are more difficult to maintain (or even begin to understand) by non-Django experts.
Notes to self, as I try to work out what the hell is going on:
Note that HTMLarea invokes a widget which sets a CSS class which calls javascript in
templates/html_editor_scripts_css.html - which imports jquery and codemirror directly, without
declaring it anywhere or locally installing it. (!)
Django handles three distinct parts of the work involved in forms:
- preparing and restructuring data to make it ready for rendering
- creating HTML forms for the data
- receiving and processing submitted forms and data from the client
It is possible to write code that does all of this manually, but Django can take care of it all for you.
READ https://docs.djangoproject.com/en/5.1/topics/forms/ and thoroughly digest it, also:
https://pythontimes.com/django-forms-deep-dive-advanced-techniques-for-form-handling/
https://docs.djangoproject.com/en/5.1/ref/forms/models/
https://stackoverflow.com/questions/53035151/django-formset-factory-vs-modelformset-factory-vs-inlineformset-factory
https://micropyramid.com/blog/understanding-djangos-model-formsets-in-detail-and-their-advanced-usage
https://www.geeksforgeeks.org/django-modelformsets/
https://www.codeunderscored.com/model-formsets-in-django/
https://django-formset.fly.dev/styling/
"""
todo = """
"""
class CaveForm(ModelForm):
"""Only those fields for which we want to override defaults are listed here
the other fields of the class Cave are present on the form, but use the default presentation style
Extra fields, not in the model Cave, are also created here, e.g. who_are_you
see https://docs.djangoproject.com/en/5.1/topics/forms/modelforms/
"""
unofficial_number= forms.CharField(required=False,
label="Unofficial Number used to construct internal identifiers",
widget=forms.TextInput(
attrs={"size": "45", "placeholder": "2035-ZB-03"}))
official_name = forms.CharField(required=False,
label="Name:",widget=forms.TextInput(
attrs={"size": "45", "placeholder": "ideally official name in German, but any name is OK"}))
underground_description = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter page content (using HTML)"}),
)
explorers = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter page content (using HTML)"}),
)
equipment = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter page content (using HTML)"}),
)
survey = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter page content (using HTML)"}),
)
# survey = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 10}))
# kataster_status = forms.CharField(required=False,
# label = "Kataster status, see below",
# widget=forms.TextInput(attrs={"placeholder": "see example below"})
# )
kataster_code = forms.CharField(required=False,
label = "Kataster code, see explanation at bottom of page",
widget=forms.TextInput(attrs={"placeholder": "e.g. 2/S= See below"})
)
# underground_centre_line = forms.CharField(
# required=False,
# widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter page content (using HTML)"}),
# )
notes = forms.CharField(
required=False,
label = "Notes, e.g. progress on issuing kataster no.",
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter page content (using HTML)"}),
)
references = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter page content (using HTML)"}),
)
description_file = forms.CharField(required=False, label="Path of top-level description file for this cave, when a separate file is used. Otherwise blank.", widget=forms.TextInput(attrs={"size": "45","placeholder": "usually blank"}), help_text="")
survex_file = forms.CharField(
required=False, label="Survex file eg. caves-1623/000/000.svx", widget=forms.TextInput(attrs={"size": "45"})
)
length = forms.CharField(required=False, label="Length (m)", widget=forms.TextInput(attrs={"placeholder": "usually blank"}))
depth = forms.CharField(required=False, label="Depth (m)", widget=forms.TextInput(attrs={"placeholder": "usually blank"}))
extent = forms.CharField(required=False, label="Extent (m)", widget=forms.TextInput(attrs={"placeholder": "usually blank"}))
areacode = forms.CharField(required=False, label="Area code", widget=forms.TextInput(attrs={"placeholder": "e.g. 1623"}))
subarea = forms.CharField(required=False, label="Subarea (do not use for new caves)", widget=forms.TextInput(attrs={"placeholder": "usually blank, archaic"}))
#cave_slug = forms.CharField()
identified_login = forms.BooleanField(required=False,widget=forms.CheckboxInput(attrs={"onclick":"return false"})) # makes it readonly
who_are_you = forms.CharField(
widget=forms.TextInput(
attrs={"size": 100, "placeholder": "You are editing this page, who are you ? e.g. 'Becka' or 'Animal <mta@gasthof.expo>'",
"style": "vertical-align: text-top;"}
)
)
class Meta:
model = Cave
exclude = ("filename", "url", "underground_centre_line", "kataster_status")
field_order = ['unofficial_number', 'kataster_number', 'official_name', 'underground_description', 'survey',
'underground_centre_line', 'explorers', 'equipment', 'notes', 'references', 'description_file', 'survex_file',
'areacode', 'subarea', 'length', 'depth', 'extent',
'kataster_code', 'kataster_status', 'fully_explored', 'non_public', 'identified_login', 'who_are_you']
def clean_cave_slug(self):
if self.cleaned_data["cave_slug"] == "":
myArea = self.cleaned_data["areacode"]
if self.data["kataster_number"]:
cave_slug = f"{myArea}-{self.cleaned_data['kataster_number']}"
else:
cave_slug = f"{myArea}-{self.cleaned_data['unofficial_number']}"
else:
cave_slug = self.cleaned_data["cave_slug"]
# Converting a PENDING cave to a real cave by saving this form
print("EEE", cave_slug.replace("-PENDING-", "-"))
return cave_slug.replace("-PENDING-", "-")
def clean(self):
cleaned_data = super(CaveForm, self).clean() # where is this code hidden? How does this work??
if self.data.get("kataster_number") == "" and self.data.get("unofficial_number") == "":
self._errors["unofficial_number"] = self.error_class(
["Either the kataster or unoffical number is required."]
)
# if self.cleaned_data.get("kataster_number") != "" and self.cleaned_data.get("official_name") == "":
# self._errors["official_name"] = self.error_class(["This field is required when there is a kataster number."])
# if cleaned_data.get("url") == []:
# self._errors["url"] = self.error_class(["This field is required."])
# if cleaned_data.get("url") and cleaned_data.get("url").startswith("/"):
# self._errors["url"] = self.error_class(["This field cannot start with a /."])
return cleaned_data
class EntranceForm(ModelForm):
"""Only those fields for which we want to override defaults are listed here
the other fields are present on the form, but use the default presentation style
see https://docs.djangoproject.com/en/5.1/topics/forms/modelforms/
"""
name = forms.CharField(required=False, widget=forms.TextInput(attrs={"size": "45", "placeholder": "usually leave this blank"}))
entrance_description = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter text (using HTML)"}),
)
explorers = forms.CharField(required=False, widget=forms.TextInput(attrs={"size": "45"}))
# explorers = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 10}))
# map_description = forms.CharField(
# label="Map (is this used?)",
# required=False,
# widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter text (using HTML)"}),
# )
location_description = forms.CharField(
label="Location",
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter text (using HTML)"}),
)
lastvisit = forms.CharField(
required=False, widget=forms.TextInput(attrs={"size": "10"}), label="Last visit date, e.g. 2023-07-11"
)
approach = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter text (using HTML)"}),
)
underground_description = forms.CharField(
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter text (using HTML)"}),
)
photo = forms.CharField(
label="Photos (use 'image' button)",
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Use button on right to add HTML link"}),
)
marking_comment = forms.CharField(
label="Marking text",
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter exact tag text, e.g. 'CUCC 2035 ZB-03'"}),
)
findability_description = forms.CharField(
required=False,
label="How to find it",
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Enter text (using HTML)"}),
)
other_description = forms.CharField(
label="Other comments",
required=False,
widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Usually blank"}),
)
# bearings = forms.CharField(
# label="Bearings (obsolete)",
# required=False,
# widget=HTMLarea(attrs={"height": "80%", "rows": 20, "placeholder": "Usually blank"}),
# )
tag_station = forms.CharField(
required=False,
widget=forms.TextInput(attrs={"size": "50","placeholder": "e.g. 1623.t2035-zb-03a"}),
label="Tag station: Survex station id, e.g. 1623.p2023-aa-01"
)
other_station = forms.CharField(
required=False,
widget=forms.TextInput(attrs={"size": "50","placeholder": "e.g. 1623.p2035-zb-03c"}),
label="Other station: Survex station id, e.g. 1623.gps2018-aa-01"
)
lat_wgs84 = forms.CharField(
required=False, widget=forms.TextInput(attrs={"size": "10","placeholder": "e.g. 47.123456"}),
label="Latitude (WSG84) - if no other location"
)
long_wgs84 = forms.CharField(
required=False, widget=forms.TextInput(attrs={"size": "10","placeholder": "e.g. 13.123456"}),
label="Longitude (WSG84) - if no other location"
)
alt = forms.CharField(required=False, label="Altitude (m) - from GPS if you have it, but let it settle.")
# url = forms.CharField(required=False, label="URL [usually blank]", widget=forms.TextInput(attrs={"size": "45"}))
identified_login = forms.BooleanField(required=False,widget=forms.CheckboxInput(attrs={"onclick":"return false"})) # makes it readonly
who_are_you = forms.CharField(
widget=forms.TextInput(
attrs={"size": 100, "placeholder": "You are editing this page, who are you ? e.g. 'Becka' or 'Animal <mta@gasthof.expo>'",
"style": "vertical-align: text-top;"}
)
)
field_order = ['name', 'entrance_description', 'explorers', 'map_description', 'location_description', 'lastvisit', 'non_public',
'findability', 'marking', 'approach', 'underground_description', 'photo', 'marking_comment', 'findability_description', 'other_description',
'bearings', 'tag_station', 'other_station', 'easting', 'northing', 'lat_wgs84', 'long_wgs84', 'alt', 'identified_login', 'who_are_you']
class Meta:
model = Entrance
exclude = (
"cached_primary_slug",
"map_description", # No entrance has any data on this field, so it is being retired.
"filename",
"slug",
"bearings"
)
def clean(self):
# if self.cleaned_data.get("url"): # can remove this as the form does not have a url field any more, which was never used anyway
# if self.cleaned_data.get("url").startswith("/"):
# self._errors["url"] = self.error_class(["This field cannot start with a /."])
return self.cleaned_data
# # This next line is sufficient to create an entire entry for for the cave fields automatically
# # for forms which map directly onto a Django Model
# CaveAndEntranceFormSet = modelformset_factory(CaveAndEntrance, exclude=("cave",))
# # This is used only in templates/editcave.html which is called only to edit caves in core/views/cave.py
class EntranceLetterForm(ModelForm):
"""Form to link entrances to caves, along with an entrance number.
NOTE. The relationship between caves and entrances was originally designed to be a many to many relationship.
With entrances gaining new caves and letters when caves are joined.
However, so far as I can see, this was never actually done in practice on Expo and each Entrance belongs
to only one Cave.
see https://docs.djangoproject.com/en/5.1/topics/forms/modelforms/
To be re-written when we move the 'letter' field into Entrance
"""
# This only needs to be required=True for the second and subsequent entrances, not the first. Tricky.
entranceletter = forms.CharField(required=False, widget=forms.TextInput(attrs={"size": "2"}))
class Meta:
model = CaveAndEntrance
exclude = ("cave", "entrance")
def full_clean(self):
super(EntranceLetterForm, self).full_clean()
try:
self.instance.validate_unique()
except forms.ValidationError as e:
self._update_errors(e)
|