diff --git a/core/TESTS/test_imports.py b/core/TESTS/test_imports.py index e81054b..9a40041 100644 --- a/core/TESTS/test_imports.py +++ b/core/TESTS/test_imports.py @@ -54,6 +54,7 @@ class SimpleTest(SimpleTestCase): from troggle.parsers.people import GetPersonExpeditionNameLookup def test_import_views_uploads(self): + from troggle.core.views.logbook_edit import logbookedit from troggle.core.views.uploads import dwgupload def test_import_views_walletedit(self): diff --git a/core/models/caves.py b/core/models/caves.py index 0a910ef..a0ec04b 100644 --- a/core/models/caves.py +++ b/core/models/caves.py @@ -11,7 +11,7 @@ import settings from troggle.core.models.logbooks import QM from troggle.core.models.survex import SurvexStation, utmToLatLng from troggle.core.models.troggle import DataIssue, TroggleModel -from troggle.core.utils import TROG, parse_aliases +from troggle.core.utils import TROG, parse_aliases # Use the TROG global object to cache the cave lookup list. No good for multi-user.., or even multi-page. Pointless in fact. Gcavelookup = TROG["caves"]["gcavelookup"] diff --git a/core/views/caves.py b/core/views/caves.py index a40b314..84f0569 100644 --- a/core/views/caves.py +++ b/core/views/caves.py @@ -20,7 +20,14 @@ from troggle.core.forms import CaveForm, EntranceForm, EntranceLetterForm # Cav from troggle.core.models.caves import Cave, CaveAndEntrance, Entrance, GetCaveLookup, get_cave_leniently from troggle.core.models.logbooks import QM from troggle.core.models.wallets import Wallet -from troggle.core.utils import COOKIE_MAX_AGE, WriteAndCommitError, current_expo, get_cookie, git_string, write_and_commit +from troggle.core.utils import ( + COOKIE_MAX_AGE, + WriteAndCommitError, + current_expo, + get_cookie, + git_string, + write_and_commit, +) from troggle.core.views import expo from troggle.parsers.caves import read_cave, read_entrance from troggle.settings import CAVEDESCRIPTIONS, ENTRANCEDESCRIPTIONS diff --git a/core/views/expo.py b/core/views/expo.py index d3d649b..019df18 100644 --- a/core/views/expo.py +++ b/core/views/expo.py @@ -14,7 +14,14 @@ from django.views.decorators.csrf import ensure_csrf_cookie import troggle.core.views.caves import troggle.settings as settings from troggle.core.models.caves import Cave -from troggle.core.utils import COOKIE_MAX_AGE, WriteAndCommitError, current_expo, get_cookie, git_string, write_and_commit +from troggle.core.utils import ( + COOKIE_MAX_AGE, + WriteAndCommitError, + current_expo, + get_cookie, + git_string, + write_and_commit, +) from troggle.core.views.editor_helpers import HTMLarea from troggle.core.views.uploads import edittxtpage diff --git a/core/views/logbook_edit.py b/core/views/logbook_edit.py new file mode 100644 index 0000000..7411149 --- /dev/null +++ b/core/views/logbook_edit.py @@ -0,0 +1,451 @@ +import subprocess +from datetime import datetime, timedelta, timezone +from pathlib import Path + +from django import forms +from django.core.files.storage import FileSystemStorage +from django.http import HttpResponseRedirect +from django.shortcuts import redirect, render + +import settings +from troggle.core.models.caves import GetCaveLookup +from troggle.core.models.logbooks import LogbookEntry, PersonLogEntry, writelogbook +from troggle.core.models.survex import DrawingFile +from troggle.core.models.troggle import DataIssue, Expedition, PersonExpedition +from troggle.core.utils import ( + COOKIE_MAX_AGE, + alphabet_suffix, + current_expo, + get_cookie, + git_string, + sanitize_name, + unique_slug, + write_and_commit, +) +from troggle.parsers.people import GetPersonExpeditionNameLookup, known_foreigner + +# from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time* +from .auth import login_required_if_public + +"""Logbook edit 'view' +Note that there are other file upload forms in views/wallet_edit.py views/uploads.py +and that core/forms.py contains Django class-based forms for caves and entrances. +""" + +todo = """ - refactor git to use utils.py functions +""" + +def create_new_lbe_slug(date): + onthisdate = LogbookEntry.objects.filter(date=date) + n = len(onthisdate) + print(f" Already entries on this date: {n}\n {onthisdate}") + + suffix = alphabet_suffix(n+1) + + tid = f"{date}{suffix}" + if len(tid) <=4 : + print(f"BAD ERROR {tid=}") + tid = f"{date}_{LogbookEntry.objects.count()}_{n}" # fudged number + print(f"{tid=}") + return tid + +def store_edited_entry_into_database(date, place, title, text, others, author, tu, slug): + """saves a single logbook entry and related personlogentry items + + need to select out *guest and foreign friends from others + + Rather similar to similarly named function in parsers/logbooks but circular reference prevents us using it directly, + and they need refactoring anyway. + """ + + year = slug[0:4] + expedition = Expedition.objects.get(year=year) + cave = GetCaveLookup().get(place.lower()) + # print(f"store_edited_entry_into_database(): {place=} {cave=}") + + if LogbookEntry.objects.filter(slug=slug).exists(): + # oops. + message = f" ! - DUPLICATE SLUG for logbook entry {date} - {slug}" + DataIssue.objects.create(parser="logbooks", message=message) + slug = slug + "_" + unique_slug(text,2) + + otherAttribs = { + "place": place, + "text": text, + "expedition": expedition, + "time_underground": tu, + "cave": cave, + "title": f"{place} - {title}", + # "other_people": others + } + coUniqueAttribs = {"slug": slug, "date": date } + + lbo = LogbookEntry.objects.create(**otherAttribs, **coUniqueAttribs) + + pt_list = [] + # These entities have to be PersonExpedition objects + team = others.split(",") + team.append(author) + + odds = [] + for name in team: + name = name.strip() + if len(name) > 0: + if name[0] == "*": # a name prefix of "*" is special, just a string. + odds.append(name) + print(f" - adding * special name '{name}'") + else: + try: + personyear = GetPersonExpeditionNameLookup(expedition).get(name.lower()) + if not personyear: + odds.append(name) + print(f" - adding unrecognised expoer '{name}'") + if known_foreigner(name): + message = f" ! - Known foreigner: '{name}' in entry {slug=}" + print(message) + else: + message = f" ! - No name match for: '{name}' in entry {slug=}" + print(message) + DataIssue.objects.create(parser="logbooks", message=message) + else: + coUniqueAttribs = {"personexpedition": personyear, "nickname_used": name, "logbook_entry": lbo} # lbo is primary key + otherAttribs = {"time_underground": tu, "is_logbook_entry_author": (name==author)} + pt_list.append(PersonLogEntry(**otherAttribs, **coUniqueAttribs)) + + except: + # This should not happen. We do not raise exceptions in that function + message = f" ! - EXCEPTION: '{name}' in entry {slug=}" + print(message) + DataIssue.objects.create(parser="logbooks", message=message) + raise + + PersonLogEntry.objects.bulk_create(pt_list) + + lbo.other_people = ", ".join(odds) + print(f" - Saving other_people '{lbo.other_people}'") + lbo.save() + + +@login_required_if_public +def logbookedit(request, year=None, slug=None): + """Edit a logbook entry + + This 'validates' the author as being on expo in the current year, but only indicates this by + putting the text of the form prompt in red (same as for an invalid date, which is arguably more important). + No check is done on the other people on the trip as this is picked up anyway by parsing on import + and we don't really care at this point. + """ + def yesterday(): + yesterday = datetime.now() - timedelta(1) + return yesterday.strftime('%Y-%m-%d') + + def validate_year(year): + try: + expo = Expedition.objects.get(year=year) + except: + year = current_expo() # creates new Expedition object if needed + return year + + def new_entry_form(): + # set the date to be "yesterday" as this will, hopefully, usually be the case + + return render( + request, + "logbookform.html", + { + "form": form, + "year": year, + "yesterday": yesterday(), + + }, + ) + def clean_tu(tu): + if tu =="": + return 0 + try: + tu = float(tu)/1 # check numeric + except: + return 0 + return tu + + if not year: + if not slug: # not in the URL, we have not read teh POST response yet.. wich might have a slug in it + year = current_expo() + else: + year = slug[0:4] + try: + year = str(int(year)) # but maybe slug was hand-edited to be a future year.. + year = validate_year(year) # so fix that + except: + year = current_expo() + + author = "" + + if request.method == "POST": + prev_slug = "" # None value pending overwrite from submitted form + form = LogbookEditForm(request.POST) + if not form.is_valid(): + message = f'Invalid form response for logbook entry creating "{request.POST}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + else: + # if there is no slug then this is probably a completely new lbe and we need to enter it into the db + # otherwise it is an update + # validation all to be done yet.. + date = request.POST["date"].strip() + author = request.POST["author"].strip() # TODO check against personexpedition on submit + others = request.POST["others"].strip() # TODO check each against personexpedition on submit + place = request.POST["place"].strip().replace(' - ',' = ') # no hyphens ! + title = request.POST["title"].strip() + entry = request.POST["text"].strip() + if "prev_slug" in request.POST: + prev_slug = request.POST["prev_slug"].strip() # if we are re-editing the same entry again + entry = entry.replace('\r','') # remove HTML-standard CR inserted from form. + entry = entry.replace('\n\n','\n

