2
0
mirror of https://expo.survex.com/repositories/troggle/.git synced 2024-11-25 08:41:51 +00:00

fixed many problems in creating new entrances

This commit is contained in:
Philip Sargent 2023-11-07 02:12:57 +02:00
parent bd0a9332df
commit 07d9365747
8 changed files with 136 additions and 53 deletions

View File

@ -210,7 +210,7 @@ class EntranceLetterForm(ModelForm):
""" """
# This only needs to be required=True for the second and subsequent entrances, not the first. Tricky. # This only needs to be required=True for the second and subsequent entrances, not the first. Tricky.
entranceletter = forms.CharField(required=True, widget=forms.TextInput(attrs={"size": "2"})) entranceletter = forms.CharField(required=False, widget=forms.TextInput(attrs={"size": "2"}))
class Meta: class Meta:
model = CaveAndEntrance model = CaveAndEntrance

View File

@ -198,22 +198,13 @@ class Cave(TroggleModel):
res += "–" + prevR res += "–" + prevR
return res return res
def writeDataFile(self):
"""Seems to be a duplicate of file_output() ?!
REFACTOR"""
filepath = os.path.join(settings.CAVEDESCRIPTIONS, self.filename)
t = loader.get_template("dataformat/cave.xml")
now = datetime.now(timezone.utc)
print(now)
c = dict({"cave": self, "date": now})
u = t.render(c)
writetrogglefile(filepath, u)
return
def file_output(self): def file_output(self):
"""This produces the content which wll be re-saved as the cave_data html file. """This produces the content which wll be re-saved as the cave_data html file.
""" """
if not self.filename:
self.filename = self.slug() + ".html"
self.save()
filepath = Path(settings.CAVEDESCRIPTIONS, self.filename) filepath = Path(settings.CAVEDESCRIPTIONS, self.filename)
t = loader.get_template("dataformat/cave.xml") t = loader.get_template("dataformat/cave.xml")
@ -222,6 +213,11 @@ 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"),
@ -388,6 +384,9 @@ class Entrance(TroggleModel):
return Path(settings.ENTRANCEDESCRIPTIONS, self.filename) return Path(settings.ENTRANCEDESCRIPTIONS, self.filename)
def file_output(self): def file_output(self):
if not self.filename:
self.filename = self.slug + ".html"
self.save()
filepath = Path(os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename)) filepath = Path(os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename))
t = loader.get_template("dataformat/entrance.xml") t = loader.get_template("dataformat/entrance.xml")
@ -397,13 +396,8 @@ class Entrance(TroggleModel):
return (filepath, content, "utf8") return (filepath, content, "utf8")
def writeDataFile(self): def writeDataFile(self):
filepath = os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename) filepath, content, coding = self.file_output()
writetrogglefile(filepath, content)
t = loader.get_template("dataformat/entrance.xml")
now = datetime.now(timezone.utc)
c = dict({"entrance": self, "date": now})
u = t.render(c)
writetrogglefile(filepath, u)
return return
def url_parent(self): def url_parent(self):

View File

@ -152,6 +152,8 @@ def write_and_commit(files, message):
# 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()
os.makedirs(os.path.dirname(filepath), exist_ok = True) os.makedirs(os.path.dirname(filepath), exist_ok = True)
if filepath.is_dir():
return False
if encoding: if encoding:
mode = "w" mode = "w"
kwargs = {"encoding": encoding} kwargs = {"encoding": encoding}
@ -160,7 +162,7 @@ def write_and_commit(files, message):
kwargs = {} kwargs = {}
try: try:
with open(filepath, mode, **kwargs) as f: with open(filepath, mode, **kwargs) as f:
print(f"WRITING {cwd}---{filename} ") print(f"WRITING {cwd}/{filename} ")
f.write(content) f.write(content)
except PermissionError: except PermissionError:
raise WriteAndCommitError( raise WriteAndCommitError(
@ -226,6 +228,7 @@ def write_and_commit(files, message):
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."
) )
return True
class WriteAndCommitError(Exception): class WriteAndCommitError(Exception):

View File

