diff --git a/core/forms.py b/core/forms.py index f3df10a..8365a63 100644 --- a/core/forms.py +++ b/core/forms.py @@ -6,7 +6,7 @@ from django.forms import ModelForm from django.forms.models import modelformset_factory from django.contrib.admin.widgets import AdminDateWidget -#from tinymce.widgets import TinyMCE +from tinymce.widgets import TinyMCE from troggle.core.models.troggle import Person, PersonExpedition, Expedition from troggle.core.models.caves import Cave, LogbookEntry, QM, Entrance, CaveAndEntrance @@ -25,6 +25,7 @@ class CaveForm(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 ''' + official_name = forms.CharField(required = False, widget=forms.TextInput(attrs={'size': '45'})) underground_description = forms.CharField(required = False, widget=HTMLarea( attrs={"height":"80%", "rows":20, 'placeholder': "Enter page content (using HTML)"})) @@ -62,6 +63,123 @@ class CaveForm(ModelForm): if self.cleaned_data.get("url") and self.cleaned_data.get("url").startswith("/"): self._errors["url"] = self.error_class(["This field cannot start with a /."]) return self.cleaned_data + + +class CaveFormTextArea(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 + ''' + + official_name = forms.CharField(required = False, widget=forms.TextInput(attrs={'size': '45'})) + underground_description = forms.CharField(required = False, widget=forms.Textarea(attrs={'rows':9})) + explorers = forms.CharField(required = False, widget=forms.Textarea(attrs={'rows':9})) + equipment = forms.CharField(required = False, widget=forms.Textarea(attrs={'rows':9})) + survey = forms.CharField(required = False, widget=forms.Textarea(attrs={'rows':9})) + #survey = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 10})) + kataster_status = forms.CharField(required = False) + underground_centre_line = forms.CharField(required = False, widget=forms.Textarea(attrs={'rows':9})) + notes = forms.CharField(required = False, widget=forms.Textarea(attrs={'rows':9})) + references = forms.CharField(required = False, widget=forms.Textarea(attrs={'rows':9})) + description_file = forms.CharField(required = False, widget=forms.TextInput(attrs={'size': '45'})) + survex_file = forms.CharField(required = False, label="Survex file [caves-1623/000/000.svx]", widget=forms.TextInput(attrs={'size': '45'})) + url = forms.CharField(required = True, label="URL [1623/000/000]", widget=forms.TextInput(attrs={'size': '45'})) + length = forms.CharField(required = False, label="Length (m)") + depth = forms.CharField(required = False, label="Depth (m)") + extent = forms.CharField(required = False, label="Extent (m)") + class Meta: + model = Cave + exclude = ("filename",) + + def clean(self): + if self.cleaned_data.get("kataster_number") == "" and self.cleaned_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 self.cleaned_data.get("area") == []: + self._errors["area"] = self.error_class(["This field is required."]) + if self.cleaned_data.get("url") and self.cleaned_data.get("url").startswith("/"): + self._errors["url"] = self.error_class(["This field cannot start with a /."]) + return self.cleaned_data + + +class CaveFormCodeMirrorPreview(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 + ''' + + official_name = forms.CharField(required = False, widget=forms.TextInput(attrs={'size': '45'})) + underground_description = forms.CharField(required = False, widget=HTMLarea(preview = True, + attrs={"height":"80%", "rows":20, 'placeholder': "Enter page content (using HTML)"})) + explorers = forms.CharField(required = False, widget=HTMLarea(preview = True, + attrs={"height":"80%", "rows":20, 'placeholder': "Enter page content (using HTML)"})) + equipment = forms.CharField(required = False, widget=HTMLarea(preview = True, + attrs={"height":"80%", "rows":20, 'placeholder': "Enter page content (using HTML)"})) + survey = forms.CharField(required = False, widget=HTMLarea(preview = True, + 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) + underground_centre_line = forms.CharField(required = False, widget=HTMLarea(preview = True, + attrs={"height":"80%", "rows":20, 'placeholder': "Enter page content (using HTML)"})) + notes = forms.CharField(required = False, widget=HTMLarea(preview = True, + attrs={"height":"80%", "rows":20, 'placeholder': "Enter page content (using HTML)"})) + references = forms.CharField(required = False, widget=HTMLarea(preview = True, + attrs={"height":"80%", "rows":20, 'placeholder': "Enter page content (using HTML)"})) + description_file = forms.CharField(required = False, widget=forms.TextInput(attrs={'size': '45'})) + survex_file = forms.CharField(required = False, label="Survex file [caves-1623/000/000.svx]", widget=forms.TextInput(attrs={'size': '45'})) + url = forms.CharField(required = True, label="URL [1623/000/000]", widget=forms.TextInput(attrs={'size': '45'})) + length = forms.CharField(required = False, label="Length (m)") + depth = forms.CharField(required = False, label="Depth (m)") + extent = forms.CharField(required = False, label="Extent (m)") + class Meta: + model = Cave + exclude = ("filename",) + + def clean(self): + if self.cleaned_data.get("kataster_number") == "" and self.cleaned_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 self.cleaned_data.get("area") == []: + self._errors["area"] = self.error_class(["This field is required."]) + if self.cleaned_data.get("url") and self.cleaned_data.get("url").startswith("/"): + self._errors["url"] = self.error_class(["This field cannot start with a /."]) + return self.cleaned_data + +class CaveFormTinyMCE(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 + ''' + + official_name = forms.CharField(required = False, widget=forms.TextInput(attrs={'size': '45'})) + underground_description = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 30})) + explorers = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 30})) + equipment = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 30})) + survey = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 30})) + #survey = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 10})) + kataster_status = forms.CharField(required = False) + underground_centre_line = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 30})) + notes = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 30})) + references = forms.CharField(required = False, widget=TinyMCE(attrs={'cols': 80, 'rows': 30})) + description_file = forms.CharField(required = False, widget=forms.TextInput(attrs={'size': '45'})) + survex_file = forms.CharField(required = False, label="Survex file [caves-1623/000/000.svx]", widget=forms.TextInput(attrs={'size': '45'})) + url = forms.CharField(required = True, label="URL [1623/000/000]", widget=forms.TextInput(attrs={'size': '45'})) + length = forms.CharField(required = False, label="Length (m)") + depth = forms.CharField(required = False, label="Depth (m)") + extent = forms.CharField(required = False, label="Extent (m)") + class Meta: + model = Cave + exclude = ("filename",) + + def clean(self): + if self.cleaned_data.get("kataster_number") == "" and self.cleaned_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 self.cleaned_data.get("area") == []: + self._errors["area"] = self.error_class(["This field is required."]) + if self.cleaned_data.get("url") and self.cleaned_data.get("url").startswith("/"): + self._errors["url"] = self.error_class(["This field cannot start with a /."]) + return self.cleaned_data class EntranceForm(ModelForm): '''Only those fields for which we want to override defaults are listed here diff --git a/core/views/caves.py b/core/views/caves.py index 8df801b..bbc2750 100644 --- a/core/views/caves.py +++ b/core/views/caves.py @@ -18,7 +18,7 @@ import troggle.settings as settings from troggle.core.views import expo from troggle.core.models.troggle import Expedition, DataIssue from troggle.core.models.caves import CaveSlug, Cave, CaveAndEntrance, QM, EntranceSlug, Entrance, Area, SurvexStation, GetCaveLookup -from troggle.core.forms import CaveForm, CaveAndEntranceFormSet, EntranceForm, EntranceLetterForm +from troggle.core.forms import CaveForm, CaveAndEntranceFormSet, EntranceForm, EntranceLetterForm, CaveFormCodeMirrorPreview, CaveFormTextArea, CaveFormTinyMCE from .auth import login_required_if_public '''Manages the complex procedures to assemble a cave description out of the compnoents @@ -297,6 +297,42 @@ def caveEntrance(request, slug): else: return render(request,'cave_entrances.html', {'cave': cave}) +def test_edit_cave(request, editor = "codemirror"): + '''This is the form that edits all the cave data and writes out an XML file in the :expoweb: repo folder + The format for the file being saved is in templates/dataformat/cave.xml + + It does save the data into into the database directly, not by parsing the file. + It does NOT yet commit to the git repoSaving is not allowed + ''' + form_type = {"codemirror": CaveForm, "codemirrorpreview": CaveFormCodeMirrorPreview, "textarea": CaveFormTextArea, "tinymce": CaveFormTinyMCE}[editor] + + message = "" + try: + cave = Cave.objects.get(caveslug__slug = "1623-264") + except: + return render(request,'errors/badslug.html') + if request.POST: + form = form_type(request.POST, instance=cave) + ceFormSet = CaveAndEntranceFormSet(request.POST) + #versionControlForm = VersionControlCommentForm(request.POST) + if form.is_valid() and ceFormSet.is_valid(): + pass + else: + message = f'! POST data is INVALID {cave}' + print(message) + else: + form = form_type(instance=cave) + ceFormSet = CaveAndEntranceFormSet(queryset=cave.caveandentrance_set.all()) + #versionControlForm = VersionControlCommentForm() + + return render(request, + 'editcave.html', + {'form': form, 'cave': cave, 'message': message, + 'caveAndEntranceFormSet': ceFormSet, + 'editor': editor, + #'versionControlForm': versionControlForm + }) + @login_required_if_public def edit_cave(request, slug=None): '''This is the form that edits all the cave data and writes out an XML file in the :expoweb: repo folder @@ -305,6 +341,7 @@ def edit_cave(request, slug=None): It does save the data into into the database directly, not by parsing the file. It does NOT yet commit to the git repo ''' + message = "" if slug is not None: try: diff --git a/requirements.txt b/requirements.txt index b05fda2..da33a88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ reportlab==3.6.0 sqlparse==0.4.0 typing_extensions==4.2.0 Unidecode==1.3.0 +django-tinymce diff --git a/settings.py b/settings.py index bcb972e..05947c0 100644 --- a/settings.py +++ b/settings.py @@ -127,7 +127,26 @@ X_FRAME_OPTIONS = 'DENY' # changed to "DENY" after I eliminated all the iframes DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # from Django 3.2 +TINYMCE_JS_URL = 'https://cloud.tinymce.com/stable/tinymce.min.js' +TINYMCE_DEFAULT_CONFIG = { + "height": "320px", + "width": "960px", + "menubar": "file edit view insert format tools table help", + "plugins": "advlist autolink lists link image charmap print preview anchor searchreplace visualblocks code " + "fullscreen insertdatetime media table paste code help wordcount spellchecker", + "toolbar": "undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft " + "aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | forecolor " + "backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | " + "fullscreen preview save print | insertfile image media pageembed template link anchor codesample | " + "a11ycheck ltr rtl | showcomments addcomment code", + "custom_undo_redo_levels": 10, + "language": "es_ES", # To force a specific language instead of the Django current language. +} +TINYMCE_SPELLCHECKER = True +TINYMCE_COMPRESSOR = True + INSTALLED_APPS = ( + 'tinymce', 'django.contrib.admin', 'django.contrib.auth', # includes the url redirections for login, logout 'django.contrib.contenttypes', diff --git a/templates/editcave.html b/templates/editcave.html index 64c2f97..4897027 100644 --- a/templates/editcave.html +++ b/templates/editcave.html @@ -2,6 +2,17 @@ {% block title %}Edit Cave - {{cave.official_name|safe}} - {{cave.kataster_number}}{% endblock %} {% block extraheaders %} {% include 'html_editor_scripts_css.html' %} + + {% endblock %} {% block content %}