\n') # replace 2 \n with

+ tu = request.POST["tu"].strip() + tu = clean_tu(tu) + + try: + odate = datetime.strptime(date.replace(".", "-"), "%Y-%m-%d").date() + print(f"{odate.year=}") + if str(odate.year) == year: + dateflag = False + else: + print(f"Trying to change the year ! No!! {odate.year=}") + odate = datetime.strptime(f"{year}-01-01", "%Y-%m-%d").date() + dateflag = True + except: + odate = datetime.strptime(f"{year}-01-01", "%Y-%m-%d").date() + print(f"! Invalid date string {date}, setting to {odate}") + dateflag = True + date = odate.isoformat() + + year = validate_year(year) + expo = Expedition.objects.get(year=year) + personyear = GetPersonExpeditionNameLookup(expo).get(author.lower()) + if personyear: + authorflag = False + else: + authorflag = True + print(f"! Unrecognised author: {author}") + + # if somehow we get a slug set to just '2024', not eg '2020-08-10b' + # because the URL of the page is /logbookedit/2022 for a new entry + # This is a hack, why can we not reproduce this bug on the dev system? + if slug not in locals(): + slug = "" + if len(slug) < 11: + slug = "" + if len(prev_slug) < 11: + prev_slug = "" + + if not prev_slug and not slug: + # Creating a new logbook entry with all the gubbins + slug = create_new_lbe_slug(date) + + if prev_slug and not slug: + # if this was a previous post, then prev_slug will have been set on the form + # we are editing a previous thing, so we don't create a new lbe + slug = prev_slug + + # OK we could patch the object in place, but if the people on the trip have changed this + # would get very messy. So we delete it, and thus all the dependent objects, + # and recreate it and all its links. It might not exist though. + print(f"- Deleting the LogBookEntry {slug}") + LogbookEntry.objects.filter(slug=slug).delete() # works even if it does not exist + + print(f"- Creating the LogBookEntry {slug}") + year = slug[0:4] + try: + expedition = Expedition.objects.get(year=year) + except Expedition.DoesNotExist: + message = f'''! - This expo "{year}" not created yet + It needs to be created before you can save a logbook entry. + See /handbook/computing/newyear.html + + WHAT TO DO NOW: + 1. Press the Back button on your proswer to return to the screen where you typed up the entry, + 2. Copy the text of what you wrote into a new text file, + 3. Direct a nerd to fix this. It should take only a couple of minutes.''' + print(message) + return render(request, "errors/generic.html", {"message": message}) + store_edited_entry_into_database(date, place, title, entry, others, author, tu, slug) + + + print(f"- Rewriting the entire {year} logbook to disc ") + filename= "logbook.html" + try: + print(f" - Logbook for {year} to be exported and written out.") + writelogbook(year, filename) # uses a template, not the code fragment below which is just a visible hint to logged on user + except: + message = f'! - Logbook saving failed - \n!! Permissions failure ?! on attempting to save file "logbook.html"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + + # Code fragment illustration - not actually what gets saved to database + output = f''' + +

