2
0
mirror of https://expo.survex.com/repositories/troggle/.git synced 2024-11-21 14:51:51 +00:00

Much QM re-engineering

This commit is contained in:
Philip Sargent 2023-03-17 20:01:52 +00:00
parent de54576d11
commit 7345e3a328
10 changed files with 252 additions and 178 deletions

View File

@ -47,11 +47,11 @@ class SurvexBlockAdmin(TroggleModelAdmin):
inlines = (RoleInline,)
class QMsFoundInline(admin.TabularInline):
model = QM
fk_name = "found_by"
fields = ("number", "grade", "location_description", "comment") # need to add foreignkey to cave part
extra = 1
# class QMsFoundInline(admin.TabularInline):
# model = QM
# fk_name = "found_by"
# fields = ("number", "grade", "location_description", "comment") # need to add foreignkey to cave part
# extra = 1
class PersonLogEntryInline(admin.TabularInline):
@ -64,7 +64,7 @@ class LogbookEntryAdmin(TroggleModelAdmin):
prepopulated_fields = {"slug": ("title",)}
search_fields = ("title", "expedition__year")
date_heirarchy = "date"
inlines = (PersonLogEntryInline, QMsFoundInline)
# inlines = (PersonLogEntryInline, QMsFoundInline)
class Media:
css = {"all": ("css/troggleadmin.css",)} # this does not exist
@ -91,12 +91,12 @@ class PersonAdmin(TroggleModelAdmin):
class QMAdmin(TroggleModelAdmin):
search_fields = ("found_by__cave__kataster_number", "number", "found_by__date")
list_display = ("__str__", "grade", "found_by", "ticked_off_by")
search_fields = ("number", "expoyear")
list_display = ("__str__", "grade")
list_display_links = ("__str__",)
list_editable = ("found_by", "ticked_off_by", "grade")
list_per_page = 20
raw_id_fields = ("found_by", "ticked_off_by")
# list_editable = ("comment", "page_ref", "grade")
# list_per_page = 20
# raw_id_fields = ("found_by", "ticked_off_by")
class PersonExpeditionAdmin(TroggleModelAdmin):

View File

@ -1,4 +1,5 @@
import os
import os
import re
from collections import defaultdict
from datetime import datetime, timezone
@ -161,12 +162,25 @@ class Cave(TroggleModel):
def __str__(self, sep=": "):
return str(self.slug())
def get_QMs(self):
def get_open_QMs(self):
"""Searches for all QMs that reference this cave."""
# qms = self.qm_set.all().order_by('expoyear', 'block__date')
qms = QM.objects.filter(cave=self).order_by(
"expoyear", "block__date"
) # a QuerySet, see https://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by
qmsopen = qms.filter(ticked=False)
return qmsopen # a QuerySet
def get_ticked_QMs(self):
"""Searches for all QMs that reference this cave."""
qms = QM.objects.filter(cave=self).order_by(
"expoyear", "block__date"
)
qmticked = qms.filter(ticked=True)
return qmticked # a QuerySet
def get_QMs(self):
qms = self.get_open_QMs() | self.get_ticked_QMs() # set union operation
return qms # a QuerySet
def kat_area(self):

View File

