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 .
OR we could set it to return None if there are duplictes , and require the caller to
fall back on doing the actual database query it wants rathe rthna using this cache shortcut
2020-07-26 02:26:04 +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)
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 ( )
Gcavelookup [ key ] = cave
Gcave_count [ key ] + = 1
2020-06-29 21:15:42 +01:00
if cave . kataster_number :
2021-03-28 23:47:47 +01:00
Gcavelookup [ cave . kataster_number ] = cave # DUPLICATE as we have 1623-55 and 1626-55
2022-07-24 19:38:14 +01:00
Gcavelookup [ f ' { cave . area } - { cave . kataster_number } ' ] = cave
2021-03-28 23:47:47 +01:00
Gcave_count [ cave . kataster_number ] + = 1
2022-07-24 19:38:14 +01:00
Gcave_count [ f ' { cave . area } - { cave . kataster_number } ' ] + = 1
2020-06-29 21:15:42 +01:00
if cave . unofficial_number :
Gcavelookup [ cave . unofficial_number . lower ( ) ] = cave
2021-03-28 23:47:47 +01:00
Gcave_count [ cave . unofficial_number . lower ( ) ] + = 1
2020-06-29 21:15:42 +01:00
if cave . filename :
# this is the slug - usually..
Gcavelookup [ cave . filename . replace ( " .html " , " " ) . lower ( ) ] = cave
2021-03-28 23:47:47 +01:00
Gcave_count [ cave . filename . replace ( " .html " , " " ) . lower ( ) ] + = 1
2020-06-29 21:15:42 +01:00
if cave . slug ( ) :
slug = cave . slug ( )
Gcavelookup [ slug . lower ( ) ] = cave
2021-03-28 23:47:47 +01:00
Gcave_count [ slug . lower ( ) ] + = 1
2020-06-29 21:15:42 +01:00
# These are exact matches! edit to check for prefix only!
# mostly taken from expoweb/noinfo/cave-number-index
# and Becka's email of 25 may 2020 on new kataster numbers
2021-03-28 23:47:47 +01:00
# These might alse create more duplicate entries, so re-write it to check
2022-07-17 13:01:53 +01:00
# skip any missing keys as this gets called during tests when the database is not loaded
try :
Gcavelookup [ " 1987-02 " ] = Gcavelookup [ " 267 " ]
Gcavelookup [ " 1990-01 " ] = Gcavelookup [ " 171 " ]
Gcavelookup [ " 1990-02 " ] = Gcavelookup [ " 172 " ]
Gcavelookup [ " 1990-03 " ] = Gcavelookup [ " 173 " ]
Gcavelookup [ " 1990-04 " ] = Gcavelookup [ " 174 " ]
Gcavelookup [ " 1990-05 " ] = Gcavelookup [ " 175 " ]
Gcavelookup [ " 1990-06 " ] = Gcavelookup [ " 176 " ]
Gcavelookup [ " 1990-07 " ] = Gcavelookup [ " 177 " ]
Gcavelookup [ " 1990-08 " ] = Gcavelookup [ " 178 " ]
Gcavelookup [ " 1990-09 " ] = Gcavelookup [ " 179 " ]
Gcavelookup [ " 1990-10 " ] = Gcavelookup [ " 180 " ]
Gcavelookup [ " 1990-11 " ] = Gcavelookup [ " 181 " ]
Gcavelookup [ " 1990-12 " ] = Gcavelookup [ " 182 " ]
Gcavelookup [ " 1990-13 " ] = Gcavelookup [ " 183 " ]
Gcavelookup [ " 1990-14 " ] = Gcavelookup [ " 184 " ]
Gcavelookup [ " 1990-18 " ] = Gcavelookup [ " 188 " ]
Gcavelookup [ " 1990-adam " ] = Gcavelookup [ " 225 " ]
Gcavelookup [ " 1993-01 " ] = Gcavelookup [ " 200 " ]
Gcavelookup [ " 1996-02 " ] = Gcavelookup [ " 224 " ]
Gcavelookup [ " 1996-03 " ] = Gcavelookup [ " 223 " ]
Gcavelookup [ " 1996-04 " ] = Gcavelookup [ " 222 " ]
Gcavelookup [ " 1996wk2 " ] = Gcavelookup [ " 207 " ]
Gcavelookup [ " 1996wk3 " ] = Gcavelookup [ " 208 " ]
Gcavelookup [ " 1996wk5 " ] = Gcavelookup [ " 219 " ]
Gcavelookup [ " 1996wk6 " ] = Gcavelookup [ " 218 " ]
Gcavelookup [ " 1996wk8 " ] = Gcavelookup [ " 209 " ]
Gcavelookup [ " 1996wk11 " ] = Gcavelookup [ " 268 " ]
Gcavelookup [ " 96wk11 " ] = Gcavelookup [ " 268 " ]
Gcavelookup [ " 1998-01 " ] = Gcavelookup [ " 201 " ]
Gcavelookup [ " 1998-03 " ] = Gcavelookup [ " 210 " ]
Gcavelookup [ " 1999-03 " ] = Gcavelookup [ " 204 " ]
Gcavelookup [ " 1999-04 " ] = Gcavelookup [ " 230 " ]
Gcavelookup [ " 1999-10 " ] = Gcavelookup [ " 162 " ]
Gcavelookup [ " 1999-bo-01 " ] = Gcavelookup [ " 205 " ]
Gcavelookup [ " 1999-ob-01 " ] = Gcavelookup [ " 205 " ]
Gcavelookup [ " 1999-ob-03 " ] = Gcavelookup [ " 226 " ]
Gcavelookup [ " 1999-ob-04 " ] = Gcavelookup [ " 227 " ]
Gcavelookup [ " 2000-01 " ] = Gcavelookup [ " 231 " ]
Gcavelookup [ " 2000-03 " ] = Gcavelookup [ " 214 " ]
Gcavelookup [ " 2000-04 " ] = Gcavelookup [ " 220 " ]
Gcavelookup [ " 2000-05 " ] = Gcavelookup [ " 215 " ]
Gcavelookup [ " 2000-06 " ] = Gcavelookup [ " 216 " ]
Gcavelookup [ " 2000-07 " ] = Gcavelookup [ " 217 " ]
Gcavelookup [ " 2000-09 " ] = Gcavelookup [ " 234 " ]
Gcavelookup [ " 2000-aa-01 " ] = Gcavelookup [ " 250 " ]
Gcavelookup [ " 2001-04 " ] = Gcavelookup [ " 239 " ]
Gcavelookup [ " 2001-05 " ] = Gcavelookup [ " 243 " ]
Gcavelookup [ " 2002-01 " ] = Gcavelookup [ " 249 " ]
Gcavelookup [ " 2002-02 " ] = Gcavelookup [ " 234 " ]
Gcavelookup [ " 2002-04 " ] = Gcavelookup [ " 242 " ]
Gcavelookup [ " 2002-05 " ] = Gcavelookup [ " 294 " ]
Gcavelookup [ " 2003-01 " ] = Gcavelookup [ " 256 " ]
Gcavelookup [ " 2003-02 " ] = Gcavelookup [ " 248 " ]
Gcavelookup [ " 2003-03 " ] = Gcavelookup [ " 247 " ]
Gcavelookup [ " 2003-04 " ] = Gcavelookup [ " 241 " ]
Gcavelookup [ " 2003-05 " ] = Gcavelookup [ " 246 " ]
Gcavelookup [ " 2003-06 " ] = Gcavelookup [ " 161 " ]
Gcavelookup [ " 2003-08 " ] = Gcavelookup [ " 240 " ]
Gcavelookup [ " 2003-09 " ] = Gcavelookup [ " 245 " ]
Gcavelookup [ " 2003-10 " ] = Gcavelookup [ " 244 " ]
Gcavelookup [ " 2004-01 " ] = Gcavelookup [ " 269 " ]
Gcavelookup [ " 2004-03 " ] = Gcavelookup [ " 270 " ]
Gcavelookup [ " 2004-11 " ] = Gcavelookup [ " 251 " ]
Gcavelookup [ " 2004-12 " ] = Gcavelookup [ " 161 " ]
Gcavelookup [ " 2004-15 " ] = Gcavelookup [ " 253 " ]
Gcavelookup [ " 2004-19 " ] = Gcavelookup [ " 254 " ]
Gcavelookup [ " 2004-20 " ] = Gcavelookup [ " 255 " ]
Gcavelookup [ " 2005-04 " ] = Gcavelookup [ " 204 " ]
Gcavelookup [ " 2005-05 " ] = Gcavelookup [ " 264 " ]
Gcavelookup [ " 2005-07 " ] = Gcavelookup [ " 257 " ]
Gcavelookup [ " 2006-08 " ] = Gcavelookup [ " 285 " ]
Gcavelookup [ " 2006-09 " ] = Gcavelookup [ " 298 " ]
Gcavelookup [ " 2007-71 " ] = Gcavelookup [ " 271 " ]
Gcavelookup [ " 2010-01 " ] = Gcavelookup [ " 263 " ]
Gcavelookup [ " 2010-03 " ] = Gcavelookup [ " 293 " ]
Gcavelookup [ " 2011-01 " ] = Gcavelookup [ " 292 " ]
Gcavelookup [ " 2012-dd-05 " ] = Gcavelookup [ " 286 " ]
Gcavelookup [ " 2012-ns-13 " ] = Gcavelookup [ " 292 " ]
Gcavelookup [ " 2014-neo-01 " ] = Gcavelookup [ " 273 " ]
Gcavelookup [ " 2014-sd-01 " ] = Gcavelookup [ " 274 " ]
Gcavelookup [ " 2014-ms-14 " ] = Gcavelookup [ " 287 " ]
Gcavelookup [ " 2015-mf-06 " ] = Gcavelookup [ " 288 " ]
Gcavelookup [ " 2016-jb-01 " ] = Gcavelookup [ " 289 " ]
Gcavelookup [ " 2017-pw-01 " ] = Gcavelookup [ " 277 " ]
Gcavelookup [ " 2018-dm-07 " ] = Gcavelookup [ " 359 " ] # NB this is 1626
Gcavelookup [ " 2017_cucc_24 " ] = Gcavelookup [ " 291 " ] # note _ not -
Gcavelookup [ " 2017_cucc_23 " ] = Gcavelookup [ " 295 " ] # note _ not -
Gcavelookup [ " 2017_cucc_28 " ] = Gcavelookup [ " 290 " ] # note _ not -
Gcavelookup [ " bs17 " ] = Gcavelookup [ " 283 " ]
Gcavelookup [ " 1976/b11 " ] = Gcavelookup [ " 198 " ]
Gcavelookup [ " 1976/b8 " ] = Gcavelookup [ " 197 " ]
Gcavelookup [ " 1976/b9 " ] = Gcavelookup [ " 190 " ]
Gcavelookup [ " b11 " ] = Gcavelookup [ " 1976/b11 " ]
Gcavelookup [ " b8 " ] = Gcavelookup [ " 1976/b8 " ]
Gcavelookup [ " b9 " ] = Gcavelookup [ " 1976/b9 " ]
Gcavelookup [ " 2011-01-bs30 " ] = Gcavelookup [ " 190 " ]
Gcavelookup [ " bs30 " ] = Gcavelookup [ " 190 " ]
Gcavelookup [ " 87 " ] = Gcavelookup [ " 190 " ]
Gcavelookup [ " 2011-01 " ] = Gcavelookup [ " 190 " ]
Gcavelookup [ " quarriesd " ] = Gcavelookup [ " 2002-08 " ]
Gcavelookup [ " 2002-x11 " ] = Gcavelookup [ " 2005-08 " ]
Gcavelookup [ " 2002-x12 " ] = Gcavelookup [ " 2005-07 " ]
Gcavelookup [ " 2002-x13 " ] = Gcavelookup [ " 2005-06 " ]
Gcavelookup [ " 2002-x14 " ] = Gcavelookup [ " 2005-05 " ]
Gcavelookup [ " kh " ] = Gcavelookup [ " 161 " ]
Gcavelookup [ " 161-kh " ] = Gcavelookup [ " 161 " ]
Gcavelookup [ " 204-steinBH " ] = Gcavelookup [ " 204 " ]
Gcavelookup [ " stonebridge " ] = Gcavelookup [ " 204 " ]
Gcavelookup [ " hauchhole " ] = Gcavelookup [ " 234 " ]
Gcavelookup [ " hauch " ] = Gcavelookup [ " 234 " ]
Gcavelookup [ " 234-hauch " ] = Gcavelookup [ " 234 " ]
Gcavelookup [ " tunnocks " ] = Gcavelookup [ " 258 " ]
Gcavelookup [ " balcony " ] = Gcavelookup [ " 264 " ]
Gcavelookup [ " balkon " ] = Gcavelookup [ " 264 " ]
Gcavelookup [ " fgh " ] = Gcavelookup [ " 290 " ]
Gcavelookup [ " gsh " ] = Gcavelookup [ " 291 " ]
Gcavelookup [ " homecoming " ] = Gcavelookup [ " 2018-dm-07 " ]
Gcavelookup [ " heimkommen " ] = Gcavelookup [ " 2018-dm-07 " ]
Gcavelookup [ " 99ob02 " ] = Gcavelookup [ " 1999-ob-02 " ]
2022-07-24 19:38:14 +01:00
#Gcavelookup["1626-354"] = Gcavelookup["354"]
2022-07-17 13:01:53 +01:00
except :
2022-07-21 17:51:04 +01:00
raise
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 = { }
for id in Gcavelookup :
if not Gcavelookup [ id ] :
pass
elif Gcavelookup [ id ] . kataster_number :
addmore [ id ] = Gcavelookup [ id ] . kataster_number
elif Gcavelookup [ id ] . unofficial_number :
addmore [ id ] = Gcavelookup [ id ] . unofficial_number . lower ( )
2021-04-03 21:09:16 +01:00
# with open("cave-lookup.json", 'w') as f: # no permissions on server by default
# json.dump(addmore, f)
2021-03-28 23:47:47 +01:00
for c in Gcave_count :
if Gcave_count [ c ] > 1 :
message = " ** Duplicate cave id: {} : {} : {} " . format ( Gcave_count [ c ] , Gcavelookup [ c ] , c )
#print(message)
#DataIssue.objects.create(parser='caves', message=message)
# logdataissues[Gcavelookup[c]]=message # pending troggle-wide issues logging system
2020-06-29 21:15:42 +01:00
return Gcavelookup