From b001df1f5319dd8627faba3cd56c412e8584fc7d Mon Sep 17 00:00:00 2001
From: Martin Green
Date: Sun, 1 May 2011 19:32:41 +0100
Subject: [PATCH] edit logbooks, new logbook format, increased database
normalisation
---
core/admin.py | 5 +-
core/forms.py | 44 ++++++++++-
core/models.py | 59 +++++++++++---
core/models_survex.py | 5 +-
core/views_logbooks.py | 96 ++++++++++++++++++++--
databaseReset.py | 75 ++++++++++++++++--
parsers/logbooks.py | 105 ++++++++++++++++++++++++-
templates/dataformat/logbookentry.html | 25 ++++++
templates/expedition.html | 24 +++---
templates/logbookentry.html | 6 +-
templates/newlogbookentry.html | 82 +++++++++++++++++++
urls.py | 5 ++
12 files changed, 486 insertions(+), 45 deletions(-)
create mode 100644 templates/dataformat/logbookentry.html
create mode 100644 templates/newlogbookentry.html
diff --git a/core/admin.py b/core/admin.py
index f8fe9fe..8dfa7ca 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -50,14 +50,13 @@ class PhotoInline(admin.TabularInline):
class PersonTripInline(admin.TabularInline):
model = PersonTrip
- exclude = ['persontrip_next','Delete']
raw_id_fields = ('personexpedition',)
extra = 1
#class LogbookEntryAdmin(VersionAdmin):
class LogbookEntryAdmin(TroggleModelAdmin):
prepopulated_fields = {'slug':("title",)}
- raw_id_fields = ('cave','author')
+ raw_id_fields = ('cave',)
search_fields = ('title','expedition__year')
date_heirarchy = ('date')
inlines = (PersonTripInline, PhotoInline, QMsFoundInline)
@@ -140,4 +139,4 @@ def export_as_xml(modeladmin, request, queryset):
return response
#admin.site.add_action(export_as_xml)
-#admin.site.add_action(export_as_json)
\ No newline at end of file
+#admin.site.add_action(export_as_json)
diff --git a/core/forms.py b/core/forms.py
index 929c4e9..9a54b7b 100644
--- a/core/forms.py
+++ b/core/forms.py
@@ -1,10 +1,11 @@
from django.forms import ModelForm
-from models import Cave, Person, LogbookEntry, QM
+from models import Cave, Person, PersonExpedition, LogbookEntry, QM
import django.forms as forms
from django.forms.formsets import formset_factory
from django.contrib.admin.widgets import AdminDateWidget
import string
from datetime import date
+from tinymce.widgets import TinyMCE
class CaveForm(ModelForm):
class Meta:
@@ -45,4 +46,43 @@ class LogbookEntryForm(ModelForm):
def __init__(self, *args, **kwargs):
super(LogbookEntryForm, self).__init__(*args, **kwargs)
- self.fields['text'].help_text=self.wikiLinkHints()
\ No newline at end of file
+ self.fields['text'].help_text=self.wikiLinkHints()
+
+def getTripForm(expedition):
+
+ class TripForm(forms.Form):
+ date = forms.DateField()
+ title = forms.CharField(max_length=200)
+ caves = [cave.reference() for cave in Cave.objects.all()]
+ caves.sort()
+ caves = ["-----"] + caves
+ cave = forms.ChoiceField([(c, c) for c in caves], required=False)
+ location = forms.CharField(max_length=200, required=False)
+ caveOrLocation = forms.ChoiceField([("cave", "Cave"), ("location", "Location")], widget = forms.widgets.RadioSelect())
+ html = forms.CharField(widget=TinyMCE(attrs={'cols': 80, 'rows': 30}))
+
+ def clean(self):
+ print dir(self)
+ if self.cleaned_data.get("caveOrLocation") == "cave" and not self.cleaned_data.get("cave"):
+ self._errors["cave"] = self.error_class(["This field is required"])
+ if self.cleaned_data.get("caveOrLocation") == "location" and not self.cleaned_data.get("location"):
+ self._errors["location"] = self.error_class(["This field is required"])
+ return self.cleaned_data
+
+ class PersonTripForm(forms.Form):
+ def get_name(pe):
+ if pe.nickname:
+ return pe.nickname
+ else:
+ return pe.person.first_name
+ names = [get_name(pe) for pe in PersonExpedition.objects.filter(expedition = expedition)]
+ names.sort()
+ names = ["-----"] + names
+ name = forms.ChoiceField([(n, n) for n in names])
+ TU = forms.FloatField(required=False)
+ author = forms.BooleanField(required=False)
+
+ PersonTripFormSet = formset_factory(PersonTripForm, extra=1)
+
+ return PersonTripFormSet, TripForm
+
diff --git a/core/models.py b/core/models.py
index 432eca4..73063b2 100644
--- a/core/models.py
+++ b/core/models.py
@@ -232,23 +232,27 @@ class PersonExpedition(TroggleModel):
# Single parsed entry from Logbook
#
class LogbookEntry(TroggleModel):
- date = models.DateField()
- expeditionday = models.ForeignKey("ExpeditionDay", null=True)
+ date = models.DateField()#MJG wants to turn this into a datetime such that multiple Logbook entries on the same day can be ordered.
+ expeditionday = models.ForeignKey("ExpeditionDay", null=True)#MJG wants to KILL THIS (redundant information)
expedition = models.ForeignKey(Expedition,blank=True,null=True) # yes this is double-
- author = models.ForeignKey(PersonExpedition,blank=True,null=True) # the person who writes it up doesn't have to have been on the trip.
+ #author = models.ForeignKey(PersonExpedition,blank=True,null=True) # the person who writes it up doesn't have to have been on the trip.
# Re: the above- so this field should be "typist" or something, not "author". - AC 15 jun 09
- title = models.CharField(max_length=200)
+ #MJG wants to KILL THIS, as it is typically redundant with PersonTrip.is_logbook_entry_author, in the rare it was not redundanty and of actually interest it could be added to the text.
+ title = models.CharField(max_length=settings.MAX_LOGBOOK_ENTRY_TITLE_LENGTH)
cave = models.ForeignKey('Cave',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)
+ filename= models.CharField(max_length=200,null=True)
class Meta:
- verbose_name_plural = "Logbook Entries"
+ verbose_name_plural = "Logbook Entries"
# several PersonTrips point in to this object
- class Meta:
ordering = ('-date',)
+ def isLogbookEntry(self): # Function used in templates
+ return True
+
def get_absolute_url(self):
return urlparse.urljoin(settings.URL_ROOT, reverse('logbookentry',kwargs={'date':self.date,'slug':self.slug}))
@@ -282,22 +286,36 @@ class LogbookEntry(TroggleModel):
class PersonTrip(TroggleModel):
personexpedition = models.ForeignKey("PersonExpedition",null=True)
- expeditionday = models.ForeignKey("ExpeditionDay")
- date = models.DateField()
+ #expeditionday = models.ForeignKey("ExpeditionDay")#MJG wants to KILL THIS (redundant information)
+ #date = models.DateField() #MJG wants to KILL THIS (redundant information)
time_underground = models.FloatField(help_text="In decimal hours")
logbook_entry = models.ForeignKey(LogbookEntry)
is_logbook_entry_author = models.BooleanField()
# sequencing by person (difficult to solve locally)
- persontrip_next = models.ForeignKey('PersonTrip', related_name='pnext', blank=True,null=True)
- persontrip_prev = models.ForeignKey('PersonTrip', related_name='pprev', blank=True,null=True)
+ #persontrip_next = models.ForeignKey('PersonTrip', related_name='pnext', blank=True,null=True)#MJG wants to KILL THIS (and use funstion persontrip_next_auto)
+ #persontrip_prev = models.ForeignKey('PersonTrip', related_name='pprev', blank=True,null=True)#MJG wants to KILL THIS(and use funstion persontrip_prev_auto)
+ 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 __unicode__(self):
- return "%s (%s)" % (self.personexpedition, self.date)
+ return "%s (%s)" % (self.personexpedition, self.logbook_entry.date)
@@ -350,7 +368,18 @@ class Cave(TroggleModel):
survex_file = models.CharField(max_length=100,blank=True,null=True)
description_file = models.CharField(max_length=200,blank=True,null=True)
+ #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)
+
+ 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:
@@ -421,6 +450,14 @@ class Cave(TroggleModel):
res += "–" + prevR
return res
+def getCaveByReference(reference):
+ print reference
+ areaname, code = reference.split("-", 1)
+ area = Area.objects.get(short_name = areaname)
+ foundCaves = list(Cave.objects.filter(area = area, kataster_number = code).all()) + list(Cave.objects.filter(area = area, unofficial_number = code).all())
+ assert len(foundCaves) == 1
+ return foundCaves[0]
+
class OtherCaveName(TroggleModel):
name = models.CharField(max_length=160)
cave = models.ForeignKey(Cave)
diff --git a/core/models_survex.py b/core/models_survex.py
index 7b652b7..ed21658 100644
--- a/core/models_survex.py
+++ b/core/models_survex.py
@@ -92,6 +92,9 @@ class SurvexBlock(models.Model):
class Meta:
ordering = ('id',)
+ def isSurvexBlock(self): # Function used in templates
+ return True
+
def __unicode__(self):
return self.name and unicode(self.name) or 'no name'
@@ -188,4 +191,4 @@ class TunnelFile(models.Model):
class Meta:
ordering = ('tunnelpath',)
-
\ No newline at end of file
+
diff --git a/core/views_logbooks.py b/core/views_logbooks.py
index 1cdffff..cb7de3a 100644
--- a/core/views_logbooks.py
+++ b/core/views_logbooks.py
@@ -1,14 +1,18 @@
from django.shortcuts import render_to_response
-from troggle.core.models import Expedition, Person, PersonExpedition, PersonTrip, LogbookEntry
+from troggle.core.models import Expedition, Person, PersonExpedition, PersonTrip, LogbookEntry, SurvexBlock
import troggle.core.models as models
import troggle.settings as settings
import django.db.models
from troggle.parsers.logbooks import LoadLogbookForExpedition
from troggle.parsers.people import GetPersonExpeditionNameLookup
-from troggle.core.forms import PersonForm
+from troggle.core.forms import PersonForm, getTripForm
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, HttpResponse
+from django.template import Context, loader
from utils import render_with_context
+import os.path
+import troggle.parsers.logbooks as logbookparsers
+from django.template.defaultfilters import slugify
# Django uses Context, not RequestContext when you call render_to_response. We always want to use RequestContext, so that django adds the context from settings.TEMPLATE_CONTEXT_PROCESSORS. This way we automatically get necessary settings variables passed to each template. So we use a custom method, render_response instead of render_to_response. Hopefully future Django releases will make this unnecessary.
@@ -47,18 +51,23 @@ def expedition(request, expeditionname):
expedition = Expedition.objects.get(year=int(expeditionname))
expeditions = Expedition.objects.all()
personexpeditiondays = [ ]
+ dateditems = list(expedition.logbookentry_set.all()) + list(expedition.survexblock_set.all())
+ dates = list(set([item.date for item in dateditems]))
+ dates.sort()
for personexpedition in expedition.personexpedition_set.all():
prow = [ ]
- for expeditionday in expedition.expeditionday_set.all():
- pcell = { "persontrips":expeditionday.persontrip_set.filter(personexpedition=personexpedition) }
- pcell["survexblocks"] = set([survexpersonrole.survexblock for survexpersonrole in expeditionday.survexpersonrole_set.filter(personexpedition=personexpedition)])
+ for date in dates:
+ pcell = { "persontrips": PersonTrip.objects.filter(personexpedition=personexpedition,
+ logbook_entry__date=date) }
+ pcell["survexblocks"] = set(SurvexBlock.objects.filter(survexpersonrole__personexpedition=personexpedition,
+ date = date))
prow.append(pcell)
personexpeditiondays.append({"personexpedition":personexpedition, "personrow":prow})
message = ""
if "reload" in request.GET:
message = LoadLogbookForExpedition(expedition)
- return render_with_context(request,'expedition.html', {'expedition': expedition, 'expeditions':expeditions, 'personexpeditiondays':personexpeditiondays, 'message':message, 'settings':settings })
+ return render_with_context(request,'expedition.html', {'expedition': expedition, 'expeditions':expeditions, 'personexpeditiondays':personexpeditiondays, 'message':message, 'settings':settings, 'dateditems': dateditems })
def get_absolute_url(self):
return ('expedition', (expedition.year))
@@ -154,3 +163,78 @@ def experimental(request):
totalsurvexlength = sum([survexleg.tape for survexleg in survexlegs])
return render_with_context(request, 'experimental.html', { "nsurvexlegs":len(survexlegs), "totalsurvexlength":totalsurvexlength, "legsbyexpo":legsbyexpo })
+def newLogbookEntry(request, expeditionyear, pdate = None, pslug = None):
+ expedition = Expedition.objects.get(year=expeditionyear)
+ PersonTripFormSet, TripForm = getTripForm(expedition)
+ if pslug and pdate:
+ previousdate = datetime.date(*[int(x) for x in pdate.split("-")])
+ previouslbe = LogbookEntry.objects.get(slug = pslug, date = previousdate, expedition__year = year)
+ assert previouslbe.filename
+ if request.method == 'POST': # If the form has been submitted...
+ tripForm = TripForm(request.POST) # A form bound to the POST data
+ personTripFormSet = PersonTripFormSet(request.POST)
+ dateStr = tripForm.cleaned_data["date"].strftime("%Y-%m-%d")
+ directory = os.path.join(settings.EXPOWEB,
+ "years",
+ expedition.year,
+ "autologbook")
+ filename = os.path.join(directory,
+ dateStr + "." + slugify(tripForm.cleaned_data["title"])[:50] + ".html")
+ if tripForm.is_valid() and personTripFormSet.is_valid(): # All validation rules pass
+ if not os.path.isdir(directory):
+ os.mkdir(directory)
+ if pslug and pdate:
+ delLogbookEntry(previouslbe)
+ f = open(filename, "w")
+ template = loader.get_template('dataformat/logbookentry.html')
+ context = Context({'trip': tripForm.cleaned_data,
+ 'persons': personTripFormSet.cleaned_data,
+ 'date': dateStr,
+ 'expeditionyear': expeditionyear})
+ f.write(template.render(context))
+ f.close()
+ print logbookparsers.parseAutoLogBookEntry(filename)
+ return HttpResponseRedirect(reverse('expedition', args=[expedition.year])) # Redirect after POST
+ else:
+ if slug and date:
+ if lbe.cave:
+ tripForm = TripForm(date = previousdate,
+ title = previouslbe.title,
+ cave = previouslbe.cave.reference(),
+ location = None,
+ caveOrLocation = "cave",
+ html = previouslbe.text)
+ else:
+ tripForm = TripForm(date = previousdate,
+ title = previouslbe.title,
+ cave = None,
+ location = previouslbe.location,
+ caveOrLocation = "location",
+ html = previouslbe.text)
+ personTripFormSet = PersonTripFormSet(initial=[{"name": py.personexpedition.name(),
+ "TU": py.time_underground,
+ "author": py.is_logbook_entry_author}
+ for py in previouslbe.persontrip_set.all()])
+ else:
+ tripForm = TripForm() # An unbound form
+ personTripFormSet = PersonTripFormSet()
+
+ return render_with_context(request, 'newlogbookentry.html', {
+ 'tripForm': tripForm,
+ 'personTripFormSet': personTripFormSet,
+
+ })
+
+def deleteLogbookEntry(request, expeditionyear, date = None, slug = None):
+ expedition = Expedition.objects.get(year=expeditionyear)
+ previousdate = datetime.date(*[int(x) for x in pdate.split("-")])
+ previouslbe = LogbookEntry.objects.get(slug = pslug, date = previousdate, expedition__year = year)
+ delLogbookEntry(previouslbe)
+ return HttpResponseRedirect(reverse('expedition', args=[expedition.year])) # Redirect after POST
+
+def delLogbookEntry(lbe):
+ for pt in lbe.persontrip_set.all():
+ pt.delete()
+ lbe.delete()
+ os.delete(lbe.filename)
+
diff --git a/databaseReset.py b/databaseReset.py
index bc953c9..cdd7dcf 100644
--- a/databaseReset.py
+++ b/databaseReset.py
@@ -11,11 +11,17 @@ from django.http import HttpResponse
def reload_db():
- cursor = connection.cursor()
- cursor.execute("drop database %s" % settings.DATABASE_NAME)
- cursor.execute("create database %s" % settings.DATABASE_NAME)
- cursor.execute("ALTER DATABASE %s CHARACTER SET=utf8" % settings.DATABASE_NAME)
- cursor.execute("USE %s" % settings.DATABASE_NAME)
+ if settings.DATABASE_ENGINE == 'sqlite3':
+ try:
+ os.remove(settings.DATABASE_NAME)
+ except OSError:
+ pass
+ else:
+ cursor = connection.cursor()
+ cursor.execute("DROP DATABASE %s" % settings.DATABASE_NAME)
+ cursor.execute("CREATE DATABASE %s" % settings.DATABASE_NAME)
+ cursor.execute("ALTER DATABASE %s CHARACTER SET=utf8" % settings.DATABASE_NAME)
+ cursor.execute("USE %s" % settings.DATABASE_NAME)
management.call_command('syncdb', interactive=False)
user = User.objects.create_user('expo', 'goatchurch@gmail.com', 'gosser')
user.is_staff = True
@@ -111,6 +117,59 @@ def export_cavetab():
tocavetab.writeCaveTab(outfile)
outfile.close()
+def import_auto_logbooks():
+ import parsers.logbooks
+ import os
+ for pt in core.models.PersonTrip.objects.all():
+ pt.delete()
+ for lbe in core.models.LogbookEntry.objects.all():
+ lbe.delete()
+ for expedition in core.models.Expedition.objects.all():
+ directory = os.path.join(settings.EXPOWEB,
+ "years",
+ expedition.year,
+ "autologbook")
+ for root, dirs, filenames in os.walk(directory):
+ for filename in filenames:
+ print os.path.join(root, filename)
+ parsers.logbooks.parseAutoLogBookEntry(os.path.join(root, filename))
+
+#Temporary function until definative source of data transfered.
+from django.template.defaultfilters import slugify
+from django.template import Context, loader
+def dumplogbooks():
+ def get_name(pe):
+ if pe.nickname:
+ return pe.nickname
+ else:
+ return pe.person.first_name
+ for lbe in core.models.LogbookEntry.objects.all():
+ dateStr = lbe.date.strftime("%Y-%m-%d")
+ directory = os.path.join(settings.EXPOWEB,
+ "years",
+ lbe.expedition.year,
+ "autologbook")
+ if not os.path.isdir(directory):
+ os.mkdir(directory)
+ filename = os.path.join(directory,
+ dateStr + "." + slugify(lbe.title)[:50] + ".html")
+ if lbe.cave:
+ print lbe.cave.reference()
+ trip = {"title": lbe.title, "html":lbe.text, "cave": lbe.cave.reference(), "caveOrLocation": "cave"}
+ else:
+ trip = {"title": lbe.title, "html":lbe.text, "location":lbe.place, "caveOrLocation": "location"}
+ pts = [pt for pt in lbe.persontrip_set.all() if pt.personexpedition]
+ persons = [{"name": get_name(pt.personexpedition), "TU": pt.time_underground, "author": pt.is_logbook_entry_author} for pt in pts]
+ f = open(filename, "wb")
+ template = loader.get_template('dataformat/logbookentry.html')
+ context = Context({'trip': trip,
+ 'persons': persons,
+ 'date': dateStr,
+ 'expeditionyear': lbe.expedition.year})
+ output = template.render(context)
+ f.write(unicode(output).encode( "utf-8" ))
+ f.close()
+
if __name__ == "__main__":
import core.models
import sys
@@ -118,6 +177,8 @@ if __name__ == "__main__":
resetdesc()
elif "scans" in sys.argv:
import_surveyscans()
+ elif "QMs" in sys.argv:
+ import_QMs()
elif "tunnel" in sys.argv:
import_tunnelfiles()
elif "reset" in sys.argv:
@@ -129,6 +190,10 @@ if __name__ == "__main__":
elif "logbooks" in sys.argv:
management.call_command('syncdb', interactive=False) # this sets the path so that import settings works in import_survex
import_logbooks()
+ elif "autologbooks" in sys.argv:
+ import_auto_logbooks()
+ elif "dumplogbooks" in sys.argv:
+ dumplogbooks()
else:
print "Do 'python databaseReset.py reset'"
diff --git a/parsers/logbooks.py b/parsers/logbooks.py
index 9404414..e6b553b 100644
--- a/parsers/logbooks.py
+++ b/parsers/logbooks.py
@@ -90,12 +90,12 @@ def EnterLogIntoDbase(date, place, title, text, trippeople, expedition, logtime_
#Check for an existing copy of the current entry, and save
expeditionday = expedition.get_expedition_day(date)
lookupAttribs={'date':date, 'title':title}
- nonLookupAttribs={'place':place, 'text':text, 'author':author, 'expedition':expedition, 'expeditionday':expeditionday, 'cave':cave, 'slug':slugify(title)[:50]}
+ nonLookupAttribs={'place':place, 'text':text, 'expedition':expedition, 'cave':cave, 'slug':slugify(title)[:50]}
lbo, created=save_carefully(models.LogbookEntry, lookupAttribs, nonLookupAttribs)
for tripperson, time_underground in trippersons:
lookupAttribs={'personexpedition':tripperson, 'logbook_entry':lbo}
- nonLookupAttribs={'time_underground':time_underground, 'date':date, 'expeditionday':expeditionday, 'is_logbook_entry_author':(tripperson == author)}
+ nonLookupAttribs={'time_underground':time_underground, 'is_logbook_entry_author':(tripperson == author)}
#print nonLookupAttribs
save_carefully(models.PersonTrip, lookupAttribs, nonLookupAttribs)
@@ -328,4 +328,105 @@ def LoadLogbooks():
parsefunc(year, expedition, txt)
SetDatesFromLogbookEntries(expedition)
+dateRegex = re.compile('(\d\d\d\d)-(\d\d)-(\d\d)', re.S)
+expeditionYearRegex = re.compile('(.*?)', re.S)
+titleRegex = re.compile('(.*?)
', re.S)
+reportRegex = re.compile('(.*)
\s*
+{{trip.title}}
+{{date}} - {{expeditionyear}}
+
+{% if trip.caveOrLocation == "cave" %}
+{{trip.cave}}
+{% else %}
+{{trip.location}}
+{% endif %}
+
+{% for person in persons %}
+
+{{person.name}}
+TU{% if person.TU %}{{person.TU}}{% else %}0{% endif %}hours
+
+{% endfor %}
+{{trip.html}}
+