@ -144,38 +144,19 @@ class QM(TroggleModel):
number = models.IntegerField(
help_text="this is the sequential number in the year, only unique for CSV imports",
)
grade = models.CharField(max_length=1, blank=True, null=True, help_text="A/B/C/D/X")
cave = models.ForeignKey("Cave", related_name="QMs", blank=True, null=True, on_delete=models.SET_NULL)
block = models.ForeignKey("SurvexBlock", null=True, on_delete=models.SET_NULL) # only for QMs from survex files
blockname = models.TextField(blank=True, null=True) # NB truncated copy of survexblock name with last char added
expoyear = models.CharField(
max_length=4, blank=True, null=True
) # could change to datetime if logbooks similarly chnaged
found_by = models.ForeignKey(
LogbookEntry, related_name="QMs_found", blank=True, null=True, on_delete=models.SET_NULL
)
ticked = models.BooleanField(
default=False
) # for ticked QMs not attached to a logbook entry, should imply completion_description has text
ticked_off_by = models.ForeignKey(
LogbookEntry, related_name="QMs_ticked_off", blank=True, null=True, on_delete=models.SET_NULL
) # unused, ever?!
GRADE_CHOICES = (
("A", "A: Large obvious lead"),
("B", "B: Average lead"),
("C", "C: Tight unpromising lead"),
("D", "D: Dig"),
("X", "X: Unclimbable or horrid"),
("V", "V: Vertical"),
) # also seen "?" in imported data - see urls.py
grade = models.CharField(max_length=1, choices=GRADE_CHOICES)
expoyear = models.CharField(max_length=4, blank=True, null=True)
ticked = models.BooleanField(default=False)
location_description = models.TextField(blank=True)
nearest_station_description = models.CharField(max_length=400, blank=True, null=True)
completion_description = models.TextField(blank=True)
completion_date = models.DateField(blank=True, null=True)
nearest_station_name = models.CharField(max_length=200, blank=True, null=True)
resolution_station_name = models.CharField(max_length=200, blank=True, null=True)
nearest_station = models.ForeignKey("SurvexStation", blank=True, null=True, on_delete=models.SET_NULL)
area = models.CharField(max_length=100, blank=True, null=True)
completion_description = models.TextField(blank=True, null=True)
page_ref = models.TextField(blank=True, null=True)
comment = models.TextField(blank=True, null=True)
def __str__(self):
@ -228,8 +209,8 @@ class QM(TroggleModel):
),
)
def get_next_by_id(self):
def get_next_by_id(self): # called in template
return QM.objects.get(id=self.id + 1)
def get_previous_by_id(self):
def get_previous_by_id(self): # called in template
return QM.objects.get(id=self.id - 1)

View File

@ -536,9 +536,11 @@ def caveQMs(request, slug):
def qm(request, cave_id, qm_id, year, grade=None, blockname=None):
"""Reports on one specific QM
Fixed and working July 2022, for both CSV imported QMs
needs refactoring though.
290 has several QMS with the same number, grade, year (2108) and first 8 chars of the survexblock. This crashes things.
Needs refactoring though! Uses extremely baroque way of getting the QMs instead of querying for QM objects
directly, presumably as a result of a baroque history.
Many caves have several QMS with the same number, grade, year (2018) and first 8 chars of the survexblock. This crashes things, so the terminal char of the survexblock name was added
"""
year = int(year)
@ -547,7 +549,7 @@ def qm(request, cave_id, qm_id, year, grade=None, blockname=None):
# CSV import QMs, use old technique
try:
c = getCave(cave_id)
manyqms = c.get_QMs()
manyqms = c.get_open_QMs() | c.get_ticked_QMs() # set union operation
qm = manyqms.get(number=qm_id, expoyear=year)
return render(request, "qm.html", {"qm": qm})
except QM.DoesNotExist:
@ -565,12 +567,12 @@ def qm(request, cave_id, qm_id, year, grade=None, blockname=None):
qmslug = f"{cave_id}-{year}-{blockname=}{qm_id}{grade}"
print(f"{qmslug=}")
c = getCave(cave_id)
manyqms = c.get_QMs()
manyqms = c.get_open_QMs() | c.get_ticked_QMs() # set union operation
qmqs = manyqms.filter(expoyear=year, blockname=blockname, number=qm_id, grade=grade)
if len(qmqs) > 1:
for q in qmqs:
print(qmqs)
message = f"Multiple QMs with the same cave, year, number, grade AND first 8 chars of the survexblock name. (Could be caused by incomplete databasereset). Fix this in the survex file(s). {cave_id=} {year=} {qm_id=} {blockname=}"
message = f"Multiple QMs with the same cave, year, number, grade AND first-several+terminal chars of the survexblock name. (Could be caused by incomplete databasereset). Fix this in the survex file(s). {cave_id=} {year=} {qm_id=} {blockname=}"
return render(request, "errors/generic.html", {"message": message})
else:
qm = qmqs.get(expoyear=year, blockname=blockname, number=qm_id, grade=grade)
@ -588,7 +590,7 @@ def qm(request, cave_id, qm_id, year, grade=None, blockname=None):
{"badslug": f"Failed get {cave_id=} {year=} {qm_id=} {grade=} {blockname=}"},
)
except MultipleObjectsReturned:
message = f"Multiple QMs with the same cave, year, number, grade AND first 8 chars of the survexblock name. (Could be caused by incomplete databasereset). Fix this in the survex file(s). {cave_id=} {year=} {qm_id=} {blockname=}"
message = f"Multiple QMs with the same cave, year, number, grade AND first-several+terminal chars of the survexblock name. (Could be caused by incomplete databasereset). Fix this in the survex file(s). {cave_id=} {year=} {qm_id=} {blockname=}"
return render(request, "errors/generic.html", {"message": message})
except QM.DoesNotExist:
# raise

