diff --git a/core/forms.py b/core/forms.py index f377361..f0c30bf 100644 --- a/core/forms.py +++ b/core/forms.py @@ -14,7 +14,9 @@ import re """These are all the class-based Forms used by troggle. There are other, simpler, upload forms in view/uploads.py -Some are not used and need renovating or destroying. + +class-based forms are quicker to set up (for Django experts) but +are more difficult to maintain by non-Django experts. """ todo = """ diff --git a/core/views/uploads.py b/core/views/uploads.py index a22da16..31250cb 100644 --- a/core/views/uploads.py +++ b/core/views/uploads.py @@ -3,7 +3,7 @@ from pathlib import Path from django import forms from django.core.files.storage import FileSystemStorage -from django.shortcuts import render +from django.shortcuts import render, redirect import settings from troggle.core.models.survex import DrawingFile @@ -13,6 +13,8 @@ from troggle.core.models.survex import DrawingFile from .auth import login_required_if_public """File upload 'views' +Note that there are other file upload forms in views/wallet_edit.py +and that core/forms.py contains Django class-based forms for caves and entrances. """ todo = """ @@ -25,12 +27,14 @@ todo = """ Need to validate it as being a valid GPX file using an XML parser, not a dubious script or hack - Validate Tunnel & Therion files using an XML parser in dwgupload(). Though Julian says - tunnel is only mostly correct XML + tunnel is only mostly correct XML, and it does fail at least one XML parser. - parse the uploaded drawing file for links to wallets and scan files as done in parsers/drawings.py - Enable folder creation in dwguploads or as a separate form + +- Enable file rename on expofiles, particularly in /surveyscans/ (aka wallets) """ class FilesForm(forms.Form): # not a model-form, just a form-form @@ -42,7 +46,64 @@ class FilesRenameForm(forms.Form): # not a model-form, just a form-form class TextForm(forms.Form): # not a model-form, just a form-form photographer = forms.CharField(strip=True) + +class ExpofileRenameForm(forms.Form): # not a model-form, just a form-form + renameto = forms.CharField(strip=True, required=False) + +@login_required_if_public +def expofilerename(request, filepath): + """Rename any single file in /expofiles/ - eventually. + Currently this just does files within wallets i.e. in /surveyscans/ + and it returns control to the original wallet edit page + """ + if filepath: + actualpath = Path(settings.EXPOFILES) / Path(filepath) + else: + message = f'\n File to rename not specified "{filepath}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + + if not actualpath.is_file(): + message = f'\n File not found when attempting rename "{filepath}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + else: + filename = Path(filepath).name + folder = Path(actualpath).parent + if not actualpath.is_relative_to(Path(settings.SCANS_ROOT)): + message = f'\n Can only do rename within wallets (expofiles/surveyscans/) currently, sorry. "{actualpath}" ' + print(message) + return render(request, "errors/generic.html", {"message": message}) + + + if request.method == "POST": + form = ExpofileRenameForm(request.POST) + if form.is_valid(): + renameto = request.POST["renameto"] + + if (folder / renameto).is_file(): + message = f'\n Cannot rename to an existing file. "{filename}" -> "{(folder / renameto)}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + else: + actualpath.rename((folder / renameto)) + message = f'\n RENAMED "{filename}" -> "{(folder / renameto)}"' + print(message) + return redirect('/survey_scans/2023%252314/') + + else: + form = ExpofileRenameForm() + return render( + request, + "renameform.html", + { + "form": form, + "filepath": filepath, + "filename": filename, + }, + ) + @login_required_if_public def photoupload(request, folder=None): """Upload photo image files into /expofiles/photos/// @@ -55,6 +116,8 @@ def photoupload(request, folder=None): be renamed to something useful before starting the upload. Unfortunately this only works when uploading one file at at time , inevitable once you think about it. + + Pending generic file renaming capability more generally. """ year = settings.PHOTOS_YEAR filesaved = False @@ -186,7 +249,8 @@ def dwgupload(request, folder=None, gitdisable="no"): This does NOT use a Django model linked to a Django form. Just a simple Django form. You will find the Django documentation on forms very confusing, This is simpler. - We could validate the uploaded files as being a valid files using an XML parser, not a dubious script or hack + We could validate the uploaded files as being a valid files using an XML parser, not a dubious script or hack, + but this won't work on Tunnel files as Tunnel does not produce exactly valid xml We use get_or_create instead of simply creating a new object in case someone uploads the same file several times in one session, and expects them to be overwritten in the database. Although diff --git a/core/views/wallets_edit.py b/core/views/wallets_edit.py index 57ebd36..580c6c1 100644 --- a/core/views/wallets_edit.py +++ b/core/views/wallets_edit.py @@ -272,10 +272,14 @@ def walletedit(request, path=None): This does NOT use a Django model linked to a Django form. Just a simple Django form. You will find the Django documentation on forms very confusing, - as it covers many very differnet things we do not need. This is simpler. + as it covers many very different things we do not need. This is simpler. + (See also view/uploads.py for other simpler forms, as opposed to core/forms.py + which contains a couple of Django class-based forms.) This subsumes much of the code which was in the pre-2022 non-troggle wallets.py script and so this function is very long indeed and needs refactoring. + + Much of the logic used here lives in the Class functions for Wallet. REWRITE bits using the ticklist, dateify, caveify, populate etc utility functions in core.view.scans.py """ diff --git a/parsers/survex.py b/parsers/survex.py index 4ee5f0e..0d13c4d 100644 --- a/parsers/survex.py +++ b/parsers/survex.py @@ -1781,7 +1781,7 @@ class LoadingSurvex: # this is a python generator idiom. # see https://realpython.com/introduction-to-python-generators/ - # this is the first use of generators in troggle (Oct.2022) and save 21 MB of memory + # this is the first use of generators in troggle (Oct.2022) and saves 21 MB of memory with open(collatefilename, "r") as fcollate: for svxline in fcollate: self.lineno += 1 @@ -1789,22 +1789,22 @@ class LoadingSurvex: if comment: # this catches the ;|*include NEWFILE and ;|*edulcni ENDOFFILE lines too self.LoadSurvexComment(survexblock, comment) + else: + # detect a merge failure inserted by version control + mfail = self.rx_badmerge.match(sline) + if mfail: + message = f"\n ! - ERROR version control merge failure\n - '{sline}'\n" + message = ( + message + f" - line {self.lineno} in {blkid} in {survexblock}\n - NERD++ needed to fix it" + ) + print(message) + print(message, file=sys.stderr) + stash_data_issue(parser="survex", message=message) + continue # skip this line if not sline: continue # skip blank lines - # detect a merge failure inserted by version control - mfail = self.rx_badmerge.match(sline) - if mfail: - message = f"\n ! - ERROR version control merge failure\n - '{sline}'\n" - message = ( - message + f" - line {self.lineno} in {blkid} in {survexblock}\n - NERD++ needed to fix it" - ) - print(message) - print(message, file=sys.stderr) - stash_data_issue(parser="survex", message=message) - continue # skip this line - # detect a star command star = self.rx_star.match(sline) if star: @@ -1829,7 +1829,7 @@ class LoadingSurvex: mfail = self.rx_badmerge.match(svxline) if mfail: message = f"\n!! - ERROR version control merge failure\n - '{svxline}'\n" - message = message + f" - in '{path}' at line {thissvxline}\n" + message = message + f" - in '{path}' at line {self.lineno}\n" message = ( message + f" - line {self.lineno} {survexblock}\n - Parsing aborted. NERD++ needed to fix it" ) @@ -1963,7 +1963,7 @@ class LoadingSurvex: stash_data_issue(parser="survex", message=message, url=None, sb=(path)) return # skip this survex file and all things *included in it except: - message = f" ! ERROR *include file '{path}' in '{survexblock}' has unexpected error on opening file. OMITTED!" + message = f" ! ERROR *include file '{path}' in '{survexblock}' has unexpected error on opening or reading file. OMITTED!" print(message) print(message, file=sys.stderr) stash_data_issue(parser="survex", message=message, url=None, sb=(path)) diff --git a/templates/renameform.html b/templates/renameform.html new file mode 100644 index 0000000..a8aaf16 --- /dev/null +++ b/templates/renameform.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} + +{% block title %}File rename form{% endblock %} + +{% block content %} + +

