From df434cd39909d177f98dec5a7575f61ea701c102 Mon Sep 17 00:00:00 2001
From: Philip Sargent <philip.sargent@klebos.com>
Date: Wed, 1 Jul 2020 22:49:38 +0100
Subject: [PATCH] SurvexBlocks now importing in deatil

---
 core/models_survex.py |   1 +
 core/views_caves.py   |   2 +
 databaseReset.py      |   9 +-
 parsers/caves.py      |   9 +
 parsers/imports.py    |  12 +-
 parsers/logbooks.py   |   3 +
 parsers/survex.py     |  90 +++----
 troggle-inspectdb.py  | 532 ++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 605 insertions(+), 53 deletions(-)

diff --git a/core/models_survex.py b/core/models_survex.py
index d732130..65bbaaa 100644
--- a/core/models_survex.py
+++ b/core/models_survex.py
@@ -99,6 +99,7 @@ class SurvexBlockLookUpManager(models.Manager):
 class SurvexBlock(models.Model):
     objects = SurvexBlockLookUpManager()
     name       = models.CharField(max_length=100)
+    title      = models.CharField(max_length=100)
     parent     = models.ForeignKey('SurvexBlock', blank=True, null=True,on_delete=models.SET_NULL)
     cave       = models.ForeignKey('Cave', blank=True, null=True,on_delete=models.SET_NULL)
     