79
md5bash.sh Normal file
View File

@ -0,0 +1,79 @@
#!/bin/bash
# read in the input string from command line arguments
input=$1
# pad the input string with a single "1" bit
padded_input="$input"$(echo -ne '\x80')
# pad the input string to a multiple of 512 bits (64 bytes)
while (( $(echo -n "$padded_input" | wc -c) % 64 != 56 ))
do
padded_input="$padded_input"$(echo -ne '\x00')
done
# append the length of the input string (in bits) as a 64-bit little-endian integer
length=$(echo -n "$input" | wc -c)
length=$(echo "obase=16; $length * 8" | bc | xxd -p -c 16 | tac | tr -d '\n')
while (( $(echo -n "$length" | wc -c) < 16 ))
do
length="$length"0
done
padded_input="$padded_input"$(echo -ne "$length" | xxd -r -p)
# initialize the buffer (A, B, C, D)
A="67452301"
B="efcdab89"
C="98badcfe"
D="10325476"
# process the input in 512-bit (64-byte) chunks
for (( i=0; i<$(echo -n "$padded_input" | wc -c)/64; i++ ))
do
chunk=$(echo -n "$padded_input" | dd bs=64 skip=$i count=1 2>/dev/null | xxd -p -c 64)
# initialize the message schedule (M)
M=()
for (( j=0; j<16; j++ ))
do
word=$(echo -ne "${chunk:$j*8:8}" | xxd -r -p | od -An -tu4 -v)
M+=($word)
done
for (( j=16; j<64; j++ ))
do
word1=${M[j-15]}
s0=$(( (word1>>7 | word1<<25) ^ (word1>>18 | word1<<14) ^ (word1>>3) ))
word2=${M[j-2]}
s1=$(( (word2>>17 | word2<<15) ^ (word2>>19 | word2<<13) ^ (word2>>10) ))
M+=($((M[j-16] + s0 + M[j-7] + s1)))
done
# initialize the working variables
AA=$A
BB=$B
CC=$C
DD=$D
# round 1
for (( j=0; j<16; j++ ))
do
F=$(( (B & C) | (~B & D) ))
g=$j
dTemp=$((D))
D=$((C))
C=$((B))
B=$((B + ((A + F + M[g] + 0xd76aa478) & 0xffffffff)))
A=$((dTemp))
done
# round 2
for (( j=16; j<32; j++ ))
do
F=$(( (D & B) | (~D & C) ))
g=$(( (5*j + 1) % 16 ))
dTemp=$((D))
D=$((C))
C=$((B))

View File

