refactor & git author work

This commit is contained in:
2024-12-29 03:42:58 +00:00
parent 13c5d14a9f
commit 6d2484376a
6 changed files with 64 additions and 130 deletions
+1 -11
View File
@@ -11,7 +11,7 @@ import settings
from troggle.core.models.logbooks import QM from troggle.core.models.logbooks import QM
from troggle.core.models.survex import SurvexStation, utmToLatLng from troggle.core.models.survex import SurvexStation, utmToLatLng
from troggle.core.models.troggle import DataIssue, TroggleModel from troggle.core.models.troggle import DataIssue, TroggleModel
from troggle.core.utils import TROG, parse_aliases #, writetrogglefile 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. # 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"] Gcavelookup = TROG["caves"]["gcavelookup"]
@@ -224,11 +224,6 @@ class Cave(TroggleModel):
content = t.render(c) content = t.render(c)
return (filepath, content, "utf8") return (filepath, content, "utf8")
# def writeDataFile(self):
# filepath, content, coding = self.file_output()
# writetrogglefile(filepath, content)
# return
class Entrance(TroggleModel): class Entrance(TroggleModel):
MARKING_CHOICES = ( MARKING_CHOICES = (
("P", "Paint"), ("P", "Paint"),
@@ -406,11 +401,6 @@ class Entrance(TroggleModel):
content = t.render(c) content = t.render(c)
return (filepath, content, "utf8") return (filepath, content, "utf8")
# def writeDataFile(self):
# filepath, content, coding = self.file_output()
# writetrogglefile(filepath, content)
# return
def url_parent(self): def url_parent(self):
if self.url: if self.url:
return self.url.rsplit("/", 1)[0] return self.url.rsplit("/", 1)[0]
+20 -106
View File
@@ -127,7 +127,7 @@ def make_new_expo_dir(year):
content = f"<html><head><title>{ff}</title></head><body><h1>{ff}</h1>{t}</body></html>" content = f"<html><head><title>{ff}</title></head><body><h1>{ff}</h1>{t}</body></html>"
p = Path(year_dir, ff+".html") p = Path(year_dir, ff+".html")
if not p.is_file(): if not p.is_file():
writetrogglefile(p, content, commit_msg="Auto new year file creation") write_and_commit( [(p, content, "utf8")], f"Auto new year {ff} file creation", "Auto New Year <make_new_expo_dir@troggle.expo>")
def current_expo(): def current_expo():
@@ -233,7 +233,7 @@ def git_add(filename, cwd, commands=[]):
# what is the purpose of this 'git diff' ? To prevent merge conflicts happening I guess, # what is the purpose of this 'git diff' ? To prevent merge conflicts happening I guess,
# so we do not have to reverse a 'git add' # so we do not have to reverse a 'git add'
print(f"git diff {filename}") print(f"git diff {filename} in {cwd}")
cmd_diff = [git, "diff", filename] cmd_diff = [git, "diff", filename]
commands.append(cmd_diff) commands.append(cmd_diff)
cp_diff = subprocess.run(cmd_diff, cwd=cwd, capture_output=True, text=True) cp_diff = subprocess.run(cmd_diff, cwd=cwd, capture_output=True, text=True)
@@ -243,7 +243,7 @@ def git_add(filename, cwd, commands=[]):
f"CANNOT git ADD on server for this file {filename}.\n\n" + msgdata f"CANNOT git ADD on server for this file {filename}.\n\n" + msgdata
) )
print(f"git add {filename}") print(f"git add {filename} in {cwd}")
cmd_add = [git, "add", filename] cmd_add = [git, "add", filename]
commands.append(cmd_add) commands.append(cmd_add)
cp_add = subprocess.run(cmd_add, cwd=cwd, capture_output=True, text=True) cp_add = subprocess.run(cmd_add, cwd=cwd, capture_output=True, text=True)
@@ -267,6 +267,11 @@ def git_commit(cwd, message, editor, commands=[]):
cp_commit = subprocess.run(cmd_commit, cwd=cwd, capture_output=True, text=True) cp_commit = subprocess.run(cmd_commit, cwd=cwd, capture_output=True, text=True)
# This produces return code = 1 if it commits OK, but when the local repo still needs to be pushed to origin/repo # This produces return code = 1 if it commits OK, but when the local repo still needs to be pushed to origin/repo
# which will be the case when running a test troggle system on a development machine # which will be the case when running a test troggle system on a development machine
# Several ways of testing if the commit failed
#This produces return code = 1 if it commits OK, but when the repo still needs to be pushed to origin/expoweb
# if (not cp_commit.stdout) or len(cp_commit.stdout) < 2 or cp_commit.stdout.split("\n")[-2] != "nothing to commit, working tree clean":
if cp_commit.returncode == 1 and cp_commit.stdout == DEV_OK: # only good for 1 commit ahead of origin/repo if cp_commit.returncode == 1 and cp_commit.stdout == DEV_OK: # only good for 1 commit ahead of origin/repo
pass pass
else: else:
@@ -303,20 +308,23 @@ def add_commit(fname, message, editor=None):
raise WriteAndCommitError(msg) raise WriteAndCommitError(msg)
def write_and_commit(files, message, editor=None): def write_and_commit(files, message, editor):
"""Writes the content to the filepath and adds and commits the file to git. If this fails, a WriteAndCommitError is raised. """For each file in files, it writes the content to the filepath
and adds and commits the file to git.
filepath, content, encoding = file
If this fails, a WriteAndCommitError is raised.
This needs refactoring to just write and then call add_commit() This needs refactoring to just write to the filesystem and then call git_add()
for each file, and then git_commit() at the end.
message - the "-m" comment field for the git commit
editor - the "--author" field for the git commit
""" """
# GIT see also core/views/uploads.py dwgupload() # GIT see also core/views/uploads.py dwgupload()
# GIT see also core/views/expo.py editexpopage() # GIT see also core/views/expo.py editexpopage()
git = settings.GIT git = settings.GIT
commands = [] commands = []
if editor: editor = git_string(editor)
editor = git_string(editor)
else:
# cannot happen as form verification has this as an obligatory field
editor = "Write_and_commit <automaton@potatohut.expo>"
try: try:
for filepath, content, encoding in files: for filepath, content, encoding in files:
cwd = filepath.parent cwd = filepath.parent
@@ -345,56 +353,10 @@ def write_and_commit(files, message, editor=None):
raise WriteAndCommitError( raise WriteAndCommitError(
f"CANNOT write this file {filepath}. Ask a nerd to fix this: {e}" f"CANNOT write this file {filepath}. Ask a nerd to fix this: {e}"
) )
commands = git_add(filename, cwd, commands)
# what is the purpose of this 'git diff' ? To prevent merge conflicts happening I guess,
# so we do not have to reverse a 'git add'
cmd_diff = [git, "diff", filename]
cp_diff = subprocess.run(cmd_diff, cwd=cwd, capture_output=True, text=True)
commands.append(cmd_diff)
if cp_diff.returncode == 0:
cmd_add = [git, "add", filename]
cp_add = subprocess.run(cmd_add, cwd=cwd, capture_output=True, text=True)
commands.append(cmd_add)
git_add_returncode = ""
if cp_add.returncode != 0:
git_add_returncode = cp_add.returncode
msgdata = (
"Ask a nerd to fix this.\n\nstderr:\n"
+ cp_add.stderr
+ "\n\nstdout:\n"
+ cp_add.stdout
+ "\n\nreturn code: "
+ str(cp_add.returncode)
)
raise WriteAndCommitError(
f"PROBLEM with git on server for {filename}. Edits saved but [possibly] not added to git.\n\n"
+ msgdata
)
else:
print(f"No change {filepath}")
filepaths = [filepath for filepath, content, encoding in files]
# message = message + " " + str(filepaths)
commands = git_commit(cwd, message, editor, commands) commands = git_commit(cwd, message, editor, commands)
if False:
cmd_status = [git, "status"] # + filepaths
cp_status = subprocess.run(cmd_status, cwd=cwd, capture_output=True, text=True)
commands.append(cp_status)
#This produces return code = 1 if it commits OK, but when the repo still needs to be pushed to origin/expoweb
if (not cp_status.stdout) or len(cp_status.stdout) < 2 or cp_status.stdout.split("\n")[-2] != "nothing to commit, working tree clean":
msgdata = (
str(commands) +
"Ask a nerd to fix this.\n\n"
+ "Stderr: " + cp_status.stderr
+ "\n\n"
+ "Stdout: " + cp_status.stdout
+ "\n\nreturn code: " + str(cp_status.returncode)
)
raise WriteAndCommitError(
f"Error code with git on server for this file {filename}. Edits saved, added to git, but NOT committed. Git status not clean.\n\n"
+ msgdata
)
except subprocess.SubprocessError: except subprocess.SubprocessError:
raise WriteAndCommitError( raise WriteAndCommitError(
f"CANNOT git on server for this file {filename}. Subprocess error. Edits not saved.\nAsk a nerd to fix this." f"CANNOT git on server for this file {filename}. Subprocess error. Edits not saved.\nAsk a nerd to fix this."
@@ -412,54 +374,6 @@ class WriteAndCommitError(Exception):
return f"WriteAndCommitError: {self.message}" return f"WriteAndCommitError: {self.message}"
def writetrogglefile(filepath, filecontent, commit_msg=None):
"""
REPLACE with call to write_and_commit + any necessary setup
used only by cave editor in
core/models/caves.py
and by
make_new_expo_dir(year)
but NOT by core/views/caves.py
Commit the new saved file to git
"""
# GIT see also core/views/expo.py editexpopage()
# GIT see also core/views/uploads.py dwgupload()
filepath = Path(filepath)
cwd = filepath.parent
filename = filepath.name
git = settings.GIT
# do not trap exceptions, pass them up to the view that called this function
print(f"WRITING{cwd}---{filename} ")
with open(filepath, "w") as f:
f.write(filecontent)
# os.chmod(filepath, 0o664) # set file permissions to rw-rw-r--
sp = subprocess.run([git, "add", filename], cwd=cwd, capture_output=True, check=True, text=True)
if sp.returncode != 0:
out = sp.stdout
if len(out) > 160:
out = out[:75] + "\n <Long output curtailed>\n" + out[-75:]
print(f"git ADD {cwd}:\n\n" + str(sp.stderr) + "\n\n" + out + "\n\nreturn code: " + str(sp.returncode))
if not commit_msg:
commit_msg = f"Troggle online: cave or entrance edit -{filename}"
sp = subprocess.run(
[git, "commit", "-m", commit_msg],
cwd=cwd,
capture_output=True,
check=True,
text=True,
)
if sp.returncode != 0:
out = sp.stdout
if len(out) > 160:
out = out[:75] + "\n <Long output curtailed>\n" + out[-75:]
print(f"git COMMIT {cwd}:\n\n" + str(sp.stderr) + "\n\n" + out + "\n\nreturn code: " + str(sp.returncode))
# not catching and re-raising any exceptions yet, inc. the stderr etc.,. We should do that.
"""The following is a Bard converted version of Radosts's MIT copyrighted Javascript on 2023-10-27 """The following is a Bard converted version of Radosts's MIT copyrighted Javascript on 2023-10-27
with hand-editing. with hand-editing.
+17 -4
View File
@@ -11,7 +11,7 @@ from django.views.decorators.csrf import ensure_csrf_cookie
from PIL import Image from PIL import Image
import troggle.settings as settings import troggle.settings as settings
from troggle.core.utils import WriteAndCommitError, write_and_commit from troggle.core.utils import COOKIE_MAX_AGE, WriteAndCommitError, get_cookie, git_string, write_and_commit
from .auth import login_required_if_public from .auth import login_required_if_public
@@ -97,12 +97,16 @@ def new_image_form(request, path):
"""Manages a form to upload new images""" """Manages a form to upload new images"""
directory = get_dir(path) directory = get_dir(path)
print(f"new_image_form(): {directory=} {path=}") print(f"new_image_form(): {directory=} {path=}")
editor = get_cookie(request)
if request.method == "POST": if request.method == "POST":
print(f"new_image_form(): POST ") print(f"new_image_form(): POST ")
form = NewWebImageForm(request.POST, request.FILES, directory=directory) form = NewWebImageForm(request.POST, request.FILES, directory=directory)
if form.is_valid(): if form.is_valid():
print(f"new_image_form(): form is valid ") print(f"new_image_form(): form is valid ")
print(f"new_image_form(): files: {request.FILES['file_']}") print(f"new_image_form(): files: {request.FILES['file_']}")
editor = form.cleaned_data["who_are_you"]
editor = git_string(editor)
f = request.FILES["file_"] f = request.FILES["file_"]
binary_data = io.BytesIO() binary_data = io.BytesIO()
for chunk in f.chunks(): for chunk in f.chunks():
@@ -171,6 +175,7 @@ def new_image_form(request, path):
(thumb_path, tb.getbuffer(), False), (thumb_path, tb.getbuffer(), False),
], ],
f"{change_message} - online adding of an image", f"{change_message} - online adding of an image",
editor # this works, a new who_are_you typed on the Image form is used as the git comment
) )
except WriteAndCommitError as e: except WriteAndCommitError as e:
print(f"new_image_form(): WriteAndCommitError: {e.message}") print(f"new_image_form(): WriteAndCommitError: {e.message}")
@@ -183,10 +188,12 @@ def new_image_form(request, path):
html_snippet = linked_image_template.render( html_snippet = linked_image_template.render(
{"thumbnail_url": f"/{thumb_rel_path}", "page_url": f"/{desc_rel_path}"}, request {"thumbnail_url": f"/{thumb_rel_path}", "page_url": f"/{desc_rel_path}"}, request
) )
return JsonResponse({"html": html_snippet}) j_response = JsonResponse({"html": html_snippet})
j_response.set_cookie('editor_id', editor, max_age=COOKIE_MAX_AGE) # does not seem to work updating who_are_you cookie
return j_response
else: else:
print(f"new_image_form(): not POST ") print(f"new_image_form(): not POST ")
form = NewWebImageForm(directory=directory) form = NewWebImageForm(directory=directory, initial={"who_are_you":editor})
print(f"new_image_form(): POST and not POST ") print(f"new_image_form(): POST and not POST ")
template = loader.get_template("new_image_form.html") template = loader.get_template("new_image_form.html")
htmlform = template.render({"form": form, "path": path}, request) htmlform = template.render({"form": form, "path": path}, request)
@@ -212,7 +219,13 @@ class NewWebImageForm(forms.Form):
widget=forms.TextInput(attrs={"size": "60", "placeholder": "Year photo was taken"}), required=False widget=forms.TextInput(attrs={"size": "60", "placeholder": "Year photo was taken"}), required=False
) )
change_message = forms.CharField( change_message = forms.CharField(
widget=forms.Textarea(attrs={"cols": 80, "rows": 3, "placeholder": "Descibe the change made (for git)"}) widget=forms.Textarea(attrs={"cols": 80, "rows": 3, "placeholder": "Describe the change made (for git)"})
)
who_are_you = forms.CharField(
widget=forms.TextInput(
attrs={"size": 60, "placeholder": "You are editing this page, who are you ? e.g. 'Becka' or 'Animal <mta@gasthof.expo>'",
"style": "vertical-align: text-top;"}
)
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
-1
View File
@@ -80,7 +80,6 @@ def mapfile(request, path):
return render(request, "errors/generic.html", {"message": message}) return render(request, "errors/generic.html", {"message": message})
def expofilessingle(request, filepath): def expofilessingle(request, filepath):
"""sends a single binary file to the user, if not found, show the parent directory """sends a single binary file to the user, if not found, show the parent directory
If the path actually is a directory, then show that. If the path actually is a directory, then show that.
+17 -4
View File
@@ -12,7 +12,7 @@ from troggle.core.models.caves import GetCaveLookup
from troggle.core.models.logbooks import LogbookEntry, PersonLogEntry, writelogbook from troggle.core.models.logbooks import LogbookEntry, PersonLogEntry, writelogbook
from troggle.core.models.survex import DrawingFile from troggle.core.models.survex import DrawingFile
from troggle.core.models.troggle import DataIssue, Expedition, PersonExpedition from troggle.core.models.troggle import DataIssue, Expedition, PersonExpedition
from troggle.core.utils import alphabet_suffix, current_expo, 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 troggle.parsers.people import GetPersonExpeditionNameLookup, known_foreigner
# from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time* # from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time*
@@ -150,6 +150,12 @@ class ExpofileRenameForm(forms.Form): # not a model-form, just a form-form
class ExpotextfileForm(forms.Form): # not a model-form, just a form-form class ExpotextfileForm(forms.Form): # not a model-form, just a form-form
text = forms.CharField(strip=True, required=False) text = forms.CharField(strip=True, required=False)
who_are_you = forms.CharField(
widget=forms.TextInput(
attrs={"size": 100, "placeholder": "You are editing this page, who are you ? e.g. 'Wookey' or 'Animal <mta@gasthof.expo>'",
"style": "vertical-align: text-top;"}
)
)
class LogbookEditForm(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) author = forms.CharField(strip=True, required=False)
@@ -160,7 +166,8 @@ def edittxtpage(request, path, filepath):
Yes this is a security hazard as arbitrary text can be uploaded and it is not enclosed in any HTML furniture. Yes this is a security hazard as arbitrary text can be uploaded and it is not enclosed in any HTML furniture.
""" """
def simple_get(viewtext): def simple_get(viewtext):
form = ExpotextfileForm() print(f"simple_get {editor=}")
form = ExpotextfileForm(initial={"who_are_you":editor})
return render( return render(
request, request,
"textfileform.html", "textfileform.html",
@@ -188,6 +195,7 @@ def edittxtpage(request, path, filepath):
print(message) print(message)
return render(request, "errors/generic.html", {"message": message}) return render(request, "errors/generic.html", {"message": message})
editor = get_cookie(request)
if request.method == "GET": if request.method == "GET":
return simple_get(originaltext) return simple_get(originaltext)
@@ -198,6 +206,9 @@ def edittxtpage(request, path, filepath):
print(message) print(message)
return render(request, "errors/generic.html", {"message": message}) return render(request, "errors/generic.html", {"message": message})
else: else:
editor = form.cleaned_data["who_are_you"]
editor = git_string(editor)
# for i in request.POST: # for i in request.POST:
# print(":: ",i, " => ", request.POST[i]) # print(":: ",i, " => ", request.POST[i])
newtext = request.POST["text"] newtext = request.POST["text"]
@@ -215,7 +226,7 @@ def edittxtpage(request, path, filepath):
if newtext != originaltext: # Check if content has changed at all if newtext != originaltext: # Check if content has changed at all
print("text changed.. saving and committing") print("text changed.. saving and committing")
try: try:
write_and_commit([(filepath, newtext, "utf-8")], f"Online edit of {path}") write_and_commit([(filepath, newtext, "utf-8")], f"Online edit of {path}", editor)
except WriteAndCommitError as e: except WriteAndCommitError as e:
return render(request, "errors/generic.html", {"message": e.message}) return render(request, "errors/generic.html", {"message": e.message})
@@ -228,7 +239,9 @@ def edittxtpage(request, path, filepath):
return render(request, "errors/generic.html", {"message": e.message}) return render(request, "errors/generic.html", {"message": e.message})
savepath = "/" + path savepath = "/" + path
print(f"redirect {savepath}") print(f"redirect {savepath}")
return redirect(savepath) # Redirect after POST response = redirect(savepath) # Redirect after POST
response.set_cookie('editor_id', editor, max_age=COOKIE_MAX_AGE) # cookie expires after COOKIE_MAX_AGE seconds
return response
else: else:
# no changes # no changes
+5
View File
@@ -28,6 +28,11 @@ Full path on server: {{filepath}}
<br> <br>
[Edit the text by just typing in the box.] [Edit the text by just typing in the box.]
<br /><br />
<div>
<label for="id_who_are_you">Who are you:</label><br />
{{form.who_are_you}}
</div><br />
<button class="fancybutton2" style="padding: 0.5em 25px; margin-left: 110px" name="Save" type = "submit" <button class="fancybutton2" style="padding: 0.5em 25px; margin-left: 110px" name="Save" type = "submit"
title="Saves the file in UTF-8 characterset" value = "Save" > title="Saves the file in UTF-8 characterset" value = "Save" >