troggle-unchained/core/views/expo.py

369 lines
18 KiB
Python
Raw Normal View History

2020-05-24 01:57:06 +01:00
import os
import re
2021-05-03 00:52:51 +01:00
import subprocess
from pathlib import Path
from urllib.parse import urljoin, unquote as urlunquote
from urllib.request import urlopen
from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseRedirect, Http404
2020-06-18 21:50:16 +01:00
from django.urls import reverse, resolve
from django.template import Context, loader
2021-03-26 23:40:34 +00:00
from django.views.decorators.csrf import ensure_csrf_cookie
2021-03-31 17:57:43 +01:00
from django.contrib import admin
2021-03-26 23:40:34 +00:00
import django.forms as forms
2020-05-24 01:57:06 +01:00
2021-05-03 20:35:35 +01:00
from .auth import login_required_if_public
2021-04-13 00:47:17 +01:00
from troggle.core.models.caves import Cave
2021-03-31 21:51:17 +01:00
import troggle.core.views.caves
2020-05-24 01:57:06 +01:00
import troggle.settings as settings
'''Formerly a separate package called 'flatpages' written by Martin Green 2011.
This was NOT django.contrib.flatpages which stores HTML in the database, so the name was changed to expopages.
2021-03-30 21:48:36 +01:00
Then it was incorporated into troggle directly, rather than being an unnecessary external package.
'''
default_head = '''<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>CUCC Expedition - index</title>
<link rel="stylesheet" type="text/css" href="../css/main2.css" />
<link rel="stylesheet" type="text/css" href="../../css/main2.css" />
<link rel="stylesheet" type="text/css" href="../../../css/main2.css" />
</head>
<body>
<h1>Expo</h1>
<h2 id="tophead">CUCC Expedition</h2>
<ul id="links">
<li><a href="/index.htm">Home</a></li>
<li><a href="/infodx.htm">Main Index</a></li>
<li><a href="/handbook/index.htm">Handbook</a></li>
<li><a href="/handbook/computing/onlinesystems.html">Online systems</a></li>
<li><a href="/pubs.htm">Reports</a></li>
<li><a href="/areas.htm">Areas</a></li>
<li><a href="/caves">Caves</a></li>
<li><a href="/expedition/2019">Troggle</a></li>
<li><form name=P method=get action="/search" target="_top">
2022-02-23 22:51:37 +00:00
<input id="omega-autofocus" type=search name=P size=8 autofocus>
<input type=submit value="Search"></li>
2022-02-26 23:20:59 +00:00
</ul>''' # this gets overwritten by templates/menu.html by django for most normal pages
def expofiles_redirect(request, filepath):
'''This is used only when running as a test system without a local copy of /expofiles/
2021-04-10 15:30:29 +01:00
when settings.EXPOFILESREMOTE is True
'''
return redirect(urljoin('http://expo.survex.com/expofiles/', filepath))
2021-04-16 21:29:32 +01:00
def map(request):
'''Serves unadorned the expoweb/map/map.html file
'''
fn = Path(settings.EXPOWEB, 'map', 'map.html')
return HttpResponse(content=open(fn, "r"),content_type='text/html')
def mapfile(request, path):
'''Serves unadorned file
'''
fn = Path(settings.EXPOWEB, 'map', path)
return HttpResponse(content=open(fn, "r"),content_type=getmimetype(fn))
def expofilessingle(request, filepath):
2021-04-10 15:30:29 +01:00
'''sends a single binary file to the user, if not found, show the parent directory
If the path actually is a directory, then show that.
'''
#print(f' - expofilessingle {filepath}')
if filepath =="" or filepath =="/":
return expofilesdir(request, settings.EXPOFILES, "")
fn=urlunquote(filepath)
fn = Path(settings.EXPOFILES,filepath)
if fn.is_dir():
return expofilesdir(request, Path(fn), Path(filepath))
2021-04-10 15:30:29 +01:00
if fn.is_file():
return HttpResponse(content=open(fn, "rb"),content_type=getmimetype(filepath)) # any file
else:
# not a file, so show parent directory - DANGER need to check this is limited to below expofiles
if Path(fn).parent == Path(settings.EXPOFILES).parent:
return expofilesdir(request, Path(settings.EXPOFILES), Path(filepath).parent)
else:
return expofilesdir(request, Path(fn).parent, Path(filepath).parent)
def expofilesdir(request, dirpath, filepath):
'''does a directory display. If there is an index.html file we should display that.
2021-04-30 03:44:53 +01:00
- dirpath is a full Path() resolved including local machine /expofiles/
2021-04-10 15:30:29 +01:00
- filepath is a Path() and it does not have /expofiles/ in it
'''
#print(f' - expofilesdir {dirpath} settings.EXPOFILESREMOTE: {settings.EXPOFILESREMOTE}')
if filepath:
urlpath = 'expofiles' / Path(filepath)
else:
urlpath = Path('expofiles')
2021-04-10 15:30:29 +01:00
try:
for f in dirpath.iterdir():
pass
except FileNotFoundError:
#print(f' - expofilesdir error {dirpath}')
2021-04-10 15:30:29 +01:00
return expofilesdir(request, dirpath.parent, filepath.parent)
fileitems = []
2021-04-10 15:30:29 +01:00
diritems = []
for f in dirpath.iterdir():
if f.is_dir():
diritems.append((urlpath / f.parts[-1], str(f.parts[-1])))
else:
# if f.parts[-1].lower() == 'index.htm' or f.parts[-1].lower() == 'index.html': # css cwd problem
# return HttpResponse(content=open(f, "rb"),content_type=getmimetype(filepath)) # any file
# return expofilessingle(request, str(Path(filepath / f.parts[-1])))
fileitems.append((Path(urlpath) / f.parts[-1], str(f.parts[-1]), getmimetype(f)))
return render(request, 'dirdisplay.html', { 'filepath': urlpath, 'fileitems':fileitems, 'diritems': diritems,'settings': settings })
2021-03-31 17:57:43 +01:00
def expowebpage(request, expowebpath, path):
'''Adds menus and serves an HTML page
2021-03-31 17:57:43 +01:00
'''
if not Path(expowebpath / path).is_file():
2021-12-30 14:15:08 +00:00
# Should not get here if the path has suffix "_edit"
print(f' - 404 error in expowebpage() {path}')
2021-05-03 20:35:35 +01:00
return render(request, 'pagenotfound.html', {'path': path}, status="404")
2021-03-31 17:57:43 +01:00
2022-03-22 02:22:15 +00:00
try:
with open(os.path.normpath(expowebpath / path), "r") as o:
html = o.read()
except:
2022-03-30 23:53:57 +01:00
try:
with open(os.path.normpath(expowebpath / path), "rb") as o:
2022-03-31 00:07:47 +01:00
html = str(o.read()).replace("<h1>","<h1>BAD NON-UTF-* characters -")
2022-03-30 23:53:57 +01:00
except:
return HttpResponse(default_head + '<h3>UTF-8 Parsing Failure:<br>Page could not be parsed using UTF-8:<br>failure detected in expowebpage in views.expo.py</h3> Please edit this <var>:expoweb:</var> page to replace dubious umlauts and &pound; symbols with correct HTML entities e.g. <em>&amp;pound;;</em>. </body' )
2022-03-22 02:22:15 +00:00
2021-04-06 22:50:57 +01:00
m = re.search(r'(.*)<\s*head([^>]*)>(.*)<\s*/head\s*>(.*)<\s*body([^>]*)>(.*)<\s*/body\s*>(.*)', html, re.DOTALL + re.IGNORECASE)
2021-03-31 17:57:43 +01:00
if m:
preheader, headerattrs, head, postheader, bodyattrs, body, postbody = m.groups()
else:
2021-04-06 22:50:57 +01:00
return HttpResponse(default_head + html + '<h3>HTML Parsing failure:<br>Page could not be parsed into header and body:<br>failure detected in expowebpage in views.expo.py</h3> Please edit this <var>:expoweb:</var> page to be in the expected full HTML format </body' )
m = re.search(r"<title>(.*)</title>", head, re.DOTALL + re.IGNORECASE)
2021-03-31 17:57:43 +01:00
if m:
title, = m.groups()
else:
title = ""
2021-05-02 22:47:59 +01:00
m = re.search(r"^<meta([^>]*)noedit", head, re.DOTALL + re.IGNORECASE)
2021-03-31 17:57:43 +01:00
if m:
editable = False
else:
editable = os.access(Path(expowebpath / path), os.W_OK) # are file permissions writeable?
2021-03-31 17:57:43 +01:00
has_menu = False
2021-04-06 22:50:57 +01:00
menumatch = re.match(r'(.*)<div id="menu">', body, re.DOTALL + re.IGNORECASE)
2021-03-31 17:57:43 +01:00
if menumatch:
has_menu = True
2021-04-06 22:50:57 +01:00
menumatch = re.match(r'(.*)<ul id="links">', body, re.DOTALL + re.IGNORECASE)
2021-03-31 17:57:43 +01:00
if menumatch:
has_menu = True
2021-04-10 15:30:29 +01:00
return render(request, 'expopage.html', {'editable': editable, 'path': path, 'title': title,
2021-03-31 17:57:43 +01:00
'body': body, 'homepage': (path == "index.htm"), 'has_menu': has_menu})
def mediapage(request, subpath=None, doc_root=None):
'''This is for special prefix paths /photos/ /site_media/, /static/ etc.
as defined in urls.py . If given a directory, gives a failure page.
'''
#print(" - XXXXX_ROOT: {} ...{}".format(doc_root, subpath))
if doc_root is not None:
filetobeopened = Path(doc_root, subpath)
if filetobeopened.is_dir():
return render(request, 'nodirlist.html', {'path': subpath})
try:
return HttpResponse(content=open(filetobeopened, "rb"), content_type=getmimetype(subpath))
except IOError:
2021-05-03 20:35:35 +01:00
return render(request, 'pagenotfound.html', {'path': subpath}, status="404")
else:
2021-05-03 20:35:35 +01:00
return render(request, 'pagenotfound.html', {'path': subpath}, status="404")
2021-03-31 17:57:43 +01:00
2021-03-31 16:14:36 +01:00
def expopage(request, path):
'''Either renders an HTML page from expoweb with all the menus,
or serves an unadorned binary file with mime type
'''
2021-03-31 17:57:43 +01:00
#print(" - EXPOPAGES delivering the file: '{}':{} as MIME type: {}".format(request.path, path,getmimetype(path)),flush=True)
2021-04-07 21:53:17 +01:00
if path.startswith("noinfo") and settings.PUBLIC_SITE and not request.user.is_authenticated:
return HttpResponseRedirect(urljoin(reverse("auth_login"),'?next={}'.format(request.path)))
2021-03-31 17:57:43 +01:00
if path.startswith("admin/"):
# don't even attempt to handle these sorts of mistakes
return HttpResponseRedirect("/admin/")
expowebpath = Path(settings.EXPOWEB)
2021-03-31 17:57:43 +01:00
if path == "":
return expowebpage(request, expowebpath, "index.htm")
if path.endswith(".htm") or path.endswith(".html"):
return expowebpage(request, expowebpath, path)
if Path(expowebpath / path ).is_dir():
for p in ["index.html", "index.htm"]:
if (expowebpath / path / p).is_file():
# This needs to reset the path to the new subdirectory
return HttpResponseRedirect('/'+str(Path(path) / p))
2021-05-03 20:35:35 +01:00
return render(request, 'pagenotfound.html', {'path': Path(path) / "index.html"}, status="404")
2021-03-31 17:57:43 +01:00
if path.endswith("/"):
# we already know it is not a directory.
# the final / may have been appended by middleware if there was no page without it
# do not redirect to a file path without the slash as we may get in a loop. Let the user fix it:
return render(request, 'dirnotfound.html', {'path': path, 'subpath': path[0:-1]})
# So it must be a file in /expoweb/ but not .htm or .html probably an image
filetobeopened = os.path.normpath(expowebpath / path)
2021-03-31 17:57:43 +01:00
try:
return HttpResponse(content=open(filetobeopened, "rb"), content_type=getmimetype(path))
2021-03-31 17:57:43 +01:00
except IOError:
2021-05-03 20:35:35 +01:00
return render(request, 'pagenotfound.html', {'path': path}, status="404")
2021-03-31 17:57:43 +01:00
def getmimetype(path):
2021-04-10 15:30:29 +01:00
'''Our own version rather than relying on what is provided by the python library. Note that when
Apache or nginx is used to deliver /expofiles/ it will use it's own idea of mimetypes and
not these.
'''
path = str(path)
if path.lower().endswith(".css"): return "text/css"
2021-03-22 02:27:19 +00:00
if path.lower().endswith(".txt"): return "text/css"
if path.lower().endswith(".js"): return "application/javascript"
2021-03-22 02:27:19 +00:00
if path.lower().endswith(".json"): return "application/javascript"
if path.lower().endswith(".ico"): return "image/vnd.microsoft.icon"
2012-08-14 14:05:15 +01:00
if path.lower().endswith(".png"): return "image/png"
if path.lower().endswith(".tif"): return "image/tif"
if path.lower().endswith(".gif"): return "image/gif"
if path.lower().endswith(".jpeg"): return "image/jpeg"
if path.lower().endswith(".jpg"): return "image/jpeg"
if path.lower().endswith("svg"): return "image/svg+xml"
if path.lower().endswith("xml"): return "application/xml" # we use "text/xhtml" for tunnel files
2012-08-14 14:05:15 +01:00
if path.lower().endswith(".pdf"): return "application/pdf"
if path.lower().endswith(".ps"): return "application/postscript"
if path.lower().endswith(".svx"): return "application/x-survex-svx"
if path.lower().endswith(".3d"): return "application/x-survex-3d"
if path.lower().endswith(".pos"): return "application/x-survex-pos"
if path.lower().endswith(".err"): return "application/x-survex-err"
if path.lower().endswith(".odt"): return "application/vnd.oasis.opendocument.text"
if path.lower().endswith(".ods"): return "application/vnd.oasis.opendocument.spreadsheet"
if path.lower().endswith(".docx"): return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
if path.lower().endswith(".xslx"): return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
if path.lower().endswith(".gz"): return "application/x-7z-compressed"
if path.lower().endswith(".7z"): return "application/x-7z-compressed"
if path.lower().endswith(".zip"): return "application/zip"
return ""
@login_required_if_public
2021-03-26 23:40:34 +00:00
@ensure_csrf_cookie
2021-03-31 16:14:36 +01:00
def editexpopage(request, path):
2021-04-10 15:30:29 +01:00
'''Manages the 'Edit this Page' capability for expo handbook and other html pages.
Relies on HTML5 or javascript to provide the in-browser editing environment.
2021-04-10 15:30:29 +01:00
'''
try:
2021-04-10 15:30:29 +01:00
# if a cave not a webpage at all.
r = Cave.objects.get(url = path)
2021-03-31 21:51:17 +01:00
return troggle.core.views.caves.editCave(request, r.cave.slug)
except Cave.DoesNotExist:
pass
try:
2021-03-26 23:40:34 +00:00
filepath = Path(settings.EXPOWEB) / path
o = open(filepath, "r")
html = o.read()
2021-12-30 14:15:08 +00:00
autogeneratedmatch = re.search(r"\<\!--\s*(.*?(Do not edit|It is auto-generated).*?)\s*--\>", html, re.DOTALL + re.IGNORECASE)
if autogeneratedmatch:
return HttpResponse(autogeneratedmatch.group(1))
m = re.search(r"(.*)<head([^>]*)>(.*)</head>(.*)<body([^>]*)>(.*)</body>(.*)", html, re.DOTALL + re.IGNORECASE)
if m:
filefound = True
preheader, headerargs, head, postheader, bodyargs, body, postbody = m.groups()
linksmatch = re.match(r'(.*)(<ul\s+id="links">.*)', body, re.DOTALL + re.IGNORECASE)
2011-08-08 13:11:57 +01:00
if linksmatch:
body, links = linksmatch.groups()
2020-06-18 15:54:40 +01:00
# if re.search(r"iso-8859-1", html):
# body = str(body, "iso-8859-1")
else:
return HttpResponse(default_head + html + '<h3>HTML Parsing failure:<br>Page could not be parsed into header and body:<br>failure detected in expowebpage in views.expo.py</h3> Please edit this <var>:expoweb:</var> page to be in the expected full HTML format .</body' )
except IOError:
2021-03-26 23:40:34 +00:00
print("### File not found ### ", filepath)
filefound = False
if request.method == 'POST': # If the form has been submitted...
2021-04-10 15:30:29 +01:00
pageform = ExpoPageForm(request.POST) # A form bound to the POST data
if pageform.is_valid():# Form valid therefore write file
print("### \n", str(pageform)[0:300])
2021-03-26 23:40:34 +00:00
print("### \n csrfmiddlewaretoken: ",request.POST['csrfmiddlewaretoken'])
if filefound:
headmatch = re.match(r"(.*)<title>.*</title>(.*)", head, re.DOTALL + re.IGNORECASE)
if headmatch:
2021-04-10 15:30:29 +01:00
head = headmatch.group(1) + "<title>" + pageform.cleaned_data["title"] + "</title>" + headmatch.group(2)
else:
2021-04-10 15:30:29 +01:00
head = "<title>" + pageform.cleaned_data["title"] + "</title>"
else:
2021-04-10 15:30:29 +01:00
head = "<title>" + pageform.cleaned_data["title"] + "</title>"
preheader = "<html>"
headerargs = ""
postheader = ""
bodyargs = ""
2021-12-07 23:46:55 +00:00
postbody = "</html>\n"
2021-04-10 15:30:29 +01:00
body = pageform.cleaned_data["html"]
body = body.replace("\r", "")
2020-05-24 01:57:06 +01:00
result = "%s<head%s>%s</head>%s<body%s>\n%s</body>%s" % (preheader, headerargs, head, postheader, bodyargs, body, postbody)
2021-05-03 00:52:51 +01:00
cwd = filepath.parent
filename = filepath.name
git = settings.GIT
# GIT see also core/models/cave.py writetrogglefile()
# GIT see also core/views/uploads.py dwgupload()
2021-12-07 23:46:55 +00:00
try:
with open(filepath, "w") as f:
print(f'WRITING{cwd}---{filename} ')
2021-12-30 21:13:34 +00:00
# as the wsgi process www-data, we have group write-access but are not owner, so cannot chmod.
# os.chmod(filepath, 0o664) # set file permissions to rw-rw-r--
f.write(result)
2021-12-07 23:46:55 +00:00
except PermissionError:
message = f'CANNOT save this file.\nPERMISSIONS incorrectly set on server for this file {filename}. Ask a nerd to fix this.'
return render(request,'errors/generic.html', {'message': message})
2021-12-30 22:46:34 +00:00
try:
2021-12-30 23:27:42 +00:00
cp_add = subprocess.run([git, "add", filename], cwd=cwd, capture_output=True, text=True)
if cp_add.returncode != 0:
msgdata = 'Ask a nerd to fix this.\n\n' + cp_add.stderr + '\n\n' + cp_add.stdout + '\n\nreturn code: ' + str(cp_add.returncode)
message = f'CANNOT git on server for this file {filename}. Edits saved but not added to git.\n\n' + msgdata
return render(request,'errors/generic.html', {'message': message})
cp_commit = subprocess.run([git, "commit", "-m", f'Troggle online: Edit this page - {filename}'], cwd=cwd, capture_output=True, text=True)
# This produces return code = 1 if it commits OK, but when the repo still needs to be pushed to origin/expoweb
2021-12-30 23:27:42 +00:00
if cp_commit.returncode != 0 and cp_commit.stdout != 'nothing to commit, working tree clean':
msgdata = 'Ask a nerd to fix this.\n\n' + cp_commit.stderr + '\n\n' + cp_commit.stdout + '\n\nreturn code: ' + str(cp_commit.returncode)
message = f'Error code with git on server for this file {filename}. Edits saved, added to git, but NOT committed.\n\n' + msgdata
2021-12-30 23:27:42 +00:00
return render(request,'errors/generic.html', {'message': message})
2021-12-30 22:46:34 +00:00
except subprocess.SubprocessError:
2021-12-30 23:27:42 +00:00
message = f'CANNOT git on server for this file {filename}. Subprocess error. Edits not saved.\nAsk a nerd to fix this.'
2021-12-07 23:46:55 +00:00
return render(request,'errors/generic.html', {'message': message})
return HttpResponseRedirect(reverse('expopage', args=[path])) # Redirect after POST
else:
if filefound:
2011-08-08 12:58:02 +01:00
m = re.search(r"<title>(.*)</title>", head, re.DOTALL + re.IGNORECASE)
if m:
title, = m.groups()
else:
title = ""
2021-04-10 15:30:29 +01:00
pageform = ExpoPageForm({"html": body, "title": title})
else:
2021-03-26 23:40:34 +00:00
body = "### File not found ###\n" + str(filepath)
2021-04-10 15:30:29 +01:00
pageform = ExpoPageForm({"html": body, "title": "Missing"})
return render(request, 'editexpopage.html', {'path': path, 'form': pageform, })
2021-04-10 15:30:29 +01:00
class ExpoPageForm(forms.Form):
'''The form used by the editexpopage function
'''
title = forms.CharField(widget=forms.TextInput(attrs={'size':'60'}))
2020-06-13 23:16:19 +01:00
html = forms.CharField(widget=forms.Textarea(attrs={"cols":80, "rows":20}))