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.
entranceletter = forms.CharField(required=True, widget=forms.TextInput(attrs={"size": "2"}))
entranceletter = forms.CharField(required=False, widget=forms.TextInput(attrs={"size": "2"}))
class Meta:
model = CaveAndEntrance

View File

@ -198,22 +198,13 @@ class Cave(TroggleModel):
res += "–" + prevR
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):
"""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)
t = loader.get_template("dataformat/cave.xml")
@ -222,6 +213,11 @@ class Cave(TroggleModel):
content = t.render(c)
return (filepath, content, "utf8")
def writeDataFile(self):
filepath, content, coding = self.file_output()
writetrogglefile(filepath, content)
return
class Entrance(TroggleModel):
MARKING_CHOICES = (
("P", "Paint"),
@ -388,6 +384,9 @@ class Entrance(TroggleModel):
return Path(settings.ENTRANCEDESCRIPTIONS, self.filename)
def file_output(self):
if not self.filename:
self.filename = self.slug + ".html"
self.save()
filepath = Path(os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename))
t = loader.get_template("dataformat/entrance.xml")
@ -397,13 +396,8 @@ class Entrance(TroggleModel):
return (filepath, content, "utf8")
def writeDataFile(self):
filepath = os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename)
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)
filepath, content, coding = self.file_output()
writetrogglefile(filepath, content)
return
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/expo.py editexpopage()
os.makedirs(os.path.dirname(filepath), exist_ok = True)
if filepath.is_dir():
return False
if encoding:
mode = "w"
kwargs = {"encoding": encoding}
@ -160,7 +162,7 @@ def write_and_commit(files, message):
kwargs = {}
try:
with open(filepath, mode, **kwargs) as f:
print(f"WRITING {cwd}---{filename} ")
print(f"WRITING {cwd}/{filename} ")
f.write(content)
except PermissionError:
raise WriteAndCommitError(
@ -226,6 +228,7 @@ def write_and_commit(files, message):
raise WriteAndCommitError(
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):

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.
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:
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)
entlettereditable = False
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
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
# 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=}")
# 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:
print(f"POST Online edit of entrance: '{entrance}' where {cave=}")
entform = EntranceForm(request.POST, instance=entrance)
if not entlettereditable:
entranceletter = caveAndEntrance.entranceletter
ce = caveAndEntrance
entranceletter = ce.entranceletter
else:
entletterform = EntranceLetterForm(request.POST, instance=caveAndEntrance)
entletterform = EntranceLetterForm(request.POST, instance=ce)
if entletterform.is_valid():
ce = entletterform.save(commit=False)
entranceletter = entletterform.cleaned_data["entranceletter"]
message = f"- POST valid {caveslug=} {entslug=} {path=} entletterform valid \n {entletterform=}."
print(message)
else:
print(f"- POST INVALID {caveslug=} {entslug=} {path=} entletterform invalid.")
return render(request, "errors/badslug.html", {"badslug": "entletter problem in edit_entrances()"})
# if entform.is_valid() and entletterform.is_valid():
if entform.is_valid():
# maybe this doesn't matter? It just means entranceletter unset ?
# probably because 'Cave and entrance with this Cave and Entranceletter already exists.'
message = f"- POST INVALID {caveslug=} {entslug=} {path=} entletterform invalid \n{entletterform.errors=}\n{entletterform=}."
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)
print(message)
return render(request, "errors/generic.html", {"message": message})
else:
print(f"- POST {caveslug=} {entslug=} {entranceletter=} {path=}")
if entslug is None:
# we are creating a new entrance
if entranceletter:
slugname = cave.slug() + entranceletter
print(f"- POST letter {entranceletter=}")
slugname, letter = check_new_slugname_ok(cave.slug(), entranceletter)
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.cached_primary_slug = slugname
entrance.filename = slugname + ".html"
@ -541,41 +583,48 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
try:
entrance.save()
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"CAVE {cave}")
for ce in cave.entrances():
print(f"CAVE:{ce.cave} - ENT:{ce.entrance} - LETTER:'{ce.entranceletter}'")
raise
ce.entrance = entrance
# try not to do this:
# UNIQUE constraint failed: core_caveandentrance.cave_id, core_caveandentrance.entranceletter
ce.save()
entrance_file = entrance.file_output()
cave_file = cave.file_output()
print(f"- POST WRITE letter: '{ce}' {entrance=}")
write_and_commit([entrance_file, cave_file], f"Online edit of entrance {entrance.slug}")
return HttpResponseRedirect("/" + cave.url)
else: # one of the forms is not valid
print(f"- POST INVALID {caveslug=} {entslug=} {path=} entform valid:{entform.is_valid()} entletterform valid:{entletterform.is_valid()}")
if write_and_commit([entrance_file, cave_file], f"Online edit of entrance {entrance.slug}"):
return HttpResponseRedirect("/" + cave.url)
else:
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
entletterform = None
entletter = ""
print(f"ENTRANCE in db: entranceletter = '{caveAndEntrance.entranceletter}'")
print(f"ENTRANCE in db: entranceletter = '{ce.entranceletter}'")
if entrance:
# re-read entrance data from file.
filename = str(entrance.slug +".html")
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)
if entslug is None:
entletterform = EntranceLetterForm()
# print(f" Getting entletter from EntranceLetterForm")
else:
entletter = caveAndEntrance.entranceletter
entletter = ce.entranceletter
if entletter == "":
entletterform = EntranceLetterForm()
print(f" Blank value: getting entletter from EntranceLetterForm")
@ -619,7 +668,14 @@ def cave_debug(request):
{"ents": ents},
)
def caveslist(request):
caves = Cave.objects.all()
print("CAVESLIST")
return render(
request,
"caveslist.html",
{"caves": caves},
)
def get_entrances(request, caveslug):
try:
cave = Cave.objects.get(caveslug__slug=caveslug)

View File

@ -46,8 +46,8 @@ Black triangle <span style="color: black">&#x25B2;</span> against a name indicat
{% if cave.no_location %}<span title="the cave has an Entrance, but no entrances have located survey station" style="color: black">&#x25B2;</span>{% endif %}
{% else %}<span title="the cave has no Entrance (and is not 'pending')" style="color: orange">&#x25B2;</span>
{% endif %}
{% else %}<span title="the cave is 'pending' creation properly" style="color: #43C6DB">&#x25BC;</span>
{% endif %}
{% else %}<span title="the cave is 'pending' creation properly" style="color: #43C6DB">&#x25BC;</span>
{% endif %}
{% if cave.survex_file %}{% else %}<span title="no survex file is explicitly associated with the cave (but there might be a *fix somewhere)" style="color: red">*</span>{% endif %}
</td></tr>

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>
</div>
<a href="/piclinks/wkmob.htm"><img align="right" style="margin:10px" src="/images/wkmob.jpg"></a>
<div class='middle3 login'>
<div class='space'></div>
<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>
<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.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,
edit_entrance, get_entrances, qm, expo_kml, expo_kmz)
from troggle.core.views.drawings import dwgallfiles, dwgfilesingle
@ -94,8 +94,9 @@ trogglepatterns = [
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'^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/', admin.site.urls), # includes admin login & logout urls & /admin/jsi18n/