@ -475,6 +475,30 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
GET RID of all this entranceletter stuff. Far too overcomplexified. GET RID of all this entranceletter stuff. Far too overcomplexified.
We don't need it. Just the entrance slug is fine, then check uniqueness. We don't need it. Just the entrance slug is fine, then check uniqueness.
""" """
def check_new_slugname_ok(slug, letter):
"""In Nov.2023 it is possible to create a 2nd entrance and not set an entrance letter,
which leads to a constraint uniqueness crash. FIX THIS.
The letter may be set to an existing letter, OR it may be unset, but there may
be an existing unlettered single entrance. Both of these will crash unless fixed.
"""
slugname = f"{slug}{letter}"
nents = Entrance.objects.filter(slug=slugname).count()
print(f"NUM ents {slugname=} => {nents}")
if nents == 0:
# looks good, but we need to check the CaveaAndEntrance object too
e = entrance #Entrance.objects.get(slug=slugname) # does not exist yet!
gcl = GetCaveLookup()
c = gcl[slug]
nce = CaveAndEntrance.objects.filter(cave=c, entrance=e).count()
if nce == 0 :
return slugname, letter
# That entrance already exists, or the CE does, OK.. do recursive call, starting at "b"
if letter =="":
return check_new_slugname_ok(slug, "b")
else:
nextletter = chr(ord(letter)+1)
return check_new_slugname_ok(slug, nextletter)
try: try:
cave = Cave.objects.get(caveslug__slug=caveslug) cave = Cave.objects.get(caveslug__slug=caveslug)
@ -495,42 +519,60 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
caveAndEntrance = CaveAndEntrance.objects.get(entrance=entrance, cave=cave) caveAndEntrance = CaveAndEntrance.objects.get(entrance=entrance, cave=cave)
entlettereditable = False entlettereditable = False
else: else:
caveAndEntrance = CaveAndEntrance(cave=cave, entrance=Entrance()) caveAndEntrance = CaveAndEntrance(cave=cave, entrance=Entrance()) # creates a new Entrance object as well as a new CE object
entlettereditable = True entlettereditable = True
if caveAndEntrance.entranceletter == "" and cave.entrances().count() > 0 : ce = caveAndEntrance
if ce.entranceletter == "" and cave.entrances().count() > 0 :
# this should not be blank on a multiple-entrance cave # this should not be blank on a multiple-entrance cave
# but it doesn't trigger the entrance letter form unless entletter has a value # but it doesn't trigger the entrance letter form unless entletter has a value
entlettereditable = True entlettereditable = True # but the user has to remember to actually set it...
print(f"{entlettereditable=}") print(f"{entlettereditable=}")
# if the entletter is no editable, then the entletterform does not appear and so is always invalid. # if the entletter is not editable, then the entletterform does not appear and so is always invalid.
if request.POST: if request.POST:
print(f"POST Online edit of entrance: '{entrance}' where {cave=}") print(f"POST Online edit of entrance: '{entrance}' where {cave=}")
entform = EntranceForm(request.POST, instance=entrance) entform = EntranceForm(request.POST, instance=entrance)
if not entlettereditable: if not entlettereditable:
entranceletter = caveAndEntrance.entranceletter entranceletter = ce.entranceletter
ce = caveAndEntrance
else: else:
entletterform = EntranceLetterForm(request.POST, instance=caveAndEntrance) entletterform = EntranceLetterForm(request.POST, instance=ce)
if entletterform.is_valid(): if entletterform.is_valid():
ce = entletterform.save(commit=False) ce = entletterform.save(commit=False)
entranceletter = entletterform.cleaned_data["entranceletter"] entranceletter = entletterform.cleaned_data["entranceletter"]
message = f"- POST valid {caveslug=} {entslug=} {path=} entletterform valid \n {entletterform=}."
print(message)
else: else:
print(f"- POST INVALID {caveslug=} {entslug=} {path=} entletterform invalid.") # maybe this doesn't matter? It just means entranceletter unset ?
return render(request, "errors/badslug.html", {"badslug": "entletter problem in edit_entrances()"}) # probably because 'Cave and entrance with this Cave and Entranceletter already exists.'
# if entform.is_valid() and entletterform.is_valid(): message = f"- POST INVALID {caveslug=} {entslug=} {path=} entletterform invalid \n{entletterform.errors=}\n{entletterform=}."
if entform.is_valid(): print(message)
# if entletterform.errors:
# for field in entletterform:
# for error in field.errors:
# print(f"ERR {field=} {error=}")
# return render(request, "errors/generic.html", {"message": message})
entranceletter=""
if not entform.is_valid():
message = f"- POST INVALID {caveslug=} {entslug=} {path=} entform valid:{entform.is_valid()} entletterform valid:{entletterform.is_valid()}"
entrance = entform.save(commit=False) entrance = entform.save(commit=False)
print(message)
return render(request, "errors/generic.html", {"message": message})
else:
print(f"- POST {caveslug=} {entslug=} {entranceletter=} {path=}") print(f"- POST {caveslug=} {entslug=} {entranceletter=} {path=}")
if entslug is None: if entslug is None:
# we are creating a new entrance
if entranceletter: if entranceletter:
slugname = cave.slug() + entranceletter slugname, letter = check_new_slugname_ok(cave.slug(), entranceletter)
print(f"- POST letter {entranceletter=}")
else: else:
slugname = cave.slug() slugname, letter = check_new_slugname_ok(cave.slug(), "")
ce.entranceletter = letter
entrance = ce.entrance # the one we created earlier
entrance.slug = slugname entrance.slug = slugname
entrance.cached_primary_slug = slugname entrance.cached_primary_slug = slugname
entrance.filename = slugname + ".html" entrance.filename = slugname + ".html"
@ -541,41 +583,48 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
try: try:
entrance.save() entrance.save()
except: except:
# fails with uniqueness constraint failure. Which is on CaveAndEntrance, not just on entrance, which is bizarre. # fails with uniqueness constraint failure. Which is on CaveAndEntrance, not just on entrance,
# which is confusing to a user who is just editing an Entrance.
# Can happen when user specifies an existing letter! (or none, when they should set one)
print(f"SAVE EXCEPTION FAIL {entrance=}") print(f"SAVE EXCEPTION FAIL {entrance=}")
print(f"CAVE {cave}") print(f"CAVE {cave}")
for ce in cave.entrances(): for ce in cave.entrances():
print(f"CAVE:{ce.cave} - ENT:{ce.entrance} - LETTER:'{ce.entranceletter}'") print(f"CAVE:{ce.cave} - ENT:{ce.entrance} - LETTER:'{ce.entranceletter}'")
raise raise
ce.entrance = entrance ce.entrance = entrance
# try not to do this:
# UNIQUE constraint failed: core_caveandentrance.cave_id, core_caveandentrance.entranceletter
ce.save() ce.save()
entrance_file = entrance.file_output() entrance_file = entrance.file_output()
cave_file = cave.file_output() cave_file = cave.file_output()
print(f"- POST WRITE letter: '{ce}' {entrance=}") print(f"- POST WRITE letter: '{ce}' {entrance=}")
write_and_commit([entrance_file, cave_file], f"Online edit of entrance {entrance.slug}") if write_and_commit([entrance_file, cave_file], f"Online edit of entrance {entrance.slug}"):
return HttpResponseRedirect("/" + cave.url) return HttpResponseRedirect("/" + cave.url)
else: # one of the forms is not valid else:
print(f"- POST INVALID {caveslug=} {entslug=} {path=} entform valid:{entform.is_valid()} entletterform valid:{entletterform.is_valid()}") efilepath, econtent, eencoding = entrance_file
cfilepath, ccontent, cencoding = cave_file
message = f"- FAIL write_and_commit \n entr:'{efilepath}'\n cave:'{cfilepath}'"
print(message)
return render(request, "errors/generic.html", {"message": message})
else: # GET the page, not POST, or if either of the forms were invalid when POSTed else: # GET the page, not POST, or if either of the forms were invalid when POSTed
entletterform = None entletterform = None
entletter = "" entletter = ""
print(f"ENTRANCE in db: entranceletter = '{caveAndEntrance.entranceletter}'") print(f"ENTRANCE in db: entranceletter = '{ce.entranceletter}'")
if entrance: if entrance:
# re-read entrance data from file. # re-read entrance data from file.
filename = str(entrance.slug +".html") filename = str(entrance.slug +".html")
ent = read_entrance(filename, ent=entrance) ent = read_entrance(filename, ent=entrance)
print(f"ENTRANCE from file: entranceletter = '{caveAndEntrance.entranceletter}'") print(f"ENTRANCE from file: entranceletter = '{ce.entranceletter}'")
entform = EntranceForm(instance=entrance) entform = EntranceForm(instance=entrance)
if entslug is None: if entslug is None:
entletterform = EntranceLetterForm() entletterform = EntranceLetterForm()
# print(f" Getting entletter from EntranceLetterForm") # print(f" Getting entletter from EntranceLetterForm")
else: else:
entletter = caveAndEntrance.entranceletter entletter = ce.entranceletter
if entletter == "": if entletter == "":
entletterform = EntranceLetterForm() entletterform = EntranceLetterForm()
print(f" Blank value: getting entletter from EntranceLetterForm") print(f" Blank value: getting entletter from EntranceLetterForm")
@ -619,7 +668,14 @@ def cave_debug(request):
{"ents": ents}, {"ents": ents},
) )
def caveslist(request):
caves = Cave.objects.all()
print("CAVESLIST")
return render(
request,
"caveslist.html",
{"caves": caves},
)
def get_entrances(request, caveslug): def get_entrances(request, caveslug):
try: try:
cave = Cave.objects.get(caveslug__slug=caveslug) cave = Cave.objects.get(caveslug__slug=caveslug)

