mirror of
https://expo.survex.com/repositories/troggle/.git
synced 2026-05-13 16:07:00 +01:00
609 lines
26 KiB
Python
609 lines
26 KiB
Python
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.safestring import mark_safe
|
|
from django.conf import settings
|
|
|
|
from troggle.core.models.caves import Cave, CaveAndEntrance, Entrance, GetCaveLookup
|
|
from troggle.parsers.people import who_is_this
|
|
# from troggle.core.views.editor_helpers import HTMLarea
|
|
from troggle.core.utils import (
|
|
get_cookie_max_age,
|
|
WriteAndCommitError,
|
|
add_commit,
|
|
current_expo,
|
|
get_editor,
|
|
git_commit,
|
|
git_string,
|
|
sanitize_name,
|
|
is_identified_user,
|
|
write_and_commit,
|
|
)
|
|
# TO DO check if wallet already exists and if so put a blue label against it & check names are same people
|
|
# assuming this is a new wallet for now - so add a check for this?
|
|
|
|
from troggle.core.position_utils import which_area # file-type import, not module type.
|
|
|
|
class NewHoleForm(forms.Form):
|
|
"""The validation on this form is a bit of a beast, sorry.
|
|
"""
|
|
discovery_date = forms.DateField(label="Trip date", widget=forms.DateInput(attrs={'type': 'date'}), required=True)
|
|
# Identification
|
|
cave_id = forms.CharField(label="New Cave Identifier for internal identifiers. Cannot easily be changed.",
|
|
widget=forms.TextInput(attrs={'placeholder':
|
|
'e.g. 2035-ZB-03 '}),
|
|
max_length=50, required=True)
|
|
tag_text = forms.CharField(label="Exact text on tag if placed, or seen in-place",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. 29 CUCC 35'}),
|
|
max_length=50, required=False)
|
|
# Naming
|
|
proposed_name = forms.CharField(label="Proposed Cave Name, can easily be changed later",
|
|
max_length=90, required=True)
|
|
|
|
# Discovery
|
|
discoverers = forms.CharField(label="Discoverers / Investigators today",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. Dour, Animal, Becka'}),
|
|
max_length=255, required=True)
|
|
surface_wallet = forms.CharField(label="Old wallet used to find the entrance (if any)",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. 2005 # 63'}),
|
|
max_length=100, required=False)
|
|
survey_wallet = forms.CharField(label="New Wallet for all this data (must match the year of the trip)",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. 2029 # 88'}),
|
|
required=True)
|
|
|
|
# GPS Data
|
|
gps_owner = forms.CharField(label="GPS: Whose device?",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. Becka'}),
|
|
max_length=100, required=True)
|
|
gps_lat = forms.FloatField(
|
|
label="GPS Latitude",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. 47.6964483 N'}), required=True
|
|
)
|
|
gps_long = forms.FloatField(
|
|
label="GPS Longitude",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. 13.8160500 E'}), required=True
|
|
)
|
|
gps_time = forms.TimeField(
|
|
label="Time of GPS reading",
|
|
widget=forms.TimeInput(attrs={'type': 'time'})
|
|
)
|
|
gps_screenshot = forms.BooleanField(label="Screenshot taken of GPSTest while GPS device in situ?", required=False)
|
|
gps_photo = forms.BooleanField(label="Photo taken of GPS device in situ with view of entrance?", required=False)
|
|
|
|
# Navigation
|
|
dist_to_ent = forms.FloatField(label="Distance from GPS to entrance (m)",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. 11.5'})
|
|
)
|
|
bear_to_ent = forms.FloatField(label="Compass bearing from GPS to entrance (degrees)",
|
|
widget=forms.TextInput(attrs={'placeholder': 'e.g. 217'})
|
|
)
|
|
|
|
# Status & Surveys
|
|
is_explored = forms.BooleanField(label="Exploration complete?", required=False)
|
|
ug_survey_done = forms.BooleanField(label="Survex data recorded?", required=False)
|
|
|
|
# Media: Entrance Photo (Replaced dropdown with checkboxes)
|
|
photo_ent_no = forms.BooleanField(label="Entrance photos ?", required=False)
|
|
photo_ent_who = forms.CharField(label="Who has photos of entrance, tag and GPS?", required=False)
|
|
|
|
# Entrance description and approach
|
|
entrance_description = forms.CharField(
|
|
label="Entrance description",
|
|
required=True,
|
|
widget=forms.Textarea(attrs={
|
|
"height": "80%", "rows": 2,
|
|
"placeholder": "horizontal slot at foot level in 3m high NE-facing cliff"}),
|
|
)
|
|
|
|
approach = forms.CharField(
|
|
label="Approach",
|
|
max_length=100,
|
|
required=True,
|
|
widget=forms.Textarea(attrs={
|
|
"height": "80%", "rows": 2,
|
|
"placeholder": "from top camp, go NE round the side of Augst Eck, in some trees"}),
|
|
)
|
|
|
|
|
|
identified_login = forms.BooleanField(required=False,widget=forms.CheckboxInput(attrs={"onclick":"return false"})) # makes it readonly
|
|
|
|
who_are_you = forms.CharField(strip=True,
|
|
label="Who are you ? (You do not need to have been on this trip)",
|
|
required=True,
|
|
widget=forms.TextInput(
|
|
attrs={"size": 100, "placeholder": "You are entering data, who are you ? e.g. 'Becka' or 'Animal <mta@gasthof.expo>'",
|
|
"style": "vertical-align: text-top;"}
|
|
)
|
|
)
|
|
# VALIDATIONS
|
|
# Django's validation logic will automatically trigger these.
|
|
# Trigger: When form.is_valid() is called in your new_hole view, Django automatically looks
|
|
# for methods named clean_<fieldname>.
|
|
|
|
# Using self.cleaned_data.get('fieldname') is a safer habit than direct square brackets.
|
|
# If a field fails basic validation (e.g., someone typed letters into a numeric field),
|
|
# it won't exist in cleaned_data. .get() returns None
|
|
|
|
def _validate_caver_list(self, field_name, year, raw_data):
|
|
"""
|
|
Helper to split comma-separated names and validate them against who_is_this.
|
|
Returns a list of validated person_ids.
|
|
"""
|
|
if not raw_data:
|
|
self._add_caver_error(field_name, "No one", year)
|
|
return []
|
|
|
|
# Handle both single names and comma-separated lists
|
|
names = [n.strip() for n in raw_data.split(',') if n.strip()]
|
|
validated_ids = []
|
|
for name in names:
|
|
try:
|
|
person_id = who_is_this(year, name)
|
|
if person_id:
|
|
validated_ids.append(person_id)
|
|
else:
|
|
self._add_caver_error(field_name, name, year)
|
|
except (ValueError, IndexError) as e:
|
|
self._add_caver_error(field_name, name, year)
|
|
|
|
return validated_ids
|
|
|
|
def _add_caver_error(self, field_name, name, year):
|
|
"""Standardized HTML error reporter"""
|
|
error_html = mark_safe(
|
|
f"'{name}' is not a recognized caver for the year {year}. "
|
|
f"See <a href='/aliases/{year}'>aliases list</a>"
|
|
)
|
|
self.add_error(field_name, error_html)
|
|
|
|
def clean(self):
|
|
# Unlike clean_<fieldname>, which validates one field at a time, the general clean() method
|
|
# allows you to compare multiple fields against each other.
|
|
|
|
# Always call the parent clean() first to get cleaned_data dictionary
|
|
cleaned_data = super().clean()
|
|
|
|
trip_date = cleaned_data.get("discovery_date")
|
|
wallet_id = cleaned_data.get("survey_wallet")
|
|
cave_id = cleaned_data.get("cave_id")
|
|
|
|
if cave_id and trip_date:
|
|
try:
|
|
clean_cave = "".join(cave_id.split()) # removes whitespace
|
|
cave_year = int(clean_cave[:4])
|
|
|
|
trip_year = trip_date.year
|
|
if cave_year != trip_year:
|
|
self.add_error('cave_id',
|
|
f"Year mismatch: Cave identifier year ({cave_year}) does not match Trip date year ({trip_year}).")
|
|
|
|
except (ValueError, IndexError):
|
|
# Individual field cleaners (regex) will handle malformed wallet strings
|
|
pass
|
|
|
|
if wallet_id and trip_date:
|
|
try:
|
|
clean_wallet = "".join(wallet_id.split()) # should already be cleaned
|
|
wallet_year = int(clean_wallet[:4])
|
|
|
|
trip_year = trip_date.year
|
|
|
|
if wallet_year != trip_year:
|
|
self.add_error('survey_wallet',
|
|
f"Year mismatch: Wallet year ({wallet_year}) does not match Trip date year ({trip_year}).")
|
|
|
|
except (ValueError, IndexError):
|
|
# Individual field cleaners (regex) will handle malformed wallet strings
|
|
pass
|
|
|
|
# 2. Extract the year from survey_wallet (YYYY#NN)
|
|
# We only proceed if wallet_id passed its own validation earlier,
|
|
# which removes whitespace and checks the year
|
|
if wallet_id and "#" in wallet_id:
|
|
year = int(clean_wallet[:4])
|
|
|
|
intrepids = self._validate_caver_list(
|
|
'discoverers', year, cleaned_data.get("discoverers"))
|
|
|
|
if cleaned_data.get("photo_ent_no") or cleaned_data.get("gps_photo"):
|
|
cameramen = self._validate_caver_list(
|
|
'photo_ent_who', year, cleaned_data.get("photo_ent_who"))
|
|
|
|
gps_user = self._validate_caver_list(
|
|
'gps_owner', year, cleaned_data.get("gps_owner"))
|
|
|
|
# can now store these lists in cleaned_data if we want
|
|
# cleaned_data['intrepid_ids'] = intrepids
|
|
# cleaned_data['cameramen_ids'] = cameramen
|
|
|
|
gps_lat = cleaned_data.get("gps_lat")
|
|
gps_long = cleaned_data.get("gps_long")
|
|
valid_area, area = which_area(gps_lat, gps_long)
|
|
if not valid_area:
|
|
self.add_error('gps_lat', "Not in Area 1626 or 1623")
|
|
self.add_error('gps_long', "Not in Area 1626 or 1623")
|
|
else:
|
|
if Cave.objects.filter(unofficial_number=cave_id, areacode=area).exists():
|
|
slug = f"{area}-{cave_id}"
|
|
error_html = mark_safe(
|
|
f"This Cave already exists, pick another identifier, or edit it here: "
|
|
f"<a href='/{area}/{cave_id}/{area}-{cave_id}_cave_edit/'>{slug}</a>"
|
|
)
|
|
self.add_error('cave_id', error_html)
|
|
|
|
# Entrance Photo Logic
|
|
photo_ent_on_camera = cleaned_data.get("photo_ent_on_camera")
|
|
gps_screenshot = cleaned_data.get("gps_screenshot")
|
|
photo_ent_who = cleaned_data.get("photo_ent_who")
|
|
|
|
if photo_ent_on_camera and not photo_ent_who:
|
|
# This attaches the error specifically to the 'who' field
|
|
self.add_error('photo_ent_who', "Please specify who has the entrance photo.")
|
|
|
|
if gps_screenshot and not photo_ent_who:
|
|
# This attaches the error specifically to the 'who' field
|
|
self.add_error('photo_ent_who', "Please specify who has the photo of the GPS device.")
|
|
|
|
# Tag Photo Logic (Applying the same logic for consistency)
|
|
photo_tag_on_camera = cleaned_data.get("photo_tag_on_camera")
|
|
photo_tag_who = cleaned_data.get("photo_tag_who")
|
|
|
|
if photo_tag_on_camera and not photo_tag_who:
|
|
self.add_error('photo_tag_who', "Please specify who has the tag photo.")
|
|
|
|
return cleaned_data
|
|
|
|
def clean_cave_id(self):
|
|
data = self.cleaned_data.get('cave_id')
|
|
|
|
# 1. Remove whitespace and force uppercase for consistency
|
|
clean_text = "".join(data.split()).upper()
|
|
|
|
# 2. Regex check: 4 digits, hyphen, 1-4 letters, hyphen, 2 digits
|
|
# \d{4} = year, [A-Z]{1,4} = 1 to 4 letters, \d{2} = number
|
|
match = re.match(r'^(\d{4})-([A-Z]{1,4})-(\d{2})$', clean_text)
|
|
|
|
if not match:
|
|
raise forms.ValidationError(
|
|
"Tag ID must be in format 'YYYY-A[AAA]-NN' (e.g., 2035-AA-99). "
|
|
"The middle can be 1 to 4 letters."
|
|
)
|
|
|
|
# 3. Validate year range from the first capture group
|
|
year = int(match.group(1))
|
|
if year < 1976 or year > 2099:
|
|
raise forms.ValidationError(
|
|
f"The year {year} must be between 1976 and 2099."
|
|
)
|
|
|
|
return clean_text
|
|
|
|
def clean_gps_lat(self):
|
|
data = self.cleaned_data['gps_lat']
|
|
min_val = 47.5
|
|
max_val = 47.9
|
|
if data < min_val or data > max_val:
|
|
raise ValidationError(
|
|
f"A valid latitude is between {min_val} and {max_val}")
|
|
return data
|
|
|
|
def clean_gps_long(self):
|
|
data = self.cleaned_data['gps_long']
|
|
placeholder_val = 13.8160500
|
|
min_val = 13.6
|
|
max_val = 14.0
|
|
if data < min_val or data > max_val:
|
|
raise ValidationError(
|
|
f"A valid longitude is between {min_val} and {max_val}")
|
|
return data
|
|
|
|
def clean_dist_to_ent(self):
|
|
dist = self.cleaned_data.get('dist_to_ent')
|
|
if dist is None:
|
|
raise forms.ValidationError("Distance cannot empty. Please enter a value between 0 and 20.")
|
|
else:
|
|
if dist < 0 or dist > 20:
|
|
raise forms.ValidationError("Distance must be between 0 and 20 meters.")
|
|
return dist
|
|
|
|
def clean_bear_to_ent(self):
|
|
bearing = self.cleaned_data.get('bear_to_ent')
|
|
if bearing is None:
|
|
raise forms.ValidationError("Bearing cannot empty. Please enter a value between 0 and 360.")
|
|
else:
|
|
if bearing < 0 or bearing > 360:
|
|
raise forms.ValidationError("Bearing must be between 0 and 360 degrees.")
|
|
return bearing
|
|
|
|
def clean_survey_wallet(self):
|
|
data = self.cleaned_data.get('survey_wallet')
|
|
|
|
if not data:
|
|
return data # Skip if empty (assumes required=False)
|
|
|
|
# 1. Remove all whitespace, catches tabs etc (\t), (\n)
|
|
clean_text = "".join(data.split())
|
|
|
|
# 2. Check general format using Regex (4 digits, #, 2 digits)
|
|
# ^\d{4}#\d{2}$ ensures the entire string matches exactly
|
|
if not re.match(r'^\d{4}#\d{2}$', clean_text):
|
|
raise forms.ValidationError(
|
|
"Survey Wallet must be in the format 'YYYY#NN' (e.g., 2019#99)."
|
|
)
|
|
|
|
# 3. Validate the year range (1976 to 2099)
|
|
year = int(clean_text[:4])
|
|
if year < 1976 or year > 2099:
|
|
raise forms.ValidationError(
|
|
f"The year {year} must be between 1976 and 2099."
|
|
)
|
|
|
|
# Return the whitespace-stripped version to be saved
|
|
return clean_text
|
|
|
|
|
|
|
|
from textwrap import dedent
|
|
from django.shortcuts import render, redirect
|
|
from django.contrib import messages
|
|
from .auth import login_required_if_public
|
|
from troggle.parsers.caves import make_cave
|
|
|
|
@login_required_if_public
|
|
def new_hole(request):
|
|
identified_login = is_identified_user(request.user)
|
|
editor = get_editor(request)
|
|
|
|
areatext = None
|
|
if request.method == 'POST':
|
|
form = NewHoleForm(request.POST, request.FILES)
|
|
lat = float(form.data['gps_lat'])
|
|
long = float(form.data['gps_long'])
|
|
valid_area, area = which_area(lat, long)
|
|
if valid_area:
|
|
areatext = f"in {area}"
|
|
else:
|
|
areatext = "Not in 1623 or 1626"
|
|
|
|
if form.is_valid():
|
|
editor = form.cleaned_data["who_are_you"]
|
|
editor = git_string(editor)
|
|
|
|
if valid_area:
|
|
process_new_hole(form, area)
|
|
|
|
messages.success(request, "New prospect save data successfully saved.")
|
|
|
|
success_url = "/walletedit/" + form.cleaned_data.get('survey_wallet').replace("#",":")
|
|
return redirect(success_url)
|
|
else:
|
|
# not in 1623 or 1626
|
|
messages.failure(request, "Not in 1623 or 1626. Fix this.")
|
|
return render(request, 'new_hole.html', {'form': form,
|
|
"identified_login": identified_login,
|
|
"areatext": areatext})
|
|
|
|
# GET
|
|
else:
|
|
form = NewHoleForm(initial={"identified_login": identified_login, "who_are_you":editor})
|
|
if identified_login:
|
|
# disable editing the git id string as we get it from the logged-on user data
|
|
form.fields["who_are_you"].widget.attrs["readonly"]="readonly"
|
|
|
|
return render(request, 'new_hole.html', {'form': form,
|
|
"identified_login": identified_login,
|
|
"areatext": areatext})
|
|
|
|
def get_auto_file():
|
|
auto_gps_file = settings.SURVEX_DATA / "fixedpts/gps/auto.svx"
|
|
if not auto_gps_file.exists():
|
|
auto_gps_file.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(auto_gps_file, "w") as auto:
|
|
auto.write(dedent(""" ; Auto-created GPS fixes from 2026 for new caves in BOTH 1623 and 1626
|
|
|
|
*begin
|
|
*cs out UTM33
|
|
*cs LONG-LAT
|
|
*end
|
|
"""))
|
|
|
|
with open(auto_gps_file, "r") as auto:
|
|
old_content = auto.readlines()
|
|
|
|
content_list = [line for line in old_content if "*end" not in line]
|
|
content = "".join(content_list)
|
|
print(content)
|
|
return auto_gps_file, content
|
|
|
|
def process_new_hole(form, area):
|
|
"""
|
|
🚧 under construction.
|
|
|
|
✅Detect if logged-in & set who_am_i using the cookie system, make read-only if logged in
|
|
|
|
✅ Create a fixed point *fix record by inserting into :loser:/fixedpts/gps/auto.svx
|
|
✅ Do a git commit (loser) of the new GPS position
|
|
✅Create a new Cave description file
|
|
⚡Create an associated new Entrance description file with GPS location using *fix
|
|
✅Update the database with this new Cave
|
|
⚡Update the database with this new Entrance
|
|
⚡Do a git commit (expoweb) of the new Cave and Entrance description files
|
|
⚡+ Link page to go to
|
|
Entrance (upload approach & photos, already done Other Station location),
|
|
Wallet (auto-set related Cave & People)
|
|
GPSlog upload
|
|
Logbook entry for the trip (date, people)
|
|
Cave (if cave description, but probably not)
|
|
Survex new (needs new creation page) (date, people, cave id)
|
|
+ list of "happened this day" at the bottom, as for other pages.
|
|
|
|
⚡Update the database "locations" with the new *fix (is this even possible 🧩 without reset?)
|
|
|
|
writes:
|
|
*fix 1623.g2025-bz-06 47.6964481 13.816103 0
|
|
we do not put "reference" in the *fix because we know it is used in the Entrance we are creating
|
|
❌BUT we DO put reference in now because we haven't written the Entrance bit of the code yet!
|
|
"""
|
|
|
|
|
|
editor = git_string(form.cleaned_data["who_are_you"])
|
|
fix_id = f"{area}.g{form.cleaned_data.get("cave_id").lower()}"
|
|
|
|
_newfix(form, area, editor, fix_id)
|
|
|
|
cave = _makecave(form, area)
|
|
_newent(form, area, editor, fix_id, cave) # yes, make the Entrance first
|
|
_savecave(form, area, editor, cave)
|
|
|
|
_newwallet(form, area, editor)
|
|
return
|
|
|
|
def _newfix(form, area, editor, fix_id):
|
|
auto_gps_file, content = get_auto_file()
|
|
fix_line = f"*fix {fix_id} reference {form.cleaned_data.get("gps_lat")} {form.cleaned_data.get("gps_long")} 0\n"
|
|
|
|
content += f"\n; {form.cleaned_data.get("discovery_date")} wallet: {form.cleaned_data.get("survey_wallet")} \n"
|
|
content += fix_line
|
|
content += f"*entrance {fix_id}\n"
|
|
content +=f"\n*end\n"
|
|
|
|
files = [(auto_gps_file, content, "utf8")]
|
|
|
|
try:
|
|
write_and_commit(files, f"Online *fix {fix_id} ", editor)
|
|
except PermissionError:
|
|
message = f"CANNOT save this file.\nPERMISSIONS incorrectly set on server for this file {auto_gps_file}. Ask a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
except WriteAndCommitError as e:
|
|
message = f"CANNOT git on server for this file {auto_gps_file}.\n{e}\nEdits may not be committed.\nAsk a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": e.message})
|
|
except subprocess.SubprocessError as e:
|
|
message = f"CANNOT update server for this file {auto_gps_file}.\n{e}\nEdits may not be committed.\nAsk a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
except:
|
|
raise
|
|
return
|
|
|
|
def _newent(form, areacode, editor, fix_id, cave):
|
|
"""All a bit over-complicated by the existance of teh combined Entrance & Letter Form and the
|
|
CaveAndEntrance class
|
|
|
|
Ideally we want to use the validity checks defined on the Form objects to check that this data is valid.
|
|
But really this should be done within the form we have just processed which the user has just filled in.
|
|
So we won't use the EntranceForm or the EntranceLetterForm, but just duplicate the checks above instead.
|
|
|
|
In the parser, the Entrance is created first, then the Cave. But when doing NewCave, the Cave is created
|
|
first, then the Entrance. So this code is derived from a bit of both.
|
|
"""
|
|
slug = f"{areacode}-{form.cleaned_data.get("cave_id")}" # no letter suffix a,b, or c..
|
|
imgpath = Path(areacode) / form.cleaned_data.get("cave_id")
|
|
|
|
ent = Entrance.objects.create( # creates object
|
|
slug=slug,
|
|
filename = slug + ".html",
|
|
findability="S", # Coordinates
|
|
marking="U", # Unmarked
|
|
approach= form.cleaned_data.get("approach"),
|
|
entrance_description=form.cleaned_data.get("entrance_description"),
|
|
# location_description=location_description[0],
|
|
lastvisit=form.cleaned_data.get("discovery_date"),
|
|
other_station=fix_id,
|
|
)
|
|
# link ent to cave
|
|
ce =CaveAndEntrance.objects.create(
|
|
cave=cave, entranceletter="", entrance=ent
|
|
)
|
|
|
|
#
|
|
# Add in saving Entrance to database and then .html file to filesystem and git
|
|
#
|
|
ent.save() # saves into db
|
|
try:
|
|
ent_file = ent.file_output()
|
|
write_and_commit([ent_file], f"Creating new Entrance {ent}", editor)
|
|
# leave other exceptions unhandled so that they bubble up to user interface
|
|
except PermissionError:
|
|
message = f"CANNOT save this file.\nPERMISSIONS incorrectly set on server for this file {ent.filename}. Ask a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
except WriteAndCommitError as e:
|
|
message = f"CANNOT git on server for this file {ent.filename}.\n{e}\nEdits may not be committed.\nAsk a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": e.message})
|
|
except subprocess.SubprocessError as e:
|
|
message = f"CANNOT update server for this file {ent.filename}.\n{e}\nEdits may not be committed.\nAsk a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
except:
|
|
raise
|
|
return
|
|
|
|
def _guess_survex_file(areacode, id):
|
|
survex_file = f"caves-{areacode}/{id}/{id}.svx"
|
|
if Path(settings.SURVEX_DATA, survex_file).is_file():
|
|
return survex_file
|
|
else:
|
|
survex_file = f"caves-{areacode}/{id}.svx"
|
|
if Path(settings.SURVEX_DATA, survex_file).is_file():
|
|
return survex_file
|
|
return ""
|
|
|
|
def _makecave(form, areacode):
|
|
cave_id = form.cleaned_data.get("cave_id")
|
|
slug = f"{areacode}-{cave_id}"
|
|
cave = make_cave(slug)
|
|
|
|
# Add in default text for various fields here, and links to the two Wallets.
|
|
description = f"Created from New Cave Datasheet on {form.cleaned_data.get("discovery_date")} "
|
|
f"details in wallet: {form.cleaned_data.get("survey_wallet")} "
|
|
if form.cleaned_data.get("surface_wallet"):
|
|
description += f"<br />discovered using old wallet: {form.cleaned_data.get("surface_wallet")} "
|
|
default_note = "Created from New Cave Datasheet. "
|
|
|
|
wallet = form.cleaned_data.get("survey_wallet")
|
|
wallet_url = f"/walletedit/{wallet.replace('#',':')}"
|
|
references = f"Wallet <a href='{wallet_url}'>{wallet}</a>"
|
|
|
|
# TO-DO Need to detect if the existence of the survex was ticked but the file was not found,
|
|
# but that is probably normal: people will mostly record the Cave first and then do the survex data.
|
|
|
|
cave = Cave(
|
|
official_name=form.cleaned_data.get("proposed_name"),
|
|
underground_description=description,
|
|
unofficial_number=cave_id,
|
|
survex_file=_guess_survex_file(areacode, cave_id), # possible that they did the survex file already?
|
|
url=f"{areacode}/{cave_id}/{cave_id}.html",
|
|
notes=default_note,
|
|
areacode=areacode,
|
|
fully_explored=form.cleaned_data.get("is_explored"),
|
|
unexplored= not form.cleaned_data.get("is_explored"),
|
|
references=references,
|
|
)
|
|
cave.save()
|
|
return cave
|
|
|
|
def _savecave(form, areacode, editor, cave):
|
|
cave.save()
|
|
# need a CaveForm f we do it this way, which is a ModelForm.
|
|
# form.save_m2m() # this does the many-to-many relationship saving between caves and entrances
|
|
# can we do this manually?
|
|
try:
|
|
cave_file = cave.file_output()
|
|
write_and_commit([cave_file], f"Creating new cave {cave}", editor)
|
|
# leave other exceptions unhandled so that they bubble up to user interface
|
|
except PermissionError:
|
|
message = f"CANNOT save this file.\nPERMISSIONS incorrectly set on server for this file {cave.filename}. Ask a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
except WriteAndCommitError as e:
|
|
message = f"CANNOT git on server for this file {cave.filename}.\n{e}\nEdits may not be committed.\nAsk a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": e.message})
|
|
except subprocess.SubprocessError as e:
|
|
message = f"CANNOT update server for this file {cave.filename}.\n{e}\nEdits may not be committed.\nAsk a nerd to fix this."
|
|
return render(request, "errors/generic.html", {"message": message})
|
|
except:
|
|
raise
|
|
|
|
return
|
|
|
|
def _newwallet(form, areacode, editor):
|
|
return |