@ -65,7 +65,6 @@ def parseCaveQMs(cave, inputFile, ticked=False):
nqms = parse_KH_QMs(kh, inputFile=inputFile, ticked=ticked)
return nqms
# qmPath = settings.EXPOWEB+inputFile
qmPath = Path(settings.EXPOWEB, inputFile)
with open(qmPath, "r") as qmCSVContents:
@ -76,15 +75,17 @@ def parseCaveQMs(cave, inputFile, ticked=False):
n = 0
nqms = 0
for line in qmReader:
#"Number","Grade","Area","Description","Page reference","Nearest survey station","Completion description","Comment"
try:
n += 1
year = int(line[0][1:5])
f"PH_{int(year)}_{int(n):02d}"
QMnum = re.match(r".*?-\d*?-X?(?P<numb>\d*)", line[0]).group("numb")
newQM = QM()
newQM = QM() # creates python object, does not touch db yet
# newQM.found_by=placeholder
newQM.number = QMnum
newQM.cave = caveid
newQM.expoyear = year
newQM.blockname = ""
if line[1] == "Dig":
newQM.grade = "D"
@ -92,32 +93,28 @@ def parseCaveQMs(cave, inputFile, ticked=False):
newQM.grade = line[1]
newQM.area = line[2]
newQM.location_description = line[3]
newQM.page_ref = line[4]
# In the table, completion is indicated by the presence of a completion discription.
newQM.completion_description = line[4]
newQM.nearest_station_description = line[5]
newQM.nearest_station_name = line[5]
newQM.completion_description = line[6]
if newQM.completion_description:
newQM.ticked = True
else:
newQM.ticked = False
newQM.comment = line[6]
newQM.comment = line[7]
try:
# year and number are unique for a cave in CSV imports
preexistingQM = QM.objects.get(
number=QMnum, found_by__date__year=year
) # if we don't have this one in the DB, save it
if (
preexistingQM.new_since_parsing is False
): # if the pre-existing QM has not been modified, overwrite it - VERY OLD THING
number=QMnum, expoyear=year, cave=caveid,
)
if preexistingQM:
message = f" ! - {qmPath} PRE-EXISTING QM - should not exist ! {str(line)} "
print(message)
DataIssue.objects.create(parser="QMs", message=message)
preexistingQM.delete()
newQM.expoyear = year
newQM.save()
else: # otherwise, print that it was ignored
print((" - preserving " + str(preexistingQM) + ", which was edited in admin \r"))
except QM.DoesNotExist: # if there is no pre-existing QM, save the new one
newQM.expoyear = year
newQM.save()
nqms += 1
except KeyError: # check on this one
@ -130,6 +127,12 @@ def parseCaveQMs(cave, inputFile, ticked=False):
print(message)
DataIssue.objects.create(parser="QMs", message=message)
continue
except:
message = f" ! - {qmPath} UNKNOWN error {str(line)} "
print(message)
DataIssue.objects.create(parser="QMs", message=message)
raise
continue
return nqms
@ -139,8 +142,9 @@ def parse_KH_QMs(kh, inputFile, ticked):
khQMs = khQMfile.readlines()
nqms = 0
for line in khQMs:
# <dt><a href="sibria.htm#qC1997-161-27" name="C1997-161-27">C1997-161-27</a> A<dd>Sib: pitch at end of Fuzzy Logic [Paradox Rift - continues] [sep.fuzzy.13]
res = re.search(
r"name=\"[CB](?P<year>\d*)-(?P<cave>\d*)-(?P<number>\d*).*</a> (?P<grade>[ABDCV])<dd>(?P<description>.*)\[(?P<nearest_station>.*)\]",
r"name=\"[CB](?P<year>\d*)-(?P<cave>\d*)-(?P<number>\d*).*</a> (?P<grade>[ABCDX])<dd>(?P<location_description>.*)\[(?P<nearest_station_name>.*)\]",
line,
)
if res:
@ -157,8 +161,10 @@ def parse_KH_QMs(kh, inputFile, ticked):
}
nonLookupAttribs = {
"ticked": ticked,
"nearest_station_name": res["nearest_station"],
"location_description": res["description"],
"page_ref": "",
"completion_description": "",
"nearest_station_name": res["nearest_station_name"],
"location_description": res["location_description"],
}
# Create new. We know it doesn't exist as we deleted evrything when we started.
instance = QM.objects.create(**nonLookupAttribs, **lookupAttribs)
@ -169,8 +175,11 @@ def parse_KH_QMs(kh, inputFile, ticked):
def Load_QMs():
deleteQMs()
n204 = parseCaveQMs(cave="204-steinBH", inputFile=r"1623/204/qm.csv")
n234 = parseCaveQMs(cave="234-Hauch", inputFile=r"1623/234/qm.csv")
#Number Grade Area Description Page reference Nearest station Completion description Comment
n204 = parseCaveQMs(cave="204-steinBH", inputFile=r"1623/204/qm-204.csv") # TAB separated values
#"Number","Grade","Area","Description","Page reference","Nearest survey station","Completion description","Comment"
n234 = parseCaveQMs(cave="234-Hauch", inputFile=r"1623/234/qm-234.csv") # COMMA separated values, with quotes.
n161 = parseCaveQMs(cave="161-KH", inputFile="1623/161/qmtodo.htm", ticked=False)
t161 = parseCaveQMs(cave="161-KH", inputFile="1623/161/qmdone.htm", ticked=True)
# parseCaveQMs(cave='balkonhoehle',inputFile=r"1623/264/qm.csv")