Edit Cave - {{cave.official_name|safe}} - {{cave.kataster_number}}

diff --git a/urls.py b/urls.py index 5f5a711..2b5b0b6 100644 --- a/urls.py +++ b/urls.py @@ -13,7 +13,7 @@ from troggle.core.views.drawings import dwgallfiles, dwgfilesingle from troggle.core.views.uploads import dwgupload, scanupload, photoupload from troggle.core.views.other import troggle404, frontpage, todos, controlpanel, frontpage from troggle.core.views.other import exportlogbook -from troggle.core.views.caves import ent, cavepage, caveindex, get_entrances, get_qms, edit_cave, cave3d, caveEntrance, edit_entrance, caveQMs, qm +from troggle.core.views.caves import ent, cavepage, caveindex, get_entrances, get_qms, edit_cave, cave3d, caveEntrance, edit_entrance, caveQMs, qm, test_edit_cave from troggle.core.views.logbooks import get_logbook_entries, logbookentry, logbookSearch from troggle.core.views.logbooks import notablepersons, person, get_people from troggle.core.views.logbooks import expedition, personexpedition, Expeditions_tsvListView, Expeditions_jsonListView @@ -76,6 +76,8 @@ else: # Some overlap with 'admin.site.urls' needs to be investigated. trogglepatterns = [ + path('tinymce/', include('tinymce.urls')), + path('expofiles/', include(expofilesurls)), # intercepted by Apache, if it is running. path('expofiles', include(expofilesurls)), # curious interaction with the include() here, not just a slash problem. @@ -135,6 +137,7 @@ trogglepatterns = [ re_path(r'^(?P\d\d\d\d)(?P.*)$', cavepage, name="cavepage"), # shorthand /1623/264 or 1623/161/top.htm # Note that urls eg '/1623/161/l/rl89a.htm' are handled by cavepage which redirects them to 'expopage' # Note that _edit$ for a cave description page in a subfolder e.g. /1623/204/204.html_edit gets caught here and breaks with 404 + re_path(r'^test_cave_edit/(?P.*)$', test_edit_cave, name="test_edit_cave"), # edit_cave needed by cave.html template for url matching # Entrances re_path(r'^cave/entrance/([^/]+)/?$', caveEntrance), # lists all entrances !!!BAD, local links fail