{date}
+
{author}, {others}
+
{place} - {title}
+ +{entry} + +
T/U {tu} hrs
+
+ +''' + # Successful POST + # So save to database and then write out whole new logbook.html file + + # We do author validation on the form as displayed by GET, not at the moment of POST. + # If we had JS validation then we could be more timely. + git = settings.GIT + dirpath = Path(settings.EXPOWEB) / "years" / str(year) + lbe_add = subprocess.run( + [git, "add", filename], cwd=dirpath, capture_output=True, text=True + ) + msgdata = ( + lbe_add.stderr + + "\n" + + lbe_add.stdout + + "\nreturn code: " + + str(lbe_add.returncode) + ) + message = f'! - FORM Logbook Edit {slug} - Success: git ADD on server for this file {filename}.' + msgdata + print(message) + if lbe_add.returncode != 0: + msgdata = ( + "Ask a nerd to fix this.\n\n" + + lbe_add.stderr + + "\n\n" + + lbe_add.stdout + + "\n\nreturn code: " + + str(lbe_add.returncode) + ) + message = ( + f"! - FORM Logbook Edit - CANNOT git ADD on server for this file {filename}. {slug} edits saved but not added to git.\n" + + msgdata + ) + print(message) + return render(request, "errors/generic.html", {"message": message}) + + lbe_commit = subprocess.run( + [git, "commit", "-m", f"Logbook edited {slug}"], + cwd=dirpath, + capture_output=True, + text=True, + ) + message = f'! - FORM Logbook Edit - {filename}. {slug} edits saved, added to git, and COMMITTED.\n' + msgdata + print(message) + #This produces return code = 1 if it commits OK + if lbe_commit.returncode != 0: + msgdata = ( + "Ask a nerd to fix this.\n\n" + + lbe_commit.stderr + + "\n" + + lbe_commit.stdout + + "\nreturn code: " + + str(lbe_commit.returncode) + ) + message = ( + f"! - FORM Logbook Edit - Error code '{lbe_commit.returncode}' with git on server for {filename}. {slug} edits saved, added to git, but NOT committed.\n" + + msgdata + ) + print(message) + if not (lbe_commit.returncode ==1 and settings.DEVSERVER): + # rc=1 is OK on the development server + return render(request, "errors/generic.html", {"message": message}) + + # This does not change the URL in the browser, so despite a new slug being created, + # the next time this code is run it thinks a new slug needs to be created. So we should + # actually redirect to a new URL (an edit not a create) not simply return a render object. + # logbookedit/2022-08-21a + + # HOWEVER by doing a redirect rather than returning a rendered page, we lose all the + # error settings e.g dateflag and authroflag so the user gets no feedback about bad data entered. + # so we need to pass the flags explicitly int he url and then extract them from the request in the GET bit. sigh. + return HttpResponseRedirect(f"/logbookedit/{slug}?dateflag={dateflag}&authorflag={authorflag}") + + # return render( + # request, + # "logbookform.html", + # { + # "form": form, + # "year": year, + # "date": date, "dateflag": dateflag, + # "author": author, "authorflag": authorflag, + # "others": others, + # "place": place, + # "title": title, + # "tu": tu, + # "entry": entry, + # "output": output, + # "slug": slug, + # }, + # ) + # GET here + else: + form = LogbookEditForm() + year = validate_year(year) + + if request.GET.get('dateflag', 'False') == "True": + dateflag = True + else: + dateflag = False + + if request.GET.get('authorflag', 'False') == "True": + authorflag = True + else: + authorflag = False + + if not slug: # no slug or bad slug for an lbe which does not exist + return new_entry_form() + else: + lbes = LogbookEntry.objects.filter(slug=slug) + if not lbes: + return new_entry_form() + else: + if len(lbes) > 1: + return render(request, "object_list.html", {"object_list": lbes}) # ie a bug + else: + lbe = lbes[0] + print(f"{lbe}") + tu = clean_tu(lbe.time_underground) + + people = [] + for p in lbe.personlogentry_set.filter(logbook_entry=lbe): # p is a PersonLogEntry object + if p.is_logbook_entry_author: + # author = p.personexpedition.person.fullname + author = p.nickname_used + else: + # people.append(p.personexpedition.person.fullname) + people.append(p.nickname_used) + others =', '.join(people) + if lbe.other_people: + others = others + ", " + lbe.other_people + lenothers = min(70,max(20, len(others))) + + text = lbe.text + rows = max(5,len(text)/50) + return render( + request, + "logbookform.html", + { + "form": form, + "year": year, + "date": lbe.date.isoformat(), "dateflag": dateflag, + "author": author, "authorflag": authorflag, + "others": others, + "lenothers": lenothers, + "place": lbe.place, + "title": lbe.title.replace(f"{lbe.place} - ",""), + "tu": tu, + "entry": text, + "textrows": rows, + "slug": slug, + }, + ) + +class LogbookEditForm(forms.Form): # not a model-form, just a form-form + author = forms.CharField(strip=True, required=False) diff --git a/core/views/logbooks.py b/core/views/logbooks.py index c71bab0..003b67f 100644 --- a/core/views/logbooks.py +++ b/core/views/logbooks.py @@ -1,9 +1,9 @@ import re +from django.core.exceptions import ValidationError from django.db.models import Q from django.shortcuts import redirect, render from django.views.generic.list import ListView -from django.core.exceptions import ValidationError import troggle.settings as settings from troggle.core.models.logbooks import QM, LogbookEntry, PersonLogEntry, writelogbook diff --git a/core/views/survex.py b/core/views/survex.py index 4a258d4..f1877af 100644 --- a/core/views/survex.py +++ b/core/views/survex.py @@ -18,7 +18,7 @@ from troggle.core.models.caves import Cave, GetCaveLookup from troggle.core.models.logbooks import LogbookEntry from troggle.core.models.survex import SurvexBlock, SurvexFile #, SurvexDirectory from troggle.core.models.wallets import Wallet -from troggle.core.utils import COOKIE_MAX_AGE, current_expo, get_cookie, git_string, add_commit +from troggle.core.utils import COOKIE_MAX_AGE, add_commit, current_expo, get_cookie, git_string from troggle.parsers.survex import parse_one_file """Everything that views survexfiles diff --git a/core/views/uploads.py b/core/views/uploads.py index f4e2278..98e2c43 100644 --- a/core/views/uploads.py +++ b/core/views/uploads.py @@ -9,10 +9,18 @@ from django.shortcuts import redirect, render import settings from troggle.core.models.caves import GetCaveLookup -from troggle.core.models.logbooks import LogbookEntry, PersonLogEntry, writelogbook from troggle.core.models.survex import DrawingFile from troggle.core.models.troggle import DataIssue, Expedition, PersonExpedition -from troggle.core.utils import COOKIE_MAX_AGE, alphabet_suffix, current_expo, get_cookie, git_string, sanitize_name, unique_slug, write_and_commit +from troggle.core.utils import ( + COOKIE_MAX_AGE, + alphabet_suffix, + current_expo, + get_cookie, + git_string, + sanitize_name, + unique_slug, + write_and_commit, +) from troggle.parsers.people import GetPersonExpeditionNameLookup, known_foreigner # from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time* @@ -20,7 +28,7 @@ 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. +and views/logbook_edit.py and that core/forms.py contains Django class-based forms for caves and entrances. """ todo = """ @@ -42,95 +50,6 @@ todo = """ - Make file rename utility less ugly. """ -def create_new_lbe_slug(date): - onthisdate = LogbookEntry.objects.filter(date=date) - n = len(onthisdate) - print(f" Already entries on this date: {n}\n {onthisdate}") - - suffix = alphabet_suffix(n+1) - - tid = f"{date}{suffix}" - if len(tid) <=4 : - print(f"BAD ERROR {tid=}") - tid = f"{date}_{LogbookEntry.objects.count()}_{n}" # fudged number - print(f"{tid=}") - return tid - -def store_edited_entry_into_database(date, place, title, text, others, author, tu, slug): - """saves a single logbook entry and related personlogentry items - - need to select out *guest and foreign friends from others - - Rather similar to similarly named function in parsers/logbooks but circular reference prevents us using it directly, - and they need refactoring anyway. - """ - - year = slug[0:4] - expedition = Expedition.objects.get(year=year) - cave = GetCaveLookup().get(place.lower()) - # print(f"store_edited_entry_into_database(): {place=} {cave=}") - - if LogbookEntry.objects.filter(slug=slug).exists(): - # oops. - message = f" ! - DUPLICATE SLUG for logbook entry {date} - {slug}" - DataIssue.objects.create(parser="logbooks", message=message) - slug = slug + "_" + unique_slug(text,2) - - otherAttribs = { - "place": place, - "text": text, - "expedition": expedition, - "time_underground": tu, - "cave": cave, - "title": f"{place} - {title}", - # "other_people": others - } - coUniqueAttribs = {"slug": slug, "date": date } - - lbo = LogbookEntry.objects.create(**otherAttribs, **coUniqueAttribs) - - pt_list = [] - # These entities have to be PersonExpedition objects - team = others.split(",") - team.append(author) - - odds = [] - for name in team: - name = name.strip() - if len(name) > 0: - if name[0] == "*": # a name prefix of "*" is special, just a string. - odds.append(name) - print(f" - adding * special name '{name}'") - else: - try: - personyear = GetPersonExpeditionNameLookup(expedition).get(name.lower()) - if not personyear: - odds.append(name) - print(f" - adding unrecognised expoer '{name}'") - if known_foreigner(name): - message = f" ! - Known foreigner: '{name}' in entry {slug=}" - print(message) - else: - message = f" ! - No name match for: '{name}' in entry {slug=}" - print(message) - DataIssue.objects.create(parser="logbooks", message=message) - else: - coUniqueAttribs = {"personexpedition": personyear, "nickname_used": name, "logbook_entry": lbo} # lbo is primary key - otherAttribs = {"time_underground": tu, "is_logbook_entry_author": (name==author)} - pt_list.append(PersonLogEntry(**otherAttribs, **coUniqueAttribs)) - - except: - # This should not happen. We do not raise exceptions in that function - message = f" ! - EXCEPTION: '{name}' in entry {slug=}" - print(message) - DataIssue.objects.create(parser="logbooks", message=message) - raise - - PersonLogEntry.objects.bulk_create(pt_list) - - lbo.other_people = ", ".join(odds) - print(f" - Saving other_people '{lbo.other_people}'") - lbo.save() class FilesForm(forms.Form): # not a model-form, just a form-form uploadfiles = forms.FileField() @@ -157,8 +76,6 @@ class ExpotextfileForm(forms.Form): # not a model-form, just a form-form ) ) -class LogbookEditForm(forms.Form): # not a model-form, just a form-form - author = forms.CharField(strip=True, required=False) @login_required_if_public def edittxtpage(request, path, filepath): @@ -254,327 +171,6 @@ def edittxtpage(request, path, filepath): return simple_get(originaltext) -@login_required_if_public -def logbookedit(request, year=None, slug=None): - """Edit a logbook entry - - This 'validates' the author as being on expo in the current year, but only indicates this by - putting the text of the form prompt in red (same as for an invalid date, which is arguably more important). - No check is done on the other people on the trip as this is picked up anyway by parsing on import - and we don't really care at this point. - """ - def yesterday(): - yesterday = datetime.now() - timedelta(1) - return yesterday.strftime('%Y-%m-%d') - - def validate_year(year): - try: - expo = Expedition.objects.get(year=year) - except: - year = current_expo() # creates new Expedition object if needed - return year - - def new_entry_form(): - # set the date to be "yesterday" as this will, hopefully, usually be the case - - return render( - request, - "logbookform.html", - { - "form": form, - "year": year, - "yesterday": yesterday(), - - }, - ) - def clean_tu(tu): - if tu =="": - return 0 - try: - tu = float(tu)/1 # check numeric - except: - return 0 - return tu - - if not year: - if not slug: # not in the URL, we have not read teh POST response yet.. wich might have a slug in it - year = current_expo() - else: - year = slug[0:4] - try: - year = str(int(year)) # but maybe slug was hand-edited to be a future year.. - year = validate_year(year) # so fix that - except: - year = current_expo() - - author = "" - - if request.method == "POST": - prev_slug = "" # None value pending overwrite from submitted form - form = LogbookEditForm(request.POST) - if not form.is_valid(): - message = f'Invalid form response for logbook entry creating "{request.POST}"' - print(message) - return render(request, "errors/generic.html", {"message": message}) - else: - # if there is no slug then this is probably a completely new lbe and we need to enter it into the db - # otherwise it is an update - # validation all to be done yet.. - date = request.POST["date"].strip() - author = request.POST["author"].strip() # TODO check against personexpedition on submit - others = request.POST["others"].strip() # TODO check each against personexpedition on submit - place = request.POST["place"].strip().replace(' - ',' = ') # no hyphens ! - title = request.POST["title"].strip() - entry = request.POST["text"].strip() - if "prev_slug" in request.POST: - prev_slug = request.POST["prev_slug"].strip() # if we are re-editing the same entry again - entry = entry.replace('\r','') # remove HTML-standard CR inserted from form. - entry = entry.replace('\n\n','\n

\n') # replace 2 \n with

- tu = request.POST["tu"].strip() - tu = clean_tu(tu) - - try: - odate = datetime.strptime(date.replace(".", "-"), "%Y-%m-%d").date() - print(f"{odate.year=}") - if str(odate.year) == year: - dateflag = False - else: - print(f"Trying to change the year ! No!! {odate.year=}") - odate = datetime.strptime(f"{year}-01-01", "%Y-%m-%d").date() - dateflag = True - except: - odate = datetime.strptime(f"{year}-01-01", "%Y-%m-%d").date() - print(f"! Invalid date string {date}, setting to {odate}") - dateflag = True - date = odate.isoformat() - - year = validate_year(year) - expo = Expedition.objects.get(year=year) - personyear = GetPersonExpeditionNameLookup(expo).get(author.lower()) - if personyear: - authorflag = False - else: - authorflag = True - print(f"! Unrecognised author: {author}") - - # if somehow we get a slug set to just '2024', not eg '2020-08-10b' - # because the URL of the page is /logbookedit/2022 for a new entry - # This is a hack, why can we not reproduce this bug on the dev system? - if slug not in locals(): - slug = "" - if len(slug) < 11: - slug = "" - if len(prev_slug) < 11: - prev_slug = "" - - if not prev_slug and not slug: - # Creating a new logbook entry with all the gubbins - slug = create_new_lbe_slug(date) - - if prev_slug and not slug: - # if this was a previous post, then prev_slug will have been set on the form - # we are editing a previous thing, so we don't create a new lbe - slug = prev_slug - - # OK we could patch the object in place, but if the people on the trip have changed this - # would get very messy. So we delete it, and thus all the dependent objects, - # and recreate it and all its links. It might not exist though. - print(f"- Deleting the LogBookEntry {slug}") - LogbookEntry.objects.filter(slug=slug).delete() # works even if it does not exist - - print(f"- Creating the LogBookEntry {slug}") - year = slug[0:4] - try: - expedition = Expedition.objects.get(year=year) - except Expedition.DoesNotExist: - message = f'''! - This expo "{year}" not created yet - It needs to be created before you can save a logbook entry. - See /handbook/computing/newyear.html - - WHAT TO DO NOW: - 1. Press the Back button on your proswer to return to the screen where you typed up the entry, - 2. Copy the text of what you wrote into a new text file, - 3. Direct a nerd to fix this. It should take only a couple of minutes.''' - print(message) - return render(request, "errors/generic.html", {"message": message}) - store_edited_entry_into_database(date, place, title, entry, others, author, tu, slug) - - - print(f"- Rewriting the entire {year} logbook to disc ") - filename= "logbook.html" - try: - print(f" - Logbook for {year} to be exported and written out.") - writelogbook(year, filename) # uses a template, not the code fragment below which is just a visible hint to logged on user - except: - message = f'! - Logbook saving failed - \n!! Permissions failure ?! on attempting to save file "logbook.html"' - print(message) - return render(request, "errors/generic.html", {"message": message}) - - # Code fragment illustration - not actually what gets saved to database - output = f''' - -