diff --git a/core/views_caves.py b/core/views_caves.py
index dfcbf39..96efe9e 100644
--- a/core/views_caves.py
+++ b/core/views_caves.py
@@ -78,6 +78,8 @@ def caveKey(x):
     Note that cave kataster numbers are not generally integers.
     This needs to be fixed make a decent sort order.
     """ 
+    if not x.kataster_number:
+        return "~"
     return x.kataster_number
 
 def getnotablecaves():
diff --git a/databaseReset.py b/databaseReset.py
index bfb7244..76cc7ff 100644
--- a/databaseReset.py
+++ b/databaseReset.py
@@ -3,6 +3,7 @@ import os
 import time
 import timeit
 import json
+import resource
 
 import settings
 os.environ['PYTHONPATH'] = settings.PYTHON_PATH
@@ -11,13 +12,13 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
 print(" - settings on loading databaseReset.py")
 
 import django
-print(" - django.setup - next")
+print(" - Memory footprint before loading Django: {:.3f} MB".format(resource.getrusage(resource.RUSAGE_SELF)[2]/1024.0))
 try:
     django.setup()
 except:
     print(" ! Cyclic reference failure. Can occur when the initial db is empty. Fixed now (in UploadFileForm) but easy to reintroduce..")
     raise
-print(" - django.setup - done")
+print(" - Memory footprint after loading Django: {:.3f} MB".format(resource.getrusage(resource.RUSAGE_SELF)[2]/1024.0))
 
 import troggle.flatpages.models
 import troggle.core.models
@@ -194,7 +195,7 @@ class JobQueue():
             memend = get_process_memory()
             duration = time.time()-start
             #print(" - MEMORY start:{:.3f} MB end:{:.3f} MB change={:.3f} MB".format(memstart,memend, ))
-            print("\n*- Ended \"",  runfunction[0], "\"  {:.1f} seconds + {:.3f} MB".format(duration, memend-memstart))
+            print("\n*- Ended \"",  runfunction[0], "\"  {:.1f} seconds + {:.3f} MB ({:.3f} MB)".format(duration, memend-memstart, memend))
             self.results[runfunction[0]].pop()  # the null item
             self.results[runfunction[0]].append(duration)
                
@@ -307,7 +308,7 @@ if __name__ == "__main__":
         runlabel = sys.argv[len(sys.argv)-1]
     else: 
         runlabel=None
-
+        
     jq = JobQueue(runlabel)
     
     if len(sys.argv)==1:
diff --git a/parsers/caves.py b/parsers/caves.py
index d1e7406..2bb2ccc 100644
--- a/parsers/caves.py
+++ b/parsers/caves.py
@@ -192,6 +192,8 @@ def readcave(filename):
                          url = url[0],
                          filename = filename)
             except:
+                # this slow db query happens on every cave, but on import we have all this in memory
+                # and don't need to do a db query. Fix this to speed it up!
                 # need to cope with duplicates
                 print(" ! FAILED to get only one CAVE when updating using: "+filename)
                 kaves = models_caves.Cave.objects.all().filter(kataster_number=kataster_number[0])
@@ -206,6 +208,8 @@ def readcave(filename):
                         c = k
                 
             for area_slug in areas:
+                # this slow db query happens on every cave, but on import we have all this in memory
+                # and don't need to do a db query. Fix this to speed it up!
                 area = models_caves.Area.objects.filter(short_name = area_slug)
                 if area:
                     newArea = area[0]
@@ -216,6 +220,8 @@ def readcave(filename):
             primary = True
             for slug in slugs:
                 try:
+                    # this slow db query happens on every cave, but on import we have all this in memory
+                    # and don't need to do a db query. Fix this to speed it up!
                     cs = models_caves.CaveSlug.objects.update_or_create(cave = c,
                               slug = slug,
                               primary = primary)
@@ -225,10 +231,13 @@ def readcave(filename):
                     print(message)
                     
                 primary = False
+
             for entrance in entrances:
                 slug = getXML(entrance, "entranceslug", maxItems = 1, context = context)[0]
                 letter = getXML(entrance, "letter", maxItems = 1, context = context)[0]
                 try:
+                    # this slow db query happens on every entrance, but on import we have all this in memory
+                    # and don't need to do a db query. Fix this to speed it up!
                     entrance = models_caves.Entrance.objects.get(entranceslug__slug = slug)
                     ce = models_caves.CaveAndEntrance.objects.update_or_create(cave = c, entrance_letter = letter, entrance = entrance)
                 except:
diff --git a/parsers/imports.py b/parsers/imports.py
index c2965c2..f8c98a3 100644
--- a/parsers/imports.py
+++ b/parsers/imports.py
@@ -16,21 +16,21 @@ import troggle.parsers.logbooks
 import troggle.parsers.QMs
 
 def import_caves():
-    print("Importing Caves to ",end="")
+    print("-- Importing Caves to ",end="")
     print(django.db.connections.databases['default']['NAME'])
     troggle.parsers.caves.readcaves()
 
 def import_people():
-    print("Importing People (folk.csv) to ",end="")
+    print("-- Importing People (folk.csv) to ",end="")
     print(django.db.connections.databases['default']['NAME'])
     troggle.parsers.people.LoadPersonsExpos()
 
 def import_surveyscans():
-    print("Importing Survey Scans")
+    print("-- Importing Survey Scans")
     troggle.parsers.surveys.LoadListScans()
 
 def import_logbooks():
-    print("Importing Logbooks")
+    print("-- Importing Logbooks")
     troggle.parsers.logbooks.LoadLogbooks()
 
 def import_QMs():
@@ -40,7 +40,7 @@ def import_QMs():
 def import_survex():
     # when this import is moved to the top with the rest it all crashes horribly
     import troggle.parsers.survex 
-    print("Importing Survex Blocks")
+    print("-- Importing Survex Blocks")
     print(" - Survex Blocks")
     troggle.parsers.survex.LoadSurvexBlocks()
     print(" - Survex entrances x/y/z Positions")
@@ -53,6 +53,6 @@ def import_loadpos():
     troggle.parsers.survex.LoadPos()
 
 def import_drawingsfiles():
-    print("Importing Drawings files")
+    print("-- Importing Drawings files")
     troggle.parsers.surveys.LoadDrawingFiles()
 
diff --git a/parsers/logbooks.py b/parsers/logbooks.py
index c4f2c9c..b18e839 100644
--- a/parsers/logbooks.py
+++ b/parsers/logbooks.py
@@ -114,6 +114,7 @@ def EnterLogIntoDbase(date, place, title, text, trippeople, expedition, logtime_
     expeditionday = expedition.get_expedition_day(date)
     lookupAttribs={'date':date, 'title':title}
     # 'cave' is converted to a string doing this, which renders as the cave slug.
+    # but it is a db query which we should try to avoid - rewrite this
     nonLookupAttribs={'place':place, 'text':text, 'expedition':expedition, 'cave_slug':str(cave), 'slug':slugify(title)[:50], 'entry_type':entry_type}
     lbo, created=save_carefully(LogbookEntry, lookupAttribs, nonLookupAttribs)
 
@@ -356,6 +357,8 @@ def SetDatesFromLogbookEntries(expedition):
     Sets the date_from and date_to field for an expedition based on persontrips.
     Then sets the expedition date_from and date_to based on the personexpeditions.
     """
+    # Probably a faster way to do this. This uses a lot of db queries, but we have all this
+    # in memory..
     for personexpedition in expedition.personexpedition_set.all():
         persontrips = personexpedition.persontrip_set.order_by('logbook_entry__date')
         # sequencing is difficult to do
diff --git a/parsers/survex.py b/parsers/survex.py
index da0395d..7db8af0 100644
--- a/parsers/survex.py
+++ b/parsers/survex.py
@@ -324,7 +324,8 @@ class LoadingSurvex():
                     return self.caveslist[g]
             print('    ! Failed to find cave for {}'.format(cavepath.lower()))
         else:
-            print('    ! No regex cave match for %s' % cavepath.lower())
+            # not a cave, but that is fine.
+            # print('    ! No regex(standard identifier) cave match for %s' % cavepath.lower())
             return None
 
     def GetSurvexDirectory(self, headpath):
