From 5639435058191221687cc7bb2d0b64f0531d9164 Mon Sep 17 00:00:00 2001 From: Philip Sargent Date: Sun, 29 Jan 2023 16:47:46 +0000 Subject: [PATCH] Moved Logbooks to separate model file --- core/admin.py | 5 +- core/forms.py | 4 +- core/models/caves.py | 148 -------------------------------- core/models/logbooks.py | 181 +++++++++++++++++++++++++++++++++++++++ core/views/caves.py | 3 +- core/views/logbooks.py | 2 +- core/views/other.py | 3 +- core/views/statistics.py | 3 +- core/views/survex.py | 3 +- core/views/uploads.py | 3 +- parsers/QMs.py | 3 +- parsers/logbooks.py | 3 +- parsers/survex.py | 3 +- 13 files changed, 203 insertions(+), 161 deletions(-) create mode 100644 core/models/logbooks.py diff --git a/core/admin.py b/core/admin.py index f7c75db..bc01b52 100644 --- a/core/admin.py +++ b/core/admin.py @@ -4,8 +4,9 @@ from django.core import serializers from django.forms import ModelForm from django.http import HttpResponse -from troggle.core.models.caves import (QM, Area, Cave, CaveAndEntrance, - Entrance, LogbookEntry, PersonTrip) +from troggle.core.models.caves import (Area, Cave, CaveAndEntrance, + Entrance) +from troggle.core.models.logbooks import (QM, LogbookEntry, PersonTrip) from troggle.core.models.survex import (DrawingFile, SingleScan, SurvexBlock, SurvexDirectory, SurvexFile, SurvexPersonRole, SurvexStation) diff --git a/core/forms.py b/core/forms.py index 4b1399d..31f6de5 100644 --- a/core/forms.py +++ b/core/forms.py @@ -6,8 +6,8 @@ from django.contrib.admin.widgets import AdminDateWidget from django.forms import ModelForm from django.forms.models import modelformset_factory -from troggle.core.models.caves import (QM, Cave, CaveAndEntrance, Entrance, - LogbookEntry) +from troggle.core.models.caves import Cave, CaveAndEntrance, Entrance +from troggle.core.models.logbooks import QM, LogbookEntry from troggle.core.models.troggle import Expedition, Person, PersonExpedition from troggle.core.views.editor_helpers import HTMLarea diff --git a/core/models/caves.py b/core/models/caves.py index 0b3aa1d..bc2974e 100644 --- a/core/models/caves.py +++ b/core/models/caves.py @@ -41,8 +41,6 @@ todo=''' 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")) ''' @@ -428,152 +426,6 @@ class Entrance(TroggleModel): return "" -class LogbookEntry(TroggleModel): - """Single parsed entry from 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() - expeditionday = models.ForeignKey("ExpeditionDay", null=True,on_delete=models.SET_NULL)#MJG wants to KILL THIS (redundant information) - expedition = models.ForeignKey(Expedition,blank=True, null=True,on_delete=models.SET_NULL) # yes this is double- - title = models.CharField(max_length=200) - cave_slug = models.SlugField(max_length=50, blank=True, null=True) - 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) - time_underground = models.FloatField(null=True,help_text="In decimal hours") - - class Meta: - verbose_name_plural = "Logbook Entries" - # several PersonTrips point in to this object - ordering = ('-date',) - - def cave(self): # Why didn't he just make this a foreign key to Cave ? - c = CaveSlug.objects.get(slug=self.cave_slug, primary=True).cave - return c - - def isLogbookEntry(self): # Function used in templates - return True - - def get_absolute_url(self): - return urljoin(settings.URL_ROOT, reverse('logbookentry',kwargs={'date':self.date,'slug':self.slug})) - - def __str__(self): - return f'{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 DayIndex(self): - return list(self.expeditionday.logbookentry_set.all()).index(self) - -class QM(TroggleModel): - """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" - """ - cave = models.ForeignKey(Cave, related_name='QMs',blank=True, null=True,on_delete=models.SET_NULL ) - block = models.ForeignKey('SurvexBlock', null=True,on_delete=models.SET_NULL) # only for QMs from survex files - blockname=models.TextField(blank=True,null=True) # NB truncated copy of survexblock name with last char added - expoyear = models.CharField(max_length=4,blank=True, null=True) # could change to datetime if logbooks similarly chnaged - found_by = models.ForeignKey(LogbookEntry, related_name='QMs_found',blank=True, null=True,on_delete=models.SET_NULL ) - 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?! - number = models.IntegerField(help_text="this is the sequential number in the year, only unique for CSV imports", ) - GRADE_CHOICES=( - ('A', 'A: Large obvious lead'), - ('B', 'B: Average lead'), - ('C', 'C: Tight unpromising lead'), - ('D', 'D: Dig'), - ('X', 'X: Unclimbable aven') - ) # also seen "?" and "V" in imported data - see urls.py - grade = models.CharField(max_length=1, choices=GRADE_CHOICES) - location_description = models.TextField(blank=True) - nearest_station_description = models.CharField(max_length=400,blank=True, null=True) - nearest_station_name = models.CharField(max_length=200,blank=True, null=True) - nearest_station = models.ForeignKey(SurvexStation,blank=True, null=True,on_delete=models.SET_NULL) - area = models.CharField(max_length=100,blank=True, null=True) - completion_description = models.TextField(blank=True,null=True) - comment=models.TextField(blank=True,null=True) - - def __str__(self): - return f'{self.code()}' - - def code(self): - if self.cave: - cavestr = str(self.cave.slug())[5:] - else: - cavestr = "" - if self.expoyear: - expoyearstr = str(self.expoyear) - else: - expoyearstr = str(self.cave.slug())[5:9] - if self.blockname: - blocknamestr = "-" + str(self.blockname) - else: - blocknamestr = "" - return f'{cavestr}-{expoyearstr}-{self.number}{self.grade}{blocknamestr}' - - 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): - qmslug = f'{str(self.cave)}-{self.expoyear}-{self.blockname}{self.number}{self.grade}' - return qmslug - - def get_absolute_url(self): - # This reverse resolution stuff is pure magic. Just change the regex in urls.py and everything changes to suit. Whacky. - 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})) - - 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) - -class PersonTrip(TroggleModel): - """Single Person going on a trip, which may or may not be written up. - It could account for different T/U for people in same logbook entry. - """ - personexpedition = models.ForeignKey("PersonExpedition",null=True,on_delete=models.CASCADE) - time_underground = models.FloatField(help_text="In decimal hours") - logbook_entry = models.ForeignKey(LogbookEntry,on_delete=models.CASCADE) - is_logbook_entry_author = models.BooleanField(default=False) - - class Meta: - ordering = ('-personexpedition',) - #order_with_respect_to = 'personexpedition' - - 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 - - def __str__(self): - return f'{self.personexpedition} ({self.logbook_entry.date})' - - def GetCaveLookup(): """A very relaxed way of finding probably the right cave given almost any string which might serve to identify it diff --git a/core/models/logbooks.py b/core/models/logbooks.py new file mode 100644 index 0000000..da8d970 --- /dev/null +++ b/core/models/logbooks.py @@ -0,0 +1,181 @@ +import datetime +import json +import operator +import os +import re +import string +import subprocess +from collections import defaultdict +from datetime import datetime, timezone +from pathlib import Path +from urllib.parse import urljoin + +from django.conf import settings +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.core.files.storage import FileSystemStorage +from django.db import models +from django.db.models import Max, Min +from django.shortcuts import render +from django.template import Context, loader +from django.urls import reverse + +import settings +from troggle.core.models.caves import (Area, Cave, CaveAndEntrance, + Entrance) +from troggle.core.models.survex import SurvexStation +from troggle.core.models.troggle import (DataIssue, Expedition, Person, + TroggleModel) + +'''The model declarations LogBookEntry, PersonTrip, QM +''' + +todo=''' +- Rename PersonTrip as PersonLogEntry or similar +''' + +class LogbookEntry(TroggleModel): + """Single parsed entry from 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() + expeditionday = models.ForeignKey("ExpeditionDay", null=True,on_delete=models.SET_NULL)#MJG wants to KILL THIS (redundant information) + expedition = models.ForeignKey(Expedition,blank=True, null=True,on_delete=models.SET_NULL) # yes this is double- + title = models.CharField(max_length=200) + cave_slug = models.SlugField(max_length=50, blank=True, null=True) + 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) + time_underground = models.FloatField(null=True,help_text="In decimal hours") + + class Meta: + verbose_name_plural = "Logbook Entries" + # several PersonTrips point in to this object + ordering = ('-date',) + + def cave(self): # Why didn't he just make this a foreign key to Cave ? + c = CaveSlug.objects.get(slug=self.cave_slug, primary=True).cave + return c + + def isLogbookEntry(self): # Function used in templates + return True + + def get_absolute_url(self): + return urljoin(settings.URL_ROOT, reverse('logbookentry',kwargs={'date':self.date,'slug':self.slug})) + + def __str__(self): + return f'{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 DayIndex(self): + return list(self.expeditionday.logbookentry_set.all()).index(self) + +class PersonTrip(TroggleModel): + """Single Person going on a trip, which may or may not be written up. + It could account for different T/U for people in same logbook entry. + """ + personexpedition = models.ForeignKey("PersonExpedition",null=True,on_delete=models.CASCADE) + time_underground = models.FloatField(help_text="In decimal hours") + logbook_entry = models.ForeignKey(LogbookEntry,on_delete=models.CASCADE) + is_logbook_entry_author = models.BooleanField(default=False) + + class Meta: + ordering = ('-personexpedition',) + #order_with_respect_to = 'personexpedition' + + 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 + + def __str__(self): + return f'{self.personexpedition} ({self.logbook_entry.date})' + +class QM(TroggleModel): + """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" + """ + cave = models.ForeignKey(Cave, related_name='QMs',blank=True, null=True,on_delete=models.SET_NULL ) + block = models.ForeignKey('SurvexBlock', null=True,on_delete=models.SET_NULL) # only for QMs from survex files + blockname=models.TextField(blank=True,null=True) # NB truncated copy of survexblock name with last char added + expoyear = models.CharField(max_length=4,blank=True, null=True) # could change to datetime if logbooks similarly chnaged + found_by = models.ForeignKey(LogbookEntry, related_name='QMs_found',blank=True, null=True,on_delete=models.SET_NULL ) + 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?! + number = models.IntegerField(help_text="this is the sequential number in the year, only unique for CSV imports", ) + GRADE_CHOICES=( + ('A', 'A: Large obvious lead'), + ('B', 'B: Average lead'), + ('C', 'C: Tight unpromising lead'), + ('D', 'D: Dig'), + ('X', 'X: Unclimbable aven') + ) # also seen "?" and "V" in imported data - see urls.py + grade = models.CharField(max_length=1, choices=GRADE_CHOICES) + location_description = models.TextField(blank=True) + nearest_station_description = models.CharField(max_length=400,blank=True, null=True) + nearest_station_name = models.CharField(max_length=200,blank=True, null=True) + nearest_station = models.ForeignKey(SurvexStation,blank=True, null=True,on_delete=models.SET_NULL) + area = models.CharField(max_length=100,blank=True, null=True) + completion_description = models.TextField(blank=True,null=True) + comment=models.TextField(blank=True,null=True) + + def __str__(self): + return f'{self.code()}' + + def code(self): + if self.cave: + cavestr = str(self.cave.slug())[5:] + else: + cavestr = "" + if self.expoyear: + expoyearstr = str(self.expoyear) + else: + expoyearstr = str(self.cave.slug())[5:9] + if self.blockname: + blocknamestr = "-" + str(self.blockname) + else: + blocknamestr = "" + return f'{cavestr}-{expoyearstr}-{self.number}{self.grade}{blocknamestr}' + + 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): + qmslug = f'{str(self.cave)}-{self.expoyear}-{self.blockname}{self.number}{self.grade}' + return qmslug + + def get_absolute_url(self): + # This reverse resolution stuff is pure magic. Just change the regex in urls.py and everything changes to suit. Whacky. + 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})) + + 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) + diff --git a/core/views/caves.py b/core/views/caves.py index 0b291f3..b7b800a 100644 --- a/core/views/caves.py +++ b/core/views/caves.py @@ -17,9 +17,10 @@ import settings import troggle.settings as settings from troggle.core.forms import (CaveAndEntranceFormSet, CaveForm, EntranceForm, EntranceLetterForm) -from troggle.core.models.caves import (QM, Area, Cave, CaveAndEntrance, +from troggle.core.models.caves import (Area, Cave, CaveAndEntrance, CaveSlug, Entrance, EntranceSlug, GetCaveLookup, SurvexStation) +from troggle.core.models.logbooks import QM from troggle.core.models.troggle import DataIssue, Expedition from troggle.core.utils import write_and_commit, writetrogglefile from troggle.core.views import expo diff --git a/core/views/logbooks.py b/core/views/logbooks.py index ece3f98..6450753 100644 --- a/core/views/logbooks.py +++ b/core/views/logbooks.py @@ -14,7 +14,7 @@ from django.utils import timezone from django.views.generic.list import ListView import troggle.settings as settings -from troggle.core.models.caves import LogbookEntry, PersonTrip +from troggle.core.models.logbooks import LogbookEntry, PersonTrip from troggle.core.models.survex import SurvexBlock, Wallet from troggle.core.models.troggle import Expedition, Person, PersonExpedition from troggle.core.utils import TROG diff --git a/core/views/other.py b/core/views/other.py index 0e02180..70bdfa4 100644 --- a/core/views/other.py +++ b/core/views/other.py @@ -12,7 +12,8 @@ from django.shortcuts import render from django.template import Context, loader from django.urls import reverse -from troggle.core.models.caves import QM, Cave, LogbookEntry, PersonTrip +from troggle.core.models.caves import Cave +from troggle.core.models.logbooks import QM, LogbookEntry, PersonTrip from troggle.core.models.survex import DrawingFile # from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time* from troggle.core.models.troggle import Expedition, Person, PersonExpedition diff --git a/core/views/statistics.py b/core/views/statistics.py index 34512d8..79bb01e 100644 --- a/core/views/statistics.py +++ b/core/views/statistics.py @@ -13,7 +13,8 @@ from django.template.defaultfilters import slugify from django.utils import timezone import troggle.settings as settings -from troggle.core.models.caves import Cave, Entrance, LogbookEntry +from troggle.core.models.caves import Cave, Entrance +from troggle.core.models.logbooks import LogbookEntry from troggle.core.models.survex import SurvexBlock, SurvexStation from troggle.core.models.troggle import (DataIssue, Expedition, Person, PersonExpedition) diff --git a/core/views/survex.py b/core/views/survex.py index 0a4c7f1..2307298 100644 --- a/core/views/survex.py +++ b/core/views/survex.py @@ -14,7 +14,8 @@ from django.views.decorators.csrf import ensure_csrf_cookie import parsers.survex import troggle.settings as settings -from troggle.core.models.caves import Cave, LogbookEntry, PersonTrip +from troggle.core.models.caves import Cave +from troggle.core.models.logbooks import LogbookEntry, PersonTrip from troggle.core.models.survex import (SurvexBlock, SurvexDirectory, SurvexFile, SurvexPersonRole) from troggle.core.models.troggle import Expedition, Person, PersonExpedition diff --git a/core/views/uploads.py b/core/views/uploads.py index 5f40b30..cceafc9 100644 --- a/core/views/uploads.py +++ b/core/views/uploads.py @@ -21,7 +21,8 @@ from django.template import Context, loader from django.urls import reverse import settings -from troggle.core.models.caves import QM, Cave, LogbookEntry, PersonTrip +from troggle.core.models.caves import Cave +from troggle.core.models.logbooks import QM, LogbookEntry, PersonTrip from troggle.core.models.survex import (DrawingFile, SurvexBlock, SurvexFile, SurvexPersonRole, Wallet) # from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time* diff --git a/parsers/QMs.py b/parsers/QMs.py index 7356efa..7a553be 100644 --- a/parsers/QMs.py +++ b/parsers/QMs.py @@ -5,7 +5,8 @@ from pathlib import Path from django.conf import settings -from troggle.core.models.caves import QM, Cave +from troggle.core.models.caves import Cave +from troggle.core.models.logbooks import QM from troggle.core.models.troggle import DataIssue """Reads the CSV files containg QMs for a select few caves diff --git a/parsers/logbooks.py b/parsers/logbooks.py index 22cdf84..f045252 100644 --- a/parsers/logbooks.py +++ b/parsers/logbooks.py @@ -11,7 +11,8 @@ from django.conf import settings from django.template.defaultfilters import slugify from parsers.people import GetPersonExpeditionNameLookup, load_people_expos -from troggle.core.models.caves import GetCaveLookup, LogbookEntry, PersonTrip +from troggle.core.models.caves import GetCaveLookup +from troggle.core.models.logbooks import LogbookEntry, PersonTrip from troggle.core.models.troggle import DataIssue, Expedition from troggle.core.utils import get_process_memory diff --git a/parsers/survex.py b/parsers/survex.py index bbe61a7..43a2639 100644 --- a/parsers/survex.py +++ b/parsers/survex.py @@ -9,7 +9,8 @@ from pathlib import Path import troggle.settings as settings -from troggle.core.models.caves import QM, Cave, Entrance +from troggle.core.models.caves import Cave, Entrance +from troggle.core.models.logbooks import QM from troggle.core.models.survex import SurvexBlock, SurvexDirectory, SurvexFile, SurvexPersonRole, SurvexStation, Wallet from troggle.core.models.troggle import DataIssue, Expedition from troggle.core.utils import chaosmonkey, get_process_memory