2020-05-28 01:16:45 +01:00
import os
import re
2021-04-02 23:21:23 +01:00
import subprocess
2023-05-08 01:10:43 +01:00
import tempfile
import zipfile
import urllib
from bs4 import BeautifulSoup
2021-04-02 23:21:23 +01:00
from pathlib import Path
2023-01-19 18:35:56 +00:00
from django . core . exceptions import MultipleObjectsReturned , ObjectDoesNotExist
2023-05-08 01:10:43 +01:00
from django . http import HttpResponse , HttpResponseNotFound , HttpResponseRedirect , FileResponse
2023-09-30 20:09:18 +01:00
from django . shortcuts import render , redirect
2023-04-30 21:42:03 +01:00
from django . urls import NoReverseMatch , reverse
2013-08-01 16:00:01 +01:00
2020-05-28 01:16:45 +01:00
import troggle . settings as settings
2023-01-30 19:04:36 +00:00
from troggle . core . forms import CaveAndEntranceFormSet , CaveForm , EntranceForm , EntranceLetterForm
2023-03-28 20:30:00 +01:00
from troggle . core . models . caves import Cave , CaveAndEntrance , Entrance , GetCaveLookup
2023-01-29 21:45:51 +00:00
from troggle . core . models . logbooks import CaveSlug , QM
2023-01-30 23:04:11 +00:00
from troggle . core . utils import write_and_commit
2021-04-01 21:44:03 +01:00
from troggle . core . views import expo
2023-04-22 01:24:32 +01:00
from troggle . settings import CAVEDESCRIPTIONS , ENTRANCEDESCRIPTIONS
2023-04-22 22:05:12 +01:00
from troggle . parsers . caves import read_cave , read_entrance
2023-04-22 01:24:32 +01:00
2023-05-08 01:10:43 +01:00
from django . template import loader
from django . utils . safestring import mark_safe
2023-01-19 18:35:56 +00:00
2021-05-03 20:35:35 +01:00
from . auth import login_required_if_public
2019-02-26 00:43:05 +00:00
2023-01-30 19:04:36 +00:00
""" Manages the complex procedures to assemble a cave description out of the compnoents
2021-04-13 01:37:42 +01:00
Manages the use of cavern to parse survex files to produce 3 d and pos files
2023-01-30 19:04:36 +00:00
"""
2021-04-13 01:37:42 +01:00
2023-02-02 21:50:40 +00:00
todo = """
- in getCaves ( ) search GCavelookup first , which should raise a MultpleObjectsReturned
exception if no duplicates
- Learn to use Django . select_related ( ) and . prefetch_related ( ) to speed things up
especially on the big report pages
https : / / zerotobyte . com / how - to - use - django - select - related - and - prefetch - related /
2023-01-30 19:04:36 +00:00
"""
2023-10-21 14:22:20 +01:00
def get_cave_leniently ( caveid ) :
try :
c = getCave ( caveid )
if c :
return c
except :
# print(f"get_cave_leniently FAIL {caveid}")
try :
c = getCave ( " 1623- " + caveid )
if c :
return c
except :
return None
2021-11-06 22:36:44 +00:00
2021-04-02 23:21:23 +01:00
def getCaves ( cave_id ) :
2023-01-30 19:04:36 +00:00
""" Only gets called if a call to getCave() raises a MultipleObjects exception
2021-04-02 23:21:23 +01:00
TO DO : search GCavelookup first , which should raise a MultpleObjectsReturned exception if there
2023-01-30 19:04:36 +00:00
are duplicates """
2021-04-02 23:21:23 +01:00
try :
caves = Cave . objects . filter ( kataster_number = cave_id )
caveset = set ( caves )
2023-01-30 19:04:36 +00:00
Gcavelookup = GetCaveLookup ( ) # dictionary makes strings to Cave objects
2021-04-02 23:21:23 +01:00
if cave_id in Gcavelookup :
caveset . add ( Gcavelookup [ cave_id ] )
return list ( caveset )
except :
return [ ]
2011-07-11 02:10:22 +01:00
def getCave ( cave_id ) :
2023-10-21 14:22:20 +01:00
""" Returns a cave object when given a cave name or number.
It is used by views including cavehref , ent , wallets and qm .
2023-01-30 19:04:36 +00:00
2021-03-28 23:47:47 +01:00
TO DO : search GCavelookup first , which should raise a MultpleObjectsReturned exception if there
2023-01-30 19:04:36 +00:00
are duplicates """
2011-07-11 02:10:22 +01:00
try :
cave = Cave . objects . get ( kataster_number = cave_id )
2021-03-31 20:18:46 +01:00
return cave
2021-03-28 23:47:47 +01:00
except Cave . MultipleObjectsReturned as ex :
2023-01-30 19:04:36 +00:00
raise MultipleObjectsReturned ( " Duplicate kataster number " ) from ex # propagate this up
2021-03-28 23:47:47 +01:00
2021-03-31 20:18:46 +01:00
except Cave . DoesNotExist as ex :
2023-01-30 19:04:36 +00:00
Gcavelookup = GetCaveLookup ( ) # dictionary makes strings to Cave objects
2021-03-28 23:47:47 +01:00
if cave_id in Gcavelookup :
2023-01-30 19:04:36 +00:00
return Gcavelookup [ cave_id ]
2021-03-31 20:18:46 +01:00
else :
2023-01-30 19:04:36 +00:00
raise ObjectDoesNotExist ( " No cave found with this identifier in any id field " ) from ex # propagate this up
2021-03-28 23:47:47 +01:00
except :
raise ObjectDoesNotExist ( " No cave found with this identifier in any id field " )
2011-07-11 02:10:22 +01:00
2023-01-30 19:04:36 +00:00
2012-08-05 18:26:24 +01:00
def pad5 ( x ) :
2023-01-30 19:04:36 +00:00
return " 0 " * ( 5 - len ( x . group ( 0 ) ) ) + x . group ( 0 )
2012-08-05 18:26:24 +01:00
def padnumber ( x ) :
2023-01-30 19:04:36 +00:00
return re . sub ( " \ d+ " , pad5 , x )
2012-08-05 18:26:24 +01:00
def numericalcmp ( x , y ) :
2023-01-30 19:04:36 +00:00
return cmp ( padnumber ( x ) , padnumber ( y ) )
2012-08-05 18:26:24 +01:00
2021-04-16 16:01:35 +01:00
def caveKey ( c ) :
""" This function goes into a lexicogrpahic sort function, and the values are strings,
but we want to sort numberically on kataster number before sorting on unofficial number .
2023-01-30 19:04:36 +00:00
"""
2021-04-16 16:01:35 +01:00
if not c . kataster_number :
return " 9999. " + c . unofficial_number
else :
if int ( c . kataster_number ) > = 100 :
return " 99. " + c . kataster_number
if int ( c . kataster_number ) > = 10 :
2023-01-30 19:04:36 +00:00
return " 9. " + c . kataster_number
2021-04-16 16:01:35 +01:00
return c . kataster_number
2012-06-10 14:59:21 +01:00
2023-01-30 19:04:36 +00:00
2020-06-07 16:13:59 +01:00
def getnotablecaves ( ) :
notablecaves = [ ]
for kataster_number in settings . NOTABLECAVESHREFS :
try :
2023-09-13 16:45:59 +01:00
cave = Cave . objects . get ( kataster_number = kataster_number , areacode = " 1623 " )
2020-06-07 16:13:59 +01:00
notablecaves . append ( cave )
except :
2023-09-16 17:53:39 +01:00
print ( " ! Notable Caves: FAILED to get only one cave per kataster_number OR invalid number for: " + kataster_number )
2023-09-13 16:45:59 +01:00
2023-09-16 17:53:39 +01:00
try :
hc = Cave . objects . get ( kataster_number = 359 , areacode = " 1626 " )
notablecaves . append ( hc )
except :
# fails during the tests because this cave has not been loaded for tests, so catch it here.
pass
2023-09-13 16:45:59 +01:00
print ( notablecaves )
2020-06-07 16:13:59 +01:00
return notablecaves
2023-01-30 19:04:36 +00:00
2011-07-11 02:10:22 +01:00
def caveindex ( request ) :
2023-10-15 16:39:00 +01:00
""" Should use Django order-by for lazy sorting, not here. But only after we have a proper slug system in place for Caves
"""
# allcaves = Cave.objects.all()
# for c in allcaves:
# if c.entrances:
# pass
2023-10-06 12:57:41 +01:00
2023-09-10 13:42:36 +01:00
caves1623 = list ( Cave . objects . filter ( areacode = " 1623 " ) )
2023-10-07 22:06:12 +01:00
caves1624 = list ( Cave . objects . filter ( areacode = " 1624 " ) )
2023-09-10 13:42:36 +01:00
caves1626 = list ( Cave . objects . filter ( areacode = " 1626 " ) )
caves1627 = list ( Cave . objects . filter ( areacode = " 1627 " ) )
2020-05-25 01:46:52 +01:00
caves1623 . sort ( key = caveKey )
2023-10-15 16:39:00 +01:00
caves1624 . sort ( key = caveKey )
2020-05-25 01:46:52 +01:00
caves1626 . sort ( key = caveKey )
2023-05-20 20:47:09 +01:00
caves1627 . sort ( key = caveKey )
2023-01-30 19:04:36 +00:00
return render (
request ,
" caveindex.html " ,
2023-10-07 22:06:12 +01:00
{ " caves1623 " : caves1623 ,
" caves1626 " : caves1626 ,
" caves1627 " : caves1627 ,
" caves1624 " : caves1624 ,
" notablecaves " : getnotablecaves ( ) ,
" cavepage " : True } ,
2023-01-30 19:04:36 +00:00
)
2023-07-03 08:00:03 +01:00
def entranceindex ( request ) :
2023-07-03 08:04:26 +01:00
ents = Entrance . objects . all ( ) . order_by ( " slug " )
2023-07-03 08:00:03 +01:00
return render (
request ,
" entranceindex.html " ,
{ " entrances " : ents } ,
)
2011-07-11 02:10:22 +01:00
2023-01-30 19:04:36 +00:00
def cave3d ( request , cave_id = " " ) :
""" This is used to create a download url in templates/cave.html if anyone wants to download the .3d file
2021-04-02 23:21:23 +01:00
The caller template tries kataster first , then unofficial_number if that kataster number does not exist
but only if Cave . survex_file is non - empty
2023-01-30 19:04:36 +00:00
"""
2021-03-28 23:47:47 +01:00
try :
2023-01-30 19:04:36 +00:00
cave = getCave ( cave_id )
2021-04-02 23:21:23 +01:00
except ObjectDoesNotExist :
2021-04-14 16:28:30 +01:00
return HttpResponseNotFound
2023-01-30 19:04:36 +00:00
except Cave . MultipleObjectsReturned :
2023-09-15 20:41:02 +01:00
# should really produce a better error message. This is a failure of ambiguous aliases probably.
caves = Cave . objects . filter ( url = kpath )
return render ( request , " svxcaveseveral.html " , { " settings " : settings , " caves " : caves } )
2021-04-02 23:21:23 +01:00
else :
return file3d ( request , cave , cave_id )
2023-01-30 19:04:36 +00:00
2021-04-02 23:21:23 +01:00
def file3d ( request , cave , cave_id ) :
2023-01-30 19:04:36 +00:00
""" Produces a .3d file directly for download.
2021-04-03 20:52:35 +01:00
survex_file should be in valid path format ' caves-1623/264/264.svx ' but it might be mis - entered as simply ' 2012-ns-10.svx '
2023-01-30 19:04:36 +00:00
Also the cave . survex_file may well not match the cave description path :
2021-04-03 20:52:35 +01:00
e . g . it might be to the whole system ' smk-system.svx ' instead of just for the specific cave .
2023-01-30 19:04:36 +00:00
2021-04-03 20:52:35 +01:00
- If the expected .3 d file corresponding to cave . survex_file is present , return it .
- If the cave . survex_file exists , generate the 3 d file , cache it and return it
- Use the cave_id to guess what the 3 d file might be and , if in the cache , return it
- Use the cave_id to guess what the . svx file might be and generate the .3 d file and return it
2023-01-30 19:04:36 +00:00
- ( Use the incomplete cave . survex_file and a guess at the missing directories to guess the real . svx file location ? )
"""
2021-04-03 20:52:35 +01:00
def runcavern ( survexpath ) :
2023-01-30 19:04:36 +00:00
""" This has not yet been properly updated with respect to putting the .3d file in the same folder as the .svx filse
as done in runcavern3d ( ) in parsers / survex . py
2022-07-20 23:22:07 +01:00
Needs testing .
2023-01-30 19:04:36 +00:00
"""
# print(" - Regenerating cavern .log and .3d for '{}'".format(survexpath))
2021-04-03 20:52:35 +01:00
if not survexpath . is_file ( ) :
2023-01-30 19:04:36 +00:00
# print(" - - Regeneration ABORT\n - - from '{}'".format(survexpath))
2021-04-03 20:54:33 +01:00
pass
2021-04-02 23:21:23 +01:00
try :
2023-01-30 19:04:36 +00:00
completed_process = subprocess . run (
[ settings . CAVERN , " --log " , f " --output= { settings . SURVEX_DATA } " , f " { survexpath } " ]
)
2021-04-02 23:21:23 +01:00
except OSError as ex :
2023-01-30 19:04:36 +00:00
# propagate this to caller.
2021-04-03 20:52:35 +01:00
raise OSError ( completed_process . stdout ) from ex
2023-01-30 19:04:36 +00:00
op3d = ( Path ( settings . SURVEX_DATA ) / Path ( survexpath ) . name ) . with_suffix ( " .3d " )
op3dlog = Path ( op3d . with_suffix ( " .log " ) )
2021-04-03 20:52:35 +01:00
if not op3d . is_file ( ) :
2022-11-23 10:48:39 +00:00
print ( f " - - Regeneration FAILED \n - - from ' { survexpath } ' \n - - to ' { op3d } ' " )
2021-04-03 20:52:35 +01:00
print ( " - - Regeneration stdout: " , completed_process . stdout )
print ( " - - Regeneration cavern log output: " , op3dlog . read_text ( ) )
2023-01-30 19:04:36 +00:00
2021-04-03 20:52:35 +01:00
def return3d ( threedpath ) :
if threedpath . is_file ( ) :
2023-01-30 19:04:36 +00:00
response = HttpResponse ( content = open ( threedpath , " rb " ) , content_type = " application/3d " )
response [ " Content-Disposition " ] = f " attachment; filename= { threedpath . name } "
2021-04-03 20:52:35 +01:00
return response
else :
2022-11-23 10:48:39 +00:00
message = f ' <h1>Path provided does not correspond to any actual 3d file.</h1><p>path: " { threedpath } " '
2023-01-30 19:04:36 +00:00
# print(message)
return HttpResponseNotFound ( message )
survexname = Path ( cave . survex_file ) . name # removes directories
2021-04-03 20:52:35 +01:00
survexpath = Path ( settings . SURVEX_DATA , cave . survex_file )
2023-01-30 19:04:36 +00:00
threedname = Path ( survexname ) . with_suffix ( " .3d " ) # removes .svx, replaces with .3d
threedpath = Path ( settings . SURVEX_DATA , threedname )
2021-04-03 20:52:35 +01:00
# These if statements need refactoring more cleanly
if cave . survex_file :
2023-01-30 19:04:36 +00:00
# print(" - cave.survex_file '{}'".format(cave.survex_file))
2022-08-25 03:31:54 +01:00
if threedpath . is_file ( ) :
2023-01-30 19:04:36 +00:00
# print(" - threedpath '{}'".format(threedpath))
2021-04-03 20:52:35 +01:00
# possible error here as several .svx files of same names in different directories will overwrite in /3d/
if survexpath . is_file ( ) :
if os . path . getmtime ( survexpath ) > os . path . getmtime ( threedpath ) :
runcavern ( survexpath )
return return3d ( threedpath )
else :
2023-01-30 19:04:36 +00:00
# print(" - - survexpath '{}'".format(survexpath))
2021-04-03 20:52:35 +01:00
if survexpath . is_file ( ) :
2023-01-30 19:04:36 +00:00
# print(" - - - survexpath '{}'".format(survexpath))
2021-04-03 20:52:35 +01:00
runcavern ( survexpath )
return return3d ( threedpath )
# Get here if cave.survex_file was set but did not correspond to a valid svx file
if survexpath . is_file ( ) :
# a file, but invalid format
2023-01-30 19:04:36 +00:00
message = f ' <h1>File is not valid .svx format.</h1><p>Could not generate 3d file from " { survexpath } " '
2021-04-02 23:21:23 +01:00
else :
2021-04-03 20:52:35 +01:00
# we could try to guess that 'caves-1623/' is missing,... nah.
2022-11-23 10:48:39 +00:00
message = f ' <h1>Path provided does not correspond to any actual file.</h1><p>path: " { survexpath } " '
2021-04-03 20:52:35 +01:00
2023-01-30 19:04:36 +00:00
return HttpResponseNotFound ( message )
def rendercave ( request , cave , slug , cave_id = " " ) :
""" Gets the data and files ready and then triggers Django to render the template.
2021-04-03 20:52:35 +01:00
The resulting html contains urls which are dispatched independently , e . g . the ' download ' link
2023-01-30 19:04:36 +00:00
"""
2022-03-18 20:00:15 +00:00
# print(" ! rendercave:'{}' START slug:'{}' cave_id:'{}'".format(cave, slug, cave_id))
2021-04-03 20:52:35 +01:00
2021-04-07 21:53:17 +01:00
if cave . non_public and settings . PUBLIC_SITE and not request . user . is_authenticated :
2023-01-30 19:04:36 +00:00
return render ( request , " nonpublic.html " , { " instance " : cave , " cavepage " : True , " cave_id " : cave_id } )
2021-04-03 20:52:35 +01:00
else :
2022-03-18 20:00:15 +00:00
# print(f" ! rendercave: slug:'{slug}' survex file:'{cave.survex_file}'")
try :
2023-01-30 19:04:36 +00:00
svx3d = Path ( cave . survex_file ) . stem
2022-03-18 20:00:15 +00:00
svxstem = Path ( settings . SURVEX_DATA ) / Path ( cave . survex_file )
# print(f" ! rendercave: slug:'{slug}' '' ++ '{svxstem}'")
except :
2023-07-05 17:43:57 +01:00
svx3d = " "
svxstem = " "
2022-03-18 20:00:15 +00:00
print ( f " ! rendercave: slug: ' { slug } ' FAIL TO MANAGE survex file: ' { cave . survex_file } ' " )
2021-04-03 20:52:35 +01:00
# NOTE the template itself loads the 3d file using javascript before it loads anything else.
# Django cannot see what this javascript is doing, so we need to ensure that the 3d file exists first.
2022-03-18 20:00:15 +00:00
# So only do this render if a valid .3d file exists. TO BE DONE -Not yet as CaveView is currently disabled
2021-11-06 20:59:10 +00:00
# see design docum in troggle/templates/cave.html
# see rendercave() in troggle/core/views/caves.py
2023-01-30 19:04:36 +00:00
templatefile = " cave.html "
2021-04-25 04:04:53 +01:00
if not cave_id :
2023-01-30 19:04:36 +00:00
cave_id = slug # cave.unofficial_number
context = {
" cave_editable " : True ,
" settings " : settings ,
" cave " : cave ,
" cavepage " : True ,
" cave_id " : cave_id ,
" svxstem " : str ( svxstem ) ,
" svx3d " : svx3d ,
}
2018-04-17 21:57:02 +01:00
2022-07-20 08:08:23 +01:00
# Do not catch any exceptions here: propagate up to caller
2023-01-30 19:04:36 +00:00
r = render (
request , templatefile , context
) # crashes here with NoReverseMatch if url not set up for 'edit_cave' in urls.py
2022-07-20 08:08:23 +01:00
return r
2023-09-30 20:09:18 +01:00
def cavepagefwd ( request , karea = None , subpath = None ) :
""" archaic, just send to the caves list page
"""
return redirect ( " /caves " )
2023-01-30 19:04:36 +00:00
2023-09-30 20:09:18 +01:00
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 ) :
2023-01-30 19:04:36 +00:00
""" Displays a cave description page
2021-04-03 20:52:35 +01:00
accessed by kataster area number specifically
2022-03-18 20:00:15 +00:00
OR
2023-01-30 19:04:36 +00:00
accessed by cave . url specifically set in data , e . g .
2023-05-02 15:42:58 +01:00
" 1623/000/000 " < = cave - data / 1623 - 000. html
2023-01-30 19:04:36 +00:00
" 1623/41/115.htm " < = cave - data / 1623 - 115. html
2023-09-06 22:01:03 +01:00
so we have to query the database to find the URL as we cannot rely on the url actually telling us the cave by inspection .
2023-05-02 15:42:58 +01:00
NOTE that old caves have " .html " ( or " .htm " ) in the URL as they used to be actual files . But since 2006 these URLs
refer to virtual pages generated on the fly by troggle , so the " .html " is confusing and redundant .
2023-01-30 19:04:36 +00:00
2023-05-02 15:42:58 +01:00
There are also A LOT OF URLS to e . g . / 1623 / 161 / l / rl89a . htm which are IMAGES and real html files
in cave descriptions . These need to be handled HERE too ( accident of history ) .
2023-01-30 19:04:36 +00:00
"""
2023-09-30 20:09:18 +01:00
2023-09-30 18:35:40 +01:00
# lack of validation for karea, it could be any 4 digits.
2023-09-30 20:09:18 +01:00
# subpath has an initial /
2022-03-18 20:00:15 +00:00
kpath = karea + subpath
# print(f" ! cavepage:'{kpath}' kataster area:'{karea}' rest of path:'{subpath}'")
2021-04-01 20:08:57 +01:00
try :
2023-01-30 19:04:36 +00:00
cave = Cave . objects . get ( url = kpath ) # ideally this will be unique
2021-04-01 20:08:57 +01:00
except Cave . DoesNotExist :
2021-04-01 21:44:03 +01:00
# probably a link to text or an image e.g. 1623/161/l/rl89a.htm i.e. an expoweb page
2022-03-22 02:22:15 +00:00
# cannot assume that this is a simple cave page, for a cave we don't know.
2022-06-23 17:18:21 +01:00
# print(f" ! cavepage: url={kpath} A cave of this name does not exist")
2022-06-18 22:41:00 +01:00
return expo . expopage ( request , kpath )
2021-04-01 20:08:57 +01:00
except Cave . MultipleObjectsReturned :
2023-01-30 19:04:36 +00:00
caves = Cave . objects . filter ( url = kpath )
2022-06-23 17:18:21 +01:00
# print(f" ! cavepage: url={kpath} multiple caves exist")
2021-04-17 23:59:11 +01:00
# we should have a -several variant for the cave pages, not just the svxcaves:
2023-01-30 19:04:36 +00:00
return render ( request , " svxcaveseveral.html " , { " settings " : settings , " caves " : caves } )
2022-06-23 17:18:21 +01:00
try :
r = rendercave ( request , cave , cave . slug ( ) )
return r
2022-06-23 17:01:57 +01:00
except NoReverseMatch :
2022-07-31 20:29:17 +01:00
if settings . DEBUG :
raise
else :
2023-01-30 19:04:36 +00:00
message = f " Failed to render cave: { kpath } (it does exist and is unique) because of a Django URL resolution error. Check urls.py. "
return render ( request , " errors/generic.html " , { " message " : message } )
2022-07-20 12:44:56 +01:00
except :
2023-01-30 19:04:36 +00:00
# anything else is a new problem. Add in specific error messages here as we discover new types of error
2022-07-20 12:44:56 +01:00
raise
2023-01-30 19:04:36 +00:00
2011-07-11 02:10:22 +01:00
@login_required_if_public
2023-01-30 19:04:36 +00:00
def edit_cave ( request , path = " " , slug = None ) :
""" This is the form that edits all the cave data and writes out an XML file in the :expoweb: repo folder
2021-04-02 23:21:23 +01:00
The format for the file being saved is in templates / dataformat / cave . xml
2023-09-11 18:38:14 +01:00
Warning . This uses Django deep magic in the CaveForm processing .
2023-01-30 19:04:36 +00:00
2023-04-22 01:24:32 +01:00
It saves the data into into the database and into the html file , which it then commits to git .
2023-01-30 19:04:36 +00:00
"""
2021-04-14 16:28:30 +01:00
message = " "
2023-01-30 19:04:36 +00:00
if slug is not None :
2021-12-05 21:45:06 +00:00
try :
2023-01-30 19:04:36 +00:00
cave = Cave . objects . get ( caveslug__slug = slug )
2021-12-05 21:45:06 +00:00
except :
2023-02-02 11:19:46 +00:00
return render ( request , " errors/badslug.html " , { " badslug " : f " { slug } - from edit_cave() " } )
2012-08-12 18:10:23 +01:00
else :
cave = Cave ( )
2012-05-23 09:23:40 +01:00
if request . POST :
form = CaveForm ( request . POST , instance = cave )
2023-07-05 18:22:08 +01:00
#ceFormSet = CaveAndEntranceFormSet(request.POST)
if form . is_valid ( ) : # and ceFormSet.is_valid():
2023-01-30 19:04:36 +00:00
# print(f'! POST is valid. {cave}')
cave = form . save ( commit = False )
2023-10-23 00:32:44 +01:00
print ( cave )
2023-07-05 17:43:57 +01:00
if not cave . filename :
2023-09-11 18:38:14 +01:00
cave . filename = cave . areacode + " - " + cave . number ( ) + " .html "
2023-07-05 17:43:57 +01:00
if not cave . url :
2023-09-11 18:38:14 +01:00
cave . url = cave . areacode + " / " + cave . number ( )
2012-08-12 18:10:23 +01:00
cave . save ( )
form . save_m2m ( )
if slug is None :
2023-09-25 23:10:50 +01:00
# it is not visible on the form so it always will be None
slug = f " { cave . areacode } - { cave . number ( ) } "
cs = CaveSlug ( cave = cave , slug = slug , primary = True )
2023-01-30 19:04:36 +00:00
cs . save ( )
2023-07-05 18:22:08 +01:00
#ceinsts = ceFormSet.save(commit=False)
#for ceinst in ceinsts:
# ceinst.cave = cave
# ceinst.save()
2021-12-30 21:13:34 +00:00
try :
2022-08-01 15:04:22 +01:00
cave_file = cave . file_output ( )
2023-04-22 03:26:53 +01:00
write_and_commit ( [ cave_file ] , f " Online edit of cave { cave } " )
2021-12-30 21:13:34 +00:00
# leave other exceptions unhandled so that they bubble up to user interface
except PermissionError :
2023-01-30 19:04:36 +00:00
message = f " CANNOT save this file. \n PERMISSIONS incorrectly set on server for this file { cave . filename } . Ask a nerd to fix this. "
return render ( request , " errors/generic.html " , { " message " : message } )
2022-07-19 17:06:56 +01:00
except subprocess . SubprocessError :
2023-01-30 19:04:36 +00:00
message = f " CANNOT git on server for this file { cave . filename } . Edits may not be committed. \n Ask a nerd to fix this. "
return render ( request , " errors/generic.html " , { " message " : message } )
2023-04-30 21:42:03 +01:00
if cave . entrances ( ) . count ( ) > 0 :
return HttpResponseRedirect ( " / " + cave . url )
else :
return HttpResponseRedirect ( reverse ( " newentrance " , args = [ cave . url_parent ( ) , cave . slug ( ) ] ) )
2023-04-30 19:05:57 +01:00
2012-05-23 09:23:40 +01:00
else :
2023-04-30 19:05:57 +01:00
if slug is not None :
# re-read cave data from file.
2023-07-05 18:22:08 +01:00
if cave . filename :
read_cave ( cave . filename , cave = cave )
2023-04-22 01:24:32 +01:00
2023-04-30 19:05:57 +01:00
form = CaveForm ( instance = cave , initial = { ' cave_slug ' : cave . slug ( ) } )
2023-07-05 18:22:08 +01:00
#ceFormSet = CaveAndEntranceFormSet(queryset=cave.caveandentrance_set.all())
2023-04-30 19:05:57 +01:00
else :
form = CaveForm ( )
2023-07-05 18:22:08 +01:00
#ceFormSet = CaveAndEntranceFormSet(queryset=CaveAndEntrance.objects.none())
2023-01-30 19:04:36 +00:00
return render (
request ,
" editcave.html " ,
{
" form " : form ,
" cave " : cave ,
" message " : message ,
2023-07-05 18:22:08 +01:00
#"caveAndEntranceFormSet": ceFormSet,
2023-10-07 00:26:52 +01:00
" path " : path + " / " , # used for saving images if attached
2023-01-30 19:04:36 +00:00
} ,
)
2012-06-10 14:59:21 +01:00
@login_required_if_public
2023-04-22 03:26:53 +01:00
def edit_entrance ( request , path = " " , caveslug = None , entslug = None ) :
2023-01-30 19:04:36 +00:00
""" This is the form that edits the entrance data for a single entrance and writes out
2021-04-14 16:28:30 +01:00
an XML file in the : expoweb : repo folder
2023-03-28 20:30:00 +01:00
2021-04-14 16:28:30 +01:00
The format for the file being saved is in templates / dataformat / entrance . xml
2023-03-28 20:30:00 +01:00
Warning . This uses Django deep magic for multiple forms and the CaveAndEntrance class .
2023-01-30 19:04:36 +00:00
2021-04-14 16:28:30 +01:00
It does save the data into into the database directly , not by parsing the file .
2023-10-11 15:02:11 +01:00
GET RID of all this entranceletter stuff . Far too overcomplexified .
We don ' t need it. Just the entrance slug is fine, then check uniqueness.
2023-01-30 19:04:36 +00:00
"""
2022-08-01 15:04:22 +01:00
try :
2023-01-30 19:04:36 +00:00
cave = Cave . objects . get ( caveslug__slug = caveslug )
2022-08-01 15:04:22 +01:00
except :
2023-04-22 03:26:53 +01:00
return render ( request , " errors/badslug.html " , { " badslug " : f " for cave { caveslug } - from edit_entrance() " } )
2022-08-01 15:04:22 +01:00
2023-04-30 19:05:57 +01:00
if entslug :
try :
entrance = Entrance . objects . get ( slug = entslug )
except :
return render ( request , " errors/badslug.html " , { " badslug " : f " for entrance { entslug } - from edit_entrance() " } )
else :
2023-08-04 21:33:49 +01:00
# a new entrance on a cave
2023-04-30 19:05:57 +01:00
entrance = None
2023-04-22 03:26:53 +01:00
if entslug :
2023-08-04 07:08:18 +01:00
print ( f " { caveslug =} { entslug =} { path =} number of ents: { cave . entrances ( ) . count ( ) } " )
2023-01-30 19:04:36 +00:00
caveAndEntrance = CaveAndEntrance . objects . get ( entrance = entrance , cave = cave )
2023-08-04 07:08:18 +01:00
entlettereditable = False
2012-08-12 18:10:23 +01:00
else :
2023-04-30 19:05:57 +01:00
caveAndEntrance = CaveAndEntrance ( cave = cave , entrance = Entrance ( ) )
2022-08-01 15:04:22 +01:00
entlettereditable = True
2023-08-04 07:08:18 +01:00
if caveAndEntrance . entranceletter == " " and cave . entrances ( ) . count ( ) > 0 :
# this should not be blank on a multiple-entrance cave
2023-10-11 15:02:11 +01:00
# but it doesn't trigger the entrance letter form unless entletter has a value
2023-08-04 07:08:18 +01:00
entlettereditable = True
print ( f " { entlettereditable =} " )
2023-10-11 15:02:11 +01:00
# if the entletter is no editable, then the entletterform does not appear and so is always invalid.
2023-08-04 07:08:18 +01:00
2012-06-10 14:59:21 +01:00
if request . POST :
2023-08-04 21:33:49 +01:00
print ( f " POST Online edit of entrance: ' { entrance } ' where { cave =} " )
2023-10-11 15:02:11 +01:00
entform = EntranceForm ( request . POST , instance = entrance )
if not entlettereditable :
entranceletter = caveAndEntrance . entranceletter
ce = caveAndEntrance
else :
entletterform = EntranceLetterForm ( request . POST , instance = caveAndEntrance )
if entletterform . is_valid ( ) :
ce = entletterform . save ( commit = False )
entranceletter = entletterform . cleaned_data [ " entranceletter " ]
else :
print ( f " - POST INVALID { caveslug =} { entslug =} { path =} entletterform invalid. " )
2023-11-05 13:20:45 +00:00
return render ( request , " errors/badslug.html " , { " badslug " : " entletter problem in edit_entrances() " } )
2023-10-11 15:02:11 +01:00
# if entform.is_valid() and entletterform.is_valid():
if entform . is_valid ( ) :
entrance = entform . save ( commit = False )
print ( f " - POST { caveslug =} { entslug =} { entranceletter =} { path =} " )
2023-04-22 03:26:53 +01:00
if entslug is None :
2023-10-11 15:02:11 +01:00
if entranceletter :
slugname = cave . slug ( ) + entranceletter
print ( f " - POST letter { entranceletter =} " )
2021-04-25 04:04:53 +01:00
else :
slugname = cave . slug ( )
2023-04-22 03:26:53 +01:00
entrance . slug = slugname
2012-08-14 21:51:15 +01:00
entrance . cached_primary_slug = slugname
entrance . filename = slugname + " .html "
2023-07-26 14:54:37 +01:00
else :
entrance . slug = entslug
entrance . cached_primary_slug = entslug
entrance . filename = entslug + " .html "
2023-08-04 21:33:49 +01:00
try :
entrance . save ( )
except :
# fails with uniqueness constraint failure. Which is on CaveAndEntrance, not just on entrance, which is bizarre.
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
ce . save ( )
2023-07-24 23:03:12 +01:00
2022-08-01 15:04:22 +01:00
entrance_file = entrance . file_output ( )
cave_file = cave . file_output ( )
2023-08-03 14:11:46 +01:00
2023-08-04 21:33:49 +01:00
print ( f " - POST WRITE letter: ' { ce } ' { entrance =} " )
2023-04-22 03:26:53 +01:00
write_and_commit ( [ entrance_file , cave_file ] , f " Online edit of entrance { entrance . slug } " )
2023-01-30 19:04:36 +00:00
return HttpResponseRedirect ( " / " + cave . url )
2023-10-11 15:02:11 +01:00
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 ( ) } " )
2023-08-02 08:17:48 +01:00
else : # GET the page, not POST, or if either of the forms were invalid when POSTed
2023-08-04 08:29:20 +01:00
entletterform = None
2023-08-04 14:17:52 +01:00
entletter = " "
2023-08-03 14:11:46 +01:00
print ( f " ENTRANCE in db: entranceletter = ' { caveAndEntrance . entranceletter } ' " )
2023-04-30 19:05:57 +01:00
if entrance :
# re-read entrance data from file.
filename = str ( entrance . slug + " .html " )
2023-08-03 14:11:46 +01:00
ent = read_entrance ( filename , ent = entrance )
print ( f " ENTRANCE from file: entranceletter = ' { caveAndEntrance . entranceletter } ' " )
2023-04-22 03:26:53 +01:00
2023-08-04 14:11:35 +01:00
entform = EntranceForm ( instance = entrance )
2023-04-30 19:05:57 +01:00
if entslug is None :
2023-08-04 07:29:25 +01:00
entletterform = EntranceLetterForm ( )
# print(f" Getting entletter from EntranceLetterForm")
2023-04-30 19:05:57 +01:00
else :
2023-08-03 14:11:46 +01:00
entletter = caveAndEntrance . entranceletter
2023-08-04 07:29:25 +01:00
if entletter == " " :
entletterform = EntranceLetterForm ( )
print ( f " Blank value: getting entletter from EntranceLetterForm " )
2023-08-04 08:29:20 +01:00
print ( f " { entletter =} " )
2012-08-14 21:51:15 +01:00
else :
2023-08-04 07:29:25 +01:00
entform = EntranceForm ( )
entletterform = EntranceLetterForm ( )
2022-08-01 15:04:22 +01:00
2023-01-30 19:04:36 +00:00
return render (
request ,
" editentrance.html " ,
{
2023-08-04 14:17:52 +01:00
" entform " : entform ,
2023-01-30 19:04:36 +00:00
" cave " : cave ,
" entletter " : entletter ,
2023-08-04 08:29:20 +01:00
" entletterform " : entletterform , # is unset if not being used
2023-01-30 19:04:36 +00:00
" entlettereditable " : entlettereditable ,
2023-10-07 00:26:52 +01:00
" path " : path + " / " , # used for saving images if attached
2023-01-30 19:04:36 +00:00
} ,
)
2011-07-11 02:10:22 +01:00
def ent ( request , cave_id , ent_letter ) :
2023-01-30 19:04:36 +00:00
cave = Cave . objects . filter ( kataster_number = cave_id ) [ 0 ]
2023-08-03 14:11:46 +01:00
cave_and_ent = CaveAndEntrance . objects . filter ( cave = cave ) . filter ( entranceletter = ent_letter ) [ 0 ]
2023-01-30 19:04:36 +00:00
return render (
request ,
" entrance.html " ,
{
" cave " : cave ,
" entrance " : cave_and_ent . entrance ,
2023-08-03 14:11:46 +01:00
" letter " : cave_and_ent . entranceletter ,
2023-01-30 19:04:36 +00:00
} ,
)
2023-03-28 15:37:25 +01:00
def cave_debug ( request ) :
2023-03-28 17:08:55 +01:00
ents = Entrance . objects . all ( ) . order_by ( ' id ' )
2023-03-28 15:37:25 +01:00
return render (
request ,
" cave_debug.html " ,
{ " ents " : ents } ,
)
2011-07-11 02:10:22 +01:00
def get_entrances ( request , caveslug ) :
2021-12-05 21:45:06 +00:00
try :
2023-01-30 19:04:36 +00:00
cave = Cave . objects . get ( caveslug__slug = caveslug )
2021-12-05 21:45:06 +00:00
except :
2023-02-02 11:19:46 +00:00
return render ( request , " errors/badslug.html " , { " badslug " : f " { caveslug } - from get_entrances() " } )
2023-01-30 19:04:36 +00:00
return render (
request , " options.html " , { " items " : [ ( e . entrance . slug ( ) , e . entrance . slug ( ) ) for e in cave . entrances ( ) ] }
)
2011-07-11 02:10:22 +01:00
2023-04-05 23:13:12 +01:00
def caveQMs ( request , slug , open = False ) :
2023-01-30 19:04:36 +00:00
""" Lists all the QMs on a particular cave
2022-07-05 13:38:23 +01:00
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
2023-01-30 19:04:36 +00:00
"""
2022-03-18 20:00:15 +00:00
try :
2023-01-30 19:04:36 +00:00
cave = Cave . objects . get ( caveslug__slug = slug )
2022-03-18 20:00:15 +00:00
except :
2023-02-02 11:19:46 +00:00
return render ( request , " errors/badslug.html " , { " badslug " : f " { slug } - from caveQMs() " } )
2023-01-30 19:04:36 +00:00
2022-03-18 20:00:15 +00:00
if cave . non_public and settings . PUBLIC_SITE and not request . user . is_authenticated :
2023-01-30 19:04:36 +00:00
return render ( request , " nonpublic.html " , { " instance " : cave } )
2023-04-05 23:13:12 +01:00
elif open :
return render ( request , " cave_open_qms.html " , { " cave " : cave } )
2022-03-18 20:00:15 +00:00
else :
2023-01-30 19:04:36 +00:00
return render ( request , " cave_qms.html " , { " cave " : cave } )
2022-03-18 20:00:15 +00:00
2023-01-30 19:04:36 +00:00
def qm ( request , cave_id , qm_id , year , grade = None , blockname = None ) :
""" Reports on one specific QM
Fixed and working July 2022 , for both CSV imported QMs
2023-03-17 20:01:52 +00:00
Needs refactoring though ! Uses extremely baroque way of getting the QMs instead of querying for QM objects
directly , presumably as a result of a baroque history .
2023-01-30 19:04:36 +00:00
2023-03-17 20:01:52 +00:00
Many caves have several QMS with the same number , grade , year ( 2018 ) and first 8 chars of the survexblock . This crashes things , so the terminal char of the survexblock name was added
2023-01-30 19:04:36 +00:00
"""
year = int ( year )
if blockname == " " or not blockname :
2022-07-06 13:38:53 +01:00
# CSV import QMs, use old technique
try :
2023-01-30 19:04:36 +00:00
c = getCave ( cave_id )
2023-03-17 20:01:52 +00:00
manyqms = c . get_open_QMs ( ) | c . get_ticked_QMs ( ) # set union operation
2023-03-18 03:03:06 +00:00
qm = manyqms . get ( number = qm_id , expoyear = year , grade = grade )
2023-01-30 19:04:36 +00:00
return render ( request , " qm.html " , { " qm " : qm } )
2022-07-06 13:38:53 +01:00
except QM . DoesNotExist :
2023-01-30 19:04:36 +00:00
# raise
return render (
request ,
" errors/badslug.html " ,
{
" badslug " : f " QM.DoesNotExist blockname is empty string: { cave_id =} { year =} { qm_id =} { grade =} { blockname =} "
} ,
)
2023-03-18 03:03:06 +00:00
except QM . MultipleObjectsReturned :
# raise
qms = manyqms . filter ( number = qm_id , expoyear = year )
return render (
request ,
" errors/badslug.html " ,
{
" badslug " : f " QM.MultipleObjectsReturned { cave_id =} { year =} { qm_id =} { grade =} { blockname =} { qms =} "
} ,
)
2022-07-06 13:38:53 +01:00
else :
try :
2023-01-30 19:04:36 +00:00
qmslug = f " { cave_id } - { year } - { blockname =} { qm_id } { grade } "
print ( f " { qmslug =} " )
c = getCave ( cave_id )
2023-03-17 20:01:52 +00:00
manyqms = c . get_open_QMs ( ) | c . get_ticked_QMs ( ) # set union operation
2023-01-30 19:04:36 +00:00
qmqs = manyqms . filter ( expoyear = year , blockname = blockname , number = qm_id , grade = grade )
if len ( qmqs ) > 1 :
2022-07-06 15:35:08 +01:00
for q in qmqs :
print ( qmqs )
2023-03-17 20:01:52 +00:00
message = f " Multiple QMs with the same cave, year, number, grade AND first-several+terminal chars of the survexblock name. (Could be caused by incomplete databasereset). Fix this in the survex file(s). { cave_id =} { year =} { qm_id =} { blockname =} "
2023-01-30 19:04:36 +00:00
return render ( request , " errors/generic.html " , { " message " : message } )
2022-07-06 13:38:53 +01:00
else :
2023-01-30 19:04:36 +00:00
qm = qmqs . get ( expoyear = year , blockname = blockname , number = qm_id , grade = grade )
2022-07-06 15:35:08 +01:00
if qm :
2023-01-30 19:04:36 +00:00
print (
qm ,
f " { qmslug =} : { cave_id =} { year =} { qm_id =} { blockname =} { qm . expoyear =} { qm . completion_description =} " ,
)
return render ( request , " qm.html " , { " qm " : qm } )
2022-07-06 15:35:08 +01:00
else :
2023-01-30 19:04:36 +00:00
# raise
return render (
request ,
" errors/badslug.html " ,
{ " badslug " : f " Failed get { cave_id =} { year =} { qm_id =} { grade =} { blockname =} " } ,
)
2022-07-06 13:38:53 +01:00
except MultipleObjectsReturned :
2023-03-17 20:01:52 +00:00
message = f " Multiple QMs with the same cave, year, number, grade AND first-several+terminal chars of the survexblock name. (Could be caused by incomplete databasereset). Fix this in the survex file(s). { cave_id =} { year =} { qm_id =} { blockname =} "
2023-01-30 19:04:36 +00:00
return render ( request , " errors/generic.html " , { " message " : message } )
2022-07-06 13:38:53 +01:00
except QM . DoesNotExist :
2023-01-30 19:04:36 +00:00
# raise
return render (
request ,
" errors/badslug.html " ,
{
" badslug " : f " QM.DoesNotExist blockname is not empty string { cave_id =} { year =} { qm_id =} { grade =} { blockname =} "
} ,
)
2023-05-01 00:01:41 +01:00
def expo_kml ( request ) :
return render (
request ,
" expo.kml " ,
{
" entrances " : Entrance . objects . all ( )
} ,
content_type = " application/vnd.google-earth.kml+xml "
)
2023-05-08 01:10:43 +01:00
def expo_kmz ( request ) :
notablecaves = set ( getnotablecaves ( ) )
#Zip file written to a file, to save this function using too much memory
with tempfile . TemporaryDirectory ( ) as tmpdirname :
zippath = os . path . join ( tmpdirname , ' expo.kmz ' )
with zipfile . ZipFile ( zippath , ' w ' , compression = zipfile . ZIP_DEFLATED ) as myzip :
entrances = [ ]
for e in Entrance . objects . all ( ) :
html = loader . get_template ( " entrance_html.kml " ) . render ( { " entrance " : e } , request )
soup = BeautifulSoup ( html )
for img in soup . find_all ( " img " ) :
#src_orig = img['src']
src = urllib . parse . urljoin ( e . cavelist ( ) [ 0 ] . url . rpartition ( " / " ) [ 0 ] + " / " , img [ ' src ' ] )
img [ ' src ' ] = src
p = os . path . join ( settings . EXPOWEB , src )
#print(e.cavelist()[0].url, e.cavelist()[0].url.rpartition("/")[0] + "/", src_orig, p)
if os . path . isfile ( p ) :
myzip . write ( p , src )
for a in soup . find_all ( " a " ) :
try :
ao = a [ ' href ' ]
aa = urllib . parse . urljoin ( e . cavelist ( ) [ 0 ] . url . rpartition ( " / " ) [ 0 ] + " / " , ao )
a [ ' href ' ] = urllib . parse . urljoin ( " https://expo.survex.com/ " , aa )
print ( e . cavelist ( ) [ 0 ] . url . rpartition ( " / " ) [ 0 ] + " / " , ao , a [ ' href ' ] )
except :
pass
html = mark_safe ( soup . prettify ( " utf-8 " ) . decode ( " utf-8 " ) )
size = { True : " large " , False : " small " } [ bool ( set ( e . cavelist ( ) ) & notablecaves ) ]
entrances . append ( loader . get_template ( " entrance.kml " ) . render ( { " entrance " : e , " html " : html , " size " : size } , request ) )
s = loader . get_template ( " expo.kml " ) . render ( { " entrances " : entrances } , request )
myzip . writestr ( " expo.kml " , s )
for f in os . listdir ( settings . KMZ_ICONS_PATH ) :
p = os . path . join ( settings . KMZ_ICONS_PATH , f )
if os . path . isfile ( p ) :
myzip . write ( p , os . path . join ( " icons " , f ) )
return FileResponse ( open ( zippath , ' rb ' ) , content_type = " application/vnd.google-earth.kmz .kmz " )