2023-01-19 18:35:56 +00:00
|
|
|
import subprocess
|
2023-08-10 21:17:03 +01:00
|
|
|
import hashlib
|
2023-08-26 16:39:29 +01:00
|
|
|
from datetime import datetime
|
2023-01-19 18:35:56 +00:00
|
|
|
from pathlib import Path
|
2021-05-05 00:35:10 +01:00
|
|
|
|
|
|
|
from django import forms
|
2023-01-30 23:04:11 +00:00
|
|
|
from django.core.files.storage import FileSystemStorage
|
2023-07-31 13:49:54 +01:00
|
|
|
from django.shortcuts import render, redirect
|
2022-03-18 11:28:35 +00:00
|
|
|
|
2023-01-19 18:35:56 +00:00
|
|
|
import settings
|
2023-08-10 21:17:03 +01:00
|
|
|
from troggle.core.models.logbooks import LogbookEntry, PersonLogEntry
|
2023-01-31 17:13:41 +00:00
|
|
|
from troggle.core.models.survex import DrawingFile
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
# from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time*
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
from .auth import login_required_if_public
|
2023-01-19 18:35:56 +00:00
|
|
|
|
2023-01-30 19:04:36 +00:00
|
|
|
"""File upload 'views'
|
2023-07-31 13:49:54 +01:00
|
|
|
Note that there are other file upload forms in views/wallet_edit.py
|
|
|
|
and that core/forms.py contains Django class-based forms for caves and entrances.
|
2023-01-30 19:04:36 +00:00
|
|
|
"""
|
2021-05-05 00:35:10 +01:00
|
|
|
|
2023-01-30 19:04:36 +00:00
|
|
|
todo = """
|
2023-08-26 16:39:29 +01:00
|
|
|
- munge the URL of images in the logbook entry so that they work from both the " /logbookedit/" page,
|
|
|
|
the logbook /years/2023/ page and the logbook fragment page /logbookentry/<date>/
|
|
|
|
Note that this munging has already been done when the entry is imported into the database, so
|
|
|
|
when doing an online edit it has already been fixed.
|
|
|
|
|
2023-02-02 22:02:16 +00:00
|
|
|
- Ideally we should validate uploaded file as being a valid file type, not a dubious script or hack
|
|
|
|
Validate image files using a magic recogniser in walletedit()
|
|
|
|
https://pypi.org/project/reportlab/ or
|
|
|
|
https://stackoverflow.com/questions/889333/how-to-check-if-a-file-is-a-valid-image-file
|
2022-03-08 22:59:04 +00:00
|
|
|
|
2023-01-31 17:13:41 +00:00
|
|
|
- Write equivalent GPX upload form system, similar to walletedit() but in expofiles/gpslogs/
|
2022-03-08 22:59:04 +00:00
|
|
|
Need to validate it as being a valid GPX file using an XML parser, not a dubious script or hack
|
|
|
|
|
2023-02-02 22:02:16 +00:00
|
|
|
- Validate Tunnel & Therion files using an XML parser in dwgupload(). Though Julian says
|
2023-07-31 13:49:54 +01:00
|
|
|
tunnel is only mostly correct XML, and it does fail at least one XML parser.
|
2023-02-02 22:02:16 +00:00
|
|
|
|
|
|
|
- parse the uploaded drawing file for links to wallets and scan files as done
|
|
|
|
in parsers/drawings.py
|
2022-03-08 22:59:04 +00:00
|
|
|
|
|
|
|
- Enable folder creation in dwguploads or as a separate form
|
2023-07-31 13:49:54 +01:00
|
|
|
|
2023-07-31 14:16:43 +01:00
|
|
|
- Enable file rename on expofiles, not just for /surveyscans/ (aka wallets)
|
|
|
|
|
|
|
|
- Make file rename utility less ugly.
|
2023-01-30 19:04:36 +00:00
|
|
|
"""
|
2023-08-10 21:17:03 +01:00
|
|
|
sha = hashlib.new('sha256')
|
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
|
|
|
|
class FilesForm(forms.Form): # not a model-form, just a form-form
|
2023-01-30 19:04:36 +00:00
|
|
|
uploadfiles = forms.FileField()
|
|
|
|
|
2022-08-11 19:19:52 +01:00
|
|
|
class FilesRenameForm(forms.Form): # not a model-form, just a form-form
|
2023-01-30 19:04:36 +00:00
|
|
|
uploadfiles = forms.FileField()
|
2022-08-11 21:35:53 +01:00
|
|
|
renameto = forms.CharField(strip=True, required=False)
|
2022-08-14 20:52:14 +01:00
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
class TextForm(forms.Form): # not a model-form, just a form-form
|
2023-01-30 19:04:36 +00:00
|
|
|
photographer = forms.CharField(strip=True)
|
2023-07-31 13:49:54 +01:00
|
|
|
|
|
|
|
class ExpofileRenameForm(forms.Form): # not a model-form, just a form-form
|
|
|
|
renameto = forms.CharField(strip=True, required=False)
|
|
|
|
|
2023-08-06 11:47:09 +01:00
|
|
|
class LogbookEditForm(forms.Form): # not a model-form, just a form-form
|
|
|
|
author = forms.CharField(strip=True, required=False)
|
|
|
|
|
|
|
|
@login_required_if_public
|
2023-08-10 21:17:03 +01:00
|
|
|
def logbookedit(request, year=None, slug=None):
|
|
|
|
"""Edit a logbook entry
|
2023-08-26 16:39:29 +01:00
|
|
|
This is daft: we have the parsed identity of the person and we render it to text as 'nickname_used'
|
|
|
|
(or, previously, 'fullname'), to be re-parsed on re-importing.
|
|
|
|
And there is no guarantee that this will be the same thing.
|
2023-08-10 21:36:56 +01:00
|
|
|
|
2023-08-26 16:39:29 +01:00
|
|
|
Someone can put in a nickname which is invalid (e.g. 2 Sophies on expo). When is this checked?
|
2023-08-06 11:47:09 +01:00
|
|
|
"""
|
2023-08-10 21:17:03 +01:00
|
|
|
def clean_tu(tu):
|
|
|
|
if tu =="":
|
|
|
|
return 0
|
|
|
|
try:
|
|
|
|
tu = float(tu)/1 # check numeric
|
|
|
|
except:
|
|
|
|
return 0
|
|
|
|
return tu
|
|
|
|
|
|
|
|
def unique_id(text, n):
|
|
|
|
"""This gives each logbook entry a unique id based on the date+content, so the order of entries on a particular day
|
|
|
|
does not matter. This is a change (August 2023) from previous process.
|
|
|
|
Otherwise we could get 2023-07-20a and 2023-07-20b swapped on exporting and re-importing logbooks
|
2023-08-10 21:36:56 +01:00
|
|
|
because the database does not record precedence.
|
|
|
|
2 hex digits would seem adequate for each expo day, but we might get a collision.
|
|
|
|
The hash is based on the content after substitution of <p> so should be stable. Which means these ids
|
|
|
|
can be used elsewhere in the troggle system as permanent slugs.
|
2023-08-26 16:39:29 +01:00
|
|
|
|
|
|
|
When SAVING an edited entry (as opposed to a new one) we will have a different hash so we will have to
|
|
|
|
delete the original database object
|
2023-08-10 21:17:03 +01:00
|
|
|
"""
|
|
|
|
sha.update(text.encode('utf-8'))
|
|
|
|
return sha.hexdigest()[0:n]
|
|
|
|
|
2023-08-06 11:47:09 +01:00
|
|
|
if not year:
|
2023-08-26 16:39:29 +01:00
|
|
|
if not slug:
|
|
|
|
year = 2023
|
|
|
|
else:
|
|
|
|
year = slug[0:4]
|
|
|
|
print(year)
|
2023-08-07 22:43:12 +01:00
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
form = LogbookEditForm(request.POST)
|
|
|
|
if not form.is_valid():
|
|
|
|
message = f'Invalid form response for logbook entry creating "{request.POST}"'
|
|
|
|
print(message)
|
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
|
|
else:
|
|
|
|
# validation all to be done yet..
|
2023-08-30 19:17:25 +01:00
|
|
|
date = request.POST["date"].strip()
|
2023-08-08 17:24:18 +01:00
|
|
|
author = request.POST["author"].strip() # TODO check against personexpedition
|
|
|
|
others = request.POST["others"].strip() # TODO check each against personexpedition
|
2023-08-08 17:23:55 +01:00
|
|
|
place = request.POST["place"].strip().replace('-','=') # no hyphens !
|
|
|
|
title = request.POST["title"].strip()
|
2023-08-08 17:24:18 +01:00
|
|
|
entry = request.POST["text"].strip()
|
2023-08-08 17:23:55 +01:00
|
|
|
entry = entry.replace('\r','') # remove HTML-standard CR inserted
|
2023-08-10 21:17:03 +01:00
|
|
|
entry = entry.replace('\n\n','\n<br />\n<br />\n') # replace 2 \n with <br><br>
|
|
|
|
entry = entry.replace('<p','<br />\n<br') # replace <p> tag, even if it has attributes, with <br><br>
|
2023-08-08 17:23:55 +01:00
|
|
|
entry = entry.replace('<br>','<br />') # clean up previous hack
|
|
|
|
tu = request.POST["tu"].strip()
|
2023-08-10 21:17:03 +01:00
|
|
|
tu = clean_tu(tu)
|
2023-08-26 16:39:29 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
odate = datetime.strptime(date.replace(".", "-"), "%Y-%m-%d").date()
|
|
|
|
dateflag = False
|
|
|
|
except:
|
|
|
|
odate = datetime.strptime(f"{year}-01-01", "%Y-%m-%d").date()
|
|
|
|
print(f"! Invalid date string {date}, setting to {odate}")
|
|
|
|
dateflag = True
|
|
|
|
date = odate.isoformat()
|
|
|
|
|
|
|
|
newslug = f"{date}_{unique_id(entry,2)}"
|
|
|
|
if slug:
|
|
|
|
if slug != newslug:
|
|
|
|
print(f"! Entry id changed! from {slug} to {newslug}")
|
|
|
|
|
2023-08-07 22:43:12 +01:00
|
|
|
# OK this could be done by rendering a template, but for such a small bit of HTML, it is easier to have
|
|
|
|
# it all in one place: here
|
|
|
|
output = f'''
|
|
|
|
<hr />
|
2023-08-26 16:39:29 +01:00
|
|
|
<div class="tripdate" id="{newslug}">{date}</div>
|
2023-08-07 22:43:12 +01:00
|
|
|
<div class="trippeople"><u>{author}</u>, {others}</div>
|
|
|
|
<div class="triptitle">{place} - {title}</div>
|
|
|
|
{entry}
|
|
|
|
<div class="timeug">T/U {tu} hrs</div>'''
|
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"logbookform.html",
|
|
|
|
{
|
|
|
|
"form": form,
|
|
|
|
"year": year,
|
2023-08-26 16:39:29 +01:00
|
|
|
"date": date, "dateflag": dateflag,
|
2023-08-07 23:27:20 +01:00
|
|
|
"author": author,
|
|
|
|
"others": others,
|
2023-08-07 23:17:36 +01:00
|
|
|
"place": place,
|
|
|
|
"title": title,
|
|
|
|
"tu": tu,
|
|
|
|
"entry": entry,
|
2023-08-07 22:43:12 +01:00
|
|
|
"output": output,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
# GET here
|
|
|
|
else:
|
|
|
|
form = LogbookEditForm()
|
|
|
|
|
2023-08-10 21:17:03 +01:00
|
|
|
if slug:
|
|
|
|
lbes = LogbookEntry.objects.filter(slug=slug)
|
|
|
|
if lbes:
|
|
|
|
if len(lbes) > 1:
|
|
|
|
return render(request, "object_list.html", {"object_list": lbe}) # ie a bug
|
|
|
|
else:
|
|
|
|
lbe = lbes[0]
|
|
|
|
print(f"{lbe}")
|
|
|
|
tu = clean_tu(lbe.time_underground)
|
|
|
|
|
|
|
|
people = []
|
2023-08-26 16:39:29 +01:00
|
|
|
for p in lbe.personlogentry_set.filter(logbook_entry=lbe): # p is a PersonLogEntry object
|
2023-08-10 21:17:03 +01:00
|
|
|
if p.is_logbook_entry_author:
|
2023-08-26 16:39:29 +01:00
|
|
|
# author = p.personexpedition.person.fullname
|
|
|
|
author = p.nickname_used
|
2023-08-10 21:17:03 +01:00
|
|
|
else:
|
2023-08-26 16:39:29 +01:00
|
|
|
# people.append(p.personexpedition.person.fullname)
|
|
|
|
people.append(p.nickname_used)
|
2023-08-10 21:17:03 +01:00
|
|
|
others =', '.join(people)
|
|
|
|
lenothers = min(70,max(20, len(others)))
|
|
|
|
print(f"{lenothers}")
|
|
|
|
text = lbe.text
|
|
|
|
rows = max(5,len(text)/50)
|
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"logbookform.html",
|
|
|
|
{
|
|
|
|
"form": form,
|
|
|
|
"year": year,
|
|
|
|
"date": lbe.date.isoformat(),
|
|
|
|
"author": author,
|
|
|
|
"others": others,
|
|
|
|
"lenothers": lenothers,
|
|
|
|
"place": lbe.place,
|
|
|
|
"title": lbe.title.replace(f"{lbe.place} - ",""),
|
|
|
|
"tu": tu,
|
|
|
|
"entry": text,
|
|
|
|
"textrows": rows,
|
|
|
|
#"output": output,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"logbookform.html",
|
|
|
|
{
|
|
|
|
"form": form,
|
|
|
|
"year": year,
|
|
|
|
},
|
|
|
|
)
|
2023-08-07 22:43:12 +01:00
|
|
|
|
|
|
|
|
2023-08-06 11:47:09 +01:00
|
|
|
|
|
|
|
|
2023-07-31 13:49:54 +01:00
|
|
|
@login_required_if_public
|
|
|
|
def expofilerename(request, filepath):
|
|
|
|
"""Rename any single file in /expofiles/ - eventually.
|
|
|
|
Currently this just does files within wallets i.e. in /surveyscans/
|
|
|
|
and it returns control to the original wallet edit page
|
|
|
|
"""
|
|
|
|
if filepath:
|
|
|
|
actualpath = Path(settings.EXPOFILES) / Path(filepath)
|
|
|
|
else:
|
|
|
|
message = f'\n File to rename not specified "{filepath}"'
|
|
|
|
print(message)
|
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
|
|
|
|
|
|
if not actualpath.is_file():
|
|
|
|
message = f'\n File not found when attempting rename "{filepath}"'
|
|
|
|
print(message)
|
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
|
|
else:
|
|
|
|
filename = Path(filepath).name
|
2023-07-31 14:16:43 +01:00
|
|
|
folder = actualpath.parent
|
|
|
|
filesize = f"{actualpath.stat().st_size:,}"
|
2023-07-31 13:49:54 +01:00
|
|
|
|
|
|
|
if not actualpath.is_relative_to(Path(settings.SCANS_ROOT)):
|
|
|
|
message = f'\n Can only do rename within wallets (expofiles/surveyscans/) currently, sorry. "{actualpath}" '
|
|
|
|
print(message)
|
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
2023-07-31 20:00:46 +01:00
|
|
|
|
2023-07-31 13:49:54 +01:00
|
|
|
if request.method == "POST":
|
|
|
|
form = ExpofileRenameForm(request.POST)
|
2023-07-31 20:00:46 +01:00
|
|
|
if not form.is_valid():
|
|
|
|
message = f'Invalid form response for file renaming "{request.POST}"'
|
|
|
|
print(message)
|
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
|
|
else:
|
2023-07-31 13:49:54 +01:00
|
|
|
renameto = request.POST["renameto"]
|
|
|
|
|
2023-07-31 20:00:46 +01:00
|
|
|
if (folder / renameto).is_file() or (folder / renameto).is_dir():
|
|
|
|
rename_bad = renameto
|
|
|
|
message = f'\n Cannot rename to an existing file or folder. "{filename}" -> "{(folder / renameto)}"'
|
2023-07-31 13:49:54 +01:00
|
|
|
print(message)
|
2023-07-31 20:00:46 +01:00
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"renameform.html",
|
|
|
|
{
|
|
|
|
"form": form,
|
|
|
|
"filepath": filepath,
|
|
|
|
"filename": filename,
|
|
|
|
"filesize": filesize,
|
|
|
|
"rename_bad": rename_bad,
|
|
|
|
},
|
|
|
|
)
|
2023-07-31 13:49:54 +01:00
|
|
|
else:
|
|
|
|
actualpath.rename((folder / renameto))
|
|
|
|
message = f'\n RENAMED "{filename}" -> "{(folder / renameto)}"'
|
|
|
|
print(message)
|
2023-07-31 20:00:46 +01:00
|
|
|
walletid = actualpath.relative_to(Path(settings.SCANS_ROOT)).parent.stem.replace("#",":")
|
|
|
|
print(walletid)
|
|
|
|
return redirect(f'/survey_scans/{walletid}/')
|
|
|
|
|
2023-07-31 13:49:54 +01:00
|
|
|
else:
|
|
|
|
form = ExpofileRenameForm()
|
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"renameform.html",
|
|
|
|
{
|
|
|
|
"form": form,
|
|
|
|
"filepath": filepath,
|
|
|
|
"filename": filename,
|
2023-07-31 14:16:43 +01:00
|
|
|
"filesize": filesize,
|
2023-07-31 13:49:54 +01:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
@login_required_if_public
|
|
|
|
def photoupload(request, folder=None):
|
2023-01-30 19:04:36 +00:00
|
|
|
"""Upload photo image files into /expofiles/photos/<year>/<photographer>/
|
2022-03-13 01:01:00 +00:00
|
|
|
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.
|
2023-01-30 19:04:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
When uploading from a phone, it is useful to be able to rename the file to something
|
2022-08-11 19:19:52 +01:00
|
|
|
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.
|
2023-07-31 13:49:54 +01:00
|
|
|
|
|
|
|
Pending generic file renaming capability more generally.
|
2023-01-30 19:04:36 +00:00
|
|
|
"""
|
2022-03-13 01:01:00 +00:00
|
|
|
year = settings.PHOTOS_YEAR
|
|
|
|
filesaved = False
|
|
|
|
actual_saved = []
|
2023-01-30 19:04:36 +00:00
|
|
|
|
|
|
|
context = {"year": year, "placeholder": "AnathemaDevice"}
|
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
yearpath = Path(settings.PHOTOS_ROOT, year)
|
2023-01-30 19:04:36 +00:00
|
|
|
|
|
|
|
if folder == str(year) or folder == str(year) + "/":
|
2022-03-13 01:01:00 +00:00
|
|
|
folder = None
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
if folder is None:
|
2023-01-30 19:04:36 +00:00
|
|
|
folder = "" # improve this later
|
2022-03-13 01:01:00 +00:00
|
|
|
dirpath = Path(settings.PHOTOS_ROOT, year)
|
2023-01-30 19:04:36 +00:00
|
|
|
urlfile = f"/expofiles/photos/{year}"
|
|
|
|
urldir = f"/photoupload/{year}"
|
|
|
|
else: # it will contain the year as well as the photographer
|
2022-03-13 01:01:00 +00:00
|
|
|
dirpath = Path(settings.PHOTOS_ROOT, folder)
|
|
|
|
if dirpath.is_dir():
|
2023-01-30 19:04:36 +00:00
|
|
|
urlfile = f"/expofiles/photos/{folder}"
|
|
|
|
urldir = Path("/photoupload") / folder
|
2022-03-13 01:01:00 +00:00
|
|
|
else:
|
2023-01-30 19:04:36 +00:00
|
|
|
folder = "" # improve this later
|
2022-03-13 01:01:00 +00:00
|
|
|
dirpath = Path(settings.PHOTOS_ROOT, year)
|
2023-01-30 19:04:36 +00:00
|
|
|
urlfile = f"/expofiles/photos/{year}"
|
|
|
|
urldir = f"/photoupload/{year}"
|
|
|
|
|
2022-08-11 19:19:52 +01:00
|
|
|
form = FilesRenameForm()
|
2023-01-30 19:04:36 +00:00
|
|
|
formd = TextForm()
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
if "photographer" in request.POST:
|
2022-03-13 01:01:00 +00:00
|
|
|
formd = TextForm(request.POST)
|
|
|
|
if formd.is_valid():
|
|
|
|
newphotographer = request.POST["photographer"]
|
2022-11-23 21:59:42 +00:00
|
|
|
try:
|
|
|
|
(yearpath / newphotographer).mkdir(exist_ok=True)
|
|
|
|
except:
|
2023-01-30 19:04:36 +00:00
|
|
|
message = f'\n !! Permissions failure ?! 0 attempting to mkdir "{(yearpath / newphotographer)}"'
|
2022-11-23 21:59:42 +00:00
|
|
|
print(message)
|
2023-01-30 19:04:36 +00:00
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
2022-11-23 21:59:42 +00:00
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
else:
|
2023-01-30 19:04:36 +00:00
|
|
|
form = FilesRenameForm(request.POST, request.FILES)
|
2022-03-13 01:01:00 +00:00
|
|
|
if form.is_valid():
|
|
|
|
f = request.FILES["uploadfiles"]
|
2023-01-30 19:04:36 +00:00
|
|
|
multiple = request.FILES.getlist("uploadfiles")
|
2022-03-15 23:00:23 +00:00
|
|
|
# NO CHECK that the files being uploaded are image files
|
2022-03-13 01:01:00 +00:00
|
|
|
fs = FileSystemStorage(dirpath)
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2022-08-11 19:19:52 +01:00
|
|
|
renameto = request.POST["renameto"]
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
actual_saved = []
|
2023-01-30 19:04:36 +00:00
|
|
|
if multiple:
|
2022-08-11 19:19:52 +01:00
|
|
|
if len(multiple) == 1:
|
2022-08-11 21:35:53 +01:00
|
|
|
if renameto != "":
|
2023-01-30 19:04:36 +00:00
|
|
|
try: # crashes in Django os.chmod call if on WSL, but does save file!
|
|
|
|
saved_filename = fs.save(renameto, content=f)
|
2022-08-11 21:35:53 +01:00
|
|
|
except:
|
2023-01-30 19:04:36 +00:00
|
|
|
print(
|
|
|
|
f'\n !! Permissions failure ?! 1 attempting to save "{f.name}" in "{dirpath}" {renameto=}'
|
|
|
|
)
|
|
|
|
if "saved_filename" in locals():
|
2022-08-11 21:35:53 +01:00
|
|
|
if saved_filename.is_file():
|
|
|
|
actual_saved.append(saved_filename)
|
2023-01-30 19:04:36 +00:00
|
|
|
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)
|
2022-08-11 21:35:53 +01:00
|
|
|
except:
|
2023-01-30 19:04:36 +00:00
|
|
|
print(
|
|
|
|
f'\n !! Permissions failure ?! 2 attempting to save "{f.name}" in "{dirpath}" {renameto=}'
|
|
|
|
)
|
|
|
|
if "saved_filename" in locals():
|
2022-08-11 21:35:53 +01:00
|
|
|
if saved_filename.is_file():
|
|
|
|
actual_saved.append(saved_filename)
|
2023-01-30 19:04:36 +00:00
|
|
|
filesaved = True
|
|
|
|
else: # multiple is a list of content
|
2022-08-11 19:19:52 +01:00
|
|
|
for f in multiple:
|
2023-01-30 19:04:36 +00:00
|
|
|
try: # crashes in Django os.chmod call if on WSL, but does save file!
|
|
|
|
saved_filename = fs.save(f.name, content=f)
|
2022-08-11 19:19:52 +01:00
|
|
|
except:
|
2023-01-30 19:04:36 +00:00
|
|
|
print(
|
|
|
|
f'\n !! Permissions failure ?! 3 attempting to save "{f.name}" in "{dirpath}" {renameto=}'
|
|
|
|
)
|
|
|
|
if "saved_filename" in locals():
|
2022-08-11 19:19:52 +01:00
|
|
|
if saved_filename.is_file():
|
|
|
|
actual_saved.append(saved_filename)
|
2023-01-30 19:04:36 +00:00
|
|
|
filesaved = True
|
2022-03-13 01:01:00 +00:00
|
|
|
files = []
|
|
|
|
dirs = []
|
|
|
|
try:
|
|
|
|
for f in dirpath.iterdir():
|
|
|
|
if f.is_dir():
|
|
|
|
dirs.append(f.name)
|
|
|
|
if f.is_file():
|
2023-01-30 19:04:36 +00:00
|
|
|
files.append(f.name)
|
2022-03-13 01:01:00 +00:00
|
|
|
except FileNotFoundError:
|
2023-01-30 19:04:36 +00:00
|
|
|
files.append("(no folder yet - would be created)")
|
|
|
|
if len(files) > 0:
|
2022-03-13 01:01:00 +00:00
|
|
|
files = sorted(files)
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
if dirs:
|
|
|
|
dirs = sorted(dirs)
|
|
|
|
|
2023-01-30 19:04:36 +00:00
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"photouploadform.html",
|
|
|
|
{
|
|
|
|
"form": form,
|
|
|
|
**context,
|
|
|
|
"urlfile": urlfile,
|
|
|
|
"urldir": urldir,
|
|
|
|
"folder": folder,
|
|
|
|
"files": files,
|
|
|
|
"dirs": dirs,
|
|
|
|
"filesaved": filesaved,
|
|
|
|
"actual_saved": actual_saved,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2022-03-13 01:01:00 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
@login_required_if_public
|
2023-01-30 19:04:36 +00:00
|
|
|
def dwgupload(request, folder=None, gitdisable="no"):
|
|
|
|
"""Upload DRAWING files (tunnel or therion) into the upload folder in :drawings
|
2022-03-08 22:59:04 +00:00
|
|
|
AND registers it into the :drawings: git repo.
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
This does NOT use a Django model linked to a Django form. Just a simple Django form.
|
2022-03-08 22:59:04 +00:00
|
|
|
You will find the Django documentation on forms very confusing, This is simpler.
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2023-07-31 13:49:54 +01:00
|
|
|
We could validate the uploaded files as being a valid files using an XML parser, not a dubious script or hack,
|
|
|
|
but this won't work on Tunnel files as Tunnel does not produce exactly valid xml
|
2022-03-08 22:59:04 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
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
|
2022-03-05 20:29:01 +00:00
|
|
|
the actual file will be duplicated in the filesystem with different random name ending.
|
2023-01-30 19:04:36 +00:00
|
|
|
"""
|
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
def dwgvalid(name):
|
2023-01-30 19:04:36 +00:00
|
|
|
if name in [
|
|
|
|
".gitignore",
|
|
|
|
]:
|
2021-05-05 00:35:10 +01:00
|
|
|
return False
|
2023-01-30 19:04:36 +00:00
|
|
|
if Path(name).suffix.lower() in [".xml", ".th", ".th2", "", ".svg", ".txt"]:
|
|
|
|
return True # dangerous, we should check the actual file binary signature
|
2021-05-05 00:35:10 +01:00
|
|
|
return False
|
2022-03-05 22:16:03 +00:00
|
|
|
|
|
|
|
def dwgvaliddisp(name):
|
2023-01-30 19:04:36 +00:00
|
|
|
"""OK to display, even if we are not going to allow a new one to be uploaded"""
|
|
|
|
if name in [
|
|
|
|
".gitignore",
|
|
|
|
]:
|
2022-03-05 22:16:03 +00:00
|
|
|
return False
|
2023-01-30 19:04:36 +00:00
|
|
|
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
|
2022-03-05 22:16:03 +00:00
|
|
|
return False
|
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
filesaved = False
|
|
|
|
actual_saved = []
|
|
|
|
refused = []
|
2023-01-30 19:04:36 +00:00
|
|
|
doesnotexist = ""
|
2021-10-31 17:25:45 +00:00
|
|
|
# print(f'! - FORM dwgupload - start "{folder}" - gitdisable "{gitdisable}"')
|
2021-05-05 00:35:10 +01:00
|
|
|
if folder is None:
|
2023-01-30 19:04:36 +00:00
|
|
|
folder = "" # improve this later
|
2021-05-05 00:35:10 +01:00
|
|
|
dirpath = Path(settings.DRAWINGS_DATA)
|
2023-01-30 19:04:36 +00:00
|
|
|
urlfile = "/dwgdataraw"
|
|
|
|
urldir = "/dwgupload"
|
2021-05-05 00:35:10 +01:00
|
|
|
else:
|
|
|
|
dirpath = Path(settings.DRAWINGS_DATA, folder)
|
2023-01-30 19:04:36 +00:00
|
|
|
urlfile = Path("/dwgdataraw/") / folder
|
|
|
|
urldir = Path("/dwgupload/") / folder
|
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
form = FilesForm()
|
2023-01-30 19:04:36 +00:00
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
form = FilesForm(request.POST, request.FILES)
|
2021-05-05 00:35:10 +01:00
|
|
|
if form.is_valid():
|
2022-08-13 19:14:57 +01:00
|
|
|
# print(f'! - FORM dwgupload - POST valid: "{request.FILES["uploadfiles"]}" ')
|
2021-05-05 00:35:10 +01:00
|
|
|
f = request.FILES["uploadfiles"]
|
2023-01-30 19:04:36 +00:00
|
|
|
multiple = request.FILES.getlist("uploadfiles")
|
2022-11-23 21:59:42 +00:00
|
|
|
savepath = Path(settings.DRAWINGS_DATA, folder)
|
|
|
|
fs = FileSystemStorage(savepath)
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
actual_saved = []
|
|
|
|
refused = []
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2022-03-05 17:05:15 +00:00
|
|
|
# GIT see also core/views/expo.py editexpopage()
|
|
|
|
# GIT see also core/models/cave.py writetrogglefile()
|
2023-01-30 19:04:36 +00:00
|
|
|
if gitdisable != "yes": # set in url 'dwguploadnogit/'
|
2021-10-31 17:25:45 +00:00
|
|
|
git = settings.GIT
|
|
|
|
else:
|
2023-01-30 19:04:36 +00:00
|
|
|
git = "echo"
|
2022-08-13 19:14:57 +01:00
|
|
|
# print(f'git DISABLED {f.name}')
|
2021-10-31 17:25:45 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
if multiple:
|
|
|
|
for f in multiple:
|
2022-08-13 19:14:57 +01:00
|
|
|
# print(f'! - FORM dwgupload - file {f} in {multiple=}')
|
2021-05-05 00:35:10 +01:00
|
|
|
if dwgvalid(f.name):
|
2023-01-30 19:04:36 +00:00
|
|
|
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)
|
2022-04-06 18:43:26 +01:00
|
|
|
except:
|
2023-01-30 19:04:36 +00:00
|
|
|
print(
|
|
|
|
f'! - FORM dwgupload - \n!! Permissions failure ?! on attempting to save file "{f.name}" in "{savepath}". Attempting to continue..'
|
|
|
|
)
|
|
|
|
if "saved_filename" in locals():
|
2022-08-13 19:14:57 +01:00
|
|
|
if Path(dirpath, saved_filename).is_file():
|
|
|
|
actual_saved.append(saved_filename)
|
2023-01-30 19:04:36 +00:00
|
|
|
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)
|
|
|
|
)
|
2022-08-13 19:14:57 +01:00
|
|
|
# message = f'! - FORM dwgupload - Success: git ADD on server for this file {saved_filename}.' + msgdata
|
|
|
|
# print(message)
|
2022-04-06 18:43:26 +01:00
|
|
|
if dr_add.returncode != 0:
|
2023-01-30 19:04:36 +00:00
|
|
|
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
|
|
|
|
)
|
2022-08-13 19:14:57 +01:00
|
|
|
print(message)
|
2023-01-30 19:04:36 +00:00
|
|
|
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
|
|
|
|
)
|
2022-08-13 19:14:57 +01:00
|
|
|
dwgfile.save()
|
|
|
|
else:
|
2023-01-30 19:04:36 +00:00
|
|
|
message = f"! - FORM dwgupload - NOT A FILE {Path(dirpath, saved_filename)=}. "
|
2022-08-13 19:14:57 +01:00
|
|
|
print(message)
|
|
|
|
else:
|
2023-01-30 19:04:36 +00:00
|
|
|
message = f"! - FORM dwgupload - Save failure for {f.name}. Changes NOT saved."
|
2022-08-13 19:14:57 +01:00
|
|
|
print(message)
|
2023-01-30 19:04:36 +00:00
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
|
|
|
2022-08-13 19:14:57 +01:00
|
|
|
if saved_filename != f.name:
|
|
|
|
# message = f'! - FORM dwgupload - Save RENAME {f.name} renamed as {saved_filename}. This is OK.'
|
|
|
|
# print(message)
|
|
|
|
pass
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
else:
|
|
|
|
refused.append(f.name)
|
2022-08-13 19:14:57 +01:00
|
|
|
# print(f'REFUSED {f.name}')
|
2023-01-30 19:04:36 +00:00
|
|
|
|
|
|
|
if actual_saved:
|
2021-10-31 17:25:45 +00:00
|
|
|
filesaved = True
|
2022-03-05 22:16:03 +00:00
|
|
|
if len(actual_saved) > 1:
|
|
|
|
dots = "..."
|
|
|
|
else:
|
|
|
|
dots = ""
|
2023-01-30 19:04:36 +00:00
|
|
|
if gitdisable != "yes":
|
|
|
|
dr_commit = subprocess.run(
|
|
|
|
[git, "commit", "-m", f"Drawings upload - {actual_saved[0]}{dots}"],
|
|
|
|
cwd=dirpath,
|
|
|
|
capture_output=True,
|
|
|
|
text=True,
|
|
|
|
)
|
2022-08-13 19:14:57 +01:00
|
|
|
# message = f'! - FORM dwgupload - For uploading {actual_saved[0]}{dots}. Edits saved, added to git, and COMMITTED.\n' + msgdata
|
|
|
|
# print(message)
|
2022-03-05 22:16:03 +00:00
|
|
|
# This produces return code = 1 if it commits OK
|
|
|
|
if dr_commit.returncode != 0:
|
2023-01-30 19:04:36 +00:00
|
|
|
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
|
|
|
|
)
|
2022-08-13 19:14:57 +01:00
|
|
|
print(message)
|
2023-01-30 19:04:36 +00:00
|
|
|
return render(request, "errors/generic.html", {"message": message})
|
2022-08-13 19:14:57 +01:00
|
|
|
else:
|
|
|
|
print(f' git disabled "{git=}"')
|
2023-01-30 19:04:36 +00:00
|
|
|
else: # maybe all were refused by the suffix test in dwgvalid()
|
|
|
|
message = f"! - FORM dwgupload - Nothing actually saved. All were refused. {actual_saved=}"
|
2022-08-13 19:14:57 +01:00
|
|
|
print(message)
|
2021-10-31 17:25:45 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
files = []
|
|
|
|
dirs = []
|
2022-08-13 19:14:57 +01:00
|
|
|
# print(f'! - FORM dwgupload - start {folder=} \n"{dirpath=}" \n"{dirpath.parent=}" \n"{dirpath.exists()=}"')
|
2021-05-05 00:35:10 +01:00
|
|
|
try:
|
|
|
|
for f in dirpath.iterdir():
|
|
|
|
if f.is_dir():
|
2023-01-30 19:04:36 +00:00
|
|
|
if f.name not in [".git"]:
|
2021-05-05 00:35:10 +01:00
|
|
|
dirs.append(f.name)
|
|
|
|
continue
|
|
|
|
if f.is_file():
|
2023-01-30 19:04:36 +00:00
|
|
|
if dwgvaliddisp(f.name):
|
2021-05-05 00:35:10 +01:00
|
|
|
files.append(f.name)
|
|
|
|
continue
|
|
|
|
except FileNotFoundError:
|
|
|
|
doesnotexist = True
|
|
|
|
if files:
|
|
|
|
files = sorted(files)
|
2023-01-30 19:04:36 +00:00
|
|
|
|
2021-05-05 00:35:10 +01:00
|
|
|
if dirs:
|
|
|
|
dirs = sorted(dirs)
|
|
|
|
|
2023-01-30 19:04:36 +00:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
)
|