Rename "{{filename}}"

+
+ +
+
+
+ {% csrf_token %} +
+ + +


+ + +
+ +


+ Full urlencoded path for this file {{filepath|urlencode}} + +
+ +
+ +
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/walletform.html b/templates/walletform.html index 5290933..de669b5 100644 --- a/templates/walletform.html +++ b/templates/walletform.html @@ -72,7 +72,7 @@ {% if not create %}

{% for f in files %} - {{ f}}
+ » {{ f}}
{% empty %}

<No files in this wallet. > diff --git a/urls.py b/urls.py index b162704..2e13eb8 100644 --- a/urls.py +++ b/urls.py @@ -24,7 +24,7 @@ from troggle.core.views.other import (controlpanel, exportlogbook, frontpage, from troggle.core.views.prospect import prospecting from troggle.core.views.scans import (allscans, cavewallets, scansingle, walletslistperson, walletslistyear) -from troggle.core.views.uploads import dwgupload, photoupload +from troggle.core.views.uploads import dwgupload, photoupload, expofilerename from troggle.core.views.wallets_edit import walletedit """This sets the actualurlpatterns[] and urlpatterns[] lists which django uses to resolve urls - in both directions as these are declarative. @@ -110,6 +110,9 @@ trogglepatterns = [ path('dwguploadnogit/', dwgupload, {'gitdisable': 'yes'}, name='dwguploadnogit'), # used in testing path('dwguploadnogit/', dwgupload, {'gitdisable': 'yes'}, name='dwguploadnogit'), # used in testing +# Renaming an uploaded file + path('expofilerename/', expofilerename, name='expofilerename'), + # setting LOGIN_URL = '/accounts/login/' is default. # NB setting url pattern name to 'login' instea dof 'expologin' with override Django, see https://docs.djangoproject.com/en/dev/topics/http/urls/#naming-url-patterns path('accounts/logout/', expologout, name='expologout'), # same as in django.contrib.auth.urls