diff --git a/README.txt b/README.txt index f71a9ff..c61e08d 100644 --- a/README.txt +++ b/README.txt @@ -123,6 +123,9 @@ copy this over the installed version of django on your machine: $ cd troggle $ sudo cp django-patch/html_parser.py /usr/local/lib/python3.8/dist-packages/django/utils/ + +or, if you have django installed by person arthe rthan by system: +$ sudo cp django-patch/html_parser.py /home/philip/.local/lib/python3.8/site-packages/django/utils/ or $ cp django-patch/html_parser.py ~/.local/lib/python3.8/site-packages/django/utils/ if you didn't use sudo when installing everything using pip. diff --git a/core/models_survex.py b/core/models_survex.py index f33e7b0..4efdb59 100644 --- a/core/models_survex.py +++ b/core/models_survex.py @@ -126,10 +126,10 @@ class SurvexBlock(models.Model): survexscansfolder = models.ForeignKey("SurvexScansFolder", null=True) #refscandir = models.CharField(max_length=100) - legsall = models.IntegerField(null=True) # summary data for this block - legssplay = models.IntegerField(null=True) # summary data for this block - legssurfc = models.IntegerField(null=True) # summary data for this block - totalleglength = models.FloatField(null=True) + legsall = models.IntegerField() # summary data for this block + legssplay = models.IntegerField() # summary data for this block + legssurfc = models.IntegerField() # summary data for this block + totalleglength = models.FloatField() class Meta: ordering = ('id',) diff --git a/models.py b/models.py new file mode 100644 index 0000000..68ffe7a --- /dev/null +++ b/models.py @@ -0,0 +1,226 @@ +import string +import os +import datetime +import logging +import re +from subprocess import call + +from urllib.parse import urljoin +from decimal import Decimal, getcontext +getcontext().prec=2 #use 2 significant figures for decimal calculations + +import settings + +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 +from django.core.urlresolvers import reverse +from django.template import Context, loader + +import troggle.core.models_survex + +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))) + + return res + +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 + +#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): + return 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 +# +class Expedition(TroggleModel): + year = models.CharField(max_length=20, unique=True) + name = models.CharField(max_length=100) + + def __str__(self): + return self.year + + class Meta: + ordering = ('-year',) + get_latest_by = 'year' + + def get_absolute_url(self): + return urljoin(settings.URL_ROOT, reverse('expedition', args=[self.year])) + + # 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 + +class ExpeditionDay(TroggleModel): + expedition = models.ForeignKey("Expedition") + date = models.DateField() + + class Meta: + ordering = ('date',) + + def GetPersonTrip(self, personexpedition): + personexpeditions = self.persontrip_set.filter(expeditionday=self) + return personexpeditions and personexpeditions[0] or None + +class Person(TroggleModel): + """single Person, can go on many years + """ + 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) + 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): + return urljoin(settings.URL_ROOT,reverse('person',kwargs={'first_name':self.first_name,'last_name':self.last_name})) + + class Meta: + verbose_name_plural = "People" + ordering = ('orderref',) # "Wookey" makes too complex for: ('last_name', 'first_name') + + def __str__(self): + if self.last_name: + return "%s %s" % (self.first_name, self.last_name) + return self.first_name + + + def notability(self): + notability = Decimal(0) + max_expo_val = 0 + + max_expo_year = Expedition.objects.all().aggregate(models.Max('year')) + max_expo_val = int(max_expo_year['year__max']) + 1 + + for personexpedition in self.personexpedition_set.all(): + if not personexpedition.is_guest: + print((personexpedition.expedition.year)) + notability += Decimal(1) / (max_expo_val - int(personexpedition.expedition.year)) + 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): + """Person's attendance to one Expo + """ + 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' + + def __str__(self): + 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): + return urljoin(settings.URL_ROOT, reverse('personexpedition',kwargs={'first_name':self.person.first_name,'last_name':self.person.last_name,'year':self.expedition.year})) + + 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=models.Max("expeditionday__date")) + return res["day_max"] + + diff --git a/models_survex.py b/models_survex.py new file mode 100644 index 0000000..aed8003 --- /dev/null +++ b/models_survex.py @@ -0,0 +1,239 @@ +import os +from urllib.parse import urljoin +import re + +from django.db import models +from django.conf import settings +from django.core.urlresolvers import reverse + + +########################################################### +# These will allow browsing and editing of the survex data +########################################################### +# Needs to add: +# Equates +# reloading + +class SurvexDirectory(models.Model): + path = models.CharField(max_length=200) + cave = models.ForeignKey('Cave', blank=True, null=True) + primarysurvexfile = models.ForeignKey('SurvexFile', related_name='primarysurvexfile', blank=True, null=True) + # could also include files in directory but not referenced + + class Meta: + ordering = ('id',) + + +class SurvexFile(models.Model): + path = models.CharField(max_length=200) + survexdirectory = models.ForeignKey("SurvexDirectory", blank=True, null=True) + cave = models.ForeignKey('Cave', blank=True, null=True) + + class Meta: + ordering = ('id',) + + def exists(self): + fname = os.path.join(settings.SURVEX_DATA, self.path + ".svx") + return os.path.isfile(fname) + + def OpenFile(self): + fname = os.path.join(settings.SURVEX_DATA, self.path + ".svx") + return open(fname) + + def SetDirectory(self): + dirpath = os.path.split(self.path)[0] + survexdirectorylist = SurvexDirectory.objects.filter(cave=self.cave, path=dirpath) + if survexdirectorylist: + self.survexdirectory = survexdirectorylist[0] + else: + survexdirectory = SurvexDirectory(path=dirpath, cave=self.cave, primarysurvexfile=self) + survexdirectory.save() + self.survexdirectory = survexdirectory + self.save() + + +class SurvexEquate(models.Model): + cave = models.ForeignKey('Cave', blank=True, null=True) + +class SurvexStationLookUpManager(models.Manager): + def lookup(self, name): + blocknames, sep, stationname = name.rpartition(".") + return self.get(block = SurvexBlock.objects.lookup(blocknames), + name__iexact = stationname) + +class SurvexStation(models.Model): + name = models.CharField(max_length=100) + block = models.ForeignKey('SurvexBlock') + equate = models.ForeignKey('SurvexEquate', blank=True, null=True) + objects = SurvexStationLookUpManager() + x = models.FloatField(blank=True, null=True) + y = models.FloatField(blank=True, null=True) + z = models.FloatField(blank=True, null=True) + + def path(self): + r = self.name + b = self.block + while True: + if b.name: + r = b.name + "." + r + if b.parent: + b = b.parent + else: + return r + +# class SurvexLeg(models.Model): + # block = models.ForeignKey('SurvexBlock') + # #title = models.ForeignKey('SurvexTitle') + # stationfrom = models.ForeignKey('SurvexStation', related_name='stationfrom') + # stationto = models.ForeignKey('SurvexStation', related_name='stationto') + # tape = models.FloatField() + # compass = models.FloatField() + # clino = models.FloatField() +class SurvexLeg(): + tape = 0.0 + compass = 0.0 + clino = 0.0 + +# +# Single SurvexBlock +# +class SurvexBlockLookUpManager(models.Manager): + def lookup(self, name): + if name == "": + blocknames = [] + else: + blocknames = name.split(".") + block = SurvexBlock.objects.get(parent=None, survexfile__path=settings.SURVEX_TOPNAME) + for blockname in blocknames: + block = SurvexBlock.objects.get(parent=block, name__iexact=blockname) + return block + +class SurvexBlock(models.Model): + objects = SurvexBlockLookUpManager() + name = models.CharField(max_length=100) + parent = models.ForeignKey('SurvexBlock', blank=True, null=True) +# text = models.TextField() + cave = models.ForeignKey('Cave', blank=True, null=True) + + date = models.DateField(blank=True, null=True) + expeditionday = models.ForeignKey("ExpeditionDay", null=True) + expedition = models.ForeignKey('Expedition', blank=True, null=True) + + survexfile = models.ForeignKey("SurvexFile", blank=True, null=True) +# begin_char = models.IntegerField() # code for where in the survex data files this block sits + survexpath = models.CharField(max_length=200) # the path for the survex stations + + survexscansfolder = models.ForeignKey("SurvexScansFolder", null=True) + #refscandir = models.CharField(max_length=100) + + legsall = models.IntegerField(null=True) # summary data for this block + legssplay = models.IntegerField(null=True) # summary data for this block + legssurfc = models.IntegerField(null=True) # summary data for this block + totalleglength = models.FloatField(null=True) + + class Meta: + ordering = ('id',) + + def isSurvexBlock(self): # Function used in templates + return True + + def __str__(self): + return self.name and str(self.name) or 'no name' + + def GetPersonroles(self): + res = [ ] + for personrole in self.personrole_set.order_by('personexpedition'): + if res and res[-1]['person'] == personrole.personexpedition.person: + res[-1]['roles'] += ", " + str(personrole.role) + else: + res.append({'person':personrole.personexpedition.person, 'expeditionyear':personrole.personexpedition.expedition.year, 'roles':str(personrole.role)}) + return res + + def MakeSurvexStation(self, name): + ssl = self.survexstation_set.filter(name=name) + if ssl: + assert len(ssl) == 1 + return ssl[0] + #print name + ss = SurvexStation(name=name, block=self) + #ss.save() + return ss + + def DayIndex(self): + return list(self.expeditionday.survexblock_set.all()).index(self) + +class SurvexTitle(models.Model): + survexblock = models.ForeignKey('SurvexBlock') + title = models.CharField(max_length=200) + cave = models.ForeignKey('Cave', blank=True, null=True) + +# +# member of a SurvexBlock +# +ROLE_CHOICES = ( + ('insts','Instruments'), + ('dog','Other'), + ('notes','Notes'), + ('pics','Pictures'), + ('tape','Tape measure'), + ('useless','Useless'), + ('helper','Helper'), + ('disto','Disto'), + ('consultant','Consultant'), + ) + +class SurvexPersonRole(models.Model): + survexblock = models.ForeignKey('SurvexBlock') + nrole = models.CharField(choices=ROLE_CHOICES, max_length=200, blank=True, null=True) + # increasing levels of precision + personname = models.CharField(max_length=100) + person = models.ForeignKey('Person', blank=True, null=True) + personexpedition = models.ForeignKey('PersonExpedition', blank=True, null=True) + persontrip = models.ForeignKey('PersonTrip', blank=True, null=True) + expeditionday = models.ForeignKey("ExpeditionDay", null=True) + + def __str__(self): + return str(self.person) + " - " + str(self.survexblock) + " - " + str(self.nrole) + +class SurvexScansFolder(models.Model): + fpath = models.CharField(max_length=200) + walletname = models.CharField(max_length=200) + + class Meta: + ordering = ('walletname',) + + def get_absolute_url(self): + return urljoin(settings.URL_ROOT, reverse('surveyscansfolder', kwargs={"path":re.sub("#", "%23", self.walletname)})) + + def __str__(self): + return str(self.walletname) + " (Survey Scans Folder)" + +class SurvexScanSingle(models.Model): + ffile = models.CharField(max_length=200) + name = models.CharField(max_length=200) + survexscansfolder = models.ForeignKey("SurvexScansFolder", null=True) + + class Meta: + ordering = ('name',) + + def get_absolute_url(self): + return urljoin(settings.URL_ROOT, reverse('surveyscansingle', kwargs={"path":re.sub("#", "%23", self.survexscansfolder.walletname), "file":self.name})) + + def __str__(self): + return "Survey Scan Image: " + str(self.name) + " in " + str(self.survexscansfolder) + +class TunnelFile(models.Model): + tunnelpath = models.CharField(max_length=200) + tunnelname = models.CharField(max_length=200) + bfontcolours = models.BooleanField(default=False) + survexscansfolders = models.ManyToManyField("SurvexScansFolder") + survexscans = models.ManyToManyField("SurvexScanSingle") + survexblocks = models.ManyToManyField("SurvexBlock") + tunnelcontains = models.ManyToManyField("TunnelFile") # case when its a frame type + filesize = models.IntegerField(default=0) + npaths = models.IntegerField(default=0) + survextitles = models.ManyToManyField("SurvexTitle") + + + class Meta: + ordering = ('tunnelpath',) \ No newline at end of file diff --git a/requirements-works1.7.4.txt b/requirements-works1.7.4.txt new file mode 100644 index 0000000..aa44263 --- /dev/null +++ b/requirements-works1.7.4.txt @@ -0,0 +1,109 @@ +appdirs==1.4.3 +apturl==0.5.2 +astroid==2.4.1 +attrs==19.3.0 +Automat==0.8.0 +blinker==1.4 +catfish==1.4.13 +certifi==2019.11.28 +chardet==3.0.4 +Click==7.0 +cloud-init==20.1 +colorama==0.4.3 +command-not-found==0.3 +configobj==5.0.6 +constantly==15.1.0 +coverage==5.1 +cryptography==2.8 +cupshelpers==1.0 +dbus-python==1.2.16 +defer==1.0.6 +distlib==0.3.0 +distro==1.4.0 +distro-info===0.23ubuntu1 +Django==1.7 +django-extensions==2.2.9 +django-registration==2.0 +django-tinymce==2.0.1 +docutils==0.16 +entrypoints==0.3 +filelock==3.0.12 +httplib2==0.14.0 +hyperlink==19.0.0 +idna==2.8 +importlib-metadata==1.5.0 +incremental==16.10.1 +isort==4.3.21 +Jinja2==2.10.1 +jsonpatch==1.22 +jsonpointer==2.0 +jsonschema==3.2.0 +keyring==18.0.1 +language-selector==0.1 +launchpadlib==1.10.13 +lazr.restfulclient==0.14.2 +lazr.uri==1.0.3 +lazy-object-proxy==1.4.3 +lightdm-gtk-greeter-settings==1.2.2 +macaroonbakery==1.3.1 +MarkupSafe==1.1.0 +mccabe==0.6.1 +menulibre==2.2.1 +more-itertools==4.2.0 +mugshot==0.4.2 +netifaces==0.10.4 +oauthlib==3.1.0 +olefile==0.46 +onboard==1.4.1 +pexpect==4.6.0 +Pillow==7.1.2 +protobuf==3.6.1 +psutil==5.5.1 +pyasn1==0.4.2 +pyasn1-modules==0.2.1 +pycairo==1.16.2 +pycups==1.9.73 +Pygments==2.3.1 +PyGObject==3.36.0 +PyHamcrest==1.9.0 +PyJWT==1.7.1 +pylint==2.5.2 +pymacaroons==0.13.0 +PyNaCl==1.3.0 +pyOpenSSL==19.0.0 +pyRFC3339==1.1 +pyrsistent==0.15.5 +pyserial==3.4 +python-apt==2.0.0 +python-dateutil==2.7.3 +python-debian===0.1.36ubuntu1 +pytz==2019.3 +pyxdg==0.26 +PyYAML==5.3.1 +reportlab==3.5.34 +requests==2.22.0 +requests-unixsocket==0.2.0 +roman==2.0.0 +rope==0.17.0 +SecretStorage==2.3.1 +service-identity==18.1.0 +sgt-launcher==0.2.5 +simplejson==3.16.0 +six==1.14.0 +ssh-import-id==5.10 +systemd-python==234 +toml==0.10.1 +Twisted==18.9.0 +ubuntu-advantage-tools==20.3 +ubuntu-drivers-common==0.0.0 +ufw==0.36 +unattended-upgrades==0.1 +Unidecode==1.1.1 +urllib3==1.25.8 +virtualenv==20.0.17 +wadllib==1.3.3 +wrapt==1.12.1 +xcffib==0.8.1 +xkit==0.0.0 +zipp==1.0.0 +zope.interface==4.7.1 diff --git a/requirements-works1.8.19.txt b/requirements-works1.8.19.txt new file mode 100644 index 0000000..4f291e5 --- /dev/null +++ b/requirements-works1.8.19.txt @@ -0,0 +1,7 @@ +Django==1.8.19 +django-extensions==2.2.9 +django-registration==2.0 +Pillow==7.1.0 +six==1.15.0 +sqlparse==0.3.1 +Unidecode==1.1.0 diff --git a/templates/base.html b/templates/base.html index 495705a..d6a6b5a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -45,6 +45,20 @@ Expo2020 | Django admin +
+ + + cucc server | + expo server | + + + tasks to do | + caves | + cavers | + all expeditions | + statistics | + import/export data +