From a26310767ba885bcb403e08f8060f045e4716e08 Mon Sep 17 00:00:00 2001
From: Martin Green <martin.speleo@gmail.com>
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 += "&ndash;" + 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('<span\s+class="date">(\d\d\d\d)-(\d\d)-(\d\d)</span>', re.S)
+expeditionYearRegex = re.compile('<span\s+class="expeditionyear">(.*?)</span>', re.S)
+titleRegex = re.compile('<H1>(.*?)</H1>', re.S)
+reportRegex = re.compile('<div\s+class="report">(.*)</div>\s*</body>', re.S)
+personRegex = re.compile('<div\s+class="person">(.*?)</div>', re.S)
+nameAuthorRegex = re.compile('<span\s+class="name(,author|)">(.*?)</span>', re.S)
+TURegex = re.compile('<span\s+class="TU">([0-9]*\.?[0-9]+)</span>', re.S)
+locationRegex = re.compile('<span\s+class="location">(.*?)</span>', re.S)
+caveRegex = re.compile('<span\s+class="cave">(.*?)</span>', re.S)
 
+def parseAutoLogBookEntry(filename):
+    errors = []
+    f = open(filename, "r")
+    contents = f.read()
+    f.close()
+
+    dateMatch = dateRegex.search(contents)
+    if dateMatch:
+        year, month, day = [int(x) for x in dateMatch.groups()]
+        date = datetime.date(year, month, day)
+    else:
+        errors.append("Date could not be found")
+
+    expeditionYearMatch = expeditionYearRegex.search(contents)
+    if expeditionYearMatch:
+        try:
+            expedition = models.Expedition.objects.get(year = expeditionYearMatch.groups()[0])
+            personExpeditionNameLookup = GetPersonExpeditionNameLookup(expedition)
+        except models.Expedition.DoesNotExist:
+            errors.append("Expedition not in database")   
+    else:
+        errors.append("Expediton Year could not be parsed")   
+
+    titleMatch = titleRegex.search(contents)
+    if titleMatch:
+        title, = titleMatch.groups()
+        if len(title) > settings.MAX_LOGBOOK_ENTRY_TITLE_LENGTH:
+            errors.append("Title too long")   
+    else:
+        errors.append("Title could not be found") 
+
+    caveMatch = caveRegex.search(contents)
+    if caveMatch:
+        caveRef, = caveMatch.groups()
+        try:
+            cave = models.getCaveByReference(caveRef)
+        except AssertionError:
+            cave = None
+            errors.append("Cave not found in database")   
+    else:
+        cave = None
+
+    locationMatch = locationRegex.search(contents)
+    if locationMatch:
+        location, = locationMatch.groups() 
+    else:
+        location = None
+       
+    if cave is None and location is None:
+        errors.append("Location nor cave could not be found") 
+
+    reportMatch = reportRegex.search(contents)
+    if reportMatch:
+        report, = reportMatch.groups()
+    else:
+        errors.append("Contents could not be found") 
+    if errors:
+        return errors # Easiest to bail out at this point as we need to make sure that we know which expedition to look for people from.
+    people = []
+    for personMatch in personRegex.findall(contents):
+       nameAuthorMatch = nameAuthorRegex.search(contents)
+       if nameAuthorMatch:
+           author, name = nameAuthorMatch.groups()
+           if name.lower() in personExpeditionNameLookup:
+               personExpo = personExpeditionNameLookup[name.lower()]
+           else:
+               errors.append("Person could not be found in database")
+           author = bool(author)
+       else:
+           errors.append("Persons name could not be found")
+       
+       TUMatch = TURegex.search(contents)
+       if TUMatch:
+           TU, = TUMatch.groups()
+       else:
+           errors.append("TU could not be found")
+       if not errors:
+           people.append((name, author, TU))
+    if errors:
+        return errors # Bail out before commiting to the database
+    logbookEntry = models.LogbookEntry(date = date, 
+                                       expedition  = expedition,
+                                       title = title, cave = cave, place = location, 
+                                       text = report, slug = slugify(title)[:50],
+                                       filename = filename)
+    logbookEntry.save()
+    for name, author, TU in people:
+        models.PersonTrip(personexpedition = personExpo,  
+                          time_underground = TU, 
+                          logbook_entry = logbookEntry, 
+                          is_logbook_entry_author = author).save()
+    print logbookEntry
diff --git a/templates/dataformat/logbookentry.html b/templates/dataformat/logbookentry.html
new file mode 100644
index 0000000..e6b83ce
--- /dev/null
+++ b/templates/dataformat/logbookentry.html
@@ -0,0 +1,25 @@
+{% autoescape off %}
+<html>
+<head>
+<style type="text/css">.author {text-decoration:underline}</style>
+</head>
+<body>
+<H1>{{trip.title}}</H1>
+<span class="date">{{date}}</span> - <span class="expeditionyear">{{expeditionyear}}</span>
+
+{% if trip.caveOrLocation == "cave" %}
+<span class="cave">{{trip.cave}}</span>
+{% else %}
+<span class="location">{{trip.location}}</span>
+{% endif %}
+
+{% for person in persons %}
+<div class="person">
+<span class="name{% if person.author %} author{% endif %}">{{person.name}}</span>
+TU<span class="TU">{% if person.TU %}{{person.TU}}{% else %}0{% endif %}</span>hours
+</div>
+{% endfor %}
+<div class="report">{{trip.html}}</div>
+</body>
+</html>
+{% endautoescape %}
diff --git a/templates/expedition.html b/templates/expedition.html
index fd78eea..814b77e 100644
--- a/templates/expedition.html
+++ b/templates/expedition.html
@@ -67,22 +67,20 @@ an "S" for a survey trip.  The colours are the same for people on the same trip.
 <form action="" method="GET"><input type="submit" name="reload" value="Reload"></form>
 
 <h3>Logbooks and survey trips per day</h3>