{date}
-
{author}, {others}
-
{place} - {title}
- -{entry} - -
T/U {tu} hrs
-
- -''' - # Successful POST - # So save to database and then write out whole new logbook.html file - - # We do author validation on the form as displayed by GET, not at the moment of POST. - # If we had JS validation then we could be more timely. - git = settings.GIT - dirpath = Path(settings.EXPOWEB) / "years" / str(year) - lbe_add = subprocess.run( - [git, "add", filename], cwd=dirpath, capture_output=True, text=True - ) - msgdata = ( - lbe_add.stderr - + "\n" - + lbe_add.stdout - + "\nreturn code: " - + str(lbe_add.returncode) - ) - message = f'! - FORM Logbook Edit {slug} - Success: git ADD on server for this file {filename}.' + msgdata - print(message) - if lbe_add.returncode != 0: - msgdata = ( - "Ask a nerd to fix this.\n\n" - + lbe_add.stderr - + "\n\n" - + lbe_add.stdout - + "\n\nreturn code: " - + str(lbe_add.returncode) - ) - message = ( - f"! - FORM Logbook Edit - CANNOT git ADD on server for this file {filename}. {slug} edits saved but not added to git.\n" - + msgdata - ) - print(message) - return render(request, "errors/generic.html", {"message": message}) - - lbe_commit = subprocess.run( - [git, "commit", "-m", f"Logbook edited {slug}"], - cwd=dirpath, - capture_output=True, - text=True, - ) - message = f'! - FORM Logbook Edit - {filename}. {slug} edits saved, added to git, and COMMITTED.\n' + msgdata - print(message) - #This produces return code = 1 if it commits OK - if lbe_commit.returncode != 0: - msgdata = ( - "Ask a nerd to fix this.\n\n" - + lbe_commit.stderr - + "\n" - + lbe_commit.stdout - + "\nreturn code: " - + str(lbe_commit.returncode) - ) - message = ( - f"! - FORM Logbook Edit - Error code '{lbe_commit.returncode}' with git on server for {filename}. {slug} edits saved, added to git, but NOT committed.\n" - + msgdata - ) - print(message) - if not (lbe_commit.returncode ==1 and settings.DEVSERVER): - # rc=1 is OK on the development server - return render(request, "errors/generic.html", {"message": message}) - - # This does not change the URL in the browser, so despite a new slug being created, - # the next time this code is run it thinks a new slug needs to be created. So we should - # actually redirect to a new URL (an edit not a create) not simply return a render object. - # logbookedit/2022-08-21a - - # HOWEVER by doing a redirect rather than returning a rendered page, we lose all the - # error settings e.g dateflag and authroflag so the user gets no feedback about bad data entered. - # so we need to pass the flags explicitly int he url and then extract them from the request in the GET bit. sigh. - return HttpResponseRedirect(f"/logbookedit/{slug}?dateflag={dateflag}&authorflag={authorflag}") - - # return render( - # request, - # "logbookform.html", - # { - # "form": form, - # "year": year, - # "date": date, "dateflag": dateflag, - # "author": author, "authorflag": authorflag, - # "others": others, - # "place": place, - # "title": title, - # "tu": tu, - # "entry": entry, - # "output": output, - # "slug": slug, - # }, - # ) - # GET here - else: - form = LogbookEditForm() - year = validate_year(year) - - if request.GET.get('dateflag', 'False') == "True": - dateflag = True - else: - dateflag = False - - if request.GET.get('authorflag', 'False') == "True": - authorflag = True - else: - authorflag = False - - if not slug: # no slug or bad slug for an lbe which does not exist - return new_entry_form() - else: - lbes = LogbookEntry.objects.filter(slug=slug) - if not lbes: - return new_entry_form() - else: - if len(lbes) > 1: - return render(request, "object_list.html", {"object_list": lbes}) # ie a bug - else: - lbe = lbes[0] - print(f"{lbe}") - tu = clean_tu(lbe.time_underground) - - people = [] - for p in lbe.personlogentry_set.filter(logbook_entry=lbe): # p is a PersonLogEntry object - if p.is_logbook_entry_author: - # author = p.personexpedition.person.fullname - author = p.nickname_used - else: - # people.append(p.personexpedition.person.fullname) - people.append(p.nickname_used) - others =', '.join(people) - if lbe.other_people: - others = others + ", " + lbe.other_people - lenothers = min(70,max(20, len(others))) - - text = lbe.text - rows = max(5,len(text)/50) - return render( - request, - "logbookform.html", - { - "form": form, - "year": year, - "date": lbe.date.isoformat(), "dateflag": dateflag, - "author": author, "authorflag": authorflag, - "others": others, - "lenothers": lenothers, - "place": lbe.place, - "title": lbe.title.replace(f"{lbe.place} - ",""), - "tu": tu, - "entry": text, - "textrows": rows, - "slug": slug, - }, - ) - @login_required_if_public def expofilerename(request, filepath): """Rename any single file in /expofiles/ - eventually. diff --git a/core/views/wallets_edit.py b/core/views/wallets_edit.py index 2c8ffe3..e36e7e4 100644 --- a/core/views/wallets_edit.py +++ b/core/views/wallets_edit.py @@ -19,8 +19,18 @@ from troggle.core.models.logbooks import LogbookEntry # , PersonLogEntry from troggle.core.models.survex import SurvexBlock, SurvexFile, SurvexPersonRole from troggle.core.models.troggle import DataIssue, Expedition from troggle.core.models.wallets import YEAR_RANGE, Wallet, make_valid_date -from troggle.core.utils import COOKIE_MAX_AGE, WriteAndCommitError, add_commit, current_expo, get_cookie, git_add, \ - git_commit, git_string, sanitize_name, write_and_commit +from troggle.core.utils import ( + COOKIE_MAX_AGE, + WriteAndCommitError, + add_commit, + current_expo, + get_cookie, + git_add, + git_commit, + git_string, + sanitize_name, + write_and_commit, +) from troggle.core.views.auth import login_required_if_public from troggle.core.views.caves import get_cave_leniently, getCave from troggle.core.views.scans import caveifywallet, oldwallet diff --git a/urls.py b/urls.py index 0390880..aa0a8d4 100644 --- a/urls.py +++ b/urls.py @@ -35,6 +35,7 @@ from troggle.core.views.expo import ( pubspage, spider, ) +from troggle.core.views.logbook_edit import logbookedit from troggle.core.views.logbooks import ( Expeditions_jsonListView, Expeditions_tsvListView, @@ -52,7 +53,7 @@ from troggle.core.views.logbooks import ( from troggle.core.views.other import controlpanel, exportlogbook, frontpage, todos 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, expofilerename, gpxupload, logbookedit, photoupload +from troggle.core.views.uploads import dwgupload, expofilerename, gpxupload, photoupload from troggle.core.views.wallets_edit import walletedit """This sets the actualurlpatterns[] and urlpatterns[] lists which django uses