@@ -353,17 +354,17 @@ class LoadingSurvex():
         print("\n"+message,file=sys.stderr)
         models.DataIssue.objects.create(parser='survex', message=message)
         
-    def LoadSurvexFile(self, includelabel):
+    def LoadSurvexFile(self, svxid):
         """Creates SurvexFile in the database, and SurvexDirectory if needed
         with links to 'cave'
-        Creates a new current survexblock with valid .survexfile and valid .survexdirectory
+        Creates a new current survexfile and valid .survexdirectory
         The survexblock passed-in is not necessarily the parent. FIX THIS.
         """
         depth = " " * self.depthbegin
-        print("{:2}{}   - NEW survexfile:'{}'".format(self.depthbegin, depth, includelabel))
-        headpath, tail = os.path.split(includelabel)
+        print("{:2}{}   - NEW survexfile:'{}'".format(self.depthbegin, depth, svxid))
+        headpath = os.path.dirname(svxid)
 
-        newfile = models_survex.SurvexFile(path=includelabel)
+        newfile = models_survex.SurvexFile(path=svxid)
         newfile.save() # until we do this there is no internal id so no foreign key works
         self.currentsurvexfile = newfile 
         newdirectory = self.GetSurvexDirectory(headpath)
@@ -383,10 +384,10 @@ class LoadingSurvex():
             newfile.cave   = cave
             #print("\n"+str(newdirectory.cave),file=sys.stderr)
         else:
-            self.ReportNonCaveIncludes(headpath, includelabel)
+            self.ReportNonCaveIncludes(headpath, svxid)
 
         if not newfile.survexdirectory:
-            message = " ! SurvexDirectory NOT SET in new SurvexFile {} ".format(includelabel)
+            message = " ! SurvexDirectory NOT SET in new SurvexFile {} ".format(svxid)
             print(message)
             print(message,file=sys.stderr)
             models.DataIssue.objects.create(parser='survex', message=message)
@@ -401,7 +402,7 @@ class LoadingSurvex():
     def ProcessIncludeLine(self, included):
         svxid = included.groups()[0]
         #depth = " " * self.depthbegin
-        #print("{:2}{}   - Include survexfile:'{}'".format(self.depthbegin, depth,  svxid))
+        #print("{:2}{}   - Include survexfile:'{}' {}".format(self.depthbegin, depth,  svxid, included))
         self.LoadSurvexFile(svxid)
         self.stacksvxfiles.append(self.currentsurvexfile)
 
@@ -426,8 +427,10 @@ class LoadingSurvex():
             self.LoadSurvexQM(survexblock, qmline)
             
         included = self.rx_comminc.match(comment)
-        # ;*include means we have been included; not 'proceed to include' which *include means
+        # ;*include means 'we have been included'; whereas *include means 'proceed to include' 
         if included:
+            #depth = " " * self.depthbegin
+            #print("{:2}{}   - Include comment:'{}' {}".format(self.depthbegin, depth,  comment, included))
             self.ProcessIncludeLine(included)
 
         edulcni = self.rx_commcni.match(comment)
@@ -457,7 +460,7 @@ class LoadingSurvex():
 
     def LinearLoad(self, survexblock, path, svxlines):
         """Loads a single survex file. Usually used to import all the survex files which have been collated
-        into a single file. Loads the begin/end blocks recursively.
+        into a single file. Loads the begin/end blocks using a stack for labels.
         """
         self.relativefilename = path
         cave = self.IdentifyCave(path) # this will produce null for survex files which are geographic collections
@@ -466,19 +469,25 @@ class LoadingSurvex():
         self.currentsurvexfile.save() # django insists on this although it is already saved !?
         
         blockcount = 0
+        lineno = 0
         def tickle():
             nonlocal blockcount
             blockcount +=1
             if blockcount % 10 ==0 :
                 print(".", file=sys.stderr,end='')
-            if blockcount % 500 ==0 :
+            if blockcount % 200 ==0 :
                 print("\n", file=sys.stderr,end='')
-            sys.stderr.flush();
+                print(" - MEM:{:7.3f} MB in use".format(models.get_process_memory()),file=sys.stderr)
+            sys.stderr.flush()
 
         for svxline in svxlines:
-            sline, comment = self.rx_comment.match(svxline.strip()).groups()
+            lineno += 1
+            sline, comment = self.rx_comment.match(svxline).groups()
             if comment:
+                depth = " " * self.depthbegin
+                print("{:4} {:2}{}   - Include comment:'{}' {}".format(lineno, self.depthbegin, depth,  comment, sline))
                 self.LoadSurvexComment(survexblock, comment) # this catches the ;*include and ;*edulcni lines too