+<a href="{% url newLogBookEntry expeditionyear=expedition.year %}">New logbook entry</a>
 <table class="expeditionlogbooks">
 <tr><th>Date</th><th>Logged trips</th><th>Surveys</th></tr>
-{% for expeditionday in expedition.expeditionday_set.all %} 
+{% regroup dateditems|dictsort:"date" by date as dates %}
+{% for date in dates %}
 <tr>
-  <td>{{expeditionday.date}}</td>
-  <td>
-    {% for logbookentry in expeditionday.logbookentry_set.all %}
-    <a href="{{ logbookentry.get_absolute_url }}">{{logbookentry.title|safe}}</a><br/>
-    {% endfor %}
-  </td>
-  <td>
-    {% for survexblock in expeditionday.survexblock_set.all %}
-    <a href="{% url svx survexblock.survexfile.path %}">{{survexblock.name}}</a>
-    {% endfor %}
-  </td>
-</tr>
+<td>{{date.grouper}}</td>
+<td>{% for item in date.list %}
+        {% if item.isLogbookEntry %}<a href="{{ item.get_absolute_url }}">{{item.title|safe}}</a><br/>{% endif %}
+    {% endfor %}</td>
+<td>{% for item in date.list %}
+        {% if item.isSurvexBlock %}<a href="{% url svx item.survexfile.path %}">{{item.name}}</a><br/>{% endif %}
+    {% endfor %}</td>
+</tr>  
 {% endfor %}
 </table>
 
diff --git a/templates/logbookentry.html b/templates/logbookentry.html
index 03fc37e..19b8a30 100644
--- a/templates/logbookentry.html
+++ b/templates/logbookentry.html
@@ -45,12 +45,12 @@
 
   <td>
   {% if persontrip.persontrip_prev %}
-    <a href="{{ persontrip.persontrip_prev.logbook_entry.get_absolute_url }}">{{persontrip.persontrip_prev.date}}</a>
+    <a href="{{ persontrip.persontrip_prev.logbook_entry.get_absolute_url }}">{{persontrip.persontrip_prev.logbook_entry.date}}</a>
   {% endif %}
   </td>
   <td>
   {% if persontrip.persontrip_next %}
-    <a href="{{ persontrip.persontrip_next.logbook_entry.get_absolute_url }}">{{persontrip.persontrip_next.date}}</a>
+    <a href="{{ persontrip.persontrip_next.logbook_entry.get_absolute_url }}">{{persontrip.persontrip_next.logbook_entry.date}}</a>
   {% endif %}
   </td>
 
@@ -69,4 +69,6 @@
 </div>
 </div>
 
+{% if logbookentry.filename %}<a href="{% url editlogbookentry year=logbookentry.year pdate=logbookentry.date pslug=logbookentry.slug %}">Edit</a> <a href="{% url deletelogbookentry year=logbookentry.year date=logbookentry.date slug=logbookentry.slug %}">Delete</a>{%endif%}
+
 {% endblock %}
