summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/forms.py12
-rw-r--r--core/models/caves.py46
-rw-r--r--core/utils.py23
-rw-r--r--core/views/caves.py81
-rw-r--r--templates/cave.html4
-rw-r--r--templates/editentrance.html6
-rw-r--r--templates/menu.html2
-rw-r--r--urls.py18
8 files changed, 124 insertions, 68 deletions
diff --git a/core/forms.py b/core/forms.py
index 9dd01db..6c83a47 100644
--- a/core/forms.py
+++ b/core/forms.py
@@ -116,11 +116,21 @@ class EntranceForm(ModelForm):
CaveAndEntranceFormSet = modelformset_factory(CaveAndEntrance, exclude=('cave',))
class EntranceLetterForm(ModelForm):
- '''Can't see what this does at all. called twice from views.caves
+ '''Form to link entrances to caves, along with an entrance number.
+
+ Nb. The relationship between caves and entrances has historically been a many to many relationship.
+ With entrances gaining new caves and letters when caves are joined.
'''
class Meta:
model = CaveAndEntrance
exclude = ('cave', 'entrance')
+
+ def full_clean(self):
+ super(EntranceLetterForm, self).full_clean()
+ try:
+ self.instance.validate_unique()
+ except forms.ValidationError as e:
+ self._update_errors(e)
diff --git a/core/models/caves.py b/core/models/caves.py
index f5ae320..9336f5b 100644
--- a/core/models/caves.py
+++ b/core/models/caves.py
@@ -70,6 +70,10 @@ class CaveAndEntrance(models.Model):
cave = models.ForeignKey('Cave',on_delete=models.CASCADE)
entrance = models.ForeignKey('Entrance',on_delete=models.CASCADE)
entrance_letter = models.CharField(max_length=20,blank=True, null=True)
+ class Meta:
+ unique_together = [['cave', 'entrance'], ['cave', 'entrance_letter']]
+ ordering = ['entrance_letter']
+
def __str__(self):
return str(self.cave) + str(self.entrance_letter)
@@ -121,7 +125,7 @@ class Cave(TroggleModel):
def hassurveydata(self):
if not self.underground_centre_line:
return "No"
- if self.survex_file:
+ if self.survex_filcavee:
return "Yes"
return "Missing"
@@ -153,6 +157,9 @@ class Cave(TroggleModel):
#return settings.URL_ROOT + '/cave/' + href + '/'
#return urljoin(settings.URL_ROOT, reverse('cave',kwargs={'cave_id':href,})) # WRONG. This produces /cave/161 and should be /1623/161
return Path(settings.URL_ROOT) / self.url # not good Django style.. NEEDS actual URL
+
+ def url_parent(self):
+ return self.url.rsplit("/", 1)[0]
def __str__(self, sep = ": "):
return str(self.slug())
@@ -172,7 +179,7 @@ class Cave(TroggleModel):
# undated.append(q)
# sortedqms = sorted(dated, key=operator.attrgetter('block.date')) # sort by date of survexblock the QM was defined in
# orderedqms = sorted(undated, key=operator.attrgetter('expoyear')) # sort by date of expoyear
- # return orderedqms + sortedqms # a list, NOT a QuerySet
+ # return orderedqmcaves + sortedqms # a list, NOT a QuerySet
# def new_QM_number(self, year=datetime.date.today().year):
@@ -181,7 +188,7 @@ class Cave(TroggleModel):
# res=QM.objects.filter(found_by__date__year=year, found_by__cave_slug=self.slug).order_by('-number')[0]
# except IndexError:
# return 1
- # return res.number+1
+ # return res.number+1CaveAndEntrance
def kat_area(self):
for a in self.area.all():
@@ -233,7 +240,15 @@ class Cave(TroggleModel):
u = t.render(c)
writetrogglefile(filepath, u)
return
+
+ def file_output(self):
+ filepath = Path(os.path.join(settings.CAVEDESCRIPTIONS, self.filename))
+ t = loader.get_template('dataformat/cave.xml')
+ #c = Context({'cave': self})
+ c = dict({'cave': self})
+ content = t.render(c)
+ return (filepath, content, "utf8")
def getArea(self):
areas = self.area.all()
@@ -292,6 +307,9 @@ class Entrance(TroggleModel):
url = models.CharField(max_length=200,blank=True, null=True)
filename = models.CharField(max_length=200)
cached_primary_slug = models.CharField(max_length=200,blank=True, null=True)
+
+ class Meta:
+ ordering = ['caveandentrance__entrance_letter']
def __str__(self):
return str(self.slug())
@@ -370,7 +388,7 @@ class Entrance(TroggleModel):
# if ancestor_titles:
# res = '/'.join((self.get_root().cave.get_absolute_url(), ancestor_titles, self.title))
# else:
- # res = '/'.join((self.get_root().cave.get_absolute_url(), self.title))
+ # res = '/'.jocavein((self.get_root().cave.get_absolute_url(), self.title))
# return res
res = '/'.join((self.get_root().cave.get_absolute_url(), self.title))
return res
@@ -399,6 +417,14 @@ class Entrance(TroggleModel):
def get_file_path(self):
return Path(settings.ENTRANCEDESCRIPTIONS, self.filename)
+
+ def file_output(self):
+ filepath = Path(os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename))
+
+ t = loader.get_template('dataformat/entrance.xml')
+ c = dict({'entrance': self})
+ content = t.render(c)
+ return (filepath, content, "utf8")
def writeDataFile(self):
filepath = os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename)
@@ -408,7 +434,17 @@ class Entrance(TroggleModel):
u = t.render(c)
writetrogglefile(filepath, u)
return
-
+
+ def url_parent(self):
+ if self.url:
+ return self.url.rsplit("/", 1)[0]
+ else:
+ cavelist = self.cavelist()
+ if len(self.cavelist()) == 1:
+ return cavelist[0].url_parent()
+ else:
+ return ""
+
class LogbookEntry(TroggleModel):
"""Single parsed entry from Logbook
diff --git a/core/utils.py b/core/utils.py
index 5b696dd..228a6ad 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -133,7 +133,7 @@ def write_and_commit(files, message):
kwargs = {"encoding": encoding}
else:
mode = "wb"
- kwargs = {}
+ kwargs = {}
try:
with open(filepath, mode, **kwargs) as f:
print(f'WRITING{cwd}---{filename} ')
@@ -143,18 +143,21 @@ def write_and_commit(files, message):
except PermissionError:
raise WriteAndCommitError(f'CANNOT save this file.\nPERMISSIONS incorrectly set on server for this file {filename}. Ask a nerd to fix this.')
- cp_add = subprocess.run([git, "add", filename], cwd=cwd, capture_output=True, text=True)
- if cp_add.returncode != 0:
- msgdata = 'Ask a nerd to fix this.\n\n' + cp_add.stderr + '\n\n' + cp_add.stdout + '\n\nreturn code: ' + str(cp_add.returncode)
- raise WriteAndCommitError(f'CANNOT git on server for this file {filename}. Edits saved but not added to git.\n\n' + msgdata)
-
+ cp_diff = subprocess.run([git, "diff", filename], cwd=cwd, capture_output=True, text=True)
+ if cp_diff.returncode == 0:
+ cp_add = subprocess.run([git, "add", filename], cwd=cwd, capture_output=True, text=True)
+ if cp_add.returncode != 0:
+ msgdata = 'Ask a nerd to fix this.\n\n' + cp_add.stderr + '\n\n' + cp_add.stdout + '\n\nreturn code: ' + str(cp_add.returncode)
+ raise WriteAndCommitError(f'CANNOT git on server for this file {filename}. Edits saved but not added to git.\n\n' + msgdata)
+ else:
+ print("No change %s" % filepah)
cp_commit = subprocess.run([git, "commit", "-m", message], cwd=cwd, capture_output=True, text=True)
+ cp_status = subprocess.run([git, "status"], cwd=cwd, capture_output=True, text=True)
# This produces return code = 1 if it commits OK, but when the repo still needs to be pushed to origin/expoweb
- if cp_commit.returncode != 0 and cp_commit.stdout != 'nothing to commit, working tree clean':
- msgdata = 'Ask a nerd to fix this.\n\n' + cp_commit.stderr + '\n\n' + cp_commit.stdout + '\n\nreturn code: ' + str(cp_commit.returncode)
- print(msgdata)
+ if cp_status.stdout.split("\n")[-2] != 'nothing to commit, working tree clean':
+ print("FOO: ", cp_status.stdout.split("\n")[-2])
+ msgdata = 'Ask a nerd to fix this.\n\n' + cp_status.stderr + '\n\n' + cp_status.stdout + '\n\nreturn code: ' + str(cp_status.returncode)
raise WriteAndCommitError(f'Error code with git on server for this file {filename}. Edits saved, added to git, but NOT committed.\n\n' + msgdata)
-
except subprocess.SubprocessError:
raise WriteAndCommitError(f'CANNOT git on server for this file {filename}. Subprocess error. Edits not saved.\nAsk a nerd to fix this.')
diff --git a/core/views/caves.py b/core/views/caves.py
index 44dbb80..26ac87f 100644
--- a/core/views/caves.py
+++ b/core/views/caves.py
@@ -19,6 +19,7 @@ from troggle.core.views import expo
from troggle.core.models.troggle import Expedition, DataIssue
from troggle.core.models.caves import CaveSlug, Cave, CaveAndEntrance, QM, EntranceSlug, Entrance, Area, SurvexStation, GetCaveLookup
from troggle.core.forms import CaveForm, CaveAndEntranceFormSet, EntranceForm, EntranceLetterForm
+from troggle.core.utils import writetrogglefile, write_and_commit
from .auth import login_required_if_public
'''Manages the complex procedures to assemble a cave description out of the compnoents
@@ -187,7 +188,7 @@ def file3d(request, cave, cave_id):
# These if statements need refactoring more cleanly
if cave.survex_file:
#print(" - cave.survex_file '{}'".format(cave.survex_file))
- if threedpath.is_file():
+ if threedpath.Pathis_file():
#print(" - threedpath '{}'".format(threedpath))
# possible error here as several .svx files of same names in different directories will overwrite in /3d/
if survexpath.is_file():
@@ -276,8 +277,11 @@ def cavepage(request, karea, subpath):
r = rendercave(request, cave, cave.slug())
return r
except NoReverseMatch:
- message = f'Failed to render cave: {kpath} (it does exist and is unique) because of a Django URL resolution error. Check urls.py.'
- return render(request,'errors/generic.html', {'message': message})
+ if settings.DEBUG:
+ raise
+ else:
+ message = f'Failed to render cave: {kpath} (it does exist and is unique) because of a Django URL resolution error. Check urls.py.'
+ return render(request,'errors/generic.html', {'message': message})
except:
# anything else is a new problem. Add in specific error messages here as we discover new types of error
raise
@@ -294,7 +298,7 @@ def caveEntrance(request, slug):
return render(request,'cave_entrances.html', {'cave': cave})
@login_required_if_public
-def edit_cave(request, slug=None):
+def edit_cave(request, path = "", slug=None):
'''This is the form that edits all the cave data and writes out an XML file in the :expoweb: repo folder
The format for the file being saved is in templates/dataformat/cave.xml
@@ -339,7 +343,8 @@ def edit_cave(request, slug=None):
ceinst.cave = cave
ceinst.save()
try:
- cave.writeDataFile()
+ cave_file = cave.file_output()
+ write_and_commit([cave_file], "Online edit of %s" % cave)
# 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.'
@@ -365,34 +370,35 @@ def edit_cave(request, slug=None):
})
@login_required_if_public
-def edit_entrance(request, caveslug=None, slug=None):
+def edit_entrance(request, path = "", caveslug=None, slug=None):
'''This is the form that edits the entrance data for a single entrance and writes out
an XML file in the :expoweb: repo folder
The format for the file being saved is in templates/dataformat/entrance.xml
It does save the data into into the database directly, not by parsing the file.
'''
- message = ""
- if caveslug is not None:
- try:
- cave = Cave.objects.get(caveslug__slug = caveslug)
- except:
- return render(request,'errors/badslug.html', {'badslug': caveslug})
- else:
- cave = Cave()
- if slug is not None:
+
+ try:
+ cave = Cave.objects.get(caveslug__slug = caveslug)
+ except:
+ return render(request,'errors/badslug.html', {'badslug': caveslug})
+
+ if slug:
entrance = Entrance.objects.get(entranceslug__slug = slug)
+ caveAndEntrance = CaveAndEntrance.objects.get(entrance = entrance, cave = cave)
+ entlettereditable = False
else:
entrance = Entrance()
+ caveAndEntrance = CaveAndEntrance(cave = cave, entrance = entrance)
+ entlettereditable = True
+
if request.POST:
form = EntranceForm(request.POST, instance = entrance)
+ entletter = EntranceLetterForm(request.POST, instance = caveAndEntrance)
#versionControlForm = VersionControlCommentForm(request.POST)
- if slug is None:
- entletter = EntranceLetterForm(request.POST)
- else:
- entletter = None
- if form.is_valid() and (slug is not None or entletter.is_valid()):
+ if form.is_valid() and entletter.is_valid():
entrance = form.save(commit = False)
+ entrance_letter = entletter.save(commit = False)
if slug is None:
if entletter.cleaned_data["entrance_letter"]:
slugname = cave.slug() + entletter.cleaned_data["entrance_letter"]
@@ -404,36 +410,29 @@ def edit_entrance(request, caveslug=None, slug=None):
if slug is None:
es = EntranceSlug(entrance = entrance, slug = slugname, primary = True)
es.save()
- el = entletter.save(commit = False)
- el.cave = cave
- el.entrance = entrance
- el.save()
- try:
- entrance.writeDataFile()
- # 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 {entrance.filename}. Ask a nerd to fix this.'
- return render(request,'errors/generic.html', {'message': message})
- except subprocess.SubprocessError:
- message = f'CANNOT git on server for this file {entrance.filename}. Edits may not be committed.\nAsk a nerd to fix this.'
- return render(request,'errors/generic.html', {'message': message})
-
+ entrance_file = entrance.file_output()
+ cave_file = cave.file_output()
+ write_and_commit([entrance_file, cave_file], "Online edit of %s%s" % (cave, entletter))
+ entrance.save()
+ if slug is None:
+ entrance_letter.save()
return HttpResponseRedirect("/" + cave.url)
- else:
- message = f'! POST data is INVALID {cave}'
- print(message)
else:
form = EntranceForm(instance = entrance)
#versionControlForm = VersionControlCommentForm()
if slug is None:
- entletter = EntranceLetterForm(request.POST)
+ entletter = EntranceLetterForm()
else:
- entletter = None
+ entletter = caveAndEntrance.entrance_letter
+
return render(request,
'editentrance.html',
- {'form': form, 'cave': cave, 'message': message,
+ {'form': form,
+
+ 'cave': cave,
#'versionControlForm': versionControlForm,
- 'entletter': entletter
+ 'entletter': entletter,
+ 'entlettereditable': entlettereditable
})
def ent(request, cave_id, ent_letter):
diff --git a/templates/cave.html b/templates/cave.html
index 416a450..7cbeef2 100644
--- a/templates/cave.html
+++ b/templates/cave.html
@@ -541,7 +541,7 @@ div#scene {
{{ ent.entrance_letter|safe }}
{% if ent.entrance.name %}
{{ ent.entrance.name|safe }}
- {% endif %}<a href="{% url "editentrance" cave.slug ent.entrance.slug %}">Edit</a>
+ {% endif %}<a href="{% url "editentrance" ent.entrance.url_parent cave.slug ent.entrance.slug %}">Edit</a>
<dl>
{% if ent.entrance.marking %}
<dt>Marking</dt><dd>{{ ent.entrance.marking_val|safe }}</dd>
@@ -601,6 +601,6 @@ div#scene {
</ul>
{% endif %}</p>
-<a href="{% url "newentrance" cave.slug %}">New Entrance</a>
+<a href="{% url "newentrance" cave.url_parent cave.slug %}">New Entrance</a>
</div>
{% endblock content %}
diff --git a/templates/editentrance.html b/templates/editentrance.html
index 83273ff..138abeb 100644
--- a/templates/editentrance.html
+++ b/templates/editentrance.html
@@ -8,8 +8,12 @@
<h1>Edit Entrance - at cave {{cave.official_name|safe}} - {{cave.kataster_number}}</h1>
{% include 'html_editor_pop_ups.html' %}
<h2>{{message}}</h2>
-<p>{{entletter}}
<form action="" method="post">{% csrf_token %}
+ {% if entlettereditable %}
+ <table>{{ entletter }}</table>
+ {% else %}
+ <table><tr><th>Entrance Letter</th><td>{{ entletter }}</td></table>
+ {% endif %}
<table>{{ form }}</table>
{{ versionControlForm }}
<p><input type="submit" value="Submit" /></p>
diff --git a/templates/menu.html b/templates/menu.html
index 2dd44f6..8953eed 100644
--- a/templates/menu.html
+++ b/templates/menu.html
@@ -43,7 +43,7 @@
<input id="omega-autofocus" type=search name=P size=8 autofocus>
<input type=submit value="Search"></form></li>
{% if editable %}<li><a href="{% url "editexpopage" path %}" class="editlink"><strong>Edit this page</strong></a></li>{% endif %}
-{% if cave_editable %}<li><a href="{% url "edit_cave" cave.slug %}" class="editlink"><strong>Edit this cave</strong></a></li>{% endif %}
+{% if cave_editable %}<li><a href="{% url "edit_cave" cave.url_parent cave.slug %}" class="editlink"><strong>Edit this cave</strong></a></li>{% endif %}
</ul>
</div>
{% endif %}
diff --git a/urls.py b/urls.py
index 09e151a..f46c3c7 100644
--- a/urls.py
+++ b/urls.py
@@ -60,7 +60,7 @@ else:
path('<path:filepath>', expofilessingle, name="single"), # local copy of EXPOFILES
]
-# see https://docs.djangoproject.com/en/dev/topics/auth/default/
+# see https://docs.djangoproject.com/en/dev/topics/auth/default/tiny
# The URLs provided by include('django.contrib.auth.urls') are:
#
# accounts/login/ [name='login']
@@ -130,15 +130,19 @@ trogglepatterns = [
#re_path(r'^cave/description/([^/]+)/?$', caves.caveDescription), #!!!BAD, local links fail..
#re_path(r'^cave/(?P<cave_id>[^/]+)/?$', caves.cave, name="cave"), # used only in testing !? XXXXXXXXXXXXXXXXXXXXXXXXXX
#re_path(r'^cave/(?P<cave_id>[^/]+)/?(?P<ent_letter>[^/])$', ent), #!!!BAD, local links fail..# view_caves.ent
- re_path(r'^(?P<slug>[^/]+)_cave_edit/$', edit_cave, name="edit_cave"), # edit_cave needed by cave.html template for url matching
+
+# Edit caves and entrances
+ re_path(r'^(?P<path>.*)/(?P<slug>[^/]+)_cave_edit/$', edit_cave, name="edit_cave"), # edit_cave needed by cave.html template for url matching
+ re_path(r'^(?P<path>.*)/(?P<caveslug>[^/]+):(?P<slug>[^:]+)_entrance_edit', edit_entrance, name = "editentrance"), #edit existing entrance
+ re_path(r'^(?P<path>.*)/(?P<caveslug>[^/]+)_entrance_new$', edit_entrance, name = "newentrance"), # new entrance for a cave
+
re_path(r'^(.*)_edit$', editexpopage, name="editexpopage"),
re_path(r'^(?P<karea>\d\d\d\d)(?P<subpath>.*)$', cavepage, name="cavepage"), # shorthand /1623/264 or 1623/161/top.htm
# Note that urls eg '/1623/161/l/rl89a.htm' are handled by cavepage which redirects them to 'expopage' # Note that _edit$ for a cave description page in a subfolder e.g. /1623/204/204.html_edit gets caught here and breaks with 404
# Entrances
re_path(r'^cave/entrance/([^/]+)/?$', caveEntrance), # lists all entrances !!!BAD, local links fail
- re_path(r'^entrance/(?P<caveslug>[^/]+)/(?P<slug>[^/]+)/edit/', edit_entrance, name = "editentrance"), #edit existing entrance
- re_path(r'^entrance/new/(?P<caveslug>[^/]+)$', edit_entrance, name = "newentrance"), # new entrance for a cave
+
# System admin and monitoring
path('statistics', statistics.stats, name="stats"),
@@ -177,11 +181,11 @@ trogglepatterns = [
# The tunnel and therion drawings files pageswalletslistcave
path('dwgfiles', dwgallfiles, name="dwgallfiles"),
- path('dwgfiles/', dwgallfiles, name="dwgallfiles"),
+ path('dwgfiles/', dwgallfiles, name="dwgallfiles"),
path('dwgdataraw/<path:path>', dwgfilesingle, name="dwgfilesingle"),
# QMs pages - must precede other /caves pages?
- re_path(r'^cave/qms/([^/]+)/?$', caveQMs, name="caveQMs"),
+ re_path(r'^cave/qms/([^/]+)/?$', caveQMs, name="caveQMs"),
re_path(r'^cave/qms/(?P<cave_id>[^/]+)/(?P<year>\d\d\d\d)-(?P<qm_id>\d*)(?P<grade>[ABCDXV\?]?)-?(?P<blockname>[a-zA-Z]+.*)?$', qm, name="qm"), # Dogs breakfast
# the resolution of a QM uses several fields together, there is no clean slug field. Artefact of history.
@@ -203,7 +207,7 @@ trogglepatterns = [
re_path(r'^new_image_form/(?P<path>.*)', new_image_form, name = 'new_image_form'),
-# Final catchall which also serves expoweb handbook pages and images
+# Final catchall which also serves expoweb handbook pages and imagestiny
re_path(r'^(.*)$', expopage, name="expopage"), # CATCHALL assumed relative to EXPOWEB
]