From ae3fe8cd423be5268d630a498361e376c6add776 Mon Sep 17 00:00:00 2001
From: substantialnoninfringinguser
Date: Thu, 2 Jul 2009 20:43:18 +0100
Subject: [svn] Renaming troggle.expo to troggle.core. To do this, used:
perl -p -i -e "s/expo(?=[\s\.']+)/core/g" `find -name \*.py`
and then manually checked each change (had to remove a couple)
---
core/__init__.py | 0
core/admin.py | 129 +++++++
core/context.py | 5 +
core/fileAbstraction.py | 43 +++
core/forms.py | 48 +++
core/imagekit_specs.py | 22 ++
core/models.py | 665 +++++++++++++++++++++++++++++++++++++
core/models_survex.py | 66 ++++
core/randSent.py | 32 ++
core/search.py | 39 +++
core/templatetags/__init__.py | 0
core/templatetags/link.py | 8 +
core/templatetags/survex_markup.py | 52 +++
core/templatetags/wiki_markup.py | 138 ++++++++
core/view_surveys.py | 161 +++++++++
core/views.py | 8 +
core/views_caves.py | 99 ++++++
core/views_logbooks.py | 124 +++++++
core/views_other.py | 155 +++++++++
core/views_survex.py | 167 ++++++++++
expo/__init__.py | 0
expo/admin.py | 129 -------
expo/context.py | 5 -
expo/fileAbstraction.py | 43 ---
expo/forms.py | 48 ---
expo/imagekit_specs.py | 22 --
expo/models.py | 665 -------------------------------------
expo/models_survex.py | 66 ----
expo/randSent.py | 32 --
expo/search.py | 39 ---
expo/templatetags/__init__.py | 0
expo/templatetags/link.py | 8 -
expo/templatetags/survex_markup.py | 52 ---
expo/templatetags/wiki_markup.py | 138 --------
expo/view_surveys.py | 161 ---------
expo/views.py | 8 -
expo/views_caves.py | 99 ------
expo/views_logbooks.py | 124 -------
expo/views_other.py | 155 ---------
expo/views_survex.py | 167 ----------
export/tocavetab.py | 2 +-
export/toqms.py | 2 +-
parsers/QMs.py | 2 +-
parsers/cavetab.py | 2 +-
parsers/logbooks.py | 2 +-
parsers/subcaves.py | 7 +-
parsers/survex.py | 2 +-
parsers/surveys.py | 4 +-
profiles/views.py | 2 +-
save_carefully.py | 2 +-
settings.py | 6 +-
urls.py | 8 +-
52 files changed, 1979 insertions(+), 1984 deletions(-)
create mode 100644 core/__init__.py
create mode 100644 core/admin.py
create mode 100644 core/context.py
create mode 100644 core/fileAbstraction.py
create mode 100644 core/forms.py
create mode 100644 core/imagekit_specs.py
create mode 100644 core/models.py
create mode 100644 core/models_survex.py
create mode 100644 core/randSent.py
create mode 100644 core/search.py
create mode 100644 core/templatetags/__init__.py
create mode 100644 core/templatetags/link.py
create mode 100644 core/templatetags/survex_markup.py
create mode 100644 core/templatetags/wiki_markup.py
create mode 100644 core/view_surveys.py
create mode 100644 core/views.py
create mode 100644 core/views_caves.py
create mode 100644 core/views_logbooks.py
create mode 100644 core/views_other.py
create mode 100644 core/views_survex.py
delete mode 100644 expo/__init__.py
delete mode 100644 expo/admin.py
delete mode 100644 expo/context.py
delete mode 100644 expo/fileAbstraction.py
delete mode 100644 expo/forms.py
delete mode 100644 expo/imagekit_specs.py
delete mode 100644 expo/models.py
delete mode 100644 expo/models_survex.py
delete mode 100644 expo/randSent.py
delete mode 100644 expo/search.py
delete mode 100644 expo/templatetags/__init__.py
delete mode 100644 expo/templatetags/link.py
delete mode 100644 expo/templatetags/survex_markup.py
delete mode 100644 expo/templatetags/wiki_markup.py
delete mode 100644 expo/view_surveys.py
delete mode 100644 expo/views.py
delete mode 100644 expo/views_caves.py
delete mode 100644 expo/views_logbooks.py
delete mode 100644 expo/views_other.py
delete mode 100644 expo/views_survex.py
diff --git a/core/__init__.py b/core/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/core/admin.py b/core/admin.py
new file mode 100644
index 0000000..e304966
--- /dev/null
+++ b/core/admin.py
@@ -0,0 +1,129 @@
+from troggle.core.models import *
+from django.contrib import admin
+from feincms.admin import editor
+from django.forms import ModelForm
+import django.forms as forms
+from core.forms import LogbookEntryForm
+from django.http import HttpResponse
+from django.core import serializers
+from core.views_other import downloadLogbook
+#from troggle.reversion.admin import VersionAdmin #django-reversion version control
+
+#overriding admin save so we have the new since parsing field
+class TroggleModelAdmin(admin.ModelAdmin):
+ def save_model(self, request, obj, form, change):
+ obj.new_since_parsing=True
+ obj.save()
+
+class RoleInline(admin.TabularInline):
+ model = PersonRole
+ extra = 4
+
+class SurvexBlockAdmin(TroggleModelAdmin):
+ inlines = (RoleInline,)
+
+class ScannedImageInline(admin.TabularInline):
+ model = ScannedImage
+ extra = 4
+
+class SurveyAdmin(TroggleModelAdmin):
+ inlines = (ScannedImageInline,)
+ search_fields = ('expedition__year','wallet_number')
+
+class QMsFoundInline(admin.TabularInline):
+ model=QM
+ fk_name='found_by'
+
+class PhotoInline(admin.TabularInline):
+ model = Photo
+ exclude = ['is_mugshot' ]
+ extra = 1
+
+class PersonTripInline(admin.TabularInline):
+ model = PersonTrip
+ exclude = ['persontrip_next','Delete']
+ extra = 1
+
+#class LogbookEntryAdmin(VersionAdmin):
+class LogbookEntryAdmin(TroggleModelAdmin):
+ prepopulated_fields = {'slug':("title",)}
+ search_fields = ('title','expedition__year')
+ date_heirarchy = ('date')
+ inlines = (PersonTripInline, PhotoInline, QMsFoundInline)
+ form = LogbookEntryForm
+
+ actions=('export_logbook_entries_as_html','export_logbook_entries_as_txt')
+
+ def export_logbook_entries_as_html(modeladmin, request, queryset):
+ response=downloadLogbook(request=request, queryset=queryset, extension='html')
+ return response
+
+ def export_logbook_entries_as_txt(modeladmin, request, queryset):
+ response=downloadLogbook(request=request, queryset=queryset, extension='txt')
+ return response
+
+class PersonExpeditionInline(admin.TabularInline):
+ model = PersonExpedition
+ extra = 1
+
+class PersonAdmin(TroggleModelAdmin):
+ search_fields = ('first_name','last_name')
+ inlines = (PersonExpeditionInline,)
+
+class QMAdmin(TroggleModelAdmin):
+ search_fields = ('found_by__cave__kataster_number','number','found_by__date')
+ list_display = ('__unicode__','grade','found_by','ticked_off_by')
+ list_display_links = ('__unicode__',)
+ list_editable = ('found_by','ticked_off_by','grade')
+ list_per_page = 20
+
+class PersonExpeditionAdmin(TroggleModelAdmin):
+ search_fields = ('person__first_name','expedition__year')
+
+class CaveAdmin(TroggleModelAdmin):
+ search_fields = ('official_name','kataster_number','unofficial_number')
+ #inlines = (QMInline,)
+ extra = 4
+
+class SubcaveAdmin(editor.TreeEditorMixin,TroggleModelAdmin):
+ pass
+
+admin.site.register(Photo)
+admin.site.register(Subcave, SubcaveAdmin)
+admin.site.register(Cave, CaveAdmin)
+admin.site.register(Area)
+admin.site.register(OtherCaveName)
+admin.site.register(CaveAndEntrance)
+admin.site.register(SurveyStation)
+admin.site.register(Entrance)
+admin.site.register(SurvexBlock, SurvexBlockAdmin)
+admin.site.register(Expedition)
+admin.site.register(Person,PersonAdmin)
+admin.site.register(PersonRole)
+admin.site.register(PersonExpedition,PersonExpeditionAdmin)
+admin.site.register(Role)
+admin.site.register(LogbookEntry, LogbookEntryAdmin)
+#admin.site.register(PersonTrip)
+admin.site.register(QM, QMAdmin)
+admin.site.register(Survey, SurveyAdmin)
+admin.site.register(ScannedImage)
+
+def export_as_json(modeladmin, request, queryset):
+ response = HttpResponse(mimetype="text/json")
+ response['Content-Disposition'] = 'attachment; filename=troggle_output.json'
+ serializers.serialize("json", queryset, stream=response)
+ return response
+
+def export_as_xml(modeladmin, request, queryset):
+ response = HttpResponse(mimetype="text/xml")
+ response['Content-Disposition'] = 'attachment; filename=troggle_output.xml'
+ serializers.serialize("xml", queryset, stream=response)
+ return response
+
+admin.site.add_action(export_as_xml)
+admin.site.add_action(export_as_json)
+
+try:
+ mptt.register(Subcave, order_insertion_by=['name'])
+except mptt.AlreadyRegistered:
+ print "mptt already registered"
diff --git a/core/context.py b/core/context.py
new file mode 100644
index 0000000..a77de49
--- /dev/null
+++ b/core/context.py
@@ -0,0 +1,5 @@
+from django.conf import settings
+from core.models import Expedition
+
+def troggle_context(request):
+ return { 'settings':settings, 'Expedition':Expedition }
\ No newline at end of file
diff --git a/core/fileAbstraction.py b/core/fileAbstraction.py
new file mode 100644
index 0000000..94b8b0c
--- /dev/null
+++ b/core/fileAbstraction.py
@@ -0,0 +1,43 @@
+import troggle.settings as settings
+import os
+import urllib
+
+def urljoin(x, y): return x + "/" + y
+
+def listdir(*path):
+ try:
+ strippedpath = [p for p in path if p]
+ root = os.path.join(settings.FILES, *strippedpath )
+ l = ""
+ #l = root + "\n"
+ isdir = os.path.isdir(root) #This seems to be required for os.path.isdir to work...
+ #l += str(isdir) + "\n"
+ for p in os.listdir(root):
+ if os.path.isdir(os.path.join(root, p)):
+ l += p + "/\n"
+
+ elif os.path.isfile(os.path.join(root, p)):
+ l += p + "\n"
+ #Ignore non-files and non-directories
+ return l
+ except:
+ if strippedpath:
+ c = reduce(urljoin, strippedpath)
+ else:
+ c = ""
+ c = c.replace("#", "%23")
+ print "FILE: ", settings.FILES + "listdir/" + c
+ return urllib.urlopen(settings.FILES + "listdir/" + c).read()
+
+def dirsAsList(*path):
+ return [d for d in listdir(*path).split("\n") if len(d) > 0 and d[-1] == "/"]
+
+def filesAsList(*path):
+ return [d for d in listdir(*path).split("\n") if len(d) > 0 and d[-1] != "/"]
+
+def readFile(*path):
+ try:
+ f = open(os.path.join(settings.FILES, *path))
+ except:
+ f = urllib.urlopen(settings.FILES + "download/" + reduce(urljoin, path))
+ return f.read()
\ No newline at end of file
diff --git a/core/forms.py b/core/forms.py
new file mode 100644
index 0000000..929c4e9
--- /dev/null
+++ b/core/forms.py
@@ -0,0 +1,48 @@
+from django.forms import ModelForm
+from models import Cave, Person, 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
+
+class CaveForm(ModelForm):
+ class Meta:
+ model = Cave
+
+class PersonForm(ModelForm):
+ class Meta:
+ model = Person
+
+class LogbookEntryForm(ModelForm):
+ class Meta:
+ model = LogbookEntry
+
+ def wikiLinkHints(LogbookEntry=None):
+ """
+ This function returns html-formatted paragraphs for each of the
+ wikilink types that are related to this logbookentry. Each paragraph
+ contains a list of all of the related wikilinks.
+
+ Perhaps an admin javascript solution would be better.
+ """
+ res = ["Please use the following wikilinks, which are related to this logbook entry:"]
+
+ res.append(r'
QMs found:')
+ for QM in LogbookEntry.instance.QMs_found.all():
+ res.append(QM.wiki_link())
+
+ res.append(r'
QMs ticked off:')
+ for QM in LogbookEntry.instance.QMs_ticked_off.all():
+ res.append(QM.wiki_link())
+
+# res.append(r'
People')
+# for persontrip in LogbookEntry.instance.persontrip_set.all():
+# res.append(persontrip.wiki_link())
+# res.append(r'
')
+
+ return string.join(res, r'
')
+
+ def __init__(self, *args, **kwargs):
+ super(LogbookEntryForm, self).__init__(*args, **kwargs)
+ self.fields['text'].help_text=self.wikiLinkHints()
\ No newline at end of file
diff --git a/core/imagekit_specs.py b/core/imagekit_specs.py
new file mode 100644
index 0000000..fd2e0a1
--- /dev/null
+++ b/core/imagekit_specs.py
@@ -0,0 +1,22 @@
+from imagekit.specs import ImageSpec
+from imagekit import processors
+
+class ResizeThumb(processors.Resize):
+ width = 100
+ crop = False
+
+class ResizeDisplay(processors.Resize):
+ width = 600
+
+#class EnhanceThumb(processors.Adjustment):
+ #contrast = 1.2
+ #sharpness = 2
+
+class Thumbnail(ImageSpec):
+ access_as = 'thumbnail_image'
+ pre_cache = True
+ processors = [ResizeThumb]
+
+class Display(ImageSpec):
+ increment_count = True
+ processors = [ResizeDisplay]
diff --git a/core/models.py b/core/models.py
new file mode 100644
index 0000000..8e00941
--- /dev/null
+++ b/core/models.py
@@ -0,0 +1,665 @@
+import urllib, urlparse, string, os, datetime, logging
+import troggle.mptt as mptt
+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.conf import settings
+from decimal import Decimal, getcontext
+from django.core.urlresolvers import reverse
+from imagekit.models import ImageModel
+getcontext().prec=2 #use 2 significant figures for decimal calculations
+
+from models_survex import *
+
+logging.basicConfig(level=logging.DEBUG,
+ filename=settings.LOGFILE,
+ filemode='w')
+
+#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 urlparse.urljoin(settings.URL_ROOT, "/admin/expo/" + self.object_name().lower() + "/" + str(self.pk))
+
+ class Meta:
+ abstract = True
+
+class TroggleImageModel(ImageModel):
+ new_since_parsing = models.BooleanField(default=False, editable=False)
+
+ def object_name(self):
+ return self._meta.object_name
+
+ def get_admin_url(self):
+ return urlparse.urljoin(settings.URL_ROOT, "/admin/expo/" + self.object_name().lower() + "/" + str(self.pk))
+
+
+ class Meta:
+ abstract = True
+
+class Expedition(TroggleModel):
+ year = models.CharField(max_length=20, unique=True)
+ name = models.CharField(max_length=100)
+ date_from = models.DateField(blank=True,null=True)
+ date_to = models.DateField(blank=True,null=True)
+
+ def __unicode__(self):
+ return self.year
+
+ class Meta:
+ ordering = ('-year',)
+ get_latest_by = 'date_from'
+
+ def get_absolute_url(self):
+ #return settings.URL_ROOT + "/expedition/%s" % self.year
+ return urlparse.urljoin(settings.URL_ROOT, reverse('expedition',args=[self.year]))
+
+
+ # lose these two functions (inelegant, and we may create a file with the dates that we can load from)
+ def GuessDateFrom(self):
+ try:
+ return self.logbookentry_set.order_by('date')[0].date
+ except IndexError:
+ pass
+
+ def GuessDateTo(self): # returns the date of the last logbook entry in the expedition
+ try:
+ return self.logbookentry_set.order_by('date')[-1].date
+ except IndexError:
+ pass
+
+ def ListDays(self):
+ if self.date_from and self.date_to:
+ res=[]
+ date=self.date_from
+ while date <= self.date_to:
+ res.append(date)
+ date+=datetime.timedelta(days=1)
+ return res
+ elif self.GuessDateFrom() and self.GuessDateTo(): # if we don't have the real dates, try it with the dates taken from the earliest and latest logbook entries
+ date=self.GuessDateFrom()
+ while date <= self.GuessDateTo():
+ res.append(date)
+ date+=datetime.timedelta(days=1)
+ return res
+
+
+
+
+class Person(TroggleModel):
+ first_name = models.CharField(max_length=100)
+ last_name = models.CharField(max_length=100)
+ is_vfho = models.BooleanField(help_text="VFHO is the Vereines für Höhlenkunde in Obersteier, a nearby Austrian caving club.")
+ 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
+
+ #the below have been removed and made methods. I'm not sure what the b in bisnotable stands for. - AC 16 Feb
+ #notability = models.FloatField() # for listing the top 20 people
+ #bisnotable = models.BooleanField()
+ user = models.OneToOneField(User, null=True, blank=True)
+ def get_absolute_url(self):
+ return urlparse.urljoin(settings.URL_ROOT,reverse('person',kwargs={'first_name':self.first_name,'last_name':self.last_name}))
+
+ class Meta:
+ verbose_name_plural = "People"
+ class Meta:
+ ordering = ('orderref',) # "Wookey" makes too complex for: ('last_name', 'first_name')
+
+ def __unicode__(self):
+ if self.last_name:
+ return "%s %s" % (self.first_name, self.last_name)
+ return self.first_name
+
+# Below are no longer needed. Use {{ person.personexpedition_set.all.0.expedition }} for Firstexpedition, and {{ person.personexpedition_set.latest.expedition }} for Lastexpedition
+ # these ought to be possible by piping through |min in the template, or getting the first of an ordered list
+# def Firstexpedition(self):
+# return self.personexpedition_set.order_by('expedition')[0]
+# def Lastexpedition(self):
+# return self.personexpedition_set.order_by('-expedition')[0]
+
+ def notability(self):
+ notability = Decimal(0)
+ for personexpedition in self.personexpedition_set.all():
+ if not personexpedition.is_guest:
+ notability += Decimal(1) / (2012 - int(personexpedition.expedition.year))
+ return notability
+
+ def bisnotable(self):
+ return self.notability() > Decimal(1)/Decimal(3)
+
+ #def Sethref(self):
+ #if self.last_name:
+ #self.href = self.first_name.lower() + "_" + self.last_name.lower()
+ #self.orderref = self.last_name + " " + self.first_name
+ #else:
+ # self.href = self.first_name.lower()
+ #self.orderref = self.first_name
+ #self.notability = 0.0 # set temporarily
+
+
+class PersonExpedition(TroggleModel):
+ expedition = models.ForeignKey(Expedition)
+ person = models.ForeignKey(Person)
+ date_from = models.DateField(blank=True,null=True)
+ date_to = models.DateField(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('survex_block'):
+ if res and res[-1]['survexpath'] == personrole.survex_block.survexpath:
+ res[-1]['roles'] += ", " + str(personrole.role)
+ else:
+ res.append({'date':personrole.survex_block.date, 'survexpath':personrole.survex_block.survexpath, 'roles':str(personrole.role)})
+ return res
+
+ class Meta:
+ ordering = ('expedition',)
+ #order_with_respect_to = 'expedition'
+ get_latest_by = 'expedition'
+
+ def GetPersonChronology(self):
+ res = { }
+ for persontrip in self.persontrip_set.all():
+ a = res.setdefault(persontrip.date, { })
+ a.setdefault("persontrips", [ ]).append(persontrip)
+ for personrole in self.personrole_set.all():
+ a = res.setdefault(personrole.survex_block.date, { })
+ b = a.setdefault("personroles", { })
+ survexpath = personrole.survex_block.survexpath
+
+ if b.get(survexpath):
+ b[survexpath] += ", " + str(personrole.role)
+ else:
+ b[survexpath] = str(personrole.role)
+# needs converting dict into list
+ return sorted(res.items())
+
+ # possibly not useful functions anyway -JT
+ # if you can find a better way to make the expo calendar table, be my guest. It isn't possible to do this logic in a django template without writing custom tags.-AC
+ def ListDays(self):
+ """
+ Returns a list of the days the person was on the expedition (i.e. the days that the PersonExpedition was in existance). Needed for expedition calendar.
+ """
+ if self.date_from and self.date_to:
+ res=[]
+ date=self.date_from
+ while date <= self.date_to:
+ res.append(date)
+ date+=datetime.timedelta(days=1)
+ return res
+
+ def ListDaysTF(self):
+ """
+ Returns a list of true / false values. Each value corresponds to one day on the expedition; True means the person was there, False means they weren't.
+ """
+ if self.date_from and self.date_to:
+ res=[]
+ for date in self.expedition.ListDays():
+ res.append(date in self.ListDays())
+ return res
+
+ def __unicode__(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 settings.URL_ROOT + '/personexpedition/' + str(self.person.first_name) + '_' + str(self.person.last_name) + '/' +self.expedition.year
+ return urlparse.urljoin(settings.URL_ROOT, reverse('personexpedition',kwargs={'first_name':self.person.first_name,'last_name':self.person.last_name,'year':self.expedition.year}))
+
+class LogbookEntry(TroggleModel):
+ date = models.DateField()
+ 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.
+ # Re: the above- so this field should be "typist" or something, not "author". - AC 15 jun 09
+ title = models.CharField(max_length=200)
+ 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)
+ #href = models.CharField(max_length=100)
+
+
+ #logbookentry_next = models.ForeignKey('LogbookEntry', related_name='pnext', blank=True,null=True)
+ #logbookentry_prev = models.ForeignKey('LogbookEntry', related_name='pprev', blank=True,null=True)
+
+ class Meta:
+ verbose_name_plural = "Logbook Entries"
+ # several PersonTrips point in to this object
+ class Meta:
+ ordering = ('-date',)
+
+ def get_absolute_url(self):
+ return urlparse.urljoin(settings.URL_ROOT, reverse('logbookentry',kwargs={'date':self.date,'slug':self.slug}))
+
+ def __unicode__(self):
+ return "%s: (%s)" % (self.date, self.title)
+
+ def get_next_by_id(self):
+ LogbookEntry.objects.get(id=self.id+1)
+
+ def get_previous_by_id(self):
+ LogbookEntry.objects.get(id=self.id-1)
+
+class PersonTrip(TroggleModel):
+ person_expedition = models.ForeignKey(PersonExpedition,null=True)
+
+ # this will be a foreign key of the place(s) the trip went through
+ # possibly a trip has a plurality of triplets pointing into it
+ #place = models.CharField(max_length=100)
+ #date = models.DateField()
+ time_underground = models.FloatField(help_text="In decimal hours")
+ logbook_entry = models.ForeignKey(LogbookEntry)
+ is_logbook_entry_author = models.BooleanField()
+
+ def date(self):
+ return self.logbook_entry.date
+
+ def place(self):
+ if self.logbook_entry.cave:
+ return self.logbook_entry.cave
+ else:
+ return self.logbook_entry.place
+
+ #persontrip_next = models.ForeignKey('PersonTrip', related_name='pnext', blank=True,null=True)
+ #persontrip_prev = models.ForeignKey('PersonTrip', related_name='pprev', blank=True,null=True)
+
+ def __unicode__(self):
+ return "%s %s (%s)" % (self.person_expedition, self.place(), self.date())
+
+ def get_persons_next_trip(self):
+ try:
+ return PersonTrip.objects.filter(person_expedition__person=self.person_expedition.person, person_expedition__date__gt=self.date)[0]
+ except:
+ return
+
+ def get_persons_previous_trip(self):
+ try:
+ return PersonTrip.objects.filter(person_expedition__person=self.person_expedition.person, person_expedition__date__lt=self.date)[0]
+ except:
+ return
+
+# def get_persons_previous_trip(self):
+#
+# 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 __unicode__(self):
+ if self.parent:
+ return unicode(self.parent) + u" - " + unicode(self.short_name)
+ else:
+ return unicode(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(TroggleModel):
+ cave = models.ForeignKey('Cave')
+ entrance = models.ForeignKey('Entrance')
+ entrance_letter = models.CharField(max_length=20,blank=True,null=True)
+ def __unicode__(self):
+ return unicode(self.cave) + unicode(self.entrance_letter)
+
+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)
+
+ #href = models.CharField(max_length=100)
+
+ def get_absolute_url(self):
+ if self.kataster_number:
+ href = self.kataster_number
+ elif self.unofficial_number:
+ href = self.unofficial_number
+ else:
+ href = official_name.lower()
+ #return settings.URL_ROOT + '/cave/' + href + '/'
+ return urlparse.urljoin(settings.URL_ROOT, reverse('cave',kwargs={'cave_id':href,}))
+
+ def __unicode__(self):
+ if self.kataster_number:
+ if self.kat_area():
+ return self.kat_area() + u": " + self.kataster_number
+ else:
+ return unicode("l") + u": " + self.kataster_number
+ else:
+ if self.kat_area():
+ return self.kat_area() + u": " + self.unofficial_number
+ else:
+ return self.unofficial_number
+
+ def get_QMs(self):
+ return QM.objects.filter(found_by__cave=self)
+
+ 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 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 += "–" + prevR
+ else:
+ prevR = r
+ n = 0
+ res += r
+ if n == 0:
+ res += ", " + prevR
+ else:
+ res += "–" + prevR
+ return res
+
+ def nextQMnumber(self, year=datetime.date.today().year):
+ """
+ Given a cave and the current year, returns the next QM number.
+ """
+ res=QM.objects.filter(found_by__date__year=year, found_by__cave=self).order_by('-number')[0]
+ return res.number+1
+
+class OtherCaveName(TroggleModel):
+ name = models.CharField(max_length=160)
+ cave = models.ForeignKey(Cave)
+ def __unicode__(self):
+ return unicode(self.name)
+
+class SurveyStation(TroggleModel):
+ name = models.CharField(max_length=200)
+ def __unicode__(self):
+ return unicode(self.name)
+
+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', 'Retagged'),
+ ('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', 'Surveyed'),
+ ('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.ForeignKey(SurveyStation, blank=True,null=True, related_name="tag_station")
+ exact_station = models.ForeignKey(SurveyStation, blank=True,null=True, related_name="exact_station")
+ other_station = models.ForeignKey(SurveyStation, blank=True,null=True, related_name="other_station")
+ other_description = models.TextField(blank=True,null=True)
+ bearings = models.TextField(blank=True,null=True)
+ def __unicode__(self):
+ a = CaveAndEntrance.objects.filter(entrance = self)
+ name = u''
+ if self.name:
+ name = unicode(self.name) + u' '
+ if len(a) == 1:
+ return name + unicode(a[0])
+ return name + unicode(a)
+ 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]
+
+class Subcave(TroggleModel):
+ description = models.TextField(blank=True, null=True)
+ title = models.CharField(max_length=200, )
+ cave = models.ForeignKey('Cave', blank=True, null=True, help_text="Only the top-level subcave should be linked to a cave!")
+ parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
+ #adjoining = models.ManyToManyField('Subcave',blank=True, null=True,)
+ legacy_description_path = models.CharField(max_length=600, blank=True, null=True)
+ def __unicode__(self):
+ return self.title
+
+ 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
+
+# This was the old way, before we were using django-mptt
+
+# def get_absolute_url(self):
+# urlString=self.name
+# if self.parent:
+# parent=self.parent
+# while parent: #recursively walk up the tree, adding parents to the left of the URL
+# urlString=parent.name+'/'+urlString
+# if parent.cave:
+# cave=parent.cave
+# parent=parent.parent
+# urlString='cave/'+unicode(cave.kataster_number)+'/'+urlString
+# else:
+# urlString='cave/'+unicode(self.cave.kataster_number)+'/'+urlString
+
+
+# return urlparse.urljoin(settings.URL_ROOT, urlString)
+try:
+ mptt.register(Subcave, order_insertion_by=['title'])
+except mptt.AlreadyRegistered:
+ print "mptt already registered"
+
+class QM(TroggleModel):
+ #based on qm.csv in trunk/expoweb/smkridge/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)
+ 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)
+ #should be a foreignkey to surveystation
+ nearest_station_description = models.CharField(max_length=400,null=True,blank=True)
+ nearest_station = models.CharField(max_length=200,blank=True,null=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)
+ #the below are unneeded- instead use the date fields of the QM's trips
+ #dateFound = models.DateField(blank=True)
+ #dateKilled = models.DateField(blank=True)
+ def __unicode__(self):
+ QMnumber=str(self.found_by.cave)+'-'+str(self.found_by.date.year)+"-"+str(self.number)+self.grade
+ return str(QMnumber)
+
+ 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 urlparse.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):
+ res = '[[cave:' + str(self.found_by.cave.kataster_number) + ' '
+ res += 'QM:' + str(self.found_by.date.year) + '-'
+ res += str(self.number) + self.grade + ']]'
+ return res
+
+photoFileStorage = FileSystemStorage(location=settings.PHOTOS_ROOT, base_url=settings.PHOTOS_URL)
+class Photo(TroggleImageModel):
+ caption = models.CharField(max_length=1000,blank=True,null=True)
+ contains_logbookentry = models.ForeignKey(LogbookEntry,blank=True,null=True)
+ contains_person = models.ManyToManyField(Person,blank=True,null=True)
+ file = models.ImageField(storage=photoFileStorage, upload_to='.',)
+ is_mugshot = models.BooleanField(default=False)
+ contains_cave = models.ForeignKey(Cave,blank=True,null=True)
+ contains_entrance = models.ForeignKey(Entrance, related_name="photo_file",blank=True,null=True)
+ nearest_survey_point = models.ForeignKey(SurveyStation,blank=True,null=True)
+ nearest_QM = models.ForeignKey(QM,blank=True,null=True)
+ lon_utm = models.FloatField(blank=True,null=True)
+ lat_utm = models.FloatField(blank=True,null=True)
+
+ class IKOptions:
+ spec_module = 'core.imagekit_specs'
+ cache_dir = 'thumbs'
+ image_field = 'file'
+
+ #content_type = models.ForeignKey(ContentType)
+ #object_id = models.PositiveIntegerField()
+ #location = generic.GenericForeignKey('content_type', 'object_id')
+
+ def __unicode__(self):
+ return self.caption
+
+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
+ number="%02d" % instance.survey.wallet_number + str(instance.survey.wallet_letter) #using %02d string formatting because convention was 2009#01
+ return os.path.join('./',year,year+r'#'+number,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)
+
+ class IKOptions:
+ spec_module = 'core.imagekit_specs'
+ cache_dir = 'thumbs'
+ image_field = 'file'
+ #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 __unicode__(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('Subcave', 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 __unicode__(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')
diff --git a/core/models_survex.py b/core/models_survex.py
new file mode 100644
index 0000000..cf339f9
--- /dev/null
+++ b/core/models_survex.py
@@ -0,0 +1,66 @@
+from django.db import models
+from django.conf import settings
+import os
+
+class SurvexBlock(models.Model):
+ name = models.CharField(max_length=100, blank=True, null=True)
+ parent = models.ForeignKey('SurvexBlock', blank=True, null=True)
+ text = models.TextField()
+
+ # non-useful representation of incomplete data
+ start_year = models.IntegerField(blank=True, null=True)
+ start_month = models.IntegerField(blank=True, null=True)
+ start_day = models.IntegerField(blank=True, null=True)
+ end_year = models.IntegerField(blank=True, null=True)
+ end_month = models.IntegerField(blank=True, null=True)
+ end_day = models.IntegerField(blank=True, null=True)
+
+ date = models.DateField(blank=True, null=True)
+ survexpath = models.CharField(max_length=100)
+
+ # superfluous
+ person = models.ManyToManyField('Person', through='PersonRole', blank=True, null=True)
+
+ # code for where in the survex data files this block sits
+ begin_file = models.CharField(max_length=200)
+ begin_char = models.IntegerField()
+ end_file = models.CharField(max_length=200, blank=True, null=True)
+ end_char = models.IntegerField(blank=True, null=True)
+
+ class Meta:
+ ordering = ('date', 'survexpath')
+
+ def __unicode__(self):
+ return unicode(self.name)
+
+ def filecontents(self):
+ f = os.path.join(settings.SURVEX_DATA, self.begin_file)
+ fin = open(f, "rb")
+ res = fin.read().decode("latin1")
+ fin.close()
+ return res
+
+ 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)})
+ print res
+ return res
+
+
+class PersonRole(models.Model):
+ personexpedition = models.ForeignKey('PersonExpedition')
+ person = models.ForeignKey('Person')
+ survex_block = models.ForeignKey('SurvexBlock')
+ role = models.ForeignKey('Role')
+ def __unicode__(self):
+ return unicode(self.person) + " - " + unicode(self.survex_block) + " - " + unicode(self.role)
+
+class Role(models.Model):
+ name = models.CharField(max_length=50)
+ def __unicode__(self):
+ return unicode(self.name)
+
diff --git a/core/randSent.py b/core/randSent.py
new file mode 100644
index 0000000..efcc005
--- /dev/null
+++ b/core/randSent.py
@@ -0,0 +1,32 @@
+import troggle.settings as settings
+from django import forms
+from troggle.core.models import LogbookEntry
+import random
+import re
+
+def weighted_choice(lst):
+ n = random.uniform(0,1)
+ for item, weight in lst:
+ if n < weight:
+ break
+ n = n - weight
+ return item
+
+def randomLogbookSentence():
+ randSent={}
+
+ # needs to handle empty logbooks without crashing
+
+ #Choose a random logbook entry
+ randSent['entry']=LogbookEntry.objects.order_by('?')[0]
+
+ #Choose again if there are no sentances (this happens if it is a placeholder entry)
+ while len(re.findall('[A-Z].*?\.',randSent['entry'].text))==0:
+ randSent['entry']=LogbookEntry.objects.order_by('?')[0]
+
+ #Choose a random sentence from that entry. Store the sentence as randSent['sentence'], and the number of that sentence in the entry as randSent['number']
+ sentenceList=re.findall('[A-Z].*?\.',randSent['entry'].text)
+ randSent['number']=random.randrange(0,len(sentenceList))
+ randSent['sentence']=sentenceList[randSent['number']]
+
+ return randSent
diff --git a/core/search.py b/core/search.py
new file mode 100644
index 0000000..5ec2ce2
--- /dev/null
+++ b/core/search.py
@@ -0,0 +1,39 @@
+import re
+
+from django.db.models import Q
+
+# search script from http://www.julienphalip.com/blog/2008/08/16/adding-search-django-site-snap/
+
+def normalize_query(query_string,
+ findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
+ normspace=re.compile(r'\s{2,}').sub):
+ ''' Splits the query string in invidual keywords, getting rid of unecessary spaces
+ and grouping quoted words together.
+ Example:
+
+ >>> normalize_query(' some random words "with quotes " and spaces')
+ ['some', 'random', 'words', 'with quotes', 'and', 'spaces']
+
+ '''
+ return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
+
+def get_query(query_string, search_fields):
+ ''' Returns a query, that is a combination of Q objects. That combination
+ aims to search keywords within a model by testing the given search fields.
+
+ '''
+ query = None # Query to search for every search term
+ terms = normalize_query(query_string)
+ for term in terms:
+ or_query = None # Query to search for a given term in each field
+ for field_name in search_fields:
+ q = Q(**{"%s__icontains" % field_name: term})
+ if or_query is None:
+ or_query = q
+ else:
+ or_query = or_query | q
+ if query is None:
+ query = or_query
+ else:
+ query = query & or_query
+ return query
\ No newline at end of file
diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/core/templatetags/link.py b/core/templatetags/link.py
new file mode 100644
index 0000000..5308ace
--- /dev/null
+++ b/core/templatetags/link.py
@@ -0,0 +1,8 @@
+from django import template
+from django.utils.safestring import mark_safe
+
+register = template.Library()
+
+@register.filter()
+def link(value):
+ return mark_safe(""%value.get_absolute_url()+unicode(value)+"")
\ No newline at end of file
diff --git a/core/templatetags/survex_markup.py b/core/templatetags/survex_markup.py
new file mode 100644
index 0000000..464a04b
--- /dev/null
+++ b/core/templatetags/survex_markup.py
@@ -0,0 +1,52 @@
+from django import template
+from django.utils.html import conditional_escape
+from django.template.defaultfilters import stringfilter
+from django.utils.safestring import mark_safe
+import re
+
+register = template.Library()
+
+# seems to add extra lines between the commented lines, which isn't so great.
+regexes = []
+regexes.append((re.compile(r"(;.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\n'))
+regexes.append((re.compile(r"^(\s*)(\*include)(\s+)([^\s]*)(.svx)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4\5'))
+regexes.append((re.compile(r"^(\s*)(\*include)(\s+)([^\s]*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*team\s+(?:notes|tape|insts|pics))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*(?:begin|end|copyright|date|entrance|equate|export|fix|prefix|require|SOLVE|title|truncate))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*calibrate\s+(?:TAPE|COMPASS|CLINO|COUNTER|DEPTH|DECLINATION|X|Y|Z)+)(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*data\s+(?:DEFAULT|NORMAL|DIVING|CARTESIAN|TOPOFIL|CYLPOLAR|NOSURVEY|passage)(?:\s+station|\s+from|\s+to|\s+FROMDEPTH|\s+TODEPTH|\s+DEPTHCHANGE|\s+newline|\s+direction|\s+tape|\s+compass|\s+clino|\s+northing|\s+easting|\s+altitude|\s+length|\s+bearing|\s+gradient|\s+ignoreall|\sleft|\sright|\sup|\sdown)*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2'))
+regexes.append((re.compile(r"^(\s*)(\*default\s+(?:CALIBRATE|DATA|UNITS)+)(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*flags\s+(?:DUPLICATE|SPLAY|SURFACE|not DUPLICATE|not SPLAY|not SURFACE))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*infer\s+(?:plumbs|equates|exports))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*instrument\s+(?:compass|clino|tape))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*instrument\s+(?:compass|clino|tape))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*sd\s+(?:TAPE|COMPASS|CLINO|COUNTER|DEPTH|DECLINATION|DX|DY|DZ))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*set\s+(?:BLANK|COMMENT|DECIMAL|EOL|KEYWORD|MINUS|NAMES|OMIT|PLUS|ROOT|SEPARATOR))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(\s*)(\*units\s+(?:TAPE|LENGTH|COMPASS|BEARING|CLINO|GRADIENT|COUNTER|DEPTH|DECLINATION|X|Y|Z))(\s+)(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1\2\3\4'))
+regexes.append((re.compile(r"^(.*)$", re.IGNORECASE|re.MULTILINE),
+ r'\1
\n'))
+
+@register.filter()
+@stringfilter
+def survex_to_html(value, autoescape=None):
+ if autoescape:
+ value = conditional_escape(value)
+ for regex, sub in regexes:
+ print sub
+ value = regex.sub(sub, value)
+ return mark_safe(value)
\ No newline at end of file
diff --git a/core/templatetags/wiki_markup.py b/core/templatetags/wiki_markup.py
new file mode 100644
index 0000000..07e6468
--- /dev/null
+++ b/core/templatetags/wiki_markup.py
@@ -0,0 +1,138 @@
+from django import template
+from django.utils.html import conditional_escape
+from django.template.defaultfilters import stringfilter
+from django.utils.safestring import mark_safe
+from django.conf import settings
+from core.models import QM, Photo
+import re, urlparse
+
+register = template.Library()
+if settings.URL_ROOT.endswith('/'):
+ url_root=settings.URL_ROOT[:-1]
+
+def wiki_list(line, listdepth):
+ l = ""
+ for d in listdepth:
+ l += d
+ mstar = re.match(l + "\*(.*)", line)
+ if mstar:
+ listdepth.append("\*")
+ return ("\n" + " " * len(listdepth) + "- %s
\n" % mstar.groups()[0], listdepth)
+ mhash = re.match(l + "#(.*)", line)
+ if mhash:
+ listdepth.append("#")
+ return ("\n" + " " * len(listdepth) + "- %s
\n" % mhash.groups()[0], listdepth)
+ mflat = re.match(l + "(.*)", line)
+ if mflat and listdepth:
+ return (" " * len(listdepth) + "- %s
\n" % mflat.groups()[0], listdepth)
+ if listdepth:
+ prev = listdepth.pop()
+ if prev == "\*":
+ t, l = wiki_list(line, listdepth)
+ return ("
\n" + t, l)
+ if prev == "#":
+ t, l = wiki_list(line, listdepth)
+ return ("\n" + t, l)
+ return (line, listdepth)
+
+@register.filter()
+@stringfilter
+def wiki_to_html(value, autoescape=None):
+ #find paragraphs
+ outValue = ""
+ for paragraph in re.split("\n\s*?\n", value, re.DOTALL):
+ outValue += ""
+ outValue += wiki_to_html_short(paragraph, autoescape)
+ outValue += "
\n"
+ return mark_safe(outValue)
+
+@register.filter()
+@stringfilter
+def wiki_to_html_short(value, autoescape=None):
+ """
+ This is the tag which turns wiki syntax into html. Aaron wonders
+ why it is called "short." It is long, and it operates on long things.
+ It even has a long name itself.
+ """
+ if autoescape:
+ value = conditional_escape(value)
+ #deescape doubly escaped characters
+ value = re.sub("&(.*?);", r"&\1;", value, re.DOTALL)
+ #italics and bold
+ value = re.sub("''''([^']+)''''", r"\1", value, re.DOTALL)
+ value = re.sub("'''([^']+)'''", r"\1", value, re.DOTALL)
+ value = re.sub("''([^']+)''", r"\1", value, re.DOTALL)
+ #make cave links
+ value = re.sub("\[\[\s*cave:([^\s]+)\s*\s*\]\]", r'\1' % url_root, value, re.DOTALL)
+ #make people links
+ value = re.sub("\[\[\s*person:(.+)\]\]",r'\1' % url_root, value, re.DOTALL)
+
+ #make qm links. this takes a little doing
+ qmMatchPattern="\[\[\s*cave:([^\s]+)\s*\s*\QM:(\d*)-(\d*)([ABCDX]?)\]\]"
+ def qmrepl(matchobj):
+ """
+ A function for replacing wikicode qm links with html qm links.
+ Given a matchobj matching a wikilink in the format
+ [[cave:204 QM:1999-24C]] where the grade (C) is optional.
+ If the QM does not exist, the function will return a link for creating it.
+ """
+ # if there are four matched groups, the fourth one should be the QM grade
+ if len(matchobj.groups())==4:
+ grade=matchobj.groups()[3]
+ else:
+ grade=''
+ qmdict={'urlroot':url_root,'cave':matchobj.groups()[0],'year':matchobj.groups()[1],'number':matchobj.groups()[2],'grade':grade}
+ try:
+ qm=QM.objects.get(found_by__cave__kataster_number=qmdict['cave'],found_by__date__year=qmdict['year'], number=qmdict['number'])
+ res=r'' + str(qm) + ''
+ except QM.DoesNotExist:
+ res = r'%(cave)s:%(year)s-%(number)s%(grade)s' % qmdict
+ return res
+ value = re.sub(qmMatchPattern,qmrepl, value, re.DOTALL)
+
+ #make photo links for [[photo:filename]] or [[photo:filename linktext]], and
+ #insert photos for [[display:left photo:filename]]
+ photoLinkPattern="\[\[\s*photo:(?P[^\s]+)\s*(?P.*)\]\]"
+ photoSrcPattern="\[\[\s*display:(?P