diff --git a/templates/newlogbookentry.html b/templates/newlogbookentry.html
new file mode 100644
index 0000000..0cce46b
--- /dev/null
+++ b/templates/newlogbookentry.html
@@ -0,0 +1,82 @@
+{% extends "base.html" %}
+{% block title %}Logbook {{logbookentry.id}}{% endblock %}
+{% block head %}
+<script>
+	$(function() {
+		$("#id_date").datepicker({dateFormat: "yy-mm-dd"});
+                $('.persontrips tbody tr').formset();
+                $(":radio[name*='caveOrLocation']").change(setLocationType);
+                $(setLocationType());
+                function setLocationType () {
+                    $("#cave").hide();
+                    $("#location").hide();
+                    $("#" + $(":radio[name*='caveOrLocation']:checked")[0].value).show();
+                    };
+           
+	});
+
+</script>
+<link rel="stylesheet" href="{{ settings.MEDIA_URL }}css/ui-lightness/jquery-ui-1.8.12.custom.css" type="text/css" media="all" />
+<script src="{{ settings.MEDIA_URL }}js/jquery-ui-1.8.12.custom.min.js" type="text/javascript"></script>
+<script src="{{ settings.MEDIA_URL }}js/jquery.formset.min.js" type="text/javascript"></script>
+<script src="{{ settings.TINY_MCE_MEDIA_URL }}tiny_mce.js" type="text/javascript"></script>
+{{ tripForm.media }}
+{% endblock %}
+{% block content %}
+
+<form action="" method="post">
+    {{ tripForm.non_field_errors }}
+    <div class="fieldWrapper">
+        {{ tripForm.title.errors }}
+        <label for="id_title">Title:</label>
+        {{ tripForm.title }}
+    </div>
+    <div class="fieldWrapper">
+        {{ tripForm.date.errors }}
+        <label for="id_date">Date:</label>
+        {{ tripForm.date }}
+    </div>
+    <div class="fieldWrapper">
+        {{ tripForm.caveOrLocation.errors }}
+        <label for="id_caveOrLocation">Location Type:</label>
+        {{ tripForm.caveOrLocation }}
+    </div>
+    <div class="fieldWrapper" id="cave">
+        {{ tripForm.cave.errors }}
+        <label for="id_cave">Cave:</label>
+        {{ tripForm.cave }}
+    </div>
+    <div class="fieldWrapper" id="location">
+        {{ tripForm.location.errors }}
+        <label for="id_location">Location:</label>
+        {{ tripForm.location }}
+    </div>
+    <table class="persontrips" border="0" cellpadding="0" cellspacing="0">
+         <tbody>
+             <tr>
+                <th>Person</th>
+                <th>TU /hours</th>
+                <th>Author</th>
+                <th></th>
+             </tr>
+             {% for form in personTripFormSet.forms %}
+             <tr>
+                <td>{{ form.name.errors }}{{ form.name }}</td>
+                <td>{{ form.TU.errors }}{{ form.TU }}</td>
+                <td>{{ form.author.errors }}{{ form.author }}</td>
+                <td></td>
+               {{ form.non_field_errors }}
+             </tr>
+             {% endfor %}
+         </tbody>
+    </table>
+    {{ personTripFormSet.management_form }}
+    <div class="fieldWrapper">
+        {{ tripForm.html.errors }}
+        <label for="id_date">Content:</label>
+        {{ tripForm.html }}
+    </div>
+    <p><input type="submit" value="Sumbit Trip Report" /></p>
+</form>
+
+{% endblock %}
diff --git a/urls.py b/urls.py
index 92f3c26..71312c0 100644
--- a/urls.py
+++ b/urls.py
@@ -33,6 +33,9 @@ urlpatterns = patterns('',
     url(r'^expeditions/?$',  object_list,  {'queryset':Expedition.objects.all(),'template_name':'object_list.html'},name="expeditions"),
     url(r'^personexpedition/(?P<first_name>[A-Z]*[a-z]*)[^a-zA-Z]*(?P<last_name>[A-Z]*[a-z]*)/(?P<year>\d+)/?$', views_logbooks.personexpedition, name="personexpedition"),
     url(r'^logbookentry/(?P<date>.*)/(?P<slug>.*)/?$', views_logbooks.logbookentry,name="logbookentry"),
+    url(r'^newlogbookentry/(?P<expeditionyear>.*)$', views_logbooks.newLogbookEntry,     name="newLogBookEntry"),
+    url(r'^editlogbookentry/(?P<expeditionyear>[^/]*)/(?P<pdate>[^/]*)/(?P<pslug>[^/]*)/$', views_logbooks.newLogbookEntry,     name="editLogBookEntry"),
+    url(r'^deletelogbookentry/(?P<expeditionyear>[^/]*)/(?P<date>[^/]*)/(?P<slug>[^/]*)/$', views_logbooks.deleteLogbookEntry, name="deleteLogBookEntry"),
 
     url(r'^cave/(?P<cave_id>[^/]+)/?$', views_caves.cave, name="cave"),
     url(r'^cavedescription/(?P<cavedescription_name>[^/]+)/?$', views_caves.cave_description, name="cavedescription"),
@@ -83,6 +86,8 @@ urlpatterns = patterns('',
 
     (r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
         {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
+    (r'^tinymce_media/(?P<path>.*)$', 'django.views.static.serve',
+        {'document_root': settings.TINY_MCE_MEDIA_ROOT, 'show_indexes': True}),
                 
         
     url(r'^survexblock/(.+)$',                     views_caves.survexblock, name="survexblock"),