mirror of
https://expo.survex.com/repositories/troggle/.git
synced 2024-12-01 06:11:51 +00:00
981 lines
46 KiB
Python
981 lines
46 KiB
Python
import re, os, socket
|
|
import subprocess
|
|
import json
|
|
import settings
|
|
import urllib
|
|
import operator
|
|
import datetime
|
|
|
|
from pathlib import Path
|
|
from functools import reduce
|
|
from urllib.parse import unquote
|
|
|
|
from django import forms
|
|
|
|
from django.conf import settings
|
|
from django.urls import reverse
|
|
from django.db.models import Q
|
|
from django.http import HttpResponse, HttpResponseRedirect
|
|
from django.shortcuts import render
|
|
from django.template import Context, loader
|
|
from django.core.files.storage import FileSystemStorage, default_storage
|
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
|
|
|
|
|
#from troggle import settings
|
|
from troggle.parsers.imports import import_caves, import_people, import_surveyscans
|
|
from troggle.parsers.imports import import_logbooks, import_QMs, import_drawingsfiles, import_survex
|
|
from troggle.parsers.scans import contentsjson
|
|
# from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time*
|
|
from troggle.core.models.troggle import DataIssue
|
|
from troggle.core.models.troggle import Expedition, Person, PersonExpedition
|
|
from troggle.core.models.caves import LogbookEntry, QM, Cave, PersonTrip
|
|
from troggle.core.models.survex import DrawingFile, Wallet, SurvexBlock, SurvexFile, SurvexPersonRole
|
|
from troggle.core.views.scans import oldwallet
|
|
from troggle.core.views.caves import getCave
|
|
|
|
|
|
from .auth import login_required_if_public
|
|
#from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
|
|
|
|
'''File upload 'views'
|
|
'''
|
|
|
|
todo = '''
|
|
- Register uploaded filenames in the Django db without needing to wait for a reset & bulk file import
|
|
|
|
- Refactor scanupload() as it contains all the wallets 'complaints' code from the old script wallets.py
|
|
|
|
- Need to validate uploaded file as being a valid image file, not a dubious script or hack
|
|
|
|
- Write equivalent GPX upload form system, similar to scanupload() but in expofiles/gpslogs/
|
|
Need to validate it as being a valid GPX file using an XML parser, not a dubious script or hack
|
|
|
|
- Validate Tunnel & Therion files using an XML parser in dwgupload(). Though Julian says tunnel is only mostly correct XML
|
|
|
|
- Validate image files using a magic recogniser in scanupload() https://pypi.org/project/reportlab/ or
|
|
https://stackoverflow.com/questions/889333/how-to-check-if-a-file-is-a-valid-image-file
|
|
|
|
- Enable folder creation in dwguploads or as a separate form
|
|
|
|
'''
|
|
WALLET_BLANK_JSON = {
|
|
"cave": "",
|
|
"date": "",
|
|
"free text": "",
|
|
# "description url": "1623/NNN",
|
|
"description written": False,
|
|
"electronic survey": False,
|
|
"elev drawn": False,
|
|
"elev not required": False,
|
|
"name": "",
|
|
"people": [
|
|
"Unknown"
|
|
],
|
|
"plan drawn": False,
|
|
"plan not required": False,
|
|
"qms written": False,
|
|
"survex file": [],
|
|
"survex not required": False,
|
|
"website updated": False}
|
|
|
|
class FilesForm(forms.Form): # not a model-form, just a form-form
|
|
uploadfiles = forms.FileField()
|
|
|
|
class FilesRenameForm(forms.Form): # not a model-form, just a form-form
|
|
uploadfiles = forms.FileField()
|
|
renameto = forms.CharField(strip=True, required=False)
|
|
|
|
class WalletGotoForm(forms.Form): # not a model-form, just a form-form
|
|
walletgoto = forms.CharField(strip=True, required=False)
|
|
|
|
class TextForm(forms.Form): # not a model-form, just a form-form
|
|
photographer = forms.CharField(strip=True)
|
|
|
|
class WalletForm(forms.Form): # not a model-form, just a form-form
|
|
descriptionw = forms.CharField(strip=True, required=False)
|
|
people = forms.CharField(strip=True, required=False)
|
|
survexnr = forms.CharField(strip=True, required=False)
|
|
qmsw = forms.CharField(strip=True, required=False)
|
|
date = forms.CharField(strip=True, required=True) # the only required field
|
|
websiteupt = forms.CharField(strip=True, required=False)
|
|
elevnr = forms.CharField(strip=True, required=False)
|
|
cave = forms.CharField(strip=True, required=False)
|
|
psg = forms.CharField(strip=True, required=False)
|
|
freetext = forms.CharField(strip=True, required=False)
|
|
plannr = forms.CharField(strip=True, required=False)
|
|
electronic = forms.CharField(strip=True, required=False)
|
|
pland = forms.CharField(strip=True, required=False)
|
|
elevd = forms.CharField(strip=True, required=False)
|
|
#url = forms.CharField(strip=True, required=False)
|
|
survex = forms.CharField(strip=True, required=False)
|
|
|
|
xlate = {
|
|
# "url": "description url",
|
|
"descriptionw": "description written",
|
|
"people": "people",
|
|
"date": "date",
|
|
"cave": "cave",
|
|
"plannr": "plan not required",
|
|
"survexnr": "survex not required",
|
|
"qmsw": "qms written",
|
|
"elevnr": "elev not required",
|
|
"websiteupt": "website updated",
|
|
"electronic": "electronic survey",
|
|
"pland": "plan drawn",
|
|
"elevd": "elev drawn",
|
|
"psg": "name", # a name for this wallet
|
|
"freetext": "free text",
|
|
"survex": "survex file",
|
|
}
|
|
|
|
def get_complaints(complaints, waldata, svxfiles, files, wallet, wurl):
|
|
'''Taken from old script wallets.py and edited to make more comprehensible
|
|
Loads the survex files names and processes all complaints
|
|
|
|
'''
|
|
# If skipping through the wallets on the upload form, the wallet may not yet exist
|
|
try:
|
|
w = Wallet.objects.get(walletname=wallet)
|
|
except ObjectDoesNotExist:
|
|
return None, None
|
|
|
|
|
|
# Date
|
|
if not waldata["date"]:
|
|
complaints.append("A date is mandatory. No data can be updated or edited unless you specify a date. Look in the survex file if there is one.")
|
|
|
|
# People
|
|
if not waldata["people"] or waldata["people"]==["NOBODY"] or waldata["people"]==["Unknown"] or waldata["people"]==[""]:
|
|
complaints.append("Someody must have done this. Look in the survex file, or in the logbook entries for this date, for the people who created this data.")
|
|
|
|
# survex, but get_ticks has already done much of this ??
|
|
survex_complaint = ""
|
|
|
|
if waldata["survex file"]:
|
|
if not type(waldata["survex file"])==list: # a string also is a sequence type, so do it this way
|
|
waldata["survex file"] = [waldata["survex file"]]
|
|
for sx in waldata["survex file"]:
|
|
#this logic appears in several places, inc get_ticks(). Refactor.
|
|
if sx !="":
|
|
if Path(sx).suffix.lower() != ".svx":
|
|
sx = sx + ".svx"
|
|
svxfiles.append(sx)
|
|
if not (Path(settings.SURVEX_DATA) / sx).is_file():
|
|
file_complaint = f"{wallet} Incorrect survex file name. File {sx} was not found in LOSER repo"
|
|
complaints.append(file_complaint)
|
|
message = f"! {file_complaint}"
|
|
print(message)
|
|
DataIssue.objects.update_or_create(parser='scans', message=message, url=wurl) # set URL to this wallet folder
|
|
else:
|
|
try:
|
|
sxpath = str(Path(sx).with_suffix(''))
|
|
svxfile = SurvexFile.objects.get(path=sxpath)
|
|
except MultipleObjectsReturned:
|
|
# can happen if connecting a wallet to a survex file.. i think..
|
|
QSsvxfiles = SurvexFile.objects.filter(path=sxpath)
|
|
for s in QSsvxfiles:
|
|
print(s.path, s.cave, s.survexdirectory)
|
|
svxfile = QSsvxfiles[0] # dont' know how this happened, fix later..
|
|
except:
|
|
file_complaint = f"{wallet} Survex file {sx} exists, but is not registered in the database {sxpath}. How?.."
|
|
complaints.append(file_complaint)
|
|
message = f"! {file_complaint}"
|
|
print(message)
|
|
DataIssue.objects.update_or_create(parser='scans', message=message, url=wurl) # set URL to this wallet folder
|
|
|
|
if waldata["survex not required"] and waldata["survex file"] != [""]:
|
|
survex_complaint = f'Survex is stated as not required and yet there is a survex file! ({waldata["survex file"]})'
|
|
if not waldata["survex not required"] and waldata["survex file"] == [""]:
|
|
survex_complaint = "A survex file is required, but has not been specified!"
|
|
if survex_complaint:
|
|
complaints.append(survex_complaint)
|
|
|
|
ticks = w.get_ticks()
|
|
|
|
|
|
# Notes required
|
|
if ticks['N'] != "green":
|
|
complaints.append("The notes needs scanning (or renaming): no noteNN.jpg or XXnote.jpg file found; and this is not an electronic survey.")
|
|
|
|
# Plan drawing required
|
|
if ticks['P'] != "green":
|
|
complaints.append("The plan needs drawing (or renaming, or tick 'Plan drawn' checkbox or 'Plan not required' checkbox): no planNN.jpg or XXplan.jpg file found.")
|
|
|
|
# Elev drawing required
|
|
if ticks['E'] != "green":
|
|
complaints.append("The elevation needs drawing (or renaming, or tick 'Elev drawn' checkbox or 'Elev not required' checkbox): no elevNN.jpg or XXelev.jpg file found.")
|
|
|
|
# ETherion
|
|
if ticks['T'] != "green":
|
|
complaints.append("Tunnel or Therion drawing files need drawing. Or if this an electronic survey, please tick the 'Electronic survey' checkbox.")
|
|
|
|
# Description
|
|
if not waldata["description written"]:
|
|
complaints.append("The guidebook description needs writing into the survex file. Tick the 'Cave description written' checkbox when this is done.")
|
|
# QMs
|
|
if not waldata["qms written"] and w.year() and int(w.year()) >= 2015:
|
|
complaints.append("The QMs needs writing into the survex file. Tick the 'QMs written' checkbox when this is done.")
|
|
|
|
# Website
|
|
if not waldata["website updated"]:
|
|
complaints.append("The cave description website is marked as needing updating using the guidebook description from the survex file. Tick the 'Website updated' checkbox when this is done.")
|
|
|
|
# Find the cave, if it exists
|
|
if waldata["cave"]:
|
|
try:
|
|
caveid = waldata["cave"]
|
|
if type(caveid) is list:
|
|
for i in caveid:
|
|
i = i.replace("/","-")
|
|
caveobject = getCave(i) # only the last one gets recorded.. ouch.
|
|
else:
|
|
caveid = caveid
|
|
caveobject = getCave(caveid)
|
|
print(f'getCave for id "{waldata["cave"]}" {caveobject}')
|
|
# if not caveobject.url == waldata["description url"]:
|
|
# complaints.append(f'The URL of cave description \"{waldata["description url"]}\" does not match the one on record for this cave which is: "{caveobject.url}". If the wallet is not for a cave, put a useful URL here.')
|
|
except Cave.MultipleObjectsReturned:
|
|
complaints.append(f'The cave ID \'{waldata["cave"]}\' is AMBIGUOUS. Please fix it.')
|
|
caveobject = None
|
|
except ObjectDoesNotExist:
|
|
complaints.append(f'The cave ID \'{waldata["cave"]}\' is not recognised. Please fix it.')
|
|
caveobject = None
|
|
else:
|
|
complaints.append(f'No cave ID is given. If there is no survex file, please give something, even if it is just "1623-000", "surface survey" or "scraps found in hut"')
|
|
caveobject = None
|
|
|
|
|
|
return complaints, caveobject
|
|
|
|
# @login_required_if_public
|
|
def scanupload(request, path=None):
|
|
'''Upload scanned image files into a wallet on /expofiles
|
|
Also display AND EDIT the contents.json data in the wallet.
|
|
|
|
This is the main wallet display and edit page.
|
|
|
|
The Wallet object and the contents.json file are created when the user
|
|
first uploads files.
|
|
|
|
This does NOT use a Django model linked to a Django form. Just a simple Django form.
|
|
You will find the Django documentation on forms very confusing,
|
|
as it covers many very differnet things we do not need. This is simpler.
|
|
|
|
This subsumes much of the code which was in the old wallets.py script and so this function is very long
|
|
indeed and needs refactoring.
|
|
|
|
REWRITE bits using the ticklist, dateify, caveify, populate etc utility functions in core.view.scans.py
|
|
'''
|
|
git = settings.GIT
|
|
filesaved = False
|
|
actual_saved = []
|
|
|
|
def read_json():
|
|
'''Read JSON from the wallet metadata file in the repo
|
|
or fills with blank data if that files can't be read
|
|
|
|
Should sanitise to ensure no spurious backslashes e.g. in windows style paths'''
|
|
waldata = {}
|
|
if contents_path.is_file():
|
|
create = False # yes wallet exists because JSON exists, even if no files in the surveyscans folder, or even if that folder does not exist
|
|
with open(contents_path) as json_file:
|
|
try:
|
|
waldata = json.load(json_file)
|
|
except:
|
|
message = f"! {wallet} Failed to load {contents_path} JSON file"
|
|
print(message)
|
|
DataIssue.objects.create(parser='scans', message=message, url=wurl) # set URL to this wallet folder
|
|
raise
|
|
else: # no JSON file exists
|
|
print(f'--- No JSON exists, so creating blank copy')
|
|
waldata = WALLET_BLANK_JSON.copy()
|
|
if not waldata["survex file"]:
|
|
try:
|
|
w = Wallet.objects.get(walletname=wallet)
|
|
b = SurvexBlock.objects.filter(scanswallet=w)
|
|
waldata["survex file"] = []
|
|
for bsf in b:
|
|
waldata["survex file"].append(bsf.survexfile.path)
|
|
except:
|
|
print(f'--- No wallet {wallet} exists in database')
|
|
return waldata
|
|
|
|
def save_json(jsondict):
|
|
newfolder = contents_path.parent
|
|
#print(f'--- Wallet directory in :drawings: repo {newfolder=} {jsondict}')
|
|
if not os.path.exists(contents_path.parent):
|
|
print(f'--- No wallet directory in :drawings: repo, so creating it')
|
|
os.makedirs(contents_path.parent)
|
|
|
|
with open(contents_path, "w") as jfile:
|
|
json.dump(jsondict, jfile, indent = 1)
|
|
# print(f'--- FINISHED saving to JSON at {contents_path}')
|
|
|
|
def make_wallet(walletname):
|
|
'''We need a wallet Object so that the django template stuff can find the files
|
|
'''
|
|
try:
|
|
w, created = Wallet.objects.get_or_create(walletname=walletname)
|
|
print(f'--- Wallet string {walletname}, wallet object {w} created new?: {created}')
|
|
if created:
|
|
w.fpath = Path(settings.SCANS_ROOT, walletname[0:4], walletname)
|
|
w.save()
|
|
except:
|
|
print(f'!-- Wallet string {walletname}, FAIL TO GET or create WALLET OBJECT')
|
|
raise
|
|
|
|
def commit_json(waldata):
|
|
destfolder = contents_path.parent
|
|
dr_add = subprocess.run([git, "add", contentsjson], cwd=destfolder, capture_output=True, text=True)
|
|
if dr_add.returncode != 0:
|
|
msgdata = 'Ask a nerd to fix this.\n--' + dr_add.stderr + '\n--' + dr_add.stdout + '\n--return code: ' + str(dr_add.returncode)
|
|
message = f'CANNOT git on server for this file {contentsjson}. Edits saved but not added to git.\n\n' + msgdata
|
|
print(message)
|
|
return render(request,'errors/generic.html', {'message': message})
|
|
else:
|
|
if socket.gethostname() != "expo":
|
|
comment = f"on dev machine '{socket.gethostname()}' "
|
|
else:
|
|
comment = ""
|
|
if "cave" in waldata:
|
|
label = waldata["cave"]
|
|
else:
|
|
if "name" in waldata:
|
|
label = waldata["name"]
|
|
else:
|
|
label = ""
|
|
|
|
dr_commit = subprocess.run([git, "commit", "-m", f'JSON update wallet {wallet} {label} {comment}'], cwd=destfolder, capture_output=True, text=True)
|
|
# This produces return code = 1 if it commits OK
|
|
if dr_commit.returncode != 0:
|
|
msgdata = 'Ask a nerd to fix this.\n\n' + dr_commit.stderr + '\n\n' + dr_commit.stdout + '\n\nreturn code: ' + str(dr_commit.returncode)
|
|
message = f'Error code with git on server for this {contentsjson}. File is added to git, but NOT committed.\n' + msgdata
|
|
print(message)
|
|
return render(request,'errors/generic.html', {'message': message})
|
|
|
|
def get_logbook_trips():
|
|
return None
|
|
|
|
checkboxes = ["description written", "survex not required", "qms written", "website updated",
|
|
"plan not required", "plan drawn", "elev not required", "elev drawn", "electronic survey" ]
|
|
if path:
|
|
wallet = urllib.parse.unquote(path)
|
|
else:
|
|
wallet = "2022#00" # improve this later
|
|
|
|
year = wallet[:4]
|
|
try:
|
|
if wallet[4] != "#" and wallet[4] != ":":
|
|
# print(f'! - FORM scanupload - {wallet[4]} unurlencoded {unquote(wallet)[4]}')
|
|
# print(f'! - FORM scanupload - start {wallet} REDIRECT TO OLDWALLET')
|
|
return(oldwallet(request, path))
|
|
except:
|
|
# if nonumeric wallet name for example
|
|
return(oldwallet(request, path))
|
|
|
|
|
|
if str(wallet).lower().endswith('indexpages'):
|
|
# print(f'! - FORM scanupload - start {wallet} REDIRECT TO OLDWALLET')
|
|
return(walletindex(request, path))
|
|
|
|
if not re.match('(19|20)\d\d[:#]\d\d', wallet):
|
|
wallet = "2022:00" # improve this later
|
|
# print(f'! - FORM scanupload - start {wallet}')
|
|
|
|
if path:
|
|
pass
|
|
# print(f'! - FORM scanupload - start wallet:{wallet}: path:{path}:')
|
|
if int(year) < 1977:
|
|
year = "1977"
|
|
if int(year) > 2050:
|
|
year = "2050"
|
|
nexty = f'{int(year)+1}'
|
|
prevy = f'{int(year)-1}'
|
|
|
|
|
|
wnumber = wallet[5:]
|
|
next = f'{int(wnumber)+1:02d}'
|
|
prev = f'{int(wnumber)-1:02d}'
|
|
|
|
if int(wnumber) == 0:
|
|
prev = f'{int(wnumber):02d}'
|
|
|
|
wurl = f"/scanupload/{wallet}".replace('#', ':')
|
|
wallet = wallet.replace(':','#')
|
|
dirpath = Path(settings.SCANS_ROOT, year, wallet)
|
|
contents_path = Path(settings.DRAWINGS_DATA, "walletjson") / year / wallet / contentsjson
|
|
|
|
form = FilesForm()
|
|
|
|
if request.method == 'POST':
|
|
if "psg" in request.POST: # handle metadata form
|
|
formj = WalletForm(request.POST)
|
|
# Beware. All fields returned as strings. Must re-type them as lists etc. before using or re-saving
|
|
# Also lots of hassle with lists of strings interpreted as a single string
|
|
# Unset checkboxes do not return any value, checked ones return "True". So need initialising to False
|
|
if formj.is_valid():
|
|
posted = request.POST.copy()
|
|
posted.pop("csrfmiddlewaretoken") # discard this
|
|
wd = WALLET_BLANK_JSON.copy()
|
|
for f in checkboxes:
|
|
wd[f] = False
|
|
#print(f'--- wd ${f}$ - {wd[f]}')
|
|
for f in posted:
|
|
wd[xlate[f]] = posted[f].replace("\'", "\"")
|
|
|
|
if posted[f] =="True":
|
|
wd[xlate[f]] = True
|
|
|
|
wd["people"] = wd["people"][1:-1].replace("\"", "").split(",")
|
|
for i, elem in enumerate(wd["people"]):
|
|
wd["people"][i] = elem.strip()
|
|
|
|
#print(f'--- ${wd["survex file"]}$ - {type(wd["survex file"])}')
|
|
if wd["survex file"]: # allow for no survex file at all
|
|
if wd["survex file"][0] == '[':
|
|
wd["survex file"] = wd["survex file"][1:-1]
|
|
wd["survex file"] = wd["survex file"].replace("\"", "").split(",")
|
|
for i, elem in enumerate(wd["survex file"]):
|
|
wd["survex file"][i] = elem.strip()
|
|
#print(f'--- {wd["survex file"]} - {type(wd["survex file"])}')
|
|
|
|
save_json(wd)
|
|
make_wallet(wallet)
|
|
commit_json(wd)
|
|
|
|
else:
|
|
print(f'--- INVALID JSON Update form submitted')
|
|
print(formj.errors)
|
|
return render(request,'errors/generic.html', {'message': formj.errors})
|
|
|
|
elif "walletgoto" in request.POST: # not editing wallet data or uploading a file.. going direct to a named wallet
|
|
formg = WalletGotoForm(request.POST,request.FILES)
|
|
if formg.is_valid():
|
|
walletgoto = request.POST["walletgoto"]
|
|
|
|
return HttpResponseRedirect(f'/scanupload/{walletgoto.replace("#",":")}')
|
|
|
|
else: # not editing wallet data, uploading a file. But should not overwrite metadata at all.
|
|
form = FilesForm(request.POST,request.FILES)
|
|
|
|
if form.is_valid():
|
|
#print(f'--- FORM scanupload multiple BUT EMPTY METADATA supposedly {WALLET_BLANK_JSON["date"]=}')
|
|
multiple = request.FILES.getlist('uploadfiles')
|
|
fs = FileSystemStorage(os.path.join(dirpath)) # creates wallet folder if necessary
|
|
|
|
waldata = read_json()
|
|
actual_saved = []
|
|
if multiple:
|
|
for f in multiple:
|
|
try: # crashes in Django os.chmod call if on WSL, but does save file!
|
|
saved_filename = fs.save(f.name, content=f)
|
|
except:
|
|
print(f'\n !! Permissions failure ?! on attempting to save scanfile {f.name}')
|
|
if 'saved_filename' in locals():
|
|
if saved_filename.is_file():
|
|
actual_saved.append(saved_filename)
|
|
# print(f'! - FORM scanupload multiple {actual_saved}')
|
|
filesaved = True
|
|
#print(f'--- FORM scanupload multiple BUT EMPTY METADATA supposedly {WALLET_BLANK_JSON["date"]=}')
|
|
save_json(waldata)
|
|
make_wallet(wallet)
|
|
commit_json(waldata)
|
|
#
|
|
# Not a POST, so a GET starts here. And also control gets here after a POST is processed.
|
|
#
|
|
files = []
|
|
dirs = []
|
|
# print(f'! - FORM scanupload - start {wallet} {dirpath}')
|
|
if dirpath.is_dir():
|
|
create = False # wallet exists because folder exists, even if nothing in it
|
|
try:
|
|
for f in dirpath.iterdir():
|
|
if f.is_dir():
|
|
for d in f.iterdir():
|
|
dirs.append(f'{f.name}/{d.name}')
|
|
if f.is_file():
|
|
files.append(f.name)
|
|
except FileNotFoundError:
|
|
files.append('(No wallet yet. It would be created if you upload a scan and then save the form with a date.)')
|
|
else:
|
|
create = True
|
|
|
|
if len(files) >0 :
|
|
files = sorted(files)
|
|
|
|
if dirs:
|
|
dirs = sorted(dirs)
|
|
try:
|
|
waldata = read_json()
|
|
except:
|
|
message = f'Nasty failure in parsing wallets metadata in {contents_path}. Probably backslash not forward slash in filename path'
|
|
return render(request, 'errors/generic.html', {'message': message})
|
|
|
|
jsonfile = Path(settings.DRAWINGS_DATA, "walletjson") / wallet[0:4] / wallet / "contents.json"
|
|
# print(f'! - FORM scanupload - jsonfile {jsonfile}')
|
|
if not Path(jsonfile).is_file():
|
|
metadataurl = ""
|
|
else:
|
|
metadataurl = Path("/dwgdataraw", "walletjson") / wallet[0:4] / wallet.replace("#",":") / "contents.json"
|
|
psg = ""
|
|
freetext = ""
|
|
chkplannr = ""
|
|
chkpland = ""
|
|
svxfiles = []
|
|
trips =[]
|
|
checked = {}
|
|
context = {}
|
|
if waldata: # should always be true as populated by blank data if json file doesn't exist
|
|
|
|
# if not type(waldata["people"])==list:
|
|
# if waldata["people"][0] == '"':
|
|
# waldata["people"] = waldata["people"][1:-1]
|
|
# waldata["people"] = list(waldata["people"])
|
|
|
|
|
|
if not waldata["date"] or not waldata["people"] or waldata["people"] == ["Unknown"] or waldata["people"] == [""] or waldata["cave"] == "": # json file does not exist, blank data, or people not typed into JSON file
|
|
# refactor into separate functions for no date set or no people set or no cave set
|
|
# print(f'No date set')
|
|
print(f'\n - Incomplete, empty or default wallet data {wallet} {waldata=}')
|
|
refs=[]
|
|
dates = []
|
|
team = []
|
|
caverefs = []
|
|
caves =[]
|
|
names =[]
|
|
svxf = ''
|
|
if waldata["survex file"]:
|
|
if not type(waldata["survex file"])==list: # a string also is a sequence type, so do it this way
|
|
waldata["survex file"] = [waldata["survex file"]]
|
|
for svxf in waldata["survex file"]:
|
|
print(f' - {svxf=}')
|
|
if svxf:
|
|
svx = Path(svxf)
|
|
if svx.suffix.lower() != ".svx":
|
|
svx = svx.with_suffix(".svx")
|
|
f = Path(settings.SURVEX_DATA) / svx
|
|
if f.is_file():
|
|
path = svx.parent / svx.stem
|
|
#print(f' - {path=}')
|
|
try:
|
|
svxfile = SurvexFile.objects.get(path=path)
|
|
|
|
print(f' - {svxfile=}')
|
|
if svxfile.cave:
|
|
caves.append(svxfile.cave)
|
|
caverefs.append(svxfile.cave.reference())
|
|
blocks = SurvexBlock.objects.filter(survexfile= svxfile)
|
|
for b in blocks:
|
|
print(f' - - {b=} {b.scanswallet=} {b.date=}')
|
|
if b.scanswallet:
|
|
refs.append(b.scanswallet)
|
|
if b.scanswallet.walletname == wallet:
|
|
if b.date:
|
|
dates.append(b.date)
|
|
if b.name != b.title:
|
|
names.append(str(b.name) + "|" + str(b.title))
|
|
else:
|
|
names.append(str(b.name))
|
|
# we can use the people, across all blocks that have this *ref
|
|
QSpeople = SurvexPersonRole.objects.filter(survexblock=b)
|
|
print(f' - - {QSpeople=}')
|
|
for p in QSpeople:
|
|
print(f' - - {p.personname} ')
|
|
team.append(p.personname)
|
|
# else:
|
|
# print(f' - Wallet not matching *ref {b.scanswallet=} {wallet}')
|
|
except:
|
|
message = f'Specified survex file not found - database may be empty, or this survex file is not *included anywhere.'
|
|
# return render(request, 'errors/generic.html', {'message': message})
|
|
pass
|
|
|
|
if dates:
|
|
waldata["date"] = min(dates).isoformat()
|
|
print(f' - - {team=} ')
|
|
team = list(set(team))
|
|
waldata["people"] = team
|
|
|
|
caverefs = list(set(caverefs))
|
|
caves = list(set(caves))
|
|
|
|
if len(caverefs) == 1:
|
|
waldata["cave"] = caverefs[0]
|
|
print(f' - Setting wallet cave to {caverefs[0]}')
|
|
# waldata["description url"] = caves[0]
|
|
elif len(caverefs) == 0:
|
|
waldata["cave"] = ""
|
|
# waldata["description url"] = ""
|
|
print(f' - No caves in this wallet {wallet}. ')
|
|
else:
|
|
waldata["cave"] = "several caves"
|
|
# waldata["description url"] = "several.."
|
|
print(f' - More than one Cave {caves} in this wallet {wallet}. Not managed in this troggle release.')
|
|
if len(names) == 1:
|
|
if waldata["name"] == '':
|
|
waldata["name"] = names[0]
|
|
print(f' - Setting wallet name to {names[0]}')
|
|
elif len(names) == 0:
|
|
waldata["name"] = ''
|
|
print(f' - Setting wallet name blank')
|
|
else:
|
|
waldata["name"] = f"several, please edit: {names}"
|
|
print(f' - More than one block name is relevant {names} in this wallet {wallet}. Not managed in this troggle release.')
|
|
|
|
if "cave" in waldata:
|
|
cave = waldata["cave"] # text string
|
|
else:
|
|
cave = ""
|
|
if waldata["name"]:
|
|
psg = waldata["name"]
|
|
if "free text" in waldata:
|
|
freetext = waldata["free text"]
|
|
|
|
# find trips and survex files of the same date
|
|
if waldata["date"]:
|
|
datestr = waldata["date"].replace('.','-')
|
|
try:
|
|
samedate = datetime.date.fromisoformat(datestr)
|
|
except ValueError:
|
|
# probably a single digit day number. HACKUS MAXIMUS.
|
|
# clearly we need to fix this when we first import date strings..
|
|
datestr = datestr[:-1] + '0' + datestr[-1]
|
|
print(f' - {datestr=} ')
|
|
try:
|
|
samedate = datetime.date.fromisoformat(datestr)
|
|
except:
|
|
try:
|
|
samedate = datetime.date.fromisoformat(datestr[:10])
|
|
except:
|
|
samedate = None
|
|
|
|
thisexpo = Expedition.objects.get(year=int(year))
|
|
if samedate:
|
|
expeditionday = thisexpo.get_expedition_day(samedate)
|
|
#print(f' - {thisexpo=} {expeditionday=}')
|
|
svxothers = SurvexBlock.objects.filter(expeditionday=expeditionday)
|
|
#print(f' - {thisexpo=} {expeditionday=} {svxothers=}')
|
|
trips = LogbookEntry.objects.filter(date=samedate)
|
|
else:
|
|
svxothers = None
|
|
trips = None
|
|
|
|
else:
|
|
svxothers = None
|
|
trips = None
|
|
|
|
|
|
#Survex and survex complaints, comes from json file on disc, not as pre-populated as above
|
|
complaints, caveobject = get_complaints([], waldata, svxfiles, files, wallet, wurl)
|
|
# print(f' - {caveobject=}')
|
|
|
|
|
|
for f in checkboxes:
|
|
if waldata[f]:
|
|
checked[f] = "checked"
|
|
|
|
survexsize = str(min(len(str(waldata["survex file"])), 46))
|
|
|
|
context = {'year': year, 'prev': prev, 'next': next, 'prevy': prevy, 'nexty': nexty,
|
|
'files': files, 'dirs': dirs, 'waldata': waldata, 'svxfiles': svxfiles,
|
|
'checked': checked,
|
|
'trips': trips,
|
|
'svxothers': svxothers,
|
|
'create': create, 'metadataurl': metadataurl,
|
|
'complaints': complaints,
|
|
'caveobject': caveobject,
|
|
'people': waldata["people"], 'peoplesize': str(len(str(waldata["people"]))),
|
|
'filesaved': filesaved, 'actual_saved': actual_saved }
|
|
|
|
return render(request, 'walletform.html',
|
|
{'form': form, 'wallet': wallet, **context,
|
|
'date': waldata["date"],
|
|
#'url': waldata["description url"], 'urlsize': str(len(str(waldata["description url"]))),
|
|
'survex': waldata["survex file"], 'survexsize': survexsize,
|
|
'cave': cave, 'psg': psg, 'freetext': freetext,'psgsize': str(max(12,len(str(psg)))), 'freetextsize': str(max(60,len(str(freetext))))})
|
|
else: # no wallet data: should never happen as their should be default data in all cases
|
|
context = {'year': year, 'prev': prev, 'next': next, 'prevy': prevy, 'nexty': nexty,
|
|
'files': files, 'dirs': dirs, 'waldata': waldata, 'svxfiles': svxfiles,
|
|
'checked': checked,
|
|
'create': create,
|
|
'people': "", 'peoplesize': 12,
|
|
'filesaved': filesaved, 'actual_saved': actual_saved }
|
|
|
|
return render(request, 'walletform.html',
|
|
{'form': form, 'wallet': wallet, **context,
|
|
'date': "",
|
|
#'url': "", 'urlsize': 12,
|
|
'survex': "", 'survexsize': 46,
|
|
'cave': cave, 'psg': psg, 'freetext': freetext,'psgsize': 12, 'freetextsize': 20})
|
|
|
|
@login_required_if_public
|
|
def photoupload(request, folder=None):
|
|
'''Upload photo image files into /expofiles/photos/<year>/<photographer>/
|
|
This does NOT use a Django model linked to a Django form. Just a simple Django form.
|
|
You will find the Django documentation on forms very confusing, This is simpler.
|
|
|
|
|
|
When uploading from a phone, it is useful to be able to rename the file to something
|
|
meaningful as this is difficult to do on a phone. Previously we had assumed files would
|
|
be renamed to something useful before starting the upload.
|
|
Unfortunately this only works when uploading one file at at time ,
|
|
inevitable once you think about it.
|
|
'''
|
|
year = settings.PHOTOS_YEAR
|
|
filesaved = False
|
|
actual_saved = []
|
|
|
|
context = {'year': year, 'placeholder': "AnathemaDevice"}
|
|
|
|
yearpath = Path(settings.PHOTOS_ROOT, year)
|
|
|
|
if folder == str(year) or folder == str(year) +"/":
|
|
folder = None
|
|
|
|
if folder is None:
|
|
folder = "" # improve this later
|
|
dirpath = Path(settings.PHOTOS_ROOT, year)
|
|
urlfile = f'/expofiles/photos/{year}'
|
|
urldir = f'/photoupload/{year}'
|
|
else: # it will contain the year as well as the photographer
|
|
dirpath = Path(settings.PHOTOS_ROOT, folder)
|
|
if dirpath.is_dir():
|
|
urlfile = f'/expofiles/photos/{folder}'
|
|
urldir = Path('/photoupload') / folder
|
|
else:
|
|
folder = "" # improve this later
|
|
dirpath = Path(settings.PHOTOS_ROOT, year)
|
|
urlfile = f'/expofiles/photos/{year}'
|
|
urldir = f'/photoupload/{year}'
|
|
|
|
|
|
form = FilesRenameForm()
|
|
formd = TextForm()
|
|
|
|
if request.method == 'POST':
|
|
if "photographer" in request.POST:
|
|
formd = TextForm(request.POST)
|
|
if formd.is_valid():
|
|
newphotographer = request.POST["photographer"]
|
|
try:
|
|
(yearpath / newphotographer).mkdir(exist_ok=True)
|
|
except:
|
|
message =f'\n !! Permissions failure ?! 0 attempting to mkdir "{(yearpath / newphotographer)}"'
|
|
print(message)
|
|
return render(request,'errors/generic.html', {'message': message})
|
|
|
|
else:
|
|
form = FilesRenameForm(request.POST,request.FILES)
|
|
if form.is_valid():
|
|
f = request.FILES["uploadfiles"]
|
|
multiple = request.FILES.getlist('uploadfiles')
|
|
# NO CHECK that the files being uploaded are image files
|
|
fs = FileSystemStorage(dirpath)
|
|
|
|
renameto = request.POST["renameto"]
|
|
|
|
actual_saved = []
|
|
if multiple:
|
|
if len(multiple) == 1:
|
|
if renameto != "":
|
|
try: # crashes in Django os.chmod call if on WSL, but does save file!
|
|
saved_filename = fs.save(renameto, content=f)
|
|
except:
|
|
print(f'\n !! Permissions failure ?! 1 attempting to save "{f.name}" in "{dirpath}" {renameto=}')
|
|
if 'saved_filename' in locals():
|
|
if saved_filename.is_file():
|
|
actual_saved.append(saved_filename)
|
|
filesaved = True
|
|
else: # multiple is the uploaded content
|
|
try: # crashes in Django os.chmod call if on WSL, but does save file!
|
|
saved_filename = fs.save(f.name, content=f)
|
|
except:
|
|
print(f'\n !! Permissions failure ?! 2 attempting to save "{f.name}" in "{dirpath}" {renameto=}')
|
|
if 'saved_filename' in locals():
|
|
if saved_filename.is_file():
|
|
actual_saved.append(saved_filename)
|
|
filesaved = True
|
|
else: # multiple is a list of content
|
|
for f in multiple:
|
|
try: # crashes in Django os.chmod call if on WSL, but does save file!
|
|
saved_filename = fs.save(f.name, content=f)
|
|
except:
|
|
print(f'\n !! Permissions failure ?! 3 attempting to save "{f.name}" in "{dirpath}" {renameto=}')
|
|
if 'saved_filename' in locals():
|
|
if saved_filename.is_file():
|
|
actual_saved.append(saved_filename)
|
|
filesaved = True
|
|
files = []
|
|
dirs = []
|
|
try:
|
|
for f in dirpath.iterdir():
|
|
if f.is_dir():
|
|
dirs.append(f.name)
|
|
if f.is_file():
|
|
files.append(f.name)
|
|
except FileNotFoundError:
|
|
files.append('(no folder yet - would be created)')
|
|
if len(files) >0 :
|
|
files = sorted(files)
|
|
|
|
if dirs:
|
|
dirs = sorted(dirs)
|
|
|
|
return render(request, 'photouploadform.html',
|
|
{'form': form, **context, 'urlfile': urlfile, 'urldir': urldir,'folder': folder, 'files': files, 'dirs': dirs, 'filesaved': filesaved, 'actual_saved': actual_saved})
|
|
|
|
@login_required_if_public
|
|
def dwgupload(request, folder=None, gitdisable='no'):
|
|
'''Upload DRAWING files (tunnel or therion) into the upload folder in :drawings
|
|
AND registers it into the :drawings: git repo.
|
|
|
|
This does NOT use a Django model linked to a Django form. Just a simple Django form.
|
|
You will find the Django documentation on forms very confusing, This is simpler.
|
|
|
|
We could validate the uploaded files as being a valid files using an XML parser, not a dubious script or hack
|
|
|
|
|
|
We use get_or_create instead of simply creating a new object in case someone uploads the same file
|
|
several times in one session, and expects them to be overwritten in the database. Although
|
|
the actual file will be duplicated in the filesystem with different random name ending.
|
|
'''
|
|
def dwgvalid(name):
|
|
if name in [ '.gitignore', ]:
|
|
return False
|
|
if Path(name).suffix.lower() in ['.xml', '.th', '.th2', '', '.svg', '.txt']:
|
|
return True # dangerous, we should check the actual file binary signature
|
|
return False
|
|
|
|
def dwgvaliddisp(name):
|
|
'''OK to display, even if we are not going to allow a new one to be uploaded
|
|
'''
|
|
if name in [ '.gitignore', ]:
|
|
return False
|
|
if Path(name).suffix.lower() in ['.xml', '.th', '.th2', '', '.svg', '.txt', '.jpg', '.jpeg', '.png', '.pdf', '.top', '.topo']:
|
|
return True # dangerous, we should check the actual file binary signature
|
|
return False
|
|
|
|
filesaved = False
|
|
actual_saved = []
|
|
refused = []
|
|
doesnotexist = ''
|
|
# print(f'! - FORM dwgupload - start "{folder}" - gitdisable "{gitdisable}"')
|
|
if folder is None:
|
|
folder = "" # improve this later
|
|
dirpath = Path(settings.DRAWINGS_DATA)
|
|
urlfile = '/dwgdataraw'
|
|
urldir = '/dwgupload'
|
|
else:
|
|
dirpath = Path(settings.DRAWINGS_DATA, folder)
|
|
urlfile = Path('/dwgdataraw/') / folder
|
|
urldir = Path('/dwgupload/') / folder
|
|
|
|
form = FilesForm()
|
|
|
|
if request.method == 'POST':
|
|
form = FilesForm(request.POST,request.FILES)
|
|
if form.is_valid():
|
|
# print(f'! - FORM dwgupload - POST valid: "{request.FILES["uploadfiles"]}" ')
|
|
f = request.FILES["uploadfiles"]
|
|
multiple = request.FILES.getlist('uploadfiles')
|
|
savepath = Path(settings.DRAWINGS_DATA, folder)
|
|
fs = FileSystemStorage(savepath)
|
|
|
|
actual_saved = []
|
|
refused = []
|
|
|
|
# GIT see also core/views/expo.py editexpopage()
|
|
# GIT see also core/models/cave.py writetrogglefile()
|
|
if gitdisable != 'yes': # set in url 'dwguploadnogit/'
|
|
git = settings.GIT
|
|
else:
|
|
git = 'echo'
|
|
# print(f'git DISABLED {f.name}')
|
|
|
|
if multiple:
|
|
for f in multiple:
|
|
# print(f'! - FORM dwgupload - file {f} in {multiple=}')
|
|
if dwgvalid(f.name):
|
|
try: # crashes in Django os.chmod call if on WSL without metadata drvfs, but does save file!
|
|
saved_filename = fs.save(f.name, content=f)
|
|
except:
|
|
print(f'! - FORM dwgupload - \n!! Permissions failure ?! on attempting to save file "{f.name}" in "{savepath}". Attempting to continue..')
|
|
if 'saved_filename' in locals():
|
|
if Path(dirpath, saved_filename).is_file():
|
|
actual_saved.append(saved_filename)
|
|
if gitdisable != 'yes':
|
|
dr_add = subprocess.run([git, "add", saved_filename], cwd=dirpath, capture_output=True, text=True)
|
|
msgdata = dr_add.stderr + '\n' + dr_add.stdout + '\nreturn code: ' + str(dr_add.returncode)
|
|
# message = f'! - FORM dwgupload - Success: git ADD on server for this file {saved_filename}.' + msgdata
|
|
# print(message)
|
|
if dr_add.returncode != 0:
|
|
msgdata = 'Ask a nerd to fix this.\n\n' + dr_add.stderr + '\n\n' + dr_add.stdout + '\n\nreturn code: ' + str(dr_add.returncode)
|
|
message = f'! - FORM dwgupload - CANNOT git ADD on server for this file {saved_filename}. Edits saved but not added to git.\n' + msgdata
|
|
print(message)
|
|
return render(request,'errors/generic.html', {'message': message})
|
|
dwgfile, created = DrawingFile.objects.get_or_create(dwgpath=saved_filename, dwgname=Path(f.name).stem, filesize=f.size)
|
|
dwgfile.save()
|
|
else:
|
|
message = f'! - FORM dwgupload - NOT A FILE {Path(dirpath, saved_filename)=}. '
|
|
print(message)
|
|
else:
|
|
message = f'! - FORM dwgupload - Save failure for {f.name}. Changes NOT saved.'
|
|
print(message)
|
|
return render(request,'errors/generic.html', {'message': message})
|
|
|
|
if saved_filename != f.name:
|
|
# message = f'! - FORM dwgupload - Save RENAME {f.name} renamed as {saved_filename}. This is OK.'
|
|
# print(message)
|
|
pass
|
|
|
|
else:
|
|
refused.append(f.name)
|
|
# print(f'REFUSED {f.name}')
|
|
|
|
if actual_saved:
|
|
filesaved = True
|
|
if len(actual_saved) > 1:
|
|
dots = "..."
|
|
else:
|
|
dots = ""
|
|
if gitdisable != 'yes':
|
|
dr_commit = subprocess.run([git, "commit", "-m", f'Drawings upload - {actual_saved[0]}{dots}'], cwd=dirpath, capture_output=True, text=True)
|
|
# message = f'! - FORM dwgupload - For uploading {actual_saved[0]}{dots}. Edits saved, added to git, and COMMITTED.\n' + msgdata
|
|
# print(message)
|
|
# This produces return code = 1 if it commits OK
|
|
if dr_commit.returncode != 0:
|
|
msgdata = 'Ask a nerd to fix this.\n\n' + dr_commit.stderr + '\n' + dr_commit.stdout + '\nreturn code: ' + str(dr_commit.returncode)
|
|
message = f'! - FORM dwgupload -Error code with git on server for this {actual_saved[0]}{dots}. Edits saved, added to git, but NOT committed.\n' + msgdata
|
|
print(message)
|
|
return render(request,'errors/generic.html', {'message': message})
|
|
else:
|
|
print(f' git disabled "{git=}"')
|
|
else:# maybe all were refused by the suffix test in dwgvalid()
|
|
message = f'! - FORM dwgupload - Nothing actually saved. All were refused. {actual_saved=}'
|
|
print(message)
|
|
|
|
files = []
|
|
dirs = []
|
|
# print(f'! - FORM dwgupload - start {folder=} \n"{dirpath=}" \n"{dirpath.parent=}" \n"{dirpath.exists()=}"')
|
|
try:
|
|
for f in dirpath.iterdir():
|
|
if f.is_dir():
|
|
if f.name not in ['.git' ]:
|
|
dirs.append(f.name)
|
|
continue
|
|
if f.is_file():
|
|
if dwgvaliddisp(f.name):
|
|
files.append(f.name)
|
|
continue
|
|
except FileNotFoundError:
|
|
doesnotexist = True
|
|
if files:
|
|
files = sorted(files)
|
|
|
|
if dirs:
|
|
dirs = sorted(dirs)
|
|
|
|
return render(request, 'dwguploadform.html',
|
|
{'form': form, 'doesnotexist': doesnotexist, 'urlfile': urlfile, 'urldir': urldir,'folder': folder, 'files': files, 'dirs': dirs, 'filesaved': filesaved, 'actual_saved': actual_saved, 'refused': refused})
|
|
|