mirror of
https://expo.survex.com/repositories/troggle/.git
synced 2026-02-08 19:22:35 +00:00
Edit Logbook Entry mostly working
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.template import loader
|
||||
|
||||
import settings
|
||||
from troggle.core.models.troggle import Expedition, TroggleModel
|
||||
@@ -50,7 +53,7 @@ class LogbookEntry(TroggleModel):
|
||||
max_length=100, blank=True, null=True, help_text="Only use this if you haven't chosen a cave"
|
||||
)
|
||||
text = models.TextField()
|
||||
slug = models.SlugField(max_length=50)
|
||||
slug = models.SlugField(max_length=50) # this is tripid
|
||||
time_underground = models.FloatField(null=True, help_text="In decimal hours")
|
||||
|
||||
class Meta:
|
||||
@@ -93,7 +96,47 @@ class LogbookEntry(TroggleModel):
|
||||
index = index % mx
|
||||
return index
|
||||
|
||||
def writelogbook(year, filename):
|
||||
current_expedition = Expedition.objects.get(year=year)
|
||||
logbook_entries = LogbookEntry.objects.filter(expedition=current_expedition).order_by(
|
||||
"slug"
|
||||
) # now that slug, aka tripid, is in our standard date form, this will preserve ordering.
|
||||
|
||||
print(f"Logbook exported has {len(logbook_entries)} entries in it.")
|
||||
|
||||
extension = "html"
|
||||
template = "logbook2005style.html"
|
||||
|
||||
t = loader.get_template(template)
|
||||
logbookfile = t.render({"logbook_entries": logbook_entries})
|
||||
|
||||
endpath = Path(settings.EXPOWEB, "years", year, "endmatter.html")
|
||||
endmatter = ""
|
||||
if endpath.is_file():
|
||||
try:
|
||||
with open(endpath, "r") as end:
|
||||
endmatter = end.read()
|
||||
except:
|
||||
print(" ! Very Bad Error opening " + endpath)
|
||||
|
||||
frontpath = Path(settings.EXPOWEB, "years", year, "frontmatter.html")
|
||||
if frontpath.is_file():
|
||||
try:
|
||||
with open(frontpath, "r") as front:
|
||||
frontmatter = front.read()
|
||||
except:
|
||||
print(" ! Very Bad Error opening " + frontpath)
|
||||
logbookfile = re.sub(r"<body>", "<body>\n" + frontmatter + endmatter, logbookfile)
|
||||
else:
|
||||
logbookfile = re.sub(r"<body>", f"<body>\n<h1>Expo {year}</h1>\n" + endmatter, logbookfile)
|
||||
|
||||
dir = Path(settings.EXPOWEB) / "years" / year
|
||||
filepath = Path(dir, filename)
|
||||
with (open(filepath, "w")) as lb:
|
||||
lb.writelines(logbookfile)
|
||||
|
||||
# print(f'Logbook exported to {filepath}')
|
||||
|
||||
class PersonLogEntry(TroggleModel):
|
||||
"""Single Person going on a trip, which may or may not be written up.
|
||||
It could account for different T/U for people in same logbook entry.
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.shortcuts import render
|
||||
from django.template import loader
|
||||
|
||||
from troggle.core.models.caves import Cave
|
||||
from troggle.core.models.logbooks import LogbookEntry # , PersonLogEntry
|
||||
from troggle.core.models.logbooks import LogbookEntry, writelogbook # , PersonLogEntry
|
||||
|
||||
# from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time*
|
||||
from troggle.core.models.troggle import Expedition
|
||||
@@ -169,69 +169,31 @@ def controlpanel(request):
|
||||
)
|
||||
|
||||
|
||||
def exportlogbook(request, year=None, extension=None):
|
||||
def exportlogbook(request, year=None):
|
||||
"""Constructs, from the database, a complete HTML formatted logbook
|
||||
for the current year. Formats available are HTML2005. Other formats
|
||||
have been retired.
|
||||
for the current year. Format available is now just HTML2005.
|
||||
Other formats have been retired.
|
||||
|
||||
There are no images stored in the database. However links to images work in the HTML text of a logbook entry.
|
||||
|
||||
This function is the recipient of the POST action os the export form in the control panel
|
||||
This function is the recipient of the POST action as the export form in the control panel
|
||||
"""
|
||||
|
||||
def lbeKey(lbe):
|
||||
"""This function goes into a lexicographic sort function"""
|
||||
return str(lbe.date)
|
||||
"""This function goes into a lexicographic sort function - but where?!"""
|
||||
return str(lbe.slug) # now that slugs are tripid such as 2023-07-30b
|
||||
|
||||
if not request.method == "POST":
|
||||
return render(request, "controlPanel.html", {"expeditions": Expedition.objects.all(), "jobs_completed": ""})
|
||||
else:
|
||||
print(f"Logbook export {request.POST}")
|
||||
# print(f"Logbook export {request.POST}")
|
||||
|
||||
year = request.POST["year"]
|
||||
current_expedition = Expedition.objects.get(year=year)
|
||||
logbook_entries = LogbookEntry.objects.filter(expedition=current_expedition).order_by(
|
||||
"date"
|
||||
) # need to be sorted by date!
|
||||
filename = "logbook-new-format.html"
|
||||
|
||||
print(f"Logbook has {len(logbook_entries)} entries in it.")
|
||||
|
||||
extension = "html"
|
||||
response = HttpResponse(content_type="text/html")
|
||||
style = "2005"
|
||||
|
||||
filename = "logbook-new-format." + extension
|
||||
template = "logbook" + style + "style." + extension
|
||||
response["Content-Disposition"] = "attachment; filename=" + filename
|
||||
t = loader.get_template(template)
|
||||
logbookfile = t.render({"logbook_entries": logbook_entries})
|
||||
|
||||
endpath = Path(settings.EXPOWEB, "years", year, "endmatter.html")
|
||||
endmatter = ""
|
||||
if endpath.is_file():
|
||||
try:
|
||||
with open(endpath, "r") as end:
|
||||
endmatter = end.read()
|
||||
except:
|
||||
print(" ! Very Bad Error opening " + endpath)
|
||||
|
||||
frontpath = Path(settings.EXPOWEB, "years", year, "frontmatter.html")
|
||||
if frontpath.is_file():
|
||||
try:
|
||||
with open(frontpath, "r") as front:
|
||||
frontmatter = front.read()
|
||||
except:
|
||||
print(" ! Very Bad Error opening " + frontpath)
|
||||
logbookfile = re.sub(r"<body>", "<body>\n" + frontmatter + endmatter, logbookfile)
|
||||
else:
|
||||
logbookfile = re.sub(r"<body>", f"<body>\n<h1>Expo {year}</h1>\n" + endmatter, logbookfile)
|
||||
|
||||
dir = Path(settings.EXPOWEB) / "years" / year
|
||||
filepath = Path(dir, filename)
|
||||
with (open(filepath, "w")) as lb:
|
||||
lb.writelines(logbookfile)
|
||||
|
||||
# print(f'Logbook exported to {filepath}')
|
||||
writelogbook(year, filename)
|
||||
#response = HttpResponse(content_type="text/html")
|
||||
#response["Content-Disposition"] = "attachment; filename=" + filename
|
||||
completed = f'Logbook exported to <a href="/years/{filename}">{filename}</a>'
|
||||
|
||||
return render(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import subprocess
|
||||
import hashlib
|
||||
import string
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
@@ -8,8 +9,11 @@ from django.core.files.storage import FileSystemStorage
|
||||
from django.shortcuts import render, redirect
|
||||
|
||||
import settings
|
||||
from troggle.core.models.logbooks import LogbookEntry, PersonLogEntry
|
||||
from troggle.core.models.caves import GetCaveLookup
|
||||
from troggle.core.models.logbooks import LogbookEntry, writelogbook, PersonLogEntry
|
||||
from troggle.core.models.survex import DrawingFile
|
||||
from troggle.core.models.troggle import DataIssue, Expedition, PersonExpedition
|
||||
from troggle.parsers.people import GetPersonExpeditionNameLookup, known_foreigner
|
||||
|
||||
# from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time*
|
||||
|
||||
@@ -21,11 +25,6 @@ and that core/forms.py contains Django class-based forms for caves and entrances
|
||||
"""
|
||||
|
||||
todo = """
|
||||
- munge the URL of images in the logbook entry so that they work from both the " /logbookedit/" page,
|
||||
the logbook /years/2023/ page and the logbook fragment page /logbookentry/<date>/
|
||||
Note that this munging has already been done when the entry is imported into the database, so
|
||||
when doing an online edit it has already been fixed.
|
||||
|
||||
- Ideally we should validate uploaded file as being a valid file type, not a dubious script or hack
|
||||
Validate image files using a magic recogniser in walletedit()
|
||||
https://pypi.org/project/reportlab/ or
|
||||
@@ -48,6 +47,89 @@ todo = """
|
||||
"""
|
||||
sha = hashlib.new('sha256')
|
||||
|
||||
def unique_slug(text, n):
|
||||
"""This gives each logbook entry a unique id based on the date+content, so the order of entries on a particular day
|
||||
does not matter. This is a change (August 2023) from previous process.
|
||||
|
||||
2 hex digits would seem adequate for each expo day, but we might get a collision.
|
||||
The hash is based on the content after substitution of <p> so should be stable. Which means these ids
|
||||
can be used elsewhere in the troggle system as permanent slugs.
|
||||
|
||||
When SAVING an edited entry (as opposed to a new one) we will have a different hash so we will have to
|
||||
delete the original database object
|
||||
"""
|
||||
sha.update(text.encode('utf-8'))
|
||||
return sha.hexdigest()[0:n]
|
||||
|
||||
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}")
|
||||
|
||||
alphabet = list(string.ascii_lowercase)
|
||||
tid = f"{date}{alphabet[n]}"
|
||||
print(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
|
||||
|
||||
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"{place} {cave=}")
|
||||
|
||||
if LogbookEntry.objects.filter(slug=slug).exists():
|
||||
# oops.
|
||||
message = " ! - DUPLICATE SLUG for logbook entry " + tripdate + " - " + slug
|
||||
DataIssue.objects.create(parser="logbooks", message=message)
|
||||
slug = slug + "_" + unique_slug(text,2)
|
||||
|
||||
nonLookupAttribs = {
|
||||
"place": place,
|
||||
"text": text,
|
||||
"expedition": expedition,
|
||||
"time_underground": tu,
|
||||
"cave_slug": str(cave),
|
||||
}
|
||||
lookupAttribs = {"slug": slug, "date": date, "title": title}
|
||||
|
||||
lbo = LogbookEntry.objects.create(**nonLookupAttribs, **lookupAttribs)
|
||||
|
||||
pt_list = []
|
||||
# These entities have to be PersonExpedition objects
|
||||
team = others.split(",")
|
||||
team.append(author)
|
||||
for name in team:
|
||||
name = name.strip()
|
||||
if name[0] != "*": # a name prefix of "*" is special, just a string.
|
||||
try:
|
||||
personyear = GetPersonExpeditionNameLookup(expedition).get(name.lower())
|
||||
if not personyear:
|
||||
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:
|
||||
lookupAttribs = {"personexpedition": personyear, "nickname_used": name, "logbook_entry": lbo} # lbo is primary key
|
||||
nonLookupAttribs = {"time_underground": tu, "is_logbook_entry_author": (name==author)}
|
||||
pt_list.append(PersonLogEntry(**nonLookupAttribs, **lookupAttribs))
|
||||
|
||||
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)
|
||||
|
||||
class FilesForm(forms.Form): # not a model-form, just a form-form
|
||||
uploadfiles = forms.FileField()
|
||||
@@ -70,9 +152,7 @@ def logbookedit(request, year=None, slug=None):
|
||||
"""Edit a logbook entry
|
||||
This is daft: we have the parsed identity of the person and we render it to text as 'nickname_used'
|
||||
(or, previously, 'fullname'), to be re-parsed on re-importing.
|
||||
And there is no guarantee that this will be the same thing.
|
||||
|
||||
Someone can put in a nickname which is invalid (e.g. 2 Sophies on expo). When is this checked?
|
||||
And there is no guarantee that this will be the same thing. Oh well.
|
||||
"""
|
||||
def clean_tu(tu):
|
||||
if tu =="":
|
||||
@@ -82,28 +162,14 @@ def logbookedit(request, year=None, slug=None):
|
||||
except:
|
||||
return 0
|
||||
return tu
|
||||
|
||||
def unique_id(text, n):
|
||||
"""This gives each logbook entry a unique id based on the date+content, so the order of entries on a particular day
|
||||
does not matter. This is a change (August 2023) from previous process.
|
||||
Otherwise we could get 2023-07-20a and 2023-07-20b swapped on exporting and re-importing logbooks
|
||||
because the database does not record precedence.
|
||||
2 hex digits would seem adequate for each expo day, but we might get a collision.
|
||||
The hash is based on the content after substitution of <p> so should be stable. Which means these ids
|
||||
can be used elsewhere in the troggle system as permanent slugs.
|
||||
|
||||
When SAVING an edited entry (as opposed to a new one) we will have a different hash so we will have to
|
||||
delete the original database object
|
||||
"""
|
||||
sha.update(text.encode('utf-8'))
|
||||
return sha.hexdigest()[0:n]
|
||||
|
||||
if not year:
|
||||
if not slug:
|
||||
year = 2023
|
||||
year = 2023 # we need a CURRENT_EXPO() function, we use this in a lot of places..
|
||||
else:
|
||||
year = slug[0:4]
|
||||
print(year)
|
||||
author = ""
|
||||
|
||||
if request.method == "POST":
|
||||
form = LogbookEditForm(request.POST)
|
||||
@@ -112,11 +178,13 @@ def logbookedit(request, year=None, slug=None):
|
||||
print(message)
|
||||
return render(request, "errors/generic.html", {"message": message})
|
||||
else:
|
||||
# if there is no slug then this is 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
|
||||
others = request.POST["others"].strip() # TODO check each against personexpedition
|
||||
place = request.POST["place"].strip().replace('-','=') # no hyphens !
|
||||
place = request.POST["place"].strip().replace(' - ',' = ') # no hyphens !
|
||||
title = request.POST["title"].strip()
|
||||
entry = request.POST["text"].strip()
|
||||
entry = entry.replace('\r','') # remove HTML-standard CR inserted
|
||||
@@ -135,16 +203,30 @@ def logbookedit(request, year=None, slug=None):
|
||||
dateflag = True
|
||||
date = odate.isoformat()
|
||||
|
||||
newslug = f"{date}_{unique_id(entry,2)}"
|
||||
if slug:
|
||||
if slug != newslug:
|
||||
print(f"! Entry id changed! from {slug} to {newslug}")
|
||||
if not slug:
|
||||
# Creating a new logbook entry with all the gubbins
|
||||
slug = create_new_lbe_slug(date)
|
||||
else:
|
||||
# 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 recreate it and all its links
|
||||
print(f"- Deleting the LogBookEntry {slug}")
|
||||
LogbookEntry.objects.filter(slug=slug).delete()
|
||||
|
||||
print(f"- Creating the LogBookEntry {slug}")
|
||||
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:
|
||||
writelogbook(year, filename) # uses a template, not the code fragment below
|
||||
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})
|
||||
|
||||
# OK this could be done by rendering a template, but for such a small bit of HTML, it is easier to have
|
||||
# it all in one place: here
|
||||
# Code fragment illustration - not actually what gets saved to database
|
||||
output = f'''
|
||||
|
||||
<div class="tripdate" id="{newslug}">{date}</div>
|
||||
<div class="tripdate" id="{slug}">{date}</div>
|
||||
<div class="trippeople"><u>{author}</u>, {others}</div>
|
||||
<div class="triptitle">{place} - {title}</div>
|
||||
|
||||
@@ -154,6 +236,65 @@ def logbookedit(request, year=None, slug=None):
|
||||
<hr />
|
||||
|
||||
'''
|
||||
# Successful POST
|
||||
# So save to database and then write out whole new logbook.html file
|
||||
|
||||
#TO DO author and team validation, and check that 'place' is not deleted and that *bloke not forgotten
|
||||
git = settings.GIT
|
||||
dirpath = Path(settings.EXPOWEB) / "years" / 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 with git on server for {filename}. {slug} edits saved, added to git, but NOT committed.\n"
|
||||
+ msgdata
|
||||
)
|
||||
print(message)
|
||||
return render(request, "errors/generic.html", {"message": message})
|
||||
|
||||
return render(
|
||||
request,
|
||||
"logbookform.html",
|
||||
@@ -212,10 +353,10 @@ def logbookedit(request, year=None, slug=None):
|
||||
"tu": tu,
|
||||
"entry": text,
|
||||
"textrows": rows,
|
||||
#"output": output,
|
||||
},
|
||||
},
|
||||
)
|
||||
else:
|
||||
else: # no slug
|
||||
# NEW logbook entry
|
||||
return render(
|
||||
request,
|
||||
"logbookform.html",
|
||||
|
||||
Reference in New Issue
Block a user