summaryrefslogtreecommitdiffstats
path: root/core/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'core/models.py')
-rw-r--r--core/models.py538
1 files changed, 21 insertions, 517 deletions
diff --git a/core/models.py b/core/models.py
index 42219e7..142cfa6 100644
--- a/core/models.py
+++ b/core/models.py
@@ -3,29 +3,25 @@ import os
import datetime
import logging
import re
-import subprocess
+from subprocess import call
-from urllib.request import *
-from urllib.parse import *
-from urllib.error import *
+from urllib.parse import urljoin
from decimal import Decimal, getcontext
getcontext().prec=2 #use 2 significant figures for decimal calculations
import settings
-from django.forms import ModelForm
from django.db import models
from django.contrib import admin
-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.db.models import Min, Max
from django.conf import settings
from django.core.urlresolvers import reverse
from django.template import Context, loader
-from troggle.core.models_survex import *
-
+import troggle.core.models_survex
+import troggle.core.models_caves as models_caves
def get_related_by_wikilinks(wiki_text):
found=re.findall(settings.QM_PATTERN,wiki_text)
@@ -33,7 +29,7 @@ def get_related_by_wikilinks(wiki_text):
for wikilink in found:
qmdict={'urlroot':settings.URL_ROOT,'cave':wikilink[2],'year':wikilink[1],'number':wikilink[3]}
try:
- cave_slugs = CaveSlug.objects.filter(cave__kataster_number = qmdict['cave'])
+ 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'])
@@ -73,10 +69,20 @@ class TroggleImageModel(models.Model):
def get_admin_url(self):
return urllib.parse.urljoin(settings.URL_ROOT, "/admin/core/" + self.object_name().lower() + "/" + str(self.pk))
-
class Meta:
abstract = True
+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)
+
#
# single Expedition, usually seen by year
#
@@ -231,6 +237,7 @@ class PersonExpedition(TroggleModel):
res = self.persontrip_set.all().aggregate(day_max=Max("expeditionday__date"))
return res["day_max"]
+
class LogbookEntry(TroggleModel):
"""Single parsed entry from Logbook
"""
@@ -258,7 +265,7 @@ class LogbookEntry(TroggleModel):
def __getattribute__(self, item):
if item == "cave":
#Allow a logbookentries cave to be directly accessed despite not having a proper foreignkey
- return CaveSlug.objects.get(slug = self.cave_slug).cave
+ return models_caves.CaveSlug.objects.get(slug = self.cave_slug).cave
# parse error in python3.8
# https://stackoverflow.com/questions/41343263/provide-classcell-example-for-python-3-6-metaclass
#https://github.com/django/django/pull/7653
@@ -269,7 +276,7 @@ class LogbookEntry(TroggleModel):
def __init__(self, *args, **kwargs):
if "cave" in list(kwargs.keys()):
if kwargs["cave"] is not None:
- kwargs["cave_slug"] = CaveSlug.objects.get(cave=kwargs["cave"], primary=True).slug
+ kwargs["cave_slug"] = models_caves.CaveSlug.objects.get(cave=kwargs["cave"], primary=True).slug
kwargs.pop("cave")
# parse error in python3.8
return TroggleModel.__init__(self, *args, **kwargs) # seems OK in 3.5 & 3.8! failure later elsewhere with 3.8
@@ -309,6 +316,7 @@ class LogbookEntry(TroggleModel):
def DayIndex(self):
return list(self.expeditionday.logbookentry_set.all()).index(self)
+
#
# 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)
#
@@ -346,507 +354,3 @@ class PersonTrip(TroggleModel):
def __str__(self):
return "%s (%s)" % (self.personexpedition, self.logbook_entry.date)
-
-##########################################
-# move following classes into models_cave
-##########################################
-
-class Area(TroggleModel):
- short_name = models.CharField(max_length=100)
- name = models.CharField(max_length=200, blank=True, null=True)
- description = models.TextField(blank=True,null=True)
- parent = models.ForeignKey('Area', blank=True, null=True)
- def __str__(self):
- if self.parent:
- return str(self.parent) + " - " + str(self.short_name)
- else:
- return str(self.short_name)
- def kat_area(self):
- if self.short_name in ["1623", "1626"]:
- return self.short_name
- elif self.parent:
- return self.parent.kat_area()
-
-class CaveAndEntrance(models.Model):
- cave = models.ForeignKey('Cave')
- entrance = models.ForeignKey('Entrance')
- entrance_letter = models.CharField(max_length=20,blank=True,null=True)
- def __str__(self):
- return str(self.cave) + str(self.entrance_letter)
-
-class CaveSlug(models.Model):
- cave = models.ForeignKey('Cave')
- 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)
- area = models.ManyToManyField(Area, blank=True, null=True)
- kataster_code = models.CharField(max_length=20,blank=True,null=True)
- 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)
- 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)
- 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 + '/'
- return urllib.parse.urljoin(settings.URL_ROOT, reverse('cave',kwargs={'cave_id':href,}))
-
- def __str__(self, sep = ": "):
- return str("slug:"+self.slug())
-
- def get_QMs(self):
- return QM.objects.filter(found_by__cave_slug=self.caveslug_set.all())
-
- 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=self).order_by('-number')[0]
- except IndexError:
- return 1
- return res.number+1
-
- 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):
- rs.append(e.entrance_letter)
- rs.sort()
- prevR = None
- 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 += "&ndash;" + prevR
- else:
- prevR = r
- n = 0
- res += r
- if n == 0:
- res += ", " + prevR
- else:
- res += "&ndash;" + prevR
- return res
-
- def writeDataFile(self):
- try:
- f = open(os.path.join(settings.CAVEDESCRIPTIONS, self.filename), "wb")
- except:
- subprocess.call(settings.FIX_PERMISSIONS)
- f = open(os.path.join(settings.CAVEDESCRIPTIONS, self.filename), "wb")
- t = loader.get_template('dataformat/cave.xml')
- c = Context({'cave': self})
- u = t.render(c)
- u8 = u.encode("utf-8")
- f.write(u8)
- f.close()
-
- def getArea(self):
- areas = self.area.all()
- lowestareas = list(areas)
- for area in areas:
- if area.parent in areas:
- try:
- lowestareas.remove(area.parent)
- except:
- pass
- return lowestareas[0]
-
-def getCaveByReference(reference):
- areaname, code = reference.split("-", 1)
- #print(areaname, code)
- area = Area.objects.get(short_name = areaname)
- #print(area)
- foundCaves = list(Cave.objects.filter(area = area, kataster_number = code).all()) + list(Cave.objects.filter(area = area, unofficial_number = code).all())
- print((list(foundCaves)))
- if len(foundCaves) == 1:
- return foundCaves[0]
- else:
- return False
-
-class OtherCaveName(TroggleModel):
- name = models.CharField(max_length=160)
- cave = models.ForeignKey(Cave)
- def __str__(self):
- return str(self.name)
-
-class EntranceSlug(models.Model):
- entrance = models.ForeignKey('Entrance')
- 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)
- 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)
- url = models.CharField(max_length=200,blank=True,null=True)
- filename = models.CharField(max_length=200)
- cached_primary_slug = models.CharField(max_length=200,blank=True,null=True)
-
- def __str__(self):
- return str(self.slug())
-
- def exact_location(self):
- return SurvexStation.objects.lookup(self.exact_station)
- def other_location(self):
- return SurvexStation.objects.lookup(self.other_station)
-
-
- def find_location(self):
- r = {'': 'To be entered ',
- '?': 'To be confirmed:',
- 'S': '',
- 'L': 'Lost:',
- 'R': 'Refindable:'}[self.findability]
- if self.tag_station:
- try:
- s = SurvexStation.objects.lookup(self.tag_station)
- return r + "%0.0fE %0.0fN %0.0fAlt" % (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.0fE %0.0fN %0.0fAlt" % (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.0fE %0.0fN %0.0fAlt %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]
- def findability_val(self):
- for f in self.FINDABLE_CHOICES:
- if f[0] == self.findability:
- return f[1]
-
- def tag(self):
- return SurvexStation.objects.lookup(self.tag_station)
-
- def needs_surface_work(self):
- return self.findability != "S" or not self.has_photo or self.marking != "T"
-
- def get_absolute_url(self):
-
- 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
-
- 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
-
- def writeDataFile(self):
- try:
- f = open(os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename), "w")
- except:
- subprocess.call(settings.FIX_PERMISSIONS)
- f = open(os.path.join(settings.ENTRANCEDESCRIPTIONS, self.filename), "w")
- t = loader.get_template('dataformat/entrance.xml')
- c = Context({'entrance': self})
- u = t.render(c)
- u8 = u.encode("utf-8")
- f.write(u8)
- f.close()
-
-class CaveDescription(TroggleModel):
- short_name = models.CharField(max_length=50, unique = True)
- long_name = models.CharField(max_length=200, blank=True, null=True)
- description = models.TextField(blank=True,null=True)
- linked_subcaves = models.ManyToManyField("NewSubCave", blank=True,null=True)
- linked_entrances = models.ManyToManyField("Entrance", blank=True,null=True)
- linked_qms = models.ManyToManyField("QM", blank=True,null=True)
-
- def __str__(self):
- if self.long_name:
- return str(self.long_name)
- else:
- return str(self.short_name)
-
- def get_absolute_url(self):
- return urllib.parse.urljoin(settings.URL_ROOT, reverse('cavedescription', args=(self.short_name,)))
-
- def save(self):
- """
- Overridden save method which stores wikilinks in text as links in database.
- """
- TroggleModel.save()
- #super(CaveDescription, self).save() # fails in python 3.8, OK in python 3.5
- qm_list=get_related_by_wikilinks(self.description)
- for qm in qm_list:
- self.linked_qms.add(qm)
- TroggleModel.save()
- #super(CaveDescription, self).save() # fails in python 3.8, OK in python 3.5
-
-class NewSubCave(TroggleModel):
- name = models.CharField(max_length=200, unique = True)
- def __str__(self):
- return str(self.name)
-
-class QM(TroggleModel):
- #based on qm.csv in trunk/expoweb/1623/204 which has the fields:
- #"Number","Grade","Area","Description","Page reference","Nearest station","Completion description","Comment"
- found_by = models.ForeignKey(LogbookEntry, related_name='QMs_found',blank=True, null=True )
- ticked_off_by = models.ForeignKey(LogbookEntry, related_name='QMs_ticked_off',null=True,blank=True)
- #cave = models.ForeignKey(Cave)
- #expedition = models.ForeignKey(Expedition)
-
- number = models.IntegerField(help_text="this is the sequential number in the year", )
- GRADE_CHOICES=(
- ('A', 'A: Large obvious lead'),
- ('B', 'B: Average lead'),
- ('C', 'C: Tight unpromising lead'),
- ('D', 'D: Dig'),
- ('X', 'X: Unclimbable aven')
- )
- grade = models.CharField(max_length=1, choices=GRADE_CHOICES)
- location_description = models.TextField(blank=True)
- nearest_station_description = models.CharField(max_length=400,null=True,blank=True)
- nearest_station_name = models.CharField(max_length=200,blank=True,null=True)
- nearest_station = models.ForeignKey(SurvexStation,null=True,blank=True)
- 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 "%s %s" % (self.code(), self.grade)
-
- def code(self):
- return "%s-%s-%s" % (str(self.found_by.cave)[6:], self.found_by.date.year, self.number)
-
- def get_absolute_url(self):
- #return settings.URL_ROOT + '/cave/' + self.found_by.cave.kataster_number + '/' + str(self.found_by.date.year) + '-' + '%02d' %self.number
- return urllib.parse.urljoin(settings.URL_ROOT, reverse('qm',kwargs={'cave_id':self.found_by.cave.kataster_number,'year':self.found_by.date.year,'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)
-
- def wiki_link(self):
- return "%s%s%s" % ('[[QM:',self.code(),']]')
-
-scansFileStorage = FileSystemStorage(location=settings.SURVEY_SCANS, base_url=settings.SURVEYS_URL)
-def get_scan_path(instance, filename):
- year=instance.survey.expedition.year
- #print("WN: ", type(instance.survey.wallet_number), instance.survey.wallet_number, instance.survey.wallet_letter)
- number=str(instance.survey.wallet_number)
- if str(instance.survey.wallet_letter) != "None":
- number=str(instance.survey.wallet_letter) + number #two strings formatting because convention is 2009#01 or 2009#X01
- return os.path.join('./',year,year+r'#'+number,str(instance.contents)+str(instance.number_in_wallet)+r'.jpg')
-
-class ScannedImage(TroggleImageModel):
- file = models.ImageField(storage=scansFileStorage, upload_to=get_scan_path)
- scanned_by = models.ForeignKey(Person,blank=True, null=True)
- scanned_on = models.DateField(null=True)
- survey = models.ForeignKey('Survey')
- contents = models.CharField(max_length=20,choices=(('notes','notes'),('plan','plan_sketch'),('elevation','elevation_sketch')))
- number_in_wallet = models.IntegerField(null=True)
- lon_utm = models.FloatField(blank=True,null=True)
- lat_utm = models.FloatField(blank=True,null=True)
-
- #content_type = models.ForeignKey(ContentType)
- #object_id = models.PositiveIntegerField()
- #location = generic.GenericForeignKey('content_type', 'object_id')
-
- #This is an ugly hack to deal with the #s in our survey scan paths. The correct thing is to write a custom file storage backend which calls urlencode on the name for making file.url but not file.path.
- def correctURL(self):
- return string.replace(self.file.url,r'#',r'%23')
-
- def __str__(self):
- return get_scan_path(self,'')
-
-class Survey(TroggleModel):
- expedition = models.ForeignKey('Expedition') #REDUNDANT (logbook_entry)
- wallet_number = models.IntegerField(blank=True,null=True)
- wallet_letter = models.CharField(max_length=1,blank=True,null=True)
- comments = models.TextField(blank=True,null=True)
- location = models.CharField(max_length=400,blank=True,null=True) #REDUNDANT
- subcave = models.ForeignKey('NewSubCave', blank=True, null=True)
- #notes_scan = models.ForeignKey('ScannedImage',related_name='notes_scan',blank=True, null=True) #Replaced by contents field of ScannedImage model
- survex_block = models.OneToOneField('SurvexBlock',blank=True, null=True)
- logbook_entry = models.ForeignKey('LogbookEntry')
- centreline_printed_on = models.DateField(blank=True, null=True)
- centreline_printed_by = models.ForeignKey('Person',related_name='centreline_printed_by',blank=True,null=True)
- #sketch_scan = models.ForeignKey(ScannedImage,blank=True, null=True) #Replaced by contents field of ScannedImage model
- tunnel_file = models.FileField(upload_to='surveyXMLfiles',blank=True, null=True)
- tunnel_main_sketch = models.ForeignKey('Survey',blank=True,null=True)
- integrated_into_main_sketch_on = models.DateField(blank=True,null=True)
- integrated_into_main_sketch_by = models.ForeignKey('Person' ,related_name='integrated_into_main_sketch_by', blank=True,null=True)
- rendered_image = models.ImageField(upload_to='renderedSurveys',blank=True,null=True)
- def __str__(self):
- return self.expedition.year+"#"+"%02d" % int(self.wallet_number)
-
- def notes(self):
- return self.scannedimage_set.filter(contents='notes')
-
- def plans(self):
- return self.scannedimage_set.filter(contents='plan')
-
- def elevations(self):
- return self.scannedimage_set.filter(contents='elevation')
-
-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) \ No newline at end of file