2020-05-28 04:54:53 +01:00
import string
import os
import datetime
import re
2020-06-29 21:15:42 +01:00
import json
2022-03-06 01:29:45 +00:00
import subprocess
2022-07-20 12:44:56 +01:00
import operator
2022-03-06 01:29:45 +00:00
2021-03-28 23:47:47 +01:00
from collections import defaultdict
2021-04-26 02:10:45 +01:00
from pathlib import Path
2020-05-28 04:54:53 +01:00
from urllib . parse import urljoin
import settings
from django . db import models
from django . core . files . storage import FileSystemStorage
from django . contrib . auth . models import User
from django . contrib . contenttypes . models import ContentType
from django . db . models import Min , Max
from django . conf import settings
2020-06-18 21:50:16 +01:00
from django . urls import reverse
2020-05-28 04:54:53 +01:00
from django . template import Context , loader
2021-12-30 19:10:13 +00:00
from django . shortcuts import render
2020-05-28 04:54:53 +01:00
2021-04-13 00:43:57 +01:00
from troggle . core . models . troggle import TroggleModel , Person , Expedition , DataIssue
2021-04-13 00:50:12 +01:00
from troggle . core . models . survex import SurvexStation
2022-07-18 14:57:13 +01:00
from troggle . core . utils import writetrogglefile
2020-05-28 04:54:53 +01:00
2021-04-26 02:10:45 +01:00
''' The model declarations for Areas, Caves and Entrances. Also LogBookENtry, QM, PersonTrip
'''
2022-07-18 14:57:13 +01:00
todo = '''
2021-04-26 02:10:45 +01:00
- Find out why we have separate objects CaveSlug and EntranceSlug and why
these are not just a single field on the Model . Do we ever need more
than one slug per cave or entrance ? Surely that would break everything ? ?
- Move PersonTrip to be with Person and Expedition elsewhere
- Restore constraint : unique_together = ( ( " area " , " kataster_number " ) , ( " area " , " unofficial_number " ) )
'''
2020-05-28 04:54:53 +01:00
class Area ( TroggleModel ) :
short_name = models . CharField ( max_length = 100 )
name = models . CharField ( max_length = 200 , blank = True , null = True )
2022-07-05 13:57:49 +01:00
description = models . TextField ( blank = True , null = True )
2022-07-21 19:52:10 +01:00
super = models . ForeignKey ( ' Area ' , blank = True , null = True , on_delete = models . SET_NULL )
2020-07-26 02:26:04 +01:00
2020-05-28 04:54:53 +01:00
def __str__ ( self ) :
2022-07-22 10:40:42 +01:00
if self . super :
return str ( self . super ) + " - " + str ( self . short_name )
2020-05-28 04:54:53 +01:00
else :
return str ( self . short_name )
2020-07-26 02:26:04 +01:00
2020-05-28 04:54:53 +01:00
def kat_area ( self ) :
2021-03-28 23:47:47 +01:00
if self . short_name in [ " 1623 " , " 1626 " , " 1624 " , " 1627 " ] :
2020-05-28 04:54:53 +01:00
return self . short_name
2022-07-22 10:40:42 +01:00
elif self . super :
return self . super . kat_area ( )
2020-05-28 04:54:53 +01:00
class CaveAndEntrance ( models . Model ) :
2020-06-18 00:20:47 +01:00
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 )
2020-05-28 04:54:53 +01:00
def __str__ ( self ) :
return str ( self . cave ) + str ( self . entrance_letter )
2020-07-26 02:26:04 +01:00
2020-05-28 04:54:53 +01:00
class CaveSlug ( models . Model ) :
2020-06-18 00:20:47 +01:00
cave = models . ForeignKey ( ' Cave ' , on_delete = models . CASCADE )
2020-05-28 04:54:53 +01:00
slug = models . SlugField ( max_length = 50 , unique = True )
primary = models . BooleanField ( default = False )
class Cave ( TroggleModel ) :
# too much here perhaps,
official_name = models . CharField ( max_length = 160 )
2020-06-16 19:28:24 +01:00
area = models . ManyToManyField ( Area , blank = True )
2020-06-18 00:20:47 +01:00
kataster_code = models . CharField ( max_length = 20 , blank = True , null = True )
2020-05-28 04:54:53 +01:00
kataster_number = models . CharField ( max_length = 10 , blank = True , null = True )
unofficial_number = models . CharField ( max_length = 60 , blank = True , null = True )
entrances = models . ManyToManyField ( ' Entrance ' , through = ' CaveAndEntrance ' )
explorers = models . TextField ( blank = True , null = True )
underground_description = models . TextField ( blank = True , null = True )
equipment = models . TextField ( blank = True , null = True )
references = models . TextField ( blank = True , null = True )
survey = models . TextField ( blank = True , null = True )
kataster_status = models . TextField ( blank = True , null = True )
underground_centre_line = models . TextField ( blank = True , null = True )
notes = models . TextField ( blank = True , null = True )
2020-06-18 00:20:47 +01:00
length = models . CharField ( max_length = 100 , blank = True , null = True )
depth = models . CharField ( max_length = 100 , blank = True , null = True )
extent = models . CharField ( max_length = 100 , blank = True , null = True )
survex_file = models . CharField ( max_length = 100 , blank = True , null = True )
description_file = models . CharField ( max_length = 200 , blank = True , null = True )
url = models . CharField ( max_length = 200 , blank = True , null = True )
2020-05-28 04:54:53 +01:00
filename = models . CharField ( max_length = 200 )
#class Meta:
# unique_together = (("area", "kataster_number"), ("area", "unofficial_number"))
# FIXME Kataster Areas and CUCC defined sub areas need seperating
#href = models.CharField(max_length=100)
class Meta :
ordering = ( ' kataster_code ' , ' unofficial_number ' )
def hassurvey ( self ) :
if not self . underground_centre_line :
return " No "
if ( self . survey . find ( " <img " ) > - 1 or self . survey . find ( " <a " ) > - 1 or self . survey . find ( " <IMG " ) > - 1 or self . survey . find ( " <A " ) > - 1 ) :
return " Yes "
return " Missing "
def hassurveydata ( self ) :
if not self . underground_centre_line :
return " No "
if self . survex_file :
return " Yes "
return " Missing "
def slug ( self ) :
primarySlugs = self . caveslug_set . filter ( primary = True )
if primarySlugs :
return primarySlugs [ 0 ] . slug
else :
slugs = self . caveslug_set . filter ( )
if slugs :
return slugs [ 0 ] . slug
def ours ( self ) :
return bool ( re . search ( r ' CUCC ' , self . explorers ) )
def reference ( self ) :
if self . kataster_number :
return " %s - %s " % ( self . kat_area ( ) , self . kataster_number )
else :
return " %s - %s " % ( self . kat_area ( ) , self . unofficial_number )
def get_absolute_url ( self ) :
if self . kataster_number :
href = self . kataster_number
elif self . unofficial_number :
href = self . unofficial_number
else :
href = self . official_name . lower ( )
#return settings.URL_ROOT + '/cave/' + href + '/'
2022-03-18 14:18:16 +00:00
#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
2020-05-28 04:54:53 +01:00
def __str__ ( self , sep = " : " ) :
2020-06-28 15:57:40 +01:00
return str ( self . slug ( ) )
2020-05-28 04:54:53 +01:00
def get_QMs ( self ) :
2022-07-20 12:44:56 +01:00
''' Searches for all QMs that reference this cave. Probably a better Django way to do this
'''
qms = QM . objects . filter ( cave = self ) . order_by ( ' expoyear ' , ' block__date ' ) # a QuerySet, see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#order-by
return qms # a QuerySet
# undated = []
# dated = []
# qms = QM.objects.filter(cave=self) # a QuerySet
# for q in qms:
# if q.block:
# dated.append(q)
# else:
# 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
2022-07-05 13:38:23 +01:00
2020-05-28 04:54:53 +01:00
2022-07-06 13:38:53 +01:00
# def new_QM_number(self, year=datetime.date.today().year):
# """Given a cave and the current year, returns the next QM number."""
# try:
# 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
2020-05-28 04:54:53 +01:00
def kat_area ( self ) :
for a in self . area . all ( ) :
if a . kat_area ( ) :
return a . kat_area ( )
def entrances ( self ) :
return CaveAndEntrance . objects . filter ( cave = self )
def singleentrance ( self ) :
return len ( CaveAndEntrance . objects . filter ( cave = self ) ) == 1
def entrancelist ( self ) :
rs = [ ]
res = " "
for e in CaveAndEntrance . objects . filter ( cave = self ) :
2021-04-27 14:51:04 +01:00
if e . entrance_letter :
rs . append ( e . entrance_letter )
2020-05-28 04:54:53 +01:00
rs . sort ( )
2021-04-18 01:58:24 +01:00
prevR = ' '
2020-05-28 04:54:53 +01:00
n = 0
for r in rs :
if prevR :
if chr ( ord ( prevR ) + 1 ) == r :
prevR = r
n + = 1
else :
if n == 0 :
res + = " , " + prevR
else :
res + = " – " + prevR
else :
prevR = r
n = 0
res + = r
if n == 0 :
2022-07-15 14:05:48 +01:00
if res :
res + = " , " + prevR
2020-05-28 04:54:53 +01:00
else :
res + = " – " + prevR
return res
def writeDataFile ( self ) :
2021-12-30 19:07:17 +00:00
filepath = os . path . join ( settings . CAVEDESCRIPTIONS , self . filename )
2020-05-28 04:54:53 +01:00
t = loader . get_template ( ' dataformat/cave.xml ' )
2021-04-02 23:21:23 +01:00
#c = Context({'cave': self})
c = dict ( { ' cave ' : self } )
2021-12-30 19:07:17 +00:00
u = t . render ( c )
writetrogglefile ( filepath , u )
return
2020-05-28 04:54:53 +01:00
def getArea ( self ) :
areas = self . area . all ( )
lowestareas = list ( areas )
for area in areas :
2022-07-21 19:52:10 +01:00
if area . super in areas :
2020-05-28 04:54:53 +01:00
try :
2022-07-21 19:52:10 +01:00
lowestareas . remove ( area . super )
2020-05-28 04:54:53 +01:00
except :
pass
return lowestareas [ 0 ]
class EntranceSlug ( models . Model ) :
2020-06-18 00:20:47 +01:00
entrance = models . ForeignKey ( ' Entrance ' , on_delete = models . CASCADE )
2020-05-28 04:54:53 +01:00
slug = models . SlugField ( max_length = 50 , unique = True )
primary = models . BooleanField ( default = False )
class Entrance ( TroggleModel ) :
name = models . CharField ( max_length = 100 , blank = True , null = True )
entrance_description = models . TextField ( blank = True , null = True )
explorers = models . TextField ( blank = True , null = True )
map_description = models . TextField ( blank = True , null = True )
location_description = models . TextField ( blank = True , null = True )
approach = models . TextField ( blank = True , null = True )
underground_description = models . TextField ( blank = True , null = True )
photo = models . TextField ( blank = True , null = True )
MARKING_CHOICES = (
( ' P ' , ' Paint ' ) ,
( ' P? ' , ' Paint (?) ' ) ,
( ' T ' , ' Tag ' ) ,
( ' T? ' , ' Tag (?) ' ) ,
( ' R ' , ' Needs Retag ' ) ,
( ' S ' , ' Spit ' ) ,
( ' S? ' , ' Spit (?) ' ) ,
( ' U ' , ' Unmarked ' ) ,
( ' ? ' , ' Unknown ' ) )
marking = models . CharField ( max_length = 2 , choices = MARKING_CHOICES )
marking_comment = models . TextField ( blank = True , null = True )
FINDABLE_CHOICES = (
( ' ? ' , ' To be confirmed ... ' ) ,
( ' S ' , ' Coordinates ' ) ,
( ' L ' , ' Lost ' ) ,
( ' R ' , ' Refindable ' ) )
findability = models . CharField ( max_length = 1 , choices = FINDABLE_CHOICES , blank = True , null = True )
findability_description = models . TextField ( blank = True , null = True )
alt = models . TextField ( blank = True , null = True )
northing = models . TextField ( blank = True , null = True )
easting = models . TextField ( blank = True , null = True )
2021-05-07 20:44:58 +01:00
lat_wgs84 = models . TextField ( blank = True , null = True )
long_wgs84 = models . TextField ( blank = True , null = True )
2020-05-28 04:54:53 +01:00
tag_station = models . TextField ( blank = True , null = True )
exact_station = models . TextField ( blank = True , null = True )
other_station = models . TextField ( blank = True , null = True )
other_description = models . TextField ( blank = True , null = True )
bearings = models . TextField ( blank = True , null = True )
2020-06-18 00:20:47 +01:00
url = models . CharField ( max_length = 200 , blank = True , null = True )
2020-05-28 04:54:53 +01:00
filename = models . CharField ( max_length = 200 )
2020-06-18 00:20:47 +01:00
cached_primary_slug = models . CharField ( max_length = 200 , blank = True , null = True )
2020-05-28 04:54:53 +01:00
def __str__ ( self ) :
return str ( self . slug ( ) )
def exact_location ( self ) :
return SurvexStation . objects . lookup ( self . exact_station )
2020-07-26 02:26:04 +01:00
2020-05-28 04:54:53 +01:00
def other_location ( self ) :
return SurvexStation . objects . lookup ( self . other_station )
def find_location ( self ) :
r = { ' ' : ' To be entered ' ,
2020-06-04 21:57:04 +01:00
' ? ' : ' To be confirmed: ' ,
2020-05-28 04:54:53 +01:00
' S ' : ' ' ,
' L ' : ' Lost: ' ,
' R ' : ' Refindable: ' } [ self . findability ]
if self . tag_station :
try :
s = SurvexStation . objects . lookup ( self . tag_station )
return r + " %0.0f E %0.0f N %0.0f Alt " % ( s . x , s . y , s . z )
except :
return r + " %s Tag Station not in dataset " % self . tag_station
if self . exact_station :
try :
s = SurvexStation . objects . lookup ( self . exact_station )
return r + " %0.0f E %0.0f N %0.0f Alt " % ( s . x , s . y , s . z )
except :
return r + " %s Exact Station not in dataset " % self . tag_station
if self . other_station :
try :
s = SurvexStation . objects . lookup ( self . other_station )
return r + " %0.0f E %0.0f N %0.0f Alt %s " % ( s . x , s . y , s . z , self . other_description )
except :
return r + " %s Other Station not in dataset " % self . tag_station
if self . FINDABLE_CHOICES == " S " :
r + = " ERROR, Entrance has been surveyed but has no survex point "
if self . bearings :
return r + self . bearings
return r
def best_station ( self ) :
if self . tag_station :
return self . tag_station
if self . exact_station :
return self . exact_station
if self . other_station :
return self . other_station
def has_photo ( self ) :
if self . photo :
if ( self . photo . find ( " <img " ) > - 1 or self . photo . find ( " <a " ) > - 1 or self . photo . find ( " <IMG " ) > - 1 or self . photo . find ( " <A " ) > - 1 ) :
return " Yes "
else :
return " Missing "
else :
return " No "
def marking_val ( self ) :
for m in self . MARKING_CHOICES :
if m [ 0 ] == self . marking :
return m [ 1 ]
2020-07-26 02:26:04 +01:00
2020-05-28 04:54:53 +01:00
def findability_val ( self ) :
for f in self . FINDABLE_CHOICES :
if f [ 0 ] == self . findability :
return f [ 1 ]
2020-07-26 02:26:04 +01:00
2020-05-28 04:54:53 +01:00
def tag ( self ) :
return SurvexStation . objects . lookup ( self . tag_station )
2020-07-26 02:26:04 +01:00
2020-05-28 04:54:53 +01:00
def needs_surface_work ( self ) :
return self . findability != " S " or not self . has_photo or self . marking != " T "
def get_absolute_url ( self ) :
2021-05-07 23:21:57 +01:00
# ancestor_titles='/'.join([subcave.title for subcave in self.get_ancestors()])
# 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))
# return res
res = ' / ' . join ( ( self . get_root ( ) . cave . get_absolute_url ( ) , self . title ) )
2020-05-28 04:54:53 +01:00
return res
def slug ( self ) :
if not self . cached_primary_slug :
primarySlugs = self . entranceslug_set . filter ( primary = True )
if primarySlugs :
self . cached_primary_slug = primarySlugs [ 0 ] . slug
self . save ( )
else :
slugs = self . entranceslug_set . filter ( )
if slugs :
self . cached_primary_slug = slugs [ 0 ] . slug
self . save ( )
return self . cached_primary_slug
2021-05-07 23:21:57 +01:00
def cavelist ( self ) :
rs = [ ]
res = " "
for e in CaveAndEntrance . objects . filter ( entrance = self ) :
if e . cave :
rs . append ( e . cave )
return rs
2021-04-26 02:10:45 +01:00
def get_file_path ( self ) :
return Path ( settings . ENTRANCEDESCRIPTIONS , self . filename )
2020-05-28 04:54:53 +01:00
def writeDataFile ( self ) :
2021-12-30 19:07:17 +00:00
filepath = os . path . join ( settings . ENTRANCEDESCRIPTIONS , self . filename )
2020-05-28 04:54:53 +01:00
t = loader . get_template ( ' dataformat/entrance.xml ' )
2021-04-14 16:28:30 +01:00
c = dict ( { ' entrance ' : self } )
2020-05-28 04:54:53 +01:00
u = t . render ( c )
2021-12-30 19:07:17 +00:00
writetrogglefile ( filepath , u )
return
2020-05-28 04:54:53 +01:00
2020-05-30 01:11:02 +01:00
class LogbookEntry ( TroggleModel ) :
""" Single parsed entry from Logbook
2021-04-27 15:38:20 +01:00
But what is all this__getattribute__ meta stuff for ? When is it needed ? ! ?
Le ' ts get rid of it and set the ' cave ' attribute to a cave object elsehwhere. This is
attempting to be Too Clever .
2020-05-30 01:11:02 +01:00
"""
LOGBOOK_ENTRY_TYPES = (
( " wiki " , " Wiki style logbook " ) ,
( " html " , " Html style logbook " )
)
date = models . DateField ( ) #MJG wants to turn this into a datetime such that multiple Logbook entries on the same day can be ordered.ld()
2020-06-30 15:26:03 +01:00
expeditionday = models . ForeignKey ( " ExpeditionDay " , null = True , on_delete = models . SET_NULL ) #MJG wants to KILL THIS (redundant information)
2021-04-27 15:38:20 +01:00
expedition = models . ForeignKey ( Expedition , blank = True , null = True , on_delete = models . SET_NULL ) # yes this is double-
2020-05-30 01:11:02 +01:00
title = models . CharField ( max_length = settings . MAX_LOGBOOK_ENTRY_TITLE_LENGTH )
2020-06-19 16:39:05 +01:00
cave_slug = models . SlugField ( max_length = 50 , blank = True , null = True )
2020-06-18 00:20:47 +01:00
place = models . CharField ( max_length = 100 , blank = True , null = True , help_text = " Only use this if you haven ' t chosen a cave " )
2020-05-30 01:11:02 +01:00
text = models . TextField ( )
slug = models . SlugField ( max_length = 50 )
entry_type = models . CharField ( default = " wiki " , null = True , choices = LOGBOOK_ENTRY_TYPES , max_length = 50 )
class Meta :
verbose_name_plural = " Logbook Entries "
# several PersonTrips point in to this object
ordering = ( ' -date ' , )
2021-05-05 00:35:10 +01:00
def cave ( self ) : # Why didn't he just make this a foreign key to Cave ? Replaces __getattrribute__ sillyness.
2021-04-30 00:24:36 +01:00
c = CaveSlug . objects . get ( slug = self . cave_slug , primary = True ) . cave
return c
2020-05-30 01:11:02 +01:00
def isLogbookEntry ( self ) : # Function used in templates
return True
def get_absolute_url ( self ) :
2020-06-02 21:38:01 +01:00
return urljoin ( settings . URL_ROOT , reverse ( ' logbookentry ' , kwargs = { ' date ' : self . date , ' slug ' : self . slug } ) )
2020-05-30 01:11:02 +01:00
def __str__ ( self ) :
2021-04-20 22:58:41 +01:00
return f ' { self . date } : { self . title } '
2020-05-30 01:11:02 +01:00
def get_next_by_id ( self ) :
LogbookEntry . objects . get ( id = self . id + 1 )
def get_previous_by_id ( self ) :
LogbookEntry . objects . get ( id = self . id - 1 )
2022-07-06 13:38:53 +01:00
# def new_QM_number(self):
# """Returns """
# if self.cave:
# nextQMnumber=self.cave.new_QM_number(self.date.year)
# else:
# return None
# return nextQMnumber
2020-05-30 01:11:02 +01:00
2022-07-06 13:38:53 +01:00
# def new_QM_found_link(self):
# """Produces a link to a new QM with the next number filled in and this LogbookEntry set as 'found by' """
# return settings.URL_ROOT + r'/admin/core/qm/add/?' + r'found_by=' + str(self.pk) +'&number=' + str(self.new_QM_number())
2020-05-30 01:11:02 +01:00
def DayIndex ( self ) :
return list ( self . expeditionday . logbookentry_set . all ( ) ) . index ( self )
2020-05-28 04:54:53 +01:00
class QM ( TroggleModel ) :
2020-07-26 02:26:04 +01:00
""" This is based on qm.csv in trunk/expoweb/1623/204 which has the fields:
" Number " , " Grade " , " Area " , " Description " , " Page reference " , " Nearest station " , " Completion description " , " Comment "
"""
2022-07-05 13:38:23 +01:00
cave = models . ForeignKey ( Cave , related_name = ' QMs ' , blank = True , null = True , on_delete = models . SET_NULL )
2022-07-19 18:54:28 +01:00
block = models . ForeignKey ( ' SurvexBlock ' , null = True , on_delete = models . SET_NULL ) # only for QMs from survex files
2022-07-20 18:47:29 +01:00
blockname = models . TextField ( blank = True , null = True ) # NB truncated copy of survexblock name with last char added
2022-07-06 13:38:53 +01:00
expoyear = models . CharField ( max_length = 4 , blank = True , null = True ) # could change to datetime if logbooks similarly chnaged
2020-06-30 15:26:03 +01:00
found_by = models . ForeignKey ( LogbookEntry , related_name = ' QMs_found ' , blank = True , null = True , on_delete = models . SET_NULL )
2022-07-20 12:44:56 +01:00
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?!
2022-07-06 13:38:53 +01:00
number = models . IntegerField ( help_text = " this is the sequential number in the year, only unique for CSV imports " , )
2020-05-28 04:54:53 +01:00
GRADE_CHOICES = (
( ' A ' , ' A: Large obvious lead ' ) ,
( ' B ' , ' B: Average lead ' ) ,
( ' C ' , ' C: Tight unpromising lead ' ) ,
( ' D ' , ' D: Dig ' ) ,
( ' X ' , ' X: Unclimbable aven ' )
2022-07-20 18:47:29 +01:00
) # also seen "?" and "V" in imported data - see urls.py
2020-05-28 04:54:53 +01:00
grade = models . CharField ( max_length = 1 , choices = GRADE_CHOICES )
location_description = models . TextField ( blank = True )
2020-06-18 00:20:47 +01:00
nearest_station_description = models . CharField ( max_length = 400 , blank = True , null = True )
nearest_station_name = models . CharField ( max_length = 200 , blank = True , null = True )
2020-06-30 15:26:03 +01:00
nearest_station = models . ForeignKey ( SurvexStation , blank = True , null = True , on_delete = models . SET_NULL )
2020-06-18 00:20:47 +01:00
area = models . CharField ( max_length = 100 , blank = True , null = True )
2020-05-28 04:54:53 +01:00
completion_description = models . TextField ( blank = True , null = True )
comment = models . TextField ( blank = True , null = True )
def __str__ ( self ) :
2022-07-05 13:38:23 +01:00
return f ' { self . code ( ) } '
2020-05-28 04:54:53 +01:00
def code ( self ) :
2022-07-11 23:29:59 +01:00
if self . cave :
cavestr = str ( self . cave . slug ( ) ) [ 5 : ]
else :
cavestr = " "
if self . expoyear :
2022-07-18 17:19:06 +01:00
expoyearstr = str ( self . expoyear )
2022-07-11 23:29:59 +01:00
else :
2022-07-18 17:19:06 +01:00
expoyearstr = str ( self . cave . slug ( ) ) [ 5 : 9 ]
2022-07-11 23:29:59 +01:00
if self . blockname :
2022-07-20 18:47:29 +01:00
blocknamestr = " - " + str ( self . blockname )
2022-07-11 23:29:59 +01:00
else :
blocknamestr = " "
2022-07-20 18:47:29 +01:00
return f ' { cavestr } - { expoyearstr } - { self . number } { self . grade } { blocknamestr } '
2022-07-20 12:44:56 +01:00
def get_completion_url ( self ) :
''' assumes html file named is in same folder as cave description file
'''
cd = None
if self . completion_description :
try :
dir = Path ( self . cave . url ) . parent
cd = dir / self . completion_description
except :
cd = None
return cd
def newslug ( self ) :
2022-07-06 13:38:53 +01:00
qmslug = f ' { str ( self . cave ) } - { self . expoyear } - { self . blockname } { self . number } { self . grade } '
return qmslug
2020-05-28 04:54:53 +01:00
def get_absolute_url ( self ) :
2022-07-20 18:47:29 +01:00
# This reverse resolution stuff is pure magic. Just change the regex in urls.py and everything changes to suit. Whacky.
2022-07-06 13:38:53 +01:00
return urljoin ( settings . URL_ROOT , reverse ( ' qm ' , kwargs = { ' cave_id ' : self . cave . slug ( ) , ' year ' : self . expoyear , ' blockname ' : self . blockname , ' qm_id ' : self . number , ' grade ' : self . grade } ) )
2022-07-05 18:24:51 +01:00
2020-05-28 04:54:53 +01:00
def get_next_by_id ( self ) :
return QM . objects . get ( id = self . id + 1 )
def get_previous_by_id ( self ) :
return QM . objects . get ( id = self . id - 1 )
2022-07-05 18:24:51 +01:00
2020-05-30 01:11:02 +01:00
class PersonTrip ( TroggleModel ) :
2020-07-26 02:26:04 +01:00
""" Single Person going on a trip, which may or may not be written up.
It accounts for different T / U for people in same logbook entry .
"""
2020-06-18 00:20:47 +01:00
personexpedition = models . ForeignKey ( " PersonExpedition " , null = True , on_delete = models . CASCADE )
2020-05-30 01:11:02 +01:00
time_underground = models . FloatField ( help_text = " In decimal hours " )
2020-06-18 00:20:47 +01:00
logbook_entry = models . ForeignKey ( LogbookEntry , on_delete = models . CASCADE )
2020-05-30 01:11:02 +01:00
is_logbook_entry_author = models . BooleanField ( default = False )
2020-07-26 02:26:04 +01:00
2020-05-30 01:11:02 +01:00
def persontrip_next ( self ) :
futurePTs = PersonTrip . objects . filter ( personexpedition = self . personexpedition , logbook_entry__date__gt = self . logbook_entry . date ) . order_by ( ' logbook_entry__date ' ) . all ( )
if len ( futurePTs ) > 0 :
return futurePTs [ 0 ]
else :
return None
def persontrip_prev ( self ) :
pastPTs = PersonTrip . objects . filter ( personexpedition = self . personexpedition , logbook_entry__date__lt = self . logbook_entry . date ) . order_by ( ' -logbook_entry__date ' ) . all ( )
if len ( pastPTs ) > 0 :
return pastPTs [ 0 ]
else :
return None
def place ( self ) :
return self . logbook_entry . cave and self . logbook_entry . cave or self . logbook_entry . place
2020-05-28 04:54:53 +01:00
2020-05-30 01:11:02 +01:00
def __str__ ( self ) :
2021-04-20 22:58:41 +01:00
return f ' { self . personexpedition } ( { self . logbook_entry . date } ) '
2020-06-29 21:15:42 +01:00
2020-07-26 02:26:04 +01:00
2020-06-29 21:15:42 +01:00
Gcavelookup = None
2021-03-28 23:47:47 +01:00
Gcave_count = None
2020-06-29 21:15:42 +01:00
def GetCaveLookup ( ) :
2022-07-24 19:38:14 +01:00
""" A very relaxed way of finding probably the right cave given almost any string which might serve to identify it
lookup function modelled on GetPersonExpeditionNameLookup
2020-07-26 02:26:04 +01:00
repeated assignment each call , needs refactoring
2021-03-28 23:47:47 +01:00
2022-07-17 13:01:53 +01:00
Used when parsing wallets contents . json file too in views / uploads . py
2021-03-28 23:47:47 +01:00
Does NOT detect duplicates ! Needs fixing .
Needs to be a proper funciton that raises an exception if there is a duplicate .
2022-07-25 00:58:13 +01:00
OR we could set it to return None if there are duplicates , and require the caller to
fall back on doing the actual database query it wants rather thna using this cache shortcut
2020-07-26 02:26:04 +01:00
"""
2022-07-25 00:58:13 +01:00
def checkcaveid ( cave , id ) :
global Gcavelookup
if id not in Gcavelookup :
Gcavelookup [ id ] = cave
Gcave_count [ id ] + = 1
else :
if cave == Gcavelookup [ id ] :
pass # same id, same cave
else : # same id but different cave
message = f " - Warning: same alias id ' { id : 3 } ' for two caves ' { Gcavelookup [ id ] } ' and ' { cave } ' . Removing this shorthand alias entirely. "
Gcavelookup . pop ( id )
print ( message )
2022-07-25 02:17:06 +01:00
DataIssue . objects . create ( parser = ' aliases ' , message = message )
2022-07-25 00:58:13 +01:00
2020-06-29 21:15:42 +01:00
global Gcavelookup
if Gcavelookup :
return Gcavelookup
2021-03-28 23:47:47 +01:00
Gcavelookup = { " NONEPLACEHOLDER " : None }
global Gcave_count
Gcave_count = defaultdict ( int ) # sets default value to int(0)
2022-07-25 02:17:06 +01:00
DataIssue . objects . filter ( parser = ' aliases ' ) . delete ( )
2020-06-29 21:15:42 +01:00
for cave in Cave . objects . all ( ) :
2021-03-28 23:47:47 +01:00
key = cave . official_name . lower ( )
2022-07-25 00:58:13 +01:00
if key != " " and key != " unamed " and key != " unnamed " :
Gcavelookup [ key ] = cave
Gcave_count [ key ] + = 1
2020-06-29 21:15:42 +01:00
if cave . kataster_number :
2022-07-25 00:58:13 +01:00
checkcaveid ( cave , cave . kataster_number ) # we do expect 1623/55 and 1626/55 to cause a warning message
# the rest of these are 'nice to have' but may validly already be set
2020-06-29 21:15:42 +01:00
if cave . unofficial_number :
2022-07-25 00:58:13 +01:00
unoffn = cave . unofficial_number . lower ( )
checkcaveid ( cave , unoffn )
2020-06-29 21:15:42 +01:00
if cave . filename :
2022-07-25 00:58:13 +01:00
# this is the slug - usually.. but usually done as as f'{cave.area}-{cave.kataster_number}'
fn = cave . filename . replace ( " .html " , " " ) . lower ( )
checkcaveid ( cave , fn )
2020-06-29 21:15:42 +01:00
if cave . slug ( ) :
2022-07-25 00:58:13 +01:00
# also possibly done already
slug = cave . slug ( ) . lower ( )
checkcaveid ( cave , slug )
# These might alse create more duplicate entries
2022-07-25 13:03:58 +01:00
# Yes, this should be set in, and imported from, settings.py
2022-07-25 00:58:13 +01:00
aliases = [
( " 1987-02 " , " 267 " ) ,
( " 1990-01 " , " 171 " ) ,
( " 1990-02 " , " 172 " ) ,
( " 1990-03 " , " 173 " ) ,
( " 1990-04 " , " 174 " ) ,
( " 1990-05 " , " 175 " ) ,
( " 1990-06 " , " 176 " ) ,
( " 1990-07 " , " 177 " ) ,
( " 1990-08 " , " 178 " ) ,
( " 1990-09 " , " 179 " ) ,
( " 1990-10 " , " 180 " ) ,
( " 1990-11 " , " 181 " ) ,
( " 1990-12 " , " 182 " ) ,
( " 1990-13 " , " 183 " ) ,
( " 1990-14 " , " 184 " ) ,
( " 1990-18 " , " 188 " ) ,
( " 1990-adam " , " 225 " ) ,
( " 1993-01 " , " 200 " ) ,
( " 1996-02 " , " 224 " ) ,
( " 1996-03 " , " 223 " ) ,
( " 1996-04 " , " 222 " ) ,
( " 1996wk2 " , " 207 " ) ,
( " 1996wk3 " , " 208 " ) ,
( " 1996wk5 " , " 219 " ) ,
( " 1996wk6 " , " 218 " ) ,
( " 1996wk8 " , " 209 " ) ,
( " 1996wk11 " , " 268 " ) ,
( " 96wk11 " , " 268 " ) ,
( " 1998-01 " , " 201 " ) ,
( " 1998-03 " , " 210 " ) ,
( " 1999-03 " , " 204 " ) ,
( " 1999-04 " , " 230 " ) ,
( " 1999-10 " , " 162 " ) ,
( " 1999-bo-01 " , " 205 " ) ,
( " 1999-ob-03 " , " 226 " ) ,
( " 1999-ob-04 " , " 227 " ) ,
( " 2000-01 " , " 231 " ) ,
( " 2000-03 " , " 214 " ) ,
( " 2000-04 " , " 220 " ) ,
( " 2000-05 " , " 215 " ) ,
( " 2000-06 " , " 216 " ) ,
( " 2000-07 " , " 217 " ) ,
( " 2000-09 " , " 234 " ) ,
( " 2000-aa-01 " , " 250 " ) ,
( " 2001-04 " , " 239 " ) ,
( " 2001-05 " , " 243 " ) ,
( " 2002-01 " , " 249 " ) ,
( " 2002-02 " , " 234 " ) ,
( " 2002-04 " , " 242 " ) ,
( " 2002-05 " , " 294 " ) ,
( " 2003-01 " , " 256 " ) ,
( " 2003-02 " , " 248 " ) ,
( " 2003-03 " , " 247 " ) ,
( " 2003-04 " , " 241 " ) ,
( " 2003-05 " , " 246 " ) ,
( " 2003-06 " , " 161 " ) ,
( " 2003-08 " , " 240 " ) ,
( " 2003-09 " , " 245 " ) ,
( " 2003-10 " , " 244 " ) ,
( " 2004-01 " , " 269 " ) ,
( " 2004-03 " , " 270 " ) ,
( " 2004-11 " , " 251 " ) ,
( " 2004-12 " , " 161 " ) ,
( " 2004-15 " , " 253 " ) ,
( " 2004-19 " , " 254 " ) ,
( " 2004-20 " , " 255 " ) ,
( " 2005-04 " , " 204 " ) ,
( " 2005-05 " , " 264 " ) ,
( " 2005-07 " , " 257 " ) ,
( " 2006-08 " , " 285 " ) ,
( " 2006-09 " , " 298 " ) ,
( " 2007-71 " , " 271 " ) ,
( " 2010-01 " , " 263 " ) ,
( " 2010-03 " , " 293 " ) ,
( " 2011-01 " , " 292 " ) ,
( " 2012-dd-05 " , " 286 " ) ,
( " 2012-ns-13 " , " 292 " ) ,
( " 2014-neo-01 " , " 273 " ) ,
( " 2014-sd-01 " , " 274 " ) ,
( " 2014-ms-14 " , " 287 " ) ,
( " 2015-mf-06 " , " 288 " ) ,
( " 2016-jb-01 " , " 289 " ) ,
( " 2017-pw-01 " , " 277 " ) ,
( " 2018-dm-07 " , " 359 " ) , # NB this is 1626
( " 2017_cucc_24 " , " 291 " ) , # note _ not -
( " 2017_cucc_23 " , " 295 " ) , # note _ not -
( " 2017_cucc_28 " , " 290 " ) , # note _ not -
( " bs17 " , " 283 " ) ,
( " 1976/b11 " , " 198 " ) ,
( " 1976/b8 " , " 197 " ) ,
( " 1976/b9 " , " 190 " ) ,
( " b11 " , " 1976/b11 " ) ,
( " b8 " , " 1976/b8 " ) ,
( " b9 " , " 1976/b9 " ) ,
( " 2011-01-bs30 " , " 190 " ) ,
( " bs30 " , " 190 " ) ,
( " 2011-01 " , " 190 " ) ,
( " quarriesd " , " 2002-08 " ) ,
( " 2002-x11 " , " 2005-08 " ) ,
( " 2002-x12 " , " 2005-07 " ) ,
( " 2002-x13 " , " 2005-06 " ) ,
( " 2002-x14 " , " 2005-05 " ) ,
( " kh " , " 161 " ) ,
( " 161-kh " , " 161 " ) ,
( " 204-steinBH " , " 204 " ) ,
( " stonebridge " , " 204 " ) ,
( " hauchhole " , " 234 " ) ,
( " hauch " , " 234 " ) ,
( " 234-hauch " , " 234 " ) ,
( " tunnocks " , " 258 " ) ,
( " balcony " , " 264 " ) ,
( " balkon " , " 264 " ) ,
( " fgh " , " 290 " ) ,
( " gsh " , " 291 " ) ,
( " homecoming " , " 2018-dm-07 " ) ,
( " heimkommen " , " 2018-dm-07 " ) ,
2022-07-25 13:03:58 +01:00
( " Heimkehr " , " 2018-dm-07 " ) ,
2022-07-25 00:58:13 +01:00
( " 99ob02 " , " 1999-ob-02 " ) ,
]
for i in aliases :
if i [ 1 ] in Gcavelookup :
if i [ 0 ] in Gcavelookup :
# already set by a different method, but is it the same cave?
if Gcavelookup [ i [ 0 ] ] == Gcavelookup [ i [ 1 ] ] :
pass
else :
Gcave_count [ i [ 0 ] ] + = 1
Gcavelookup [ i [ 0 ] ] = Gcavelookup [ i [ 1 ] ]
else :
message = f " * Coding or cave existence mistake, cave for id ' { i [ 1 ] } ' does not exist. Expecting to set alias ' { i [ 0 ] } ' to it "
#print(message)
2022-07-25 02:17:06 +01:00
DataIssue . objects . create ( parser = ' aliases ' , message = message )
2022-07-24 19:38:14 +01:00
2020-06-29 21:15:42 +01:00
addmore = { }
for id in Gcavelookup :
addmore [ id . replace ( " - " , " _ " ) ] = Gcavelookup [ id ]
addmore [ id . replace ( " _ " , " - " ) ] = Gcavelookup [ id ]
2021-04-14 16:28:30 +01:00
addmore [ id . upper ( ) ] = Gcavelookup [ id ]
2020-06-29 21:15:42 +01:00
Gcavelookup = { * * addmore , * * Gcavelookup }
addmore = { }
2022-07-25 00:58:13 +01:00
2021-03-28 23:47:47 +01:00
for c in Gcave_count :
if Gcave_count [ c ] > 1 :
2022-07-25 00:58:13 +01:00
message = f " ** Duplicate cave id count= { Gcave_count [ c ] } id: ' { Gcavelookup [ c ] } ' cave __str__: ' { c } ' "
print ( message )
2022-07-25 02:17:06 +01:00
DataIssue . objects . create ( parser = ' aliases ' , message = message )
2021-03-28 23:47:47 +01:00
# logdataissues[Gcavelookup[c]]=message # pending troggle-wide issues logging system
2020-06-29 21:15:42 +01:00
return Gcavelookup