2
0
mirror of https://expo.survex.com/repositories/troggle/.git synced 2024-11-28 18:11:52 +00:00

fix directory for uploaded cave description photos

This commit is contained in:
Philip Sargent 2024-07-10 21:07:45 +02:00
parent f5f3adf7da
commit 7908257d63
4 changed files with 132 additions and 55 deletions

View File

@ -46,7 +46,65 @@ todo = """
own directory 16xx/NNN/ even if they have no images to put in it. own directory 16xx/NNN/ even if they have no images to put in it.
""" """
# def cavepagefwd(request, karea=None, subpath=None):
# """archaic, just send to the caves list page
# """
# return redirect("/caves")
def get_cave_from_slug(caveslug):
"""This is the old way of doing it, usingt eh CaveSlug intermediate object which does
the many:many relationship between slugs and caves - whcih we don't do (and never did in practice)
"""
caves = []
print(f"get_cave_from_slug(): {caveslug} ...")
areacode = caveslug[:4] # e.g. 1623
id = caveslug[5:] # e.g. 161 or 2023-MM-02
thisarea = Cave.objects.filter(areacode=areacode)
caves_k = thisarea.filter(kataster_number=id)
if len(caves_k) == 1:
caves.append(caves_k[0])
print(f"get_cave_from_slug(): {caves_k=} {len(caves_k)=}")
caves_id = thisarea.filter(unofficial_number=id)
if len(caves_id) == 1:
caves.append(caves_id[0])
print(f"get_cave_from_slug(): {caves_id=} {len(caves_id)=}")
if len(caves) > 1:
print(f"get_cave_from_slug(): {caveslug} More than 1 \n{caves}")
if len(caves) <1:
print(f"get_cave_from_slug(): {caveslug} Nowt found..")
cave = caves[0]
print(f"get_cave_from_slug(): {caveslug} FOUND {cave}")
try:
cave_zero = Cave.objects.get(caveslug__slug=caveslug)
print(f"Getting cave from '{caveslug}'")
if cave_zero != cave:
print(f"get_cave_from_slug(): {caveslug} BAD DISCREPANCY {cave_zero=} {cave=}")
else:
print(f"get_cave_from_slug(): {caveslug} SUCCESS")
return cave_zero
except:
return None
def caveslugfwd(request, slug):
"""This is ass backwards. It would be better style to have the slug-identified request be the master, and have
other paths redirect to it, rather than what we have here.
Pending a change where we remove cave.url as a field and have an explicit fixed convention instead.
"""
if not slug:
message = f"Failed to find cave from identifier given: {slug}."
return render(request, "errors/generic.html", {"message": message})
Gcavelookup = GetCaveLookup()
if slug in Gcavelookup:
cave = Gcavelookup[slug]
return redirect(f"/{cave.url}")
def getCaves(cave_id): def getCaves(cave_id):
@ -93,7 +151,7 @@ def pad5(x):
def padnumber(x): def padnumber(x):
return re.sub("\d+", pad5, x) # SyntaxWarning: invalid escape sequence '\d' return re.sub(r"\d+", pad5, x)
def numericalcmp(x, y): def numericalcmp(x, y):
@ -311,24 +369,7 @@ def rendercave(request, cave, slug, cave_id=""):
) # crashes here with NoReverseMatch if url not set up for 'edit_cave' in urls.py ) # crashes here with NoReverseMatch if url not set up for 'edit_cave' in urls.py
return r return r
def cavepagefwd(request, karea=None, subpath=None):
"""archaic, just send to the caves list page
"""
return redirect("/caves")
def caveslugfwd(request, slug):
"""This is ass backwards. It would be better style to have the slug-identified request be the master, and have
other paths redirect to it, rather than what we have here.
Pending a change where we remove cave.url as a field and have an explicit fixed convention instead.
"""
if slug:
Gcavelookup = GetCaveLookup()
if slug in Gcavelookup:
cave = Gcavelookup[slug]
else:
message = f"Failed to find cave from identifier given: {slug}."
return render(request, "errors/generic.html", {"message": message})
return redirect(f"/{cave.url}")
def cavepage(request, karea=None, subpath=None): def cavepage(request, karea=None, subpath=None):
"""Displays a cave description page """Displays a cave description page
@ -368,9 +409,7 @@ def cavepage(request, karea=None, subpath=None):
subparts = parts[0].split(".") subparts = parts[0].split(".")
caveid = subparts[0] caveid = subparts[0]
slug = f"{karea}-{caveid}" slug = f"{karea}-{caveid}"
caves = Cave.objects.filter(caveslug__slug=slug) if cave:= get_cave_from_slug(slug): # walrus operator
if len(caves) == 1:
cave = caves[0]
return redirect(f"/{cave.url}") return redirect(f"/{cave.url}")
else: else:
return redirect(f"/caves") return redirect(f"/caves")
@ -410,31 +449,31 @@ def edit_cave(request, path="", slug=None):
""" """
print(f"edit_cave(): {path=} {slug=}") print(f"edit_cave(): {path=} {slug=}")
message = "" message = ""
if slug is not None: if slug is None:
print(f"{slug=}") cave = Cave() # create a New Cave
try:
cave = Cave.objects.get(caveslug__slug=slug)
except:
return render(request, "errors/badslug.html", {"badslug": f"{slug} - from edit_cave()"})
else: else:
cave = Cave() print(f"{slug=}")
if not (cave:= get_cave_from_slug(slug)): # walrus operator
return render(request, "errors/badslug.html", {"badslug": f"for cave {caveslug} - from edit_cave()"})
if request.POST: if request.POST:
form = CaveForm(request.POST, instance=cave) form = CaveForm(request.POST, instance=cave)
#ceFormSet = CaveAndEntranceFormSet(request.POST) #ceFormSet = CaveAndEntranceFormSet(request.POST)
if form.is_valid(): # and ceFormSet.is_valid(): if form.is_valid(): # and ceFormSet.is_valid():
# print(f'! POST is valid. {cave}') print(f'edit_cave(): POST is valid. Editing {cave}')
cave = form.save(commit=False) cave = form.save(commit=False)
print(cave) # print(cave)
if not cave.filename: if not cave.filename:
cave.filename = cave.areacode + "-" + cave.number() + ".html" cave.filename = cave.areacode + "-" + cave.number() + ".html"
if not cave.url: if not cave.url:
cave.url = cave.areacode + "/" + cave.number() cave.url = cave.areacode + "/" + cave.number()
cave.save() cave.save()
form.save_m2m() form.save_m2m() # this does the many-to-many relationship saving between caves and entrances
if slug is None: if slug is None:
# it is not visible on the form so it always will be None # it is not visible on the form so it always will be None
slug = f"{cave.areacode}-{cave.number()}" slug = f"{cave.areacode}-{cave.number()}"
cs = CaveSlug(cave=cave, slug=slug, primary=True) cs = CaveSlug(cave=cave, slug=slug, primary=True)
print(f"edit_cave(): New CaveSlug saved {slug}")
cs.save() cs.save()
#ceinsts = ceFormSet.save(commit=False) #ceinsts = ceFormSet.save(commit=False)
#for ceinst in ceinsts: #for ceinst in ceinsts:
@ -450,6 +489,8 @@ def edit_cave(request, path="", slug=None):
except subprocess.SubprocessError: except subprocess.SubprocessError:
message = f"CANNOT git on server for this file {cave.filename}. Edits may not be committed.\nAsk a nerd to fix this." message = f"CANNOT git on server for this file {cave.filename}. Edits may not be committed.\nAsk a nerd to fix this."
return render(request, "errors/generic.html", {"message": message}) return render(request, "errors/generic.html", {"message": message})
except:
raise
if cave.entrances().count() > 0: if cave.entrances().count() > 0:
return HttpResponseRedirect("/" + cave.url) return HttpResponseRedirect("/" + cave.url)
else: else:
@ -458,8 +499,8 @@ def edit_cave(request, path="", slug=None):
else: else:
if slug is not None: if slug is not None:
# re-read cave data from file. # re-read cave data from file.
#print(f"edit_cave(): {cave=} {cave.filename=}") print(f"edit_cave(): {cave=} {cave.filename=}")
#print(f"edit_cave(): {cave.slug()=}") print(f"edit_cave(): {cave.slug()=}")
if cave.filename: if cave.filename:
try: try:
read_cave(cave.filename, cave=cave) read_cave(cave.filename, cave=cave)
@ -473,6 +514,7 @@ def edit_cave(request, path="", slug=None):
form = CaveForm() form = CaveForm()
#ceFormSet = CaveAndEntranceFormSet(queryset=CaveAndEntrance.objects.none()) #ceFormSet = CaveAndEntranceFormSet(queryset=CaveAndEntrance.objects.none())
print(f"edit_cave(): returning render()")
return render( return render(
request, request,
"editcave.html", "editcave.html",
@ -486,6 +528,7 @@ def edit_cave(request, path="", slug=None):
) )
@login_required_if_public @login_required_if_public
def edit_entrance(request, path="", caveslug=None, entslug=None): def edit_entrance(request, path="", caveslug=None, entslug=None):
"""This is the form that edits the entrance data for a single entrance and writes out """This is the form that edits the entrance data for a single entrance and writes out
@ -497,6 +540,9 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
It does save the data into into the database directly, not by parsing the file. It does save the data into into the database directly, not by parsing the file.
'path' comes from the urls.py regex but is usually empty (!)
So we make the proper path for storing the images ourselves.
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.
""" """
@ -526,9 +572,7 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
nextletter = chr(ord(letter)+1) nextletter = chr(ord(letter)+1)
return check_new_slugname_ok(slug, nextletter) return check_new_slugname_ok(slug, nextletter)
try: if not (cave:= get_cave_from_slug(caveslug)): # walrus operator
cave = Cave.objects.get(caveslug__slug=caveslug)
except:
return render(request, "errors/badslug.html", {"badslug": f"for cave {caveslug} - from edit_entrance()"}) return render(request, "errors/badslug.html", {"badslug": f"for cave {caveslug} - from edit_entrance()"})
if entslug: if entslug:
@ -541,7 +585,7 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
entrance = None entrance = None
if entslug: if entslug:
print(f"{caveslug=} {entslug=} {path=} number of ents:{cave.entrances().count()}") print(f"Edit Entrance {caveslug=} {entslug=} {path=} number of ents:{cave.entrances().count()}")
caveAndEntrance = CaveAndEntrance.objects.get(entrance=entrance, cave=cave) caveAndEntrance = CaveAndEntrance.objects.get(entrance=entrance, cave=cave)
entlettereditable = False entlettereditable = False
else: else:
@ -557,6 +601,11 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
print(f"{entlettereditable=}") print(f"{entlettereditable=}")
# if the entletter is not 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.
print(f"{caveslug=}")
print(f"{cave=}")
imgpath = Path(path) / cave.areacode / cave.number()
print(f"Edit Entrance {imgpath=}")
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)
@ -683,7 +732,7 @@ def edit_entrance(request, path="", caveslug=None, entslug=None):
"entletter": entletter, "entletter": entletter,
"entletterform": entletterform, # is unset if not being used "entletterform": entletterform, # is unset if not being used
"entlettereditable": entlettereditable, "entlettereditable": entlettereditable,
"path": path + "/", # used for saving images if attached "path": str(imgpath) + "/", # used for saving images if attached
}, },
) )
@ -719,9 +768,7 @@ def caveslist(request):
{"caves": caves, "year": current_expo()}, {"caves": caves, "year": current_expo()},
) )
def get_entrances(request, caveslug): def get_entrances(request, caveslug):
try: if not (cave:= get_cave_from_slug(caveslug)): # walrus operator
cave = Cave.objects.get(caveslug__slug=caveslug)
except:
return render(request, "errors/badslug.html", {"badslug": f"{caveslug} - from get_entrances()"}) return render(request, "errors/badslug.html", {"badslug": f"{caveslug} - from get_entrances()"})
return render( return render(
request, "options.html", {"year": current_expo(), "items": [(e.entrance.slug(), e.entrance.slug()) for e in cave.entrances()]} request, "options.html", {"year": current_expo(), "items": [(e.entrance.slug(), e.entrance.slug()) for e in cave.entrances()]}
@ -733,9 +780,7 @@ def caveQMs(request, slug, open=False):
relies on the template to find all the QMs for the cave specified in the slug, e.g. '1623-161' relies on the template to find all the QMs for the cave specified in the slug, e.g. '1623-161'
Now working in July 2022 Now working in July 2022
""" """
try: if not (cave:= get_cave_from_slug(caveslug)): # walrus operator
cave = Cave.objects.get(caveslug__slug=slug)
except:
return render(request, "errors/badslug.html", {"badslug": f"{slug} - from caveQMs()"}) return render(request, "errors/badslug.html", {"badslug": f"{slug} - from caveQMs()"})
if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated: if cave.non_public and settings.PUBLIC_SITE and not request.user.is_authenticated:

View File

@ -26,6 +26,11 @@ THUMBNAIL_HEIGHT = 200
It uses a lot of opinionated Django default magic, none of which I am familiar with. It uses a lot of opinionated Django default magic, none of which I am familiar with.
Philip Philip
The function image_selector() is only called from a template file doing a popup input form,
but it stores files directly in expoweb/i /l /t instead of in
/expoweb/1623/2018-MS-01/i , /l, /t
so ALL new caves have photos in the smae place, which makes the default upload EXTREMELY CONFUSING
""" """
def get_dir(path): def get_dir(path):
@ -38,7 +43,10 @@ def get_dir(path):
def image_selector(request, path): def image_selector(request, path):
"""Returns available images""" """Returns available images
called from
templates/html_editor_scripts_css.html: $('#image_popup_content').load("{% url 'image_selector' path %}" + path, function() {
"""
directory = get_dir(path) directory = get_dir(path)
print(f" - image selector {directory=} {path=}") print(f" - image selector {directory=} {path=}")
thumbnailspath = Path(settings.EXPOWEB) / directory / "t" thumbnailspath = Path(settings.EXPOWEB) / directory / "t"
@ -89,15 +97,23 @@ def reorient_image(img, exif_dict):
def new_image_form(request, path): 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=}")
if request.method == "POST": if request.method == "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(): files: {request.FILES["file_"]}")
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():
binary_data.write(chunk) binary_data.write(chunk)
print(f"new_image_form(): binary chnks written")
i = Image.open(binary_data) i = Image.open(binary_data)
print(f"new_image_form(): {i=}")
if "exif" in i.info: if "exif" in i.info:
print(f"new_image_form(): exif: {i=}")
exif_dict = piexif.load(i.info["exif"]) exif_dict = piexif.load(i.info["exif"])
i = reorient_image(i, exif_dict) i = reorient_image(i, exif_dict)
exif_dict['Exif'][41729] = b'1' exif_dict['Exif'][41729] = b'1'
@ -109,13 +125,21 @@ def new_image_form(request, path):
else: else:
exif = None exif = None
print(f"new_image_form(): No exif problems")
width, height = i.size width, height = i.size
print(f"new_image_form(): {i.size=}")
if width > MAX_IMAGE_WIDTH or height > MAX_IMAGE_HEIGHT: if width > MAX_IMAGE_WIDTH or height > MAX_IMAGE_HEIGHT:
print(f"new_image_form(): rescaling ")
scale = max(width / MAX_IMAGE_WIDTH, height / MAX_IMAGE_HEIGHT) scale = max(width / MAX_IMAGE_WIDTH, height / MAX_IMAGE_HEIGHT)
i = i.resize((int(width / scale), int(height / scale)), Image.ANTIALIAS) print(f"new_image_form(): rescaling {scale=}")
tscale = max(width / THUMBNAIL_WIDTH, height / THUMBNAIL_HEIGHT) try:
thumbnail = i.resize((int(width / tscale), int(height / tscale)), Image.ANTIALIAS) i = i.resize((int(width / scale), int(height / scale)), Image.LANCZOS)
except Exception as e:
print(f"new_image_form(): rescaling exception: {e} ")
print(f"new_image_form(): rescaled ")
tscale = max(width / THUMBNAIL_WIDTH, height / THUMBNAIL_HEIGHT)
thumbnail = i.resize((int(width / tscale), int(height / tscale)), Image.LANCZOS)
ib = io.BytesIO() ib = io.BytesIO()
i = i.convert('RGB') i = i.convert('RGB')
i.save(ib, format="jpeg", quality = 75) i.save(ib, format="jpeg", quality = 75)
@ -123,6 +147,7 @@ def new_image_form(request, path):
thumbnail = thumbnail.convert('RGB') thumbnail = thumbnail.convert('RGB')
thumbnail.save(tb, format="jpeg", quality = 70) thumbnail.save(tb, format="jpeg", quality = 70)
image_rel_path, thumb_rel_path, desc_rel_path = form.get_rel_paths() image_rel_path, thumb_rel_path, desc_rel_path = form.get_rel_paths()
print(f"new_image_form(): \n {image_rel_path=}\n {thumb_rel_path=}\n {desc_rel_path=}")
image_page_template = loader.get_template("image_page_template.html") # this is the .html file in the /l/ folder image_page_template = loader.get_template("image_page_template.html") # this is the .html file in the /l/ folder
image_page = image_page_template.render( image_page = image_page_template.render(
{ {
@ -149,14 +174,21 @@ def new_image_form(request, path):
f"{change_message} - online adding of an image", f"{change_message} - online adding of an image",
) )
except WriteAndCommitError as e: except WriteAndCommitError as e:
print(f"new_image_form(): WriteAndCommitError: {e.message}")
return JsonResponse({"error": e.message}) return JsonResponse({"error": e.message})
except Exception as e:
print(f"new_image_form(): EXCEPTION: {e.message}")
return JsonResponse({"error": e.message})
linked_image_template = loader.get_template("linked_image_template.html") linked_image_template = loader.get_template("linked_image_template.html")
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}) return JsonResponse({"html": html_snippet})
else: else:
print(f"new_image_form(): not POST ")
form = NewWebImageForm(directory=directory) form = NewWebImageForm(directory=directory)
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)
return JsonResponse({"form": htmlform}) return JsonResponse({"form": htmlform})

