2020-05-24 01:57:06 +01:00
import string
import os
import datetime
import logging
import re
2020-05-28 04:54:53 +01:00
from subprocess import call
2020-05-24 01:57:06 +01:00
2020-05-28 04:54:53 +01:00
from urllib . parse import urljoin
2020-05-24 01:57:06 +01:00
from decimal import Decimal , getcontext
getcontext ( ) . prec = 2 #use 2 significant figures for decimal calculations
import settings
2011-07-11 02:10:22 +01:00
from django . db import models
from django . contrib import admin
from django . contrib . auth . models import User
from django . contrib . contenttypes . models import ContentType
2020-05-28 04:54:53 +01:00
#from django.db.models import Min, Max
2011-07-11 02:10:22 +01:00
from django . conf import settings
from django . core . urlresolvers import reverse
2012-06-10 14:59:21 +01:00
from django . template import Context , loader
2011-07-11 02:10:22 +01:00
2020-05-28 04:54:53 +01:00
import troggle . core . models_survex
import troggle . core . models_caves as models_caves
2011-07-11 02:10:22 +01:00
def get_related_by_wikilinks ( wiki_text ) :
found = re . findall ( settings . QM_PATTERN , wiki_text )
res = [ ]
for wikilink in found :
qmdict = { ' urlroot ' : settings . URL_ROOT , ' cave ' : wikilink [ 2 ] , ' year ' : wikilink [ 1 ] , ' number ' : wikilink [ 3 ] }
try :
2020-05-28 04:54:53 +01:00
cave_slugs = models_caves . CaveSlug . objects . filter ( cave__kataster_number = qmdict [ ' cave ' ] )
2012-08-12 18:10:23 +01:00
qm = QM . objects . get ( found_by__cave_slug__in = cave_slugs ,
2011-07-11 02:10:22 +01:00
found_by__date__year = qmdict [ ' year ' ] ,
number = qmdict [ ' number ' ] )
res . append ( qm )
except QM . DoesNotExist :
2020-05-24 01:57:06 +01:00
print ( ( ' fail on ' + str ( wikilink ) ) )
2011-07-11 02:10:22 +01:00
return res
2015-06-21 15:08:09 +01:00
try :
logging . basicConfig ( level = logging . DEBUG ,
filename = settings . LOGFILE ,
filemode = ' w ' )
except :
2020-05-26 00:54:41 +01:00
# Opening of file for writing is going to fail currently, so decide it doesn't matter for now
pass
2011-07-11 02:10:22 +01:00
#This class is for adding fields and methods which all of our models will have.
class TroggleModel ( models . Model ) :
new_since_parsing = models . BooleanField ( default = False , editable = False )
non_public = models . BooleanField ( default = False )
def object_name ( self ) :
return self . _meta . object_name
def get_admin_url ( self ) :
2020-05-24 01:57:06 +01:00
return urllib . parse . urljoin ( settings . URL_ROOT , " /admin/core/ " + self . object_name ( ) . lower ( ) + " / " + str ( self . pk ) )
2011-07-11 02:10:22 +01:00
class Meta :
2018-04-15 16:28:13 +01:00
abstract = True
2011-07-11 02:10:22 +01:00
2019-04-19 22:52:54 +01:00
class TroggleImageModel ( models . Model ) :
2011-07-11 02:10:22 +01:00
new_since_parsing = models . BooleanField ( default = False , editable = False )
def object_name ( self ) :
return self . _meta . object_name
def get_admin_url ( self ) :
2020-05-24 01:57:06 +01:00
return urllib . parse . urljoin ( settings . URL_ROOT , " /admin/core/ " + self . object_name ( ) . lower ( ) + " / " + str ( self . pk ) )
2011-07-11 02:10:22 +01:00
class Meta :
2018-04-15 16:28:13 +01:00
abstract = True
2011-07-11 02:10:22 +01:00
2020-05-28 04:54:53 +01:00
class DataIssue ( TroggleModel ) :
date = models . DateTimeField ( auto_now_add = True , blank = True )
parser = models . CharField ( max_length = 50 , blank = True , null = True )
message = models . CharField ( max_length = 400 , blank = True , null = True )
class Meta :
ordering = [ ' date ' ]
def __str__ ( self ) :
return " %s - %s " % ( self . parser , self . message )
2011-07-11 02:10:22 +01:00
#
# single Expedition, usually seen by year
#
class Expedition ( TroggleModel ) :
year = models . CharField ( max_length = 20 , unique = True )
name = models . CharField ( max_length = 100 )
2020-05-26 02:21:36 +01:00
def __str__ ( self ) :
2011-07-11 02:10:22 +01:00
return self . year
class Meta :
ordering = ( ' -year ' , )
get_latest_by = ' year '
def get_absolute_url ( self ) :
2020-05-24 01:57:06 +01:00
return urllib . parse . urljoin ( settings . URL_ROOT , reverse ( ' expedition ' , args = [ self . year ] ) )
2011-07-11 02:10:22 +01:00
# construction function. should be moved out
def get_expedition_day ( self , date ) :
expeditiondays = self . expeditionday_set . filter ( date = date )
if expeditiondays :
assert len ( expeditiondays ) == 1
return expeditiondays [ 0 ]
res = ExpeditionDay ( expedition = self , date = date )
res . save ( )
return res
def day_min ( self ) :
res = self . expeditionday_set . all ( )
return res and res [ 0 ] or None
def day_max ( self ) :
res = self . expeditionday_set . all ( )
return res and res [ len ( res ) - 1 ] or None
2019-03-31 15:39:53 +01:00
2011-07-11 02:10:22 +01:00
class ExpeditionDay ( TroggleModel ) :
expedition = models . ForeignKey ( " Expedition " )
date = models . DateField ( )
class Meta :
2019-03-31 15:39:53 +01:00
ordering = ( ' date ' , )
2011-07-11 02:10:22 +01:00
def GetPersonTrip ( self , personexpedition ) :
personexpeditions = self . persontrip_set . filter ( expeditionday = self )
return personexpeditions and personexpeditions [ 0 ] or None
2019-03-31 15:39:53 +01:00
2011-07-11 02:10:22 +01:00
class Person ( TroggleModel ) :
2020-05-24 01:57:06 +01:00
""" single Person, can go on many years
"""
2011-07-11 02:10:22 +01:00
first_name = models . CharField ( max_length = 100 )
last_name = models . CharField ( max_length = 100 )
2019-04-19 22:52:54 +01:00
fullname = models . CharField ( max_length = 200 )
2018-04-17 21:51:39 +01:00
is_vfho = models . BooleanField ( help_text = " VFHO is the Vereines für Höhlenkunde in Obersteier, a nearby Austrian caving club. " , default = False )
2011-07-11 02:10:22 +01:00
mug_shot = models . CharField ( max_length = 100 , blank = True , null = True )
blurb = models . TextField ( blank = True , null = True )
#href = models.CharField(max_length=200)
orderref = models . CharField ( max_length = 200 ) # for alphabetic
user = models . OneToOneField ( User , null = True , blank = True )
def get_absolute_url ( self ) :
2020-05-24 01:57:06 +01:00
return urllib . parse . urljoin ( settings . URL_ROOT , reverse ( ' person ' , kwargs = { ' first_name ' : self . first_name , ' last_name ' : self . last_name } ) )
2011-07-11 02:10:22 +01:00
class Meta :
2018-04-15 16:28:13 +01:00
verbose_name_plural = " People "
2011-07-11 02:10:22 +01:00
ordering = ( ' orderref ' , ) # "Wookey" makes too complex for: ('last_name', 'first_name')
2020-05-26 02:21:36 +01:00
def __str__ ( self ) :
2011-07-11 02:10:22 +01:00
if self . last_name :
return " %s %s " % ( self . first_name , self . last_name )
return self . first_name
2020-05-28 01:38:35 +01:00
2011-07-11 02:10:22 +01:00
def notability ( self ) :
notability = Decimal ( 0 )
2019-03-09 18:21:10 +00:00
max_expo_val = 0
max_expo_year = Expedition . objects . all ( ) . aggregate ( Max ( ' year ' ) )
max_expo_val = int ( max_expo_year [ ' year__max ' ] ) + 1
2011-07-11 02:10:22 +01:00
for personexpedition in self . personexpedition_set . all ( ) :
if not personexpedition . is_guest :
2020-05-24 01:57:06 +01:00
print ( ( personexpedition . expedition . year ) )
2019-03-09 18:21:10 +00:00
notability + = Decimal ( 1 ) / ( max_expo_val - int ( personexpedition . expedition . year ) )
2011-07-11 02:10:22 +01:00
return notability
def bisnotable ( self ) :
return self . notability ( ) > Decimal ( 1 ) / Decimal ( 3 )
def surveyedleglength ( self ) :
return sum ( [ personexpedition . surveyedleglength ( ) for personexpedition in self . personexpedition_set . all ( ) ] )
def first ( self ) :
return self . personexpedition_set . order_by ( ' -expedition ' ) [ 0 ]
def last ( self ) :
return self . personexpedition_set . order_by ( ' expedition ' ) [ 0 ]
class PersonExpedition ( TroggleModel ) :
2020-05-24 01:57:06 +01:00
""" Person ' s attendance to one Expo
"""
2011-07-11 02:10:22 +01:00
expedition = models . ForeignKey ( Expedition )
person = models . ForeignKey ( Person )
slugfield = models . SlugField ( max_length = 50 , blank = True , null = True )
is_guest = models . BooleanField ( default = False )
COMMITTEE_CHOICES = (
( ' leader ' , ' Expo leader ' ) ,
( ' medical ' , ' Expo medical officer ' ) ,
( ' treasurer ' , ' Expo treasurer ' ) ,
( ' sponsorship ' , ' Expo sponsorship coordinator ' ) ,
( ' research ' , ' Expo research coordinator ' ) ,
)
expo_committee_position = models . CharField ( blank = True , null = True , choices = COMMITTEE_CHOICES , max_length = 200 )
nickname = models . CharField ( max_length = 100 , blank = True , null = True )
def GetPersonroles ( self ) :
res = [ ]
for personrole in self . personrole_set . order_by ( ' survexblock ' ) :
if res and res [ - 1 ] [ ' survexpath ' ] == personrole . survexblock . survexpath :
res [ - 1 ] [ ' roles ' ] + = " , " + str ( personrole . role )
else :
res . append ( { ' date ' : personrole . survexblock . date , ' survexpath ' : personrole . survexblock . survexpath , ' roles ' : str ( personrole . role ) } )
return res
class Meta :
ordering = ( ' -expedition ' , )
#order_with_respect_to = 'expedition'
2020-05-26 02:21:36 +01:00
def __str__ ( self ) :
2011-07-11 02:10:22 +01:00
return " %s : ( %s ) " % ( self . person , self . expedition )
#why is the below a function in personexpedition, rather than in person? - AC 14 Feb 09
def name ( self ) :
if self . nickname :
return " %s ( %s ) %s " % ( self . person . first_name , self . nickname , self . person . last_name )
if self . person . last_name :
return " %s %s " % ( self . person . first_name , self . person . last_name )
return self . person . first_name
def get_absolute_url ( self ) :
2020-05-24 01:57:06 +01:00
return urllib . parse . urljoin ( settings . URL_ROOT , reverse ( ' personexpedition ' , kwargs = { ' first_name ' : self . person . first_name , ' last_name ' : self . person . last_name , ' year ' : self . expedition . year } ) )
2011-07-11 02:10:22 +01:00
def surveyedleglength ( self ) :
survexblocks = [ personrole . survexblock for personrole in self . personrole_set . all ( ) ]
return sum ( [ survexblock . totalleglength for survexblock in set ( survexblocks ) ] )
# would prefer to return actual person trips so we could link to first and last ones
def day_min ( self ) :
res = self . persontrip_set . aggregate ( day_min = Min ( " expeditionday__date " ) )
return res [ " day_min " ]
def day_max ( self ) :
res = self . persontrip_set . all ( ) . aggregate ( day_max = Max ( " expeditionday__date " ) )
return res [ " day_max " ]
2020-05-28 04:54:53 +01:00
2011-07-11 02:10:22 +01:00
class LogbookEntry ( TroggleModel ) :
2020-05-24 01:57:06 +01:00
""" Single parsed entry from Logbook
"""
2019-03-31 15:39:53 +01:00
LOGBOOK_ENTRY_TYPES = (
( " wiki " , " Wiki style logbook " ) ,
( " html " , " Html style logbook " )
)
2019-06-26 20:57:24 +01:00
date = models . DateField ( ) #MJG wants to turn this into a datetime such that multiple Logbook entries on the same day can be ordered.ld()
2011-07-11 02:10:22 +01:00
expeditionday = models . ForeignKey ( " ExpeditionDay " , null = True ) #MJG wants to KILL THIS (redundant information)
expedition = models . ForeignKey ( Expedition , blank = True , null = True ) # yes this is double-
2019-03-31 15:39:53 +01:00
title = models . CharField ( max_length = settings . MAX_LOGBOOK_ENTRY_TITLE_LENGTH )
cave_slug = models . SlugField ( max_length = 50 )
place = models . CharField ( max_length = 100 , blank = True , null = True , help_text = " Only use this if you haven ' t chosen a cave " )
text = models . TextField ( )
slug = models . SlugField ( max_length = 50 )
filename = models . CharField ( max_length = 200 , null = True )
entry_type = models . CharField ( default = " wiki " , null = True , choices = LOGBOOK_ENTRY_TYPES , max_length = 50 )
2011-07-11 02:10:22 +01:00
class Meta :
2013-08-01 16:00:01 +01:00
verbose_name_plural = " Logbook Entries "
2011-07-11 02:10:22 +01:00
# several PersonTrips point in to this object
ordering = ( ' -date ' , )
2012-08-12 18:10:23 +01:00
def __getattribute__ ( self , item ) :
2020-05-24 01:57:06 +01:00
if item == " cave " :
#Allow a logbookentries cave to be directly accessed despite not having a proper foreignkey
2020-05-28 04:54:53 +01:00
return models_caves . CaveSlug . objects . get ( slug = self . cave_slug ) . cave
2020-05-24 01:57:06 +01:00
# parse error in python3.8
# https://stackoverflow.com/questions/41343263/provide-classcell-example-for-python-3-6-metaclass
2020-05-25 01:49:02 +01:00
#https://github.com/django/django/pull/7653
#return TroggleModel.__getattribute__(item)
#return super(LogbookEntry, self).__getattribute__(item) # works in py3.5, fails in 3.8
return TroggleModel . __getattribute__ ( self , item ) # works in py 3.5 AND in 3.8
2018-04-14 21:14:19 +01:00
2012-08-12 18:10:23 +01:00
def __init__ ( self , * args , * * kwargs ) :
2020-05-24 01:57:06 +01:00
if " cave " in list ( kwargs . keys ( ) ) :
2018-04-14 21:14:19 +01:00
if kwargs [ " cave " ] is not None :
2020-05-28 04:54:53 +01:00
kwargs [ " cave_slug " ] = models_caves . CaveSlug . objects . get ( cave = kwargs [ " cave " ] , primary = True ) . slug
2018-04-14 21:14:19 +01:00
kwargs . pop ( " cave " )
2020-05-25 01:49:02 +01:00
# parse error in python3.8
2020-05-26 02:21:36 +01:00
return TroggleModel . __init__ ( self , * args , * * kwargs ) # seems OK in 3.5 & 3.8! failure later elsewhere with 3.8
2020-05-25 01:49:02 +01:00
#return TroggleModel().__init__(self, *args, **kwargs) # parses OK, fails at runtime in 3.8
2020-05-26 02:21:36 +01:00
#return super().__init__(self, *args, **kwargs) # fails in 3.8
#return super().__init__(*args, **kwargs) # works in py3.5 fails in 3.8
#return super(LogbookEntry, self).__init__(*args, **kwargs) # works in py3.5
#return TroggleModel.__init__(*args, **kwargs) # fails in py3.5, runtime fail in 3.8
2011-07-11 02:10:22 +01:00
def isLogbookEntry ( self ) : # Function used in templates
return True
def get_absolute_url ( self ) :
2020-05-24 01:57:06 +01:00
return urllib . parse . urljoin ( settings . URL_ROOT , reverse ( ' logbookentry ' , kwargs = { ' date ' : self . date , ' slug ' : self . slug } ) )
2011-07-11 02:10:22 +01:00
2020-05-26 02:21:36 +01:00
def __str__ ( self ) :
2011-07-11 02:10:22 +01:00
return " %s : ( %s ) " % ( self . date , self . title )
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 )
def new_QM_number ( self ) :
""" Returns """
if self . cave :
nextQMnumber = self . cave . new_QM_number ( self . date . year )
else :
2019-03-31 15:39:53 +01:00
return None
2011-07-11 02:10:22 +01:00
return nextQMnumber
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 ( ) )
def DayIndex ( self ) :
return list ( self . expeditionday . logbookentry_set . all ( ) ) . index ( self )
2020-05-28 04:54:53 +01:00
2011-07-11 02:10:22 +01:00
#
# Single Person going on a trip, which may or may not be written up (accounts for different T/U for people in same logbook entry)
#
class PersonTrip ( TroggleModel ) :
personexpedition = models . ForeignKey ( " PersonExpedition " , null = True )
#expeditionday = models.ForeignKey("ExpeditionDay")#MJG wants to KILL THIS (redundant information)
#date = models.DateField() #MJG wants to KILL THIS (redundant information)
time_underground = models . FloatField ( help_text = " In decimal hours " )
logbook_entry = models . ForeignKey ( LogbookEntry )
2018-04-17 21:51:39 +01:00
is_logbook_entry_author = models . BooleanField ( default = False )
2011-07-11 02:10:22 +01:00
# sequencing by person (difficult to solve locally)
#persontrip_next = models.ForeignKey('PersonTrip', related_name='pnext', blank=True,null=True)#MJG wants to KILL THIS (and use funstion persontrip_next_auto)
#persontrip_prev = models.ForeignKey('PersonTrip', related_name='pprev', blank=True,null=True)#MJG wants to KILL THIS(and use funstion persontrip_prev_auto)
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-26 02:21:36 +01:00
def __str__ ( self ) :
2011-07-11 02:10:22 +01:00
return " %s ( %s ) " % ( self . personexpedition , self . logbook_entry . date )