View File

@ -1309,6 +1309,9 @@ class LoadingSurvex:
insp = self.insp
# create a short, hopefully-unique name for this block to be used in the QM id
if len(survexblock.name) < 7:
blockname = survexblock.name
else:
blockname = survexblock.name[:6] + survexblock.name[-1:]
# logslug = f'D{int(qmyear)}_{blockname}_{int(qm_no):03d}'
qm_ticked = False # default
@ -1319,7 +1322,7 @@ class LoadingSurvex:
self.TickSurvexQM(survexblock, qmline)
return
if qm_grade not in ["A", "B", "C", "D", "X", "V", "?"]:
if qm_grade not in ["A", "B", "C", "D", "X"]: # "V", "?" not allowed in survex file QMs
message = f" ! QM{qm_no} INVALID code '{qm_grade}' [{blockname}] '{survexblock.survexfile.path}'"
print(insp + message)
stash_data_issue(
@ -1336,7 +1339,7 @@ class LoadingSurvex:
pass
else:
qm_ticked = True
print(f"{survexblock.survexfile.cave} {survexblock}:{qm_no}{qm_grade} {qmline.group(4)}", file=sys.stderr)
# print(f"{survexblock.survexfile.cave} {survexblock}:{qm_no}{qm_grade} {qmline.group(4)}", file=sys.stderr)
if resolution_station_name:
qm_ticked = True
# if qmline.group(6) and qmline.group(6) != "-":

View File

@ -20,39 +20,42 @@
<li><a href="{% url 'caveQMs' '1626-359' %}">1626-359 QMs</a> Homecoming
</ul>
<p>For how to set up your own QMs, see <a href="/handbook/survey/qmentry.html">Adding QMs </a>.
<p>For full explanation of the current status of the QM system(s), see <a href="/handbook/troggle/scriptsqms.html">scriptsqms page</a>.
{% endblock %}
{% block content %}
<h3>Extant</h3>
<p>{% if cave.get_QMs %}
<ul>
{% for QM in cave.get_QMs %}
{% if QM.ticked %}
{% else %}
<h3>Open leads</h3>
<ul>{% for QM in cave.get_open_QMs %}
<li><a href="{{QM.get_absolute_url}}">{{QM}}</a>
{% if QM.nearest_station_description %}&vellip;<em>{{QM.nearest_station_description}}</em>&vellip;{% endif %} {{QM.location_description}} <b>{{QM.grade}}</b>
{% if QM.nearest_station_name %}&sect;<em>{{QM.nearest_station_name}}</em>&sect;{% endif %}
{{QM.location_description}} <b>{{QM.grade}}</b>
{% if QM.block %} <a href="/survexfile/{{QM.block.survexfile.path}}.svx">{{QM.block}}.svx</a> {{QM.block.date}} {% endif %}</li>
{% endif %}
{% empty %}
<li>No open leads.
{% endfor %}
</ul>
&sect; QM.nearest_station_name<br />
&star; QM.resolution_station_name<br />
<h3>Ticked off</h3>
<ul>
{% for QM in cave.get_QMs %}
{% if QM.ticked %}
<ul>{% for QM in cave.get_ticked_QMs %}
<li><a href="{{QM.get_absolute_url}}">{{QM}}</a>
{% if QM.nearest_station %}&vellip;<em>{{QM.nearest_station}}</em>&vellip;{% endif %}
{% if QM.nearest_station_name %}&vellip;<em>{{QM.nearest_station_name}}</em>&vellip;{% endif %}
{% if QM.nearest_station_description %}&vellip;<em>{{QM.nearest_station_description}}</em>&vellip;{% endif %} {{QM.location_description}} <b>{{QM.grade}}</b>
{% if QM.nearest_station_name %}&sect;<em>{{QM.nearest_station_name}}</em>&sect;{% endif %}
{% if QM.resolution_station_name %}&star;<em>{{QM.resolution_station_name}}</em>&star;{% endif %}
{{QM.location_description}} <b>{{QM.grade}}</b>
{% if QM.block %} <a href="/survexfile/{{QM.block.survexfile.path}}.svx">{{QM.block}}.svx</a> {{QM.block.date}} {% endif %}
{% if QM.completion_description %}
Completion page: <a href="/{{QM.get_completion_url}}">{{QM.completion_description}}</a>
{% endif %}
{% if QM.comment %}
Comment: <a href="/{{QM.get_completion_url}}">{{QM.comment}}</a>
{% endif %}
{% empty %}
<li>No ticked leads.
{% endfor %}
</ul>
{% endif %}</p>
</p>
&sect; QM.nearest_station_name<br />
&star; QM.resolution_station_name<br />
{% endblock %}

View File

@ -18,16 +18,4 @@ Hacked up list of caves with grade "?"
{% endfor %} }
<br /><br />
------------------------------------------------------<br />
Hacked up list of caves with grade "V"
<br /><br />
{% for qm in object_list %}
{% if qm.grade == "V" %}<br />
"{{qm.id}}": ["{{qm.expoyear}}","{{qm.cave}}",{% if qm.ticked %}"TICKed",{% else %}"OPEN",{% endif %}
"{{qm.blockname}}","QM{{qm.number}}","{{qm.grade}}","{{qm.nearest_station_name}}","{{qm.resolution_station_name}}",
<a href="{{qm.get_absolute_url}}">"{{qm.get_absolute_url}}"</a>
]{% if not forloop.last %},{% endif %}
{% endif %}
{% endfor %} }
{% endblock %}