View File

@ -16,8 +16,8 @@ the form documented at
<br> <br>
<cave> <cave>
<non_public>{{ cave.non_public }}</non_public>{% for slug in cave.caveslug_set.all %}<!-- 'False' or 'True'. True if the cave should only be visible to logged-in users. Caves are normally public, so enter 'False' unless you know otherwise. --> <non_public>{{ cave.non_public }}</non_public><!-- 'False' or 'True'. True if the cave should only be visible to logged-in users. Caves are normally public, so enter 'False' unless you know otherwise. -->
<!--<caveslug>{{ slug.slug|default_if_none:"1623-000"|safe }}</caveslug>{% endfor %}--> <!-- Ignored. No longer required, we use the filename instead e.g. 1623-315 for 1623-315.html --> <!--<caveslug>{{ slug.slug|default_if_none:"1623-000"|safe }}</caveslug>--> <!-- Ignored. No longer required, we use the filename instead e.g. 1623-315 for 1623-315.html -->
<official_name>{{ cave.official_name|default_if_none:""|safe }}</official_name><!-- Name of the cave (normally in German) Use &uuml; for u+Umlaut and &ouml; for o+umlaut eg H&ouml;hle for Hohle and Gl&uuml;ck for Gluck--> <official_name>{{ cave.official_name|default_if_none:""|safe }}</official_name><!-- Name of the cave (normally in German) Use &uuml; for u+Umlaut and &ouml; for o+umlaut eg H&ouml;hle for Hohle and Gl&uuml;ck for Gluck-->
<area>{{cave.areacode |safe }}</area><!-- e.g. "1623" --> <area>{{cave.areacode |safe }}</area><!-- e.g. "1623" -->
<kataster_code>{{ cave.kataster_code|default_if_none:"-"|safe }}</kataster_code><!-- 'length-or-depth/type exploration' <kataster_code>{{ cave.kataster_code|default_if_none:"-"|safe }}</kataster_code><!-- 'length-or-depth/type exploration'

