2020-05-24 01:57:06 +01:00
import datetime
2023-01-19 18:35:56 +00:00
import os
2020-05-24 01:57:06 +01:00
import re
2020-06-24 14:10:13 +01:00
import resource
2023-01-19 18:35:56 +00:00
import string
from decimal import Decimal , getcontext
2020-05-28 04:54:53 +01:00
from subprocess import call
2020-06-18 00:20:47 +01:00
from urllib . parse import urljoin
2020-05-24 01:57:06 +01:00
2023-01-19 18:35:56 +00:00
getcontext ( ) . prec = 2 #use 2 significant figures for decimal calculations
2020-05-24 01:57:06 +01:00
2023-01-19 18:35:56 +00:00
from django . conf import settings
2011-07-11 02:10:22 +01:00
from django . contrib import admin
from django . contrib . auth . models import User
from django . contrib . contenttypes . models import ContentType
2021-04-22 02:45:28 +01:00
from django . core . files . storage import FileSystemStorage
2023-01-19 18:35:56 +00:00
from django . db import models
from django . template import Context , loader
from django . urls import reverse
2011-07-11 02:10:22 +01:00
2023-01-19 18:35:56 +00:00
import settings
import troggle . core . models . survex
2021-04-12 23:58:48 +01:00
from troggle . core . utils import get_process_memory
2011-07-11 02:10:22 +01:00
2020-07-29 22:54:09 +01:00
""" This file declares TroggleModel which inherits from django.db.models.Model
2021-04-17 01:41:06 +01:00
All TroggleModel and models . Model subclasses inherit persistence in the django relational database . This is known as
2021-04-05 14:01:15 +01:00
the django Object Relational Mapping ( ORM ) .
2020-07-29 22:54:09 +01:00
There are more subclasses define in models_caves . py models_survex . py etc .
"""
2011-07-11 02:10:22 +01:00
class TroggleModel ( models . Model ) :
2022-12-23 23:32:59 +00:00
""" This class is for adding fields and methods which all of our models will have.
"""
2011-07-11 02:10:22 +01:00
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-06-18 00:20:47 +01:00
return 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 ) :
2020-08-02 23:53:35 +01:00
""" When importing cave data any validation problems produce a message which is
recorded as a DataIssue . The django admin system automatically prodiuces a page listing
these at / admin / core / dataissue /
This is a use of the NOTIFICATION pattern :
https : / / martinfowler . com / eaaDev / Notification . html
2021-04-13 22:27:01 +01:00
We have replaced all assertions in the code with messages and local fix - ups or skips :
2020-08-02 23:53:35 +01:00
https : / / martinfowler . com / articles / replaceThrowWithNotification . html
"""
2020-05-28 04:54:53 +01:00
date = models . DateTimeField ( auto_now_add = True , blank = True )
parser = models . CharField ( max_length = 50 , blank = True , null = True )
2022-07-22 11:41:28 +01:00
message = models . CharField ( max_length = 800 , blank = True , null = True )
2021-04-12 01:28:54 +01:00
url = models . CharField ( max_length = 300 , blank = True , null = True ) # link to offending object
2020-05-28 04:54:53 +01:00
class Meta :
ordering = [ ' date ' ]
def __str__ ( self ) :
2022-11-23 10:48:39 +00:00
return f " { self . parser } - { self . message } "
2020-05-28 04:54:53 +01:00
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 )
2021-04-20 22:58:41 +01:00
logbookfile = models . CharField ( max_length = 100 , blank = True , null = True )
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 self . year
class Meta :
ordering = ( ' -year ' , )
get_latest_by = ' year '
def get_absolute_url ( self ) :
2020-06-18 00:20:47 +01:00
return 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 :
2021-03-29 02:06:19 +01:00
if len ( expeditiondays ) == 1 :
return expeditiondays [ 0 ]
else :
2022-12-17 17:05:55 +00:00
message = f ' ! - More than one expeditionday for the same date: { date } . \n - This should never happen. \n - Restart mysql and run reset to clean database. '
2021-03-29 02:06:19 +01:00
DataIssue . objects . create ( parser = ' expedition ' , message = message )
return expeditiondays [ 0 ]
2011-07-11 02:10:22 +01:00
res = ExpeditionDay ( expedition = self , date = date )
res . save ( )
return res
def day_min ( self ) :
2022-12-23 23:32:59 +00:00
""" First day of expedition
"""
2011-07-11 02:10:22 +01:00
res = self . expeditionday_set . all ( )
return res and res [ 0 ] or None
def day_max ( self ) :
2022-12-23 23:32:59 +00:00
""" last day of expedition
"""
2011-07-11 02:10:22 +01:00
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 ) :
2022-12-23 23:32:59 +00:00
""" Exists only so that we can get all logbook trips on this day
"""
2020-06-18 00:20:47 +01:00
expedition = models . ForeignKey ( " Expedition " , on_delete = models . CASCADE )
2011-07-11 02:10:22 +01:00
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 ) :
2022-12-23 23:32:59 +00:00
""" returns all logbook trips for this expediton
"""
2011-07-11 02:10:22 +01:00
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 )
2022-10-08 22:17:53 +01:00
nickname = 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 )
2022-07-31 16:58:46 +01:00
blurb = models . TextField ( blank = True , null = True )
2011-07-11 02:10:22 +01:00
#href = models.CharField(max_length=200)
orderref = models . CharField ( max_length = 200 ) # for alphabetic
2022-07-31 16:58:46 +01:00
user = models . OneToOneField ( User , null = True , blank = True , on_delete = models . CASCADE ) # not used now
2011-07-11 02:10:22 +01:00
def get_absolute_url ( self ) :
2020-06-18 00:20:47 +01:00
return 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 :
2022-11-23 10:48:39 +00:00
return f " { self . first_name } { self . last_name } "
2011-07-11 02:10:22 +01:00
return self . first_name
2020-05-28 01:38:35 +01:00
2011-07-11 02:10:22 +01:00
def notability ( self ) :
2022-12-23 23:32:59 +00:00
""" This is actually recency: all recent cavers, weighted by number of expos
"""
2011-07-11 02:10:22 +01:00
notability = Decimal ( 0 )
2019-03-09 18:21:10 +00:00
max_expo_val = 0
2020-05-31 22:35:36 +01:00
max_expo_year = Expedition . objects . all ( ) . aggregate ( models . Max ( ' year ' ) )
2019-03-09 18:21:10 +00:00
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 :
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 ) :
2022-12-23 23:32:59 +00:00
""" Boolean: is this person notable?
"""
2011-07-11 02:10:22 +01:00
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
"""
2020-06-18 00:20:47 +01:00
expedition = models . ForeignKey ( Expedition , on_delete = models . CASCADE )
person = models . ForeignKey ( Person , on_delete = models . CASCADE )
2022-10-08 22:17:53 +01:00
slugfield = models . SlugField ( max_length = 50 , blank = True , null = True ) # 2022 to be used in future
2020-07-06 01:24:43 +01:00
2011-07-11 02:10:22 +01:00
is_guest = models . BooleanField ( default = False )
2020-06-18 00:20:47 +01:00
nickname = models . CharField ( max_length = 100 , blank = True , null = True )
2011-07-11 02:10:22 +01:00
class Meta :
ordering = ( ' -expedition ' , )
#order_with_respect_to = 'expedition'
2020-05-26 02:21:36 +01:00
def __str__ ( self ) :
2022-11-23 10:48:39 +00:00
return f " { self . person } : ( { self . expedition } ) "
2011-07-11 02:10:22 +01:00
#why is the below a function in personexpedition, rather than in person? - AC 14 Feb 09
def name ( self ) :
if self . nickname :
2022-11-23 10:48:39 +00:00
return f " { self . person . first_name } ( { self . nickname } ) { self . person . last_name } "
2011-07-11 02:10:22 +01:00
if self . person . last_name :
2022-11-23 10:48:39 +00:00
return f " { self . person . first_name } { self . person . last_name } "
2011-07-11 02:10:22 +01:00
return self . person . first_name
def get_absolute_url ( self ) :
2020-06-18 00:20:47 +01:00
return 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 ) :
2022-12-23 23:32:59 +00:00
""" Survey length for this person on all survex trips on this expedition
"""
2020-07-06 01:24:43 +01:00
survexblocks = [ personrole . survexblock for personrole in self . survexpersonrole_set . all ( ) ]
2020-07-04 13:31:46 +01:00
return sum ( [ survexblock . legslength for survexblock in set ( survexblocks ) ] )
2011-07-11 02:10:22 +01:00
# 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 ) :
2020-05-31 22:35:36 +01:00
res = self . persontrip_set . all ( ) . aggregate ( day_max = models . Max ( " expeditionday__date " ) )
2011-07-11 02:10:22 +01:00
return res [ " day_max " ]