View File

@ -2,6 +2,75 @@
{% load link %}
{% block title %} QM: {{qm|safe}} {% endblock %}
{% block contentheader %}
{% endblock %}
{% block related %}
{% endblock %}
{% block content %}
<table id="cavepage">
<tr>
<th id="kat_no"><a class="whitetext" href="{{qm.get_previous_by_id.get_absolute_url}}">Previous QM</a></th>
<th id="name">QM &nbsp;&nbsp;{{qm|safe}}</th>
<th id="status"><a class="whitetext" href="{{qm.get_next_by_id.get_absolute_url}}">Next QM</a></th>
</tr>
</table>
{% if qm.ticked %}
<h3>This QM is TICKED</h3>
{% if qm.resolution_station_name %}
The survey station which record the continuation of the cave past this QM is <b>{{qm.resolution_station_name}}</b>.<br />
{%endif %}
Tick off comment: '{{qm.comment}}'</a> <br />
Tick off completion description: '{{qm.completion_description}}' </a> <br />
Tick off url: <a href="/{{qm.get_completion_url}}">{{qm.get_completion_url}}</a> WRONG <br />
Tick off date: '{{qm.completion_date}}' [For survex file ticks. not implemented yet]</a> <br />
{% endif %}
{% if qm.cave %}
<h3>Cave: {{qm.cave|safe }}</h3>
<a href="{% url 'caveQMs' qm.cave|safe %}">All QMs on this cave </a>{{ qm.cave|safe }} <br>
<a href="/{{ qm.cave.url }}"> Cave description </a> {{ qm.cave|safe }}
{% else %}
<h3>This QM is OPEN</h3>
Comment: '{{qm.comment}}'</a> <br />
{% endif %}
QM page_ref: '{{qm.page_ref}}'<br>
<h3>QM Number: {{qm.number}} Grade: {{qm.grade}} </h3>
<p>Explanation of Grade letters: <a href="/handbook/survey/qm.html">Handbook - QM grades</a>
{% if qm.block %}
<h3>Survexfile</h3>
{{qm.block.date}} <a href="/survexfile/{{qm.block.survexfile.path}}.svx">{{qm.block}}.svx</a>
{% endif %}
<h3>QM original closest survey station</h3>
{% if qm.block %}
Survex block: {{qm.blockname}}
<br>
{% endif %}
Nearest station: '{{qm.nearest_station_name}}' {{nearest_station_description}}
<h3>QM original description</h3>
{{qm.location_description}}
{% if qm.found_by %}
<h3>Creation</h3>
Found by <a href="{{qm.found_by.get_absolute_url}}">{{qm.found_by}}</a> on {{qm.found_by.date}}.
{% endif %}
<hr />
<h3>QMs available for these caves from CSV import</h3>
<ul>
<li><a href="{% url 'caveQMs' '1623-161' %}">1623-161 QMs</a> Maybe OUT OF DATE. See <a href="/1623/161/qmdone.htm">the curated list <b>instead</b></a>
@ -18,79 +87,5 @@
</ul>
<p>For full explanation of the current status of the QM system(s), see <a href="/handbook/troggle/scriptsqms.html">scriptsqms page</a>.
<table id="cavepage">
<tr>
<th id="kat_no"><a class="whitetext" href="{{qm.get_previous_by_id.get_absolute_url}}">Previous QM</a></th>
<th id="name">QM &nbsp;&nbsp;{{qm|safe}}</th>
<th id="status"><a class="whitetext" href="{{qm.get_next_by_id.get_absolute_url}}">Next QM</a></th>
</tr>
</table>
{% endblock %}
{% block related %}
{% endblock %}
{% block content %}
<h3>Cave</h3>
{% if qm.cave %}
<a href="{% url 'caveQMs' qm.cave|safe %}">{{ qm.cave|safe }} QMs</a> <br>
{% else %}
{% if qm.cave %}
<a href="{% url 'caveQMs' qm.cave|safe %}">{{ qm.cave|safe }} QMs</a> <br>
{% endif %}
{% endif %}
<a href="/{{ qm.cave.url }}">{{ qm.cave|safe }} cave description</a>
<h3>QM Number</h3>
{{qm.number}}
<h3>Grade</h3>
{{qm.grade}}
<p>Explanation of Grade letters: <a href="/handbook/survey/qm.html">Handbook - QM grades</a>
{% if qm.block %}
<h3>Survexfile</h3>
{{qm.block.date}} <a href="/survexfile/{{qm.block.survexfile.path}}.svx">{{qm.block}}.svx</a>
{% endif %}
{% if qm.ticked %}
<h3>Ticked</h3>
This QM is TICKED
{% endif %}
{% if qm.resolution_station_name %}
<h3>resolution_station_name</h3>
{{qm.resolution_station_name}}
{% endif %}
<h3>Location</h3>
{% if qm.block %}
Survex block: {{qm.blockname}}
<br>
{% endif %}
Nearest station: '{{qm.nearest_station_name}}' {{nearest_station_description}}
<h3>QM Description</h3>
{{qm.location_description}}
{% if qm.found_by %}
<h3>Creation</h3>
Found by <a href="{{qm.found_by.get_absolute_url}}">{{qm.found_by}}</a> on {{qm.found_by.date}}.
{% endif %}
<h3>Completion</h3>
{% if qm.ticked %}
Ticked off log entry: <a href="{{qm.ticked_off_by.get_absolute_url}}">{{qm.ticked_off_by}}</a><br />
Ticked off date: <a href="/{{qm.get_completion_url}}">{{qm.get_completion_url}}</a> WRONG <br />
Tick off date: [For survex file ticks. not implemented yet, needs model change]</a> <br />
Tick off completion description: {{qm.completion_description}}</a>
{% else %}
No completion description yet- STILL EXTANT.
{% endif %}
<h3>Comment</h3>
{{qm.comment}}
<hr />
{% endblock %}