View File

@ -7,7 +7,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, caveslist, from troggle.core.views.caves import (cave3d, caveindex, entranceindex, caveslist,
cavepage, caveslugfwd, cavepagefwd, caveQMs, edit_cave, cave_debug, cavepage, caveslugfwd, 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
from troggle.core.views.editor_helpers import image_selector, new_image_form from troggle.core.views.editor_helpers import image_selector, new_image_form
@ -173,9 +173,9 @@ trogglepatterns = [
# Note that urls eg '/1623/161/l/rl89a.htm' are handled by cavepage which redirects them to 'expopage' # Note that urls eg '/1623/161/l/rl89a.htm' are handled by cavepage which redirects them to 'expopage'
# Note that _edit$ for a cave description page in a subfolder e.g. /1623/204/204.html_edit gets caught here and breaks with 404 # Note that _edit$ for a cave description page in a subfolder e.g. /1623/204/204.html_edit gets caught here and breaks with 404
# These re-enable archaic URLs which are in old web pages which are still public on other servers # These re-enable archaic URLs which are in old web pages which may still be public on other servers - old
path('smkridge/<path:subpath>', cavepagefwd, {'karea': "1623"}, name="cavepagefwd"), # path('smkridge/<path:subpath>', cavepagefwd, {'karea': "1623"}, name="cavepagefwd"),
path('expo/smkridge/<path:subpath>', cavepagefwd, {'karea': "1623"}, name="cavepagefwd"), # path('expo/smkridge/<path:subpath>', cavepagefwd, {'karea': "1623"}, name="cavepagefwd"),
# Archaic, kept. This /expo/ prefix only works for expoweb HTML pages not troggle pages # Archaic, kept. This /expo/ prefix only works for expoweb HTML pages not troggle pages
path('expo/<path:path>', expopage, name="expopage"), path('expo/<path:path>', expopage, name="expopage"),