+
             if not sline:
                 continue # skip blank lines
 
@@ -503,10 +512,10 @@ class LoadingSurvex():
                             pathlist += "." + id
                     newsurvexblock = models_survex.SurvexBlock(name=blockid, parent=survexblock, 
                             survexpath=pathlist, 
-                            title = survexblock.title, # copy parent inititally
                             cave=self.currentcave, survexfile=self.currentsurvexfile, 
                             legsall=0, legssplay=0, legssurfc=0, totalleglength=0.0)
                     newsurvexblock.save()
+                    newsurvexblock.title = "("+survexblock.title+")" # copy parent inititally
                     survexblock = newsurvexblock
                     # survexblock.survexfile.save() 
                     survexblock.save() # django insists on this , but we want to save at the end !
@@ -564,7 +573,7 @@ class LoadingSurvex():
                 else:
                     pass # ignore all other sorts of data
 
-    def RecursiveScan(self, survexblock, survexfile, fin, flinear, fcollate):
+    def RecursiveScan(self, survexblock, path, fin, flinear, fcollate):
         """Follows the *include links in all the survex files from the root file 1623.svx
         and reads only the *include and *begin and *end statements. It produces a linearised
         list of the include tree
@@ -577,27 +586,27 @@ class LoadingSurvex():
         if self.callcount % 500 ==0 :
             print("\n", file=sys.stderr,end='')
 
-        if survexfile in self.svxfileslist:
-            message = " * Warning. Survex file already seen: {}".format(survexfile.path)
+        if path in self.svxfileslist:
+            message = " * Warning. Duplicate in *include list at:{} depth:{} file:{}".format(self.callcount, self.depthinclude, path)
             print(message)
             print(message,file=flinear)
-            print(message,file=sys.stderr)
+            print("\n"+message,file=sys.stderr)
             models.DataIssue.objects.create(parser='survex', message=message)
-            if self.svxfileslist.count(survexfile) > 20:
-                message = " ! ERROR. Survex file already seen 20x. Probably an infinite loop so fix your *include statements that include this. Aborting. {}".format(survexfile.path)
+            if self.svxfileslist.count(path) > 20:
+                message = " ! ERROR. Survex file already seen 20x. Probably an infinite loop so fix your *include statements that include this. Aborting. {}".format(path)
                 print(message)
                 print(message,file=flinear)
                 print(message,file=sys.stderr)
                 models.DataIssue.objects.create(parser='survex', message=message)
                 return
-        self.svxfileslist.append(survexfile)
+        self.svxfileslist.append(path)
         
         svxlines = fin.read().splitlines()
         for svxline in svxlines:
             self.lineno += 1
             includestmt =self.rx_include.match(svxline)
             if not includestmt:
-                fcollate.write("{}\n".format(svxline))
+                fcollate.write("{}\n".format(svxline.strip()))
 
             sline, comment = self.rx_comment.match(svxline.strip()).groups()
             mstar = self.rx_star.match(sline)
@@ -605,40 +614,35 @@ class LoadingSurvex():
                 cmd, args = mstar.groups()
                 cmd = cmd.lower()
                 if re.match("(?i)include$", cmd):
-                    includepath = os.path.normpath(os.path.join(os.path.split(survexfile.path)[0], re.sub(r"\.svx$", "", args)))
-                    path_match = re.search(r"caves-(\d\d\d\d)/(\d+|\d\d\d\d-?\w+-\d+)/", includepath)
+                    includepath = os.path.normpath(os.path.join(os.path.split(path)[0], re.sub(r"\.svx$", "", args)))
+                    #path_match = re.search(r"caves-(\d\d\d\d)/(\d+|\d\d\d\d-?\w+-\d+)/", includepath)
 
-                    includesurvexfile = models_survex.SurvexFile(path=includepath)
-                    includesurvexfile.save()
-
-                    if includesurvexfile.exists():
-                        # do not create SurvexFile in DB here by doing includesurvexfile.save(). Do it when reading data.
+                    fullpath = os.path.join(settings.SURVEX_DATA, includepath + ".svx")
+                    if os.path.isfile(fullpath):
                         #--------------------------------------------------------
                         self.depthinclude += 1
-                        fininclude = includesurvexfile.OpenFile()
-                        fcollate.write(";*include {}\n".format(includesurvexfile.path))
-                        flinear.write("{:2} {} *include {}\n".format(self.depthinclude, indent, includesurvexfile.path))
-                        push = includesurvexfile.path.lower()
+                        fininclude = open(fullpath,'r')
+                        fcollate.write(";*include {}\n".format(includepath))
+                        flinear.write("{:2} {} *include {}\n".format(self.depthinclude, indent, includepath))
+                        push = includepath.lower()
                         self.stackinclude.append(push)
                         #-----------------
-                        self.RecursiveScan(survexblock, includesurvexfile, fininclude, flinear, fcollate)
+                        self.RecursiveScan(survexblock, includepath, fininclude, flinear, fcollate)
                         #-----------------
                         pop = self.stackinclude.pop()
                         if pop != push:
-                            message = "!!!!!!!    ERROR pop != push {} != {} {}".format(pop, push, self.stackinclude)
+                            message = "!! ERROR mismatch *include pop!=push  {}".format(pop, push, self.stackinclude)
                             print(message)
                             print(message,file=flinear)
                             print(message,file=sys.stderr)
                             models.DataIssue.objects.create(parser='survex', message=message)
-                        includesurvexfile.path += "-TEMP"
-                        includesurvexfile = None
                         flinear.write("{:2} {} *edulcni {}\n".format(self.depthinclude, indent, pop))
                         fcollate.write(";*edulcni {}\n".format(pop))
                         fininclude.close()
                         self.depthinclude -= 1
                         #--------------------------------------------------------
                     else:
-                        message = "    ! ERROR *include file not found for [{}]:'{}'".format(includesurvexfile, includepath)
+                        message = "    ! ERROR *include file not found for:'{}'".format(includepath)
                         print(message)
                         print(message,file=sys.stderr)
                         models.DataIssue.objects.create(parser='survex', message=message)
@@ -659,7 +663,7 @@ class LoadingSurvex():
                         args = " "
                     popargs = self.stackbegin.pop()
                     if popargs != args.lower():
-                        message = "!!!!!!!    ERROR BEGIN/END pop != push {} != {}\n{}".format(popargs, args, self. stackbegin)
+                        message = "!! ERROR mismatch in BEGIN/END labels pop!=push '{}'!='{}'\n{}".format(popargs, args, self. stackbegin)
                         print(message)
                         print(message,file=flinear)
                         print(message,file=sys.stderr)
@@ -701,7 +705,7 @@ def FindAndLoadSurvex(survexblockroot):
     fcollate.write(";*include {}\n".format(survexfileroot.path))
     flinear.write("{:2} {} *include {}\n".format(svx_scan.depthinclude, indent, survexfileroot.path))
     #----------------------------------------------------------------
-    svx_scan.RecursiveScan(survexblockroot, survexfileroot, finroot, flinear, fcollate)
+    svx_scan.RecursiveScan(survexblockroot, survexfileroot.path, finroot, flinear, fcollate)
     #----------------------------------------------------------------
     flinear.write("{:2} {} *edulcni {}\n".format(svx_scan.depthinclude, indent, survexfileroot.path))
     fcollate.write(";*edulcni {}\n".format(survexfileroot.path))
@@ -712,7 +716,7 @@ def FindAndLoadSurvex(survexblockroot):
     flinear.write("    - {:,} survex files in linear include list \n".format(len(svxfileslist)))
     flinear.close()
     fcollate.close()
-    svx_scan = None
+    svx_scan = None # Hmm. Does this actually delete all the instance variables if they are lists, dicts etc.?
     print("\n -  {:,} survex files in linear include list \n".format(len(svxfileslist)),file=sys.stderr)
 
     mem1 = models.get_process_memory()
@@ -724,7 +728,7 @@ def FindAndLoadSurvex(survexblockroot):
     # entrance locations currently loaded after this by LoadPos(), but could better be done before ?
     # look in MapLocations() for how we find the entrances
    
-    print('\n - Loading All Survex Blocks (LinearRecursive)',file=sys.stderr)
+    print('\n - Loading All Survex Blocks (LinearLoad)',file=sys.stderr)
     svx_load = LoadingSurvex()
     
     svx_load.svxdirs[""] = survexfileroot.survexdirectory
diff --git a/troggle-inspectdb.py b/troggle-inspectdb.py
index e7f9401..041c454 100644
--- a/troggle-inspectdb.py
+++ b/troggle-inspectdb.py
@@ -17,6 +17,498 @@ from __future__ import unicode_literals
 from django.db import models
 
 
+class AuthGroup(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    name = models.CharField(unique=True, max_length=80)
+
+    class Meta:
+        managed = False
+        db_table = 'auth_group'
+# Unable to inspect table 'auth_group_permissions'
+# The error was: list index out of range
+
+
+class AuthPermission(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    content_type = models.ForeignKey('DjangoContentType', models.DO_NOTHING)
+    codename = models.CharField(max_length=100)
+    name = models.CharField(max_length=255)
+
+    class Meta:
+        managed = False
+        db_table = 'auth_permission'
+        unique_together = (('content_type', 'codename'),)
+
+
+class AuthUser(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    password = models.CharField(max_length=128)
+    last_login = models.DateTimeField(blank=True, null=True)
+    is_superuser = models.BooleanField()
+    first_name = models.CharField(max_length=30)
+    last_name = models.CharField(max_length=30)
+    email = models.CharField(max_length=254)
+    is_staff = models.BooleanField()
+    is_active = models.BooleanField()
+    date_joined = models.DateTimeField()
+    username = models.CharField(unique=True, max_length=150)
+
+    class Meta:
+        managed = False
+        db_table = 'auth_user'
+# Unable to inspect table 'auth_user_groups'
+# The error was: list index out of range
+# Unable to inspect table 'auth_user_user_permissions'
+# The error was: list index out of range
+
+
+class CoreArea(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    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('self', models.DO_NOTHING, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_area'
+
+
+class CoreCave(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    official_name = models.CharField(max_length=160)
+    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)
+    explorers = models.TextField(blank=True, null=True)
+    underground_description = models.TextField(blank=True, null=True)
+    equipment = models.TextField(blank=True, null=True)
+    references = models.TextField(blank=True, null=True)
+    survey = models.TextField(blank=True, null=True)
+    kataster_status = models.TextField(blank=True, null=True)
+    underground_centre_line = models.TextField(blank=True, null=True)
+    notes = models.TextField(blank=True, null=True)
+    length = models.CharField(max_length=100, blank=True, null=True)
+    depth = models.CharField(max_length=100, blank=True, null=True)
+    extent = models.CharField(max_length=100, blank=True, null=True)
+    survex_file = models.CharField(max_length=100, blank=True, null=True)
+    description_file = models.CharField(max_length=200, blank=True, null=True)
+    url = models.CharField(max_length=200, blank=True, null=True)
+    filename = models.CharField(max_length=200)
+
+    class Meta:
+        managed = False
+        db_table = 'core_cave'
+
+
+class CoreCaveArea(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    cave = models.ForeignKey(CoreCave, models.DO_NOTHING)
+    area = models.ForeignKey(CoreArea, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_cave_area'
+        unique_together = (('cave', 'area'),)
+
+
+class CoreCaveandentrance(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    entrance_letter = models.CharField(max_length=20, blank=True, null=True)
+    cave = models.ForeignKey(CoreCave, models.DO_NOTHING)
+    entrance = models.ForeignKey('CoreEntrance', models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_caveandentrance'
+
+
+class CoreCavedescription(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    short_name = models.CharField(unique=True, max_length=50)
+    long_name = models.CharField(max_length=200, blank=True, null=True)
+    description = models.TextField(blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_cavedescription'
+
+
+class CoreCavedescriptionLinkedEntrances(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    cavedescription = models.ForeignKey(CoreCavedescription, models.DO_NOTHING)
+    entrance = models.ForeignKey('CoreEntrance', models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_cavedescription_linked_entrances'
+        unique_together = (('cavedescription', 'entrance'),)
+
+
+class CoreCavedescriptionLinkedQms(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    cavedescription = models.ForeignKey(CoreCavedescription, models.DO_NOTHING)
+    qm = models.ForeignKey('CoreQm', models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_cavedescription_linked_qms'
+        unique_together = (('cavedescription', 'qm'),)
+
+
+class CoreCavedescriptionLinkedSubcaves(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    cavedescription = models.ForeignKey(CoreCavedescription, models.DO_NOTHING)
+    newsubcave = models.ForeignKey('CoreNewsubcave', models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_cavedescription_linked_subcaves'
+        unique_together = (('cavedescription', 'newsubcave'),)
+
+
+class CoreCaveslug(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    slug = models.CharField(unique=True, max_length=50)
+    primary = models.BooleanField()
+    cave = models.ForeignKey(CoreCave, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_caveslug'
+
+
+class CoreDataissue(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    date = models.DateTimeField()
+    parser = models.CharField(max_length=50, blank=True, null=True)
+    message = models.CharField(max_length=400, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_dataissue'
+
+
+class CoreEntrance(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    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 = models.CharField(max_length=2)
+    marking_comment = models.TextField(blank=True, null=True)
+    findability = models.CharField(max_length=1, blank=True, null=True)
+    findability_description = models.TextField(blank=True, null=True)
+    alt = models.TextField(blank=True, null=True)
+    northing = models.TextField(blank=True, null=True)
+    easting = models.TextField(blank=True, null=True)
+    tag_station = models.TextField(blank=True, null=True)
+    exact_station = models.TextField(blank=True, null=True)
+    other_station = models.TextField(blank=True, null=True)
+    other_description = models.TextField(blank=True, null=True)
+    bearings = models.TextField(blank=True, null=True)
+    url = models.CharField(max_length=200, blank=True, null=True)
+    filename = models.CharField(max_length=200)
+    cached_primary_slug = models.CharField(max_length=200, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_entrance'
+
+
+class CoreEntranceslug(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    slug = models.CharField(unique=True, max_length=50)
+    primary = models.BooleanField()
+    entrance = models.ForeignKey(CoreEntrance, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_entranceslug'
+
+
+class CoreExpedition(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    year = models.CharField(unique=True, max_length=20)
+    name = models.CharField(max_length=100)
+
+    class Meta:
+        managed = False
+        db_table = 'core_expedition'
+
+
+class CoreExpeditionday(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    date = models.DateField()
+    expedition = models.ForeignKey(CoreExpedition, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_expeditionday'
+
+
+class CoreLogbookentry(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    date = models.DateField()
+    title = models.CharField(max_length=200)
+    cave_slug = models.CharField(max_length=50, blank=True, null=True)
+    place = models.CharField(max_length=100, blank=True, null=True)
+    text = models.TextField()
+    slug = models.CharField(max_length=50)
+    filename = models.CharField(max_length=200, blank=True, null=True)
+    entry_type = models.CharField(max_length=50, blank=True, null=True)
+    expedition = models.ForeignKey(CoreExpedition, models.DO_NOTHING, blank=True, null=True)
+    expeditionday = models.ForeignKey(CoreExpeditionday, models.DO_NOTHING, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_logbookentry'
+
+
+class CoreNewsubcave(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    name = models.CharField(unique=True, max_length=200)
+
+    class Meta:
+        managed = False
+        db_table = 'core_newsubcave'
+
+
+class CoreOthercavename(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    name = models.CharField(max_length=160)
+    cave = models.ForeignKey(CoreCave, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_othercavename'
+
+
+class CorePerson(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    first_name = models.CharField(max_length=100)
+    last_name = models.CharField(max_length=100)
+    fullname = models.CharField(max_length=200)
+    is_vfho = models.BooleanField()
+    mug_shot = models.CharField(max_length=100, blank=True, null=True)
+    blurb = models.TextField(blank=True, null=True)
+    orderref = models.CharField(max_length=200)
+    user = models.ForeignKey(AuthUser, models.DO_NOTHING, unique=True, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_person'
+
+
+class CorePersonexpedition(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    slugfield = models.CharField(max_length=50, blank=True, null=True)
+    is_guest = models.BooleanField()
+    expo_committee_position = models.CharField(max_length=200, blank=True, null=True)
+    nickname = models.CharField(max_length=100, blank=True, null=True)
+    expedition = models.ForeignKey(CoreExpedition, models.DO_NOTHING)
+    person = models.ForeignKey(CorePerson, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_personexpedition'
+
+
+class CorePersontrip(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    time_underground = models.FloatField()
+    is_logbook_entry_author = models.BooleanField()
+    logbook_entry = models.ForeignKey(CoreLogbookentry, models.DO_NOTHING)
+    personexpedition = models.ForeignKey(CorePersonexpedition, models.DO_NOTHING, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_persontrip'
+
+
+class CoreQm(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    new_since_parsing = models.BooleanField()
+    non_public = models.BooleanField()
+    number = models.IntegerField()
+    grade = models.CharField(max_length=1)
+    location_description = models.TextField()
+    nearest_station_description = models.CharField(max_length=400, blank=True, null=True)
+    nearest_station_name = 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)
+    found_by = models.ForeignKey(CoreLogbookentry, models.DO_NOTHING, blank=True, null=True)
+    nearest_station = models.ForeignKey('CoreSurvexstation', models.DO_NOTHING, blank=True, null=True)
+    ticked_off_by = models.ForeignKey(CoreLogbookentry, models.DO_NOTHING, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_qm'
+
+
+class CoreScansfolder(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    fpath = models.CharField(max_length=200)
+    walletname = models.CharField(max_length=200)
+
+    class Meta:
+        managed = False
+        db_table = 'core_scansfolder'
+
+
+class CoreSinglescan(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    ffile = models.CharField(max_length=200)
+    name = models.CharField(max_length=200)
+    scansfolder = models.ForeignKey(CoreScansfolder, models.DO_NOTHING, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_singlescan'
+
+
+class CoreSurvexblock(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    name = models.CharField(max_length=100)
+    title = models.CharField(max_length=100)
+    date = models.DateField(blank=True, null=True)
+    survexpath = models.CharField(max_length=200)
+    legsall = models.IntegerField(blank=True, null=True)
+    legssplay = models.IntegerField(blank=True, null=True)
+    legssurfc = models.IntegerField(blank=True, null=True)
+    totalleglength = models.FloatField(blank=True, null=True)
+    cave = models.ForeignKey(CoreCave, models.DO_NOTHING, blank=True, null=True)
+    expedition = models.ForeignKey(CoreExpedition, models.DO_NOTHING, blank=True, null=True)
+    expeditionday = models.ForeignKey(CoreExpeditionday, models.DO_NOTHING, blank=True, null=True)
+    scansfolder = models.ForeignKey(CoreScansfolder, models.DO_NOTHING, blank=True, null=True)
+    survexfile = models.ForeignKey('CoreSurvexfile', models.DO_NOTHING, blank=True, null=True)
+    parent = models.ForeignKey('self', models.DO_NOTHING, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_survexblock'
+
+
+class CoreSurvexdirectory(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    path = models.CharField(max_length=200)
+    cave = models.ForeignKey(CoreCave, models.DO_NOTHING, blank=True, null=True)
+    primarysurvexfile = models.ForeignKey('CoreSurvexfile', models.DO_NOTHING, blank=True, null=True)
+
+    class Meta:
+        managed = False
+        db_table = 'core_survexdirectory'
+# Unable to inspect table 'core_survexfile'
+# The error was: list index out of range
+# Unable to inspect table 'core_survexpersonrole'
+# The error was: list index out of range
+# Unable to inspect table 'core_survexstation'
+# The error was: list index out of range
+# Unable to inspect table 'core_survextitle'
+# The error was: list index out of range
+
+
+class CoreTunnelfile(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    tunnelpath = models.CharField(max_length=200)
+    tunnelname = models.CharField(max_length=200)
+    bfontcolours = models.BooleanField()
+    filesize = models.IntegerField()
+    npaths = models.IntegerField()
+
+    class Meta:
+        managed = False
+        db_table = 'core_tunnelfile'
+
+
+class CoreTunnelfileManyscansfolders(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    tunnelfile = models.ForeignKey(CoreTunnelfile, models.DO_NOTHING)
+    scansfolder = models.ForeignKey(CoreScansfolder, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_tunnelfile_manyscansfolders'
+        unique_together = (('tunnelfile', 'scansfolder'),)
+
+
+class CoreTunnelfileScans(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    tunnelfile = models.ForeignKey(CoreTunnelfile, models.DO_NOTHING)
+    singlescan = models.ForeignKey(CoreSinglescan, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_tunnelfile_scans'
+        unique_together = (('tunnelfile', 'singlescan'),)
+
+
+class CoreTunnelfileSurvexfiles(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    tunnelfile = models.ForeignKey(CoreTunnelfile, models.DO_NOTHING)
+    survexfile = models.ForeignKey('CoreSurvexfile', models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_tunnelfile_survexfiles'
+        unique_together = (('tunnelfile', 'survexfile'),)
+
+
+class CoreTunnelfileTunnelcontains(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    from_tunnelfile = models.ForeignKey(CoreTunnelfile, models.DO_NOTHING)
+    to_tunnelfile = models.ForeignKey(CoreTunnelfile, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'core_tunnelfile_tunnelcontains'
+        unique_together = (('from_tunnelfile', 'to_tunnelfile'),)
+# Unable to inspect table 'django_admin_log'
+# The error was: list index out of range
+
+
+class DjangoContentType(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    app_label = models.CharField(max_length=100)
+    model = models.CharField(max_length=100)
+
+    class Meta:
+        managed = False
+        db_table = 'django_content_type'
+        unique_together = (('app_label', 'model'),)
+
+
 class DjangoMigrations(models.Model):
     id = models.IntegerField(primary_key=True)  # AutoField?
     app = models.CharField(max_length=255)
@@ -26,3 +518,43 @@ class DjangoMigrations(models.Model):
     class Meta:
         managed = False
         db_table = 'django_migrations'
+
+
+class DjangoSession(models.Model):
+    session_key = models.CharField(primary_key=True, max_length=40)
+    session_data = models.TextField()
+    expire_date = models.DateTimeField()
+
+    class Meta:
+        managed = False
+        db_table = 'django_session'
+
+
+class FlatpagesEntranceredirect(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    originalurl = models.CharField(db_column='originalURL', max_length=200)  # Field name made lowercase.
+    entrance = models.ForeignKey(CoreEntrance, models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+        db_table = 'flatpages_entranceredirect'
+
+
+class FlatpagesRedirect(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    originalurl = models.CharField(db_column='originalURL', unique=True, max_length=200)  # Field name made lowercase.
+    newurl = models.CharField(db_column='newURL', max_length=200)  # Field name made lowercase.
+
+    class Meta:
+        managed = False
+        db_table = 'flatpages_redirect'
+
+
+class RegistrationRegistrationprofile(models.Model):
+    id = models.IntegerField(primary_key=True)  # AutoField?
+    activation_key = models.CharField(max_length=40)
+    user = models.ForeignKey(AuthUser, models.DO_NOTHING, unique=True)
+
+    class Meta:
+        managed = False
+        db_table = 'registration_registrationprofile'