29
templates/caveslist.html Normal file
View File

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Caves List page
<!-- caveslist.html - this text visible because this template has been included -->
{% endblock %}
{% block content %}
<h2 id="cmult">Caves</h2>
<p>Debug.. gets edited to whatever is needed by programmer..
<table>
<tr><th>Cave</th>
<th>cave filename</th>
</tr>
{% for c in caves %}
<tr>
<td>
{{c}}
</td>
<td>
{{c.filename}}
</td>
{% endfor %}
</table>
{% endblock %}

View File

@ -7,11 +7,11 @@
<h2>Troggle Error</h2> <h2>Troggle Error</h2>
</div> </div>
<a href="/piclinks/wkmob.htm"><img align="right" style="margin:10px" src="/images/wkmob.jpg"></a>
<div class='middle3 login'> <div class='middle3 login'>
<div class='space'></div> <div class='space'></div>
<div class='align-center'> <div class='align-center'>
<h3>There has been an error. This is either bad data or a bug in the software.</h3> <h3>There has been an error. This is either bad data troggle can't cope with (yet), or a bug in the software.</h3>
<h4> <h4>
<font color="red"> <font color="red">

View File

@ -6,7 +6,7 @@ from django.conf.urls.static import static
from troggle.core.views import statistics, survex from troggle.core.views import statistics, survex
from troggle.core.views.auth import expologin, expologout from troggle.core.views.auth import expologin, expologout
from troggle.core.views.caves import (cave3d, caveindex, entranceindex, from troggle.core.views.caves import (cave3d, caveindex, entranceindex, caveslist,
cavepage, caveslugfwd, cavepagefwd, caveQMs, edit_cave, cave_debug, cavepage, caveslugfwd, cavepagefwd, caveQMs, edit_cave, cave_debug,
edit_entrance, get_entrances, qm, expo_kml, expo_kmz) edit_entrance, get_entrances, qm, expo_kml, expo_kmz)
from troggle.core.views.drawings import dwgallfiles, dwgfilesingle from troggle.core.views.drawings import dwgallfiles, dwgfilesingle
@ -94,8 +94,9 @@ trogglepatterns = [
re_path(r'^caves$', caveindex, name="caveindex"), re_path(r'^caves$', caveindex, name="caveindex"),
re_path(r'^indxal.htm$', caveindex, name="caveindex"), # ~420 hrefs to this url in expoweb files re_path(r'^indxal.htm$', caveindex, name="caveindex"), # ~420 hrefs to this url in expoweb files
re_path(r'^people/?$', notablepersons, name="notablepersons"), re_path(r'^people/?$', notablepersons, name="notablepersons"),
path('caveslist', caveslist, name="caveslist"),
re_path(r'^entrances$', entranceindex, name="entranceindex"), path('entrances', entranceindex, name="entranceindex"),
re_path(r'^admin/doc/', include('django.contrib.admindocs.urls')), # needs docutils Python module (http://docutils.sf.net/). re_path(r'^admin/doc/', include('django.contrib.admindocs.urls')), # needs docutils Python module (http://docutils.sf.net/).
re_path(r'^admin/', admin.site.urls), # includes admin login & logout urls & /admin/jsi18n/ re_path(r'^admin/', admin.site.urls), # includes admin login & logout urls & /admin/jsi18n/