troggle-unchained/core/models.py

252 lines
9.4 KiB
Python
Raw Normal View History

2020-05-24 01:57:06 +01:00
import string
import os
import datetime
import logging
import re
2020-06-24 14:10:13 +01:00
import resource
from subprocess import call
2020-05-24 01:57:06 +01:00
2020-06-18 00:20:47 +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
from django.conf import settings
2020-06-18 21:50:16 +01:00
from django.urls import reverse
from django.template import Context, loader
2011-07-11 02:10:22 +01:00
import troggle.core.models_survex
2011-07-11 02:10:22 +01:00
"""This file declares TroggleModel which inherits from django.db.models.Model
All TroggleModel subclasses inherit persistence in the django relational database. This is known as
the django Object Relkational Mapping (ORM).
There are more subclasses define in models_caves.py models_survex.py etc.
"""
2020-06-24 14:10:13 +01:00
def get_process_memory():
usage=resource.getrusage(resource.RUSAGE_SELF)
return usage[2]/1024.0
2020-08-02 23:53:35 +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:
# cave_slugs = models_caves.CaveSlug.objects.filter(cave__kataster_number = qmdict['cave'])
# qm=QM.objects.get(found_by__cave_slug__in = cave_slugs,
# found_by__date__year = qmdict['year'],
# number = qmdict['number'])
# res.append(qm)
# except QM.DoesNotExist:
# print(('fail on '+str(wikilink)))
2011-07-11 02:10:22 +01:00
2020-08-02 23:53:35 +01:00
# return res
2011-07-11 02:10:22 +01:00
try:
logging.basicConfig(level=logging.DEBUG,
filename=settings.LOGFILE,
filemode='w')
except:
# 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-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:
abstract = True
2011-07-11 02:10:22 +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
And we need to use it to replace all assertions in the code too:
https://martinfowler.com/articles/replaceThrowWithNotification.html
"""
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-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:
if len(expeditiondays) == 1:
return expeditiondays[0]
else:
message ='! - more than one datum in an expeditionday: {}'.format(date)
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):
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
2011-07-11 02:10:22 +01:00
class ExpeditionDay(TroggleModel):
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:
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
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)
fullname = models.CharField(max_length=200)
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
2020-06-18 00:20:47 +01:00
user = models.OneToOneField(User, null=True, blank=True,on_delete=models.CASCADE)
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:
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)
max_expo_val = 0
2020-05-31 22:35:36 +01:00
max_expo_year = Expedition.objects.all().aggregate(models.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:
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
"""
2020-06-18 00:20:47 +01:00
expedition = models.ForeignKey(Expedition,on_delete=models.CASCADE)
person = models.ForeignKey(Person,on_delete=models.CASCADE)
slugfield = models.SlugField(max_length=50,blank=True, null=True)
2011-07-11 02:10:22 +01:00
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)
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
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-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):
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"]