diff --git a/core/models/troggle.py b/core/models/troggle.py
index f1f10be..c41e6f0 100644
--- a/core/models/troggle.py
+++ b/core/models/troggle.py
@@ -24,7 +24,7 @@ import troggle.core.models.survex
 from troggle.core.utils import get_process_memory
 
 """This file declares TroggleModel which inherits from django.db.models.Model 
-All TroggleModel subclasses inherit persistence in the django relational database. This is known as
+All TroggleModel and models.Model subclasses inherit persistence in the django relational database. This is known as
 the django Object Relational Mapping (ORM).
 There are more subclasses define in models_caves.py models_survex.py etc.
 """
diff --git a/core/views/caves.py b/core/views/caves.py
index dd621aa..2b97aac 100644
--- a/core/views/caves.py
+++ b/core/views/caves.py
@@ -354,7 +354,7 @@ def edit_cave(request, slug=None):
                                 })
 
 @login_required_if_public
-def editEntrance(request, caveslug, slug=None):
+def edit_entrance(request, caveslug=None, slug=None):
     '''This is the form that edits the entrance data for a single entrance and writes out 
     an XML file in the :expoweb: repo folder
     The format for the file being saved is in templates/dataformat/entrance.xml
@@ -362,7 +362,10 @@ def editEntrance(request, caveslug, slug=None):
     It does save the data into into the database directly, not by parsing the file.
     '''
     message = ""
-    cave = Cave.objects.get(caveslug__slug = caveslug) 
+    if  caveslug is not None:
+        cave = Cave.objects.get(caveslug__slug = caveslug) 
+    else:
+        cave = Cave()
     if  slug is not None:
         entrance = Entrance.objects.get(entranceslug__slug = slug)
     else:
diff --git a/core/views/prospect.py b/core/views/prospect.py
index f7e10aa..880c8d0 100644
--- a/core/views/prospect.py
+++ b/core/views/prospect.py
@@ -13,6 +13,7 @@ from django.shortcuts import render
 import troggle.settings as settings
 from troggle.core.models.caves import Entrance, Area, SurvexStation, Cave
 from troggle.core.views.caves import caveKey
+from troggle.parsers.survex import MapLocations
 
 ''' Generates the prospecting guide document.
 
@@ -61,49 +62,7 @@ def prospecting(request):
         areas.append((name, a, caves))
     return render(request, 'prospecting.html', {"areas": areas})
     
-class MapLocations(object):
-    p = [
-        ("laser.0_7", "BNase", "Reference", "Bräuning Nase laser point"),
-        ("226-96", "BZkn", "Reference", "Bräuning Zinken trig point"),
-        ("vd1","VD1","Reference", "VD1 survey point"),
-        ("laser.kt114_96","HSK","Reference", "Hinterer Schwarzmooskogel trig point"), 
-        ("2000","Nipple","Reference", "Nipple (Weiße Warze)"),
-        ("3000","VSK","Reference", "Vorderer Schwarzmooskogel summit"),
-        ("topcamp", "OTC", "Reference", "Old Top Camp"),
-        ("laser.0", "LSR0", "Reference", "Laser Point 0"),
-        ("laser.0_1", "LSR1", "Reference", "Laser Point 0/1"),
-        ("laser.0_3", "LSR3", "Reference", "Laser Point 0/3"),
-        ("laser.0_5", "LSR5", "Reference", "Laser Point 0/5"),
-        ("225-96", "BAlm", "Reference", "Bräuning Alm trig point")
-    ]
-    def points(self):
-        for ent in Entrance.objects.all():
-            if ent.best_station():
-                try:
-                    k = ent.caveandentrance_set.all()[0].cave
-                except:
-                    message = " ! Failed to get Cave linked to Entrance:{} from:{} best:{}".format(ent.name, ent.filename, ent.best_station())
-                    DataIssue.objects.create(parser='entrances', message=message)
-                    print(message)
-                    raise
-                try:
-                    areaName = k.getArea().short_name
-                except:
-                    message = " ! Failed to get Area on cave '{}' linked to Entrance:{} from:{} best:{}".format(cave, ent.name, ent.filename, ent.best_station())
-                    DataIssue.objects.create(parser='entrances', message=message)
-                    print(message)
-                    raise
-                self.p.append((ent.best_station(), "%s-%s" % (areaName, str(ent)[5:]), ent.needs_surface_work(), str(ent)))
-        return self.p
-
-    def __str__(self):
-        return "{} map locations".format(len(self.p))
         
-
-
-
-
-
 # Parameters for big map and zoomed subarea maps:
 # big map first (zoom factor ignored)
 
@@ -143,6 +102,7 @@ R = 2
 B = 3
 ZOOM = 4
 DESC = 5
+SIZE = 5
 
 areacolours = {
 	'1a' : '#00ffff',
@@ -170,7 +130,7 @@ TEXTSIZE = 16
 CIRCLESIZE =8
 LINEWIDTH = 2
 myFont = ImageFont.truetype(FONT, TEXTSIZE)
-print(f' - myFont {myFont} {FONT} {TEXTSIZE}')
+#print(f' - myFont {myFont} {FONT} {TEXTSIZE}')
 
 def mungecoord(x, y, mapcode, img):
 	# Top of Zinken is 73 1201 = dataset 34542 81967
@@ -194,41 +154,24 @@ COL_TYPES = {True: "red",
              False: "#dddddd",
              "Reference": "#dddddd"}
 	
-def plot(surveypoint, number,  point_type, label, mapcode, draw, img):
-    try:
-        ss = SurvexStation.objects.lookup(surveypoint)
-        E, N = ss.x, ss.y
-        shortnumber = number.replace("—","")
-        (x,y) = list(map(int, mungecoord(E, N, mapcode, img)))
-        imgmaps[maparea].append( [x-4, y-SIZE/2, x+4+draw.textsize(shortnumber)[0], y+SIZE/2, shortnumber, label] )
-        draw.rectangle([(x+CIRCLESIZE, y-TEXTSIZE/2), (x+CIRCLESIZE*2+draw.textsize(shortnumber)[0], y+TEXTSIZE/2)], fill="#ffffff")
-        draw.text((x+CIRCLESIZE * 1.5,y-TEXTSIZE/2), shortnumber, fill="#000000")
-        draw.ellipse([(x-CIRCLESIZE,y-CIRCLESIZE),(x+CIRCLESIZE,y+CIRCLESIZE)], fill=COL_TYPES[point_type], outline="#000000")
-    except:
-        pass
 
 def prospecting_image(request, name):
-    # We should replace all this with something that exports an overlay for Google Maps and OpenStreetView
+    '''This draws map outlines on an existing map image.
+    But getting the entrances plotted is broken by later changes elsewhere in the system since this code was written.
 
-    # test = os.path.join(settings.EXPOFILES, "location_maps", "testmap.png")
-    # response = HttpResponse(content_type = "image/png")
-    # with Image.open(test) as im:
-
-        # draw = ImageDraw.Draw(im)
-        # draw.line((0, 0) + im.size, fill=128)
-        # draw.line((0, im.size[1], im.size[0], 0), fill=128)
-
-        # # write to stdout
-        # #im.save(sys.stdout, "PNG")
-        # img.save(response, "PNG")
+    SurvexStations are in x=latitude, y=longitude - these are what appear in essentials.gpx 
+    Entrances are in northing, easting
     
-    # return response    
+    which is why we can't simply plot all the Entrances...
+    
+    We should replace all this with something that exports an overlay for Google Maps and OpenStreetView    
+    '''
     
     mainImage = Image.open(os.path.join(settings.EXPOFILES, "location_maps", "pguidemap.jpg"))
     # if settings.PUBLIC_SITE and not request.user.is_authenticated:
         # mainImage = Image.new("RGB", mainImage.size, '#ffffff')
     m = maps[name]
-    #imgmaps = []
+    imgmaps = []
     if name == "all":
         img = mainImage
     else:
@@ -255,7 +198,7 @@ def prospecting_image(request, name):
             textlen = draw.textsize(text)[0] + 3
             draw.rectangle([l, t, l+textlen, t+TEXTSIZE+2], fill='#ffffff')
             draw.text((l+2, t+1), text, fill="#000000", font=myFont)
-            #imgmaps.append( [l, t, l+textlen, t+SIZE+2, "submap" + maparea, maparea + " subarea map"] )
+            imgmaps.append( [l, t, l+textlen, t+SIZE+2, "submap" + maparea, maparea + " subarea map"] )
             draw.line([l, t, r, t], fill='#777777', width=LINEWIDTH)
             draw.line([l, b, r, b], fill='#777777', width=LINEWIDTH)
             draw.line([l, t, l, b], fill='#777777', width=LINEWIDTH)
@@ -265,6 +208,7 @@ def prospecting_image(request, name):
             draw.line([l, t, l, t+TEXTSIZE+2], fill='#777777', width=LINEWIDTH)
             draw.line([l+textlen, t, l+textlen, t+TEXTSIZE+2], fill='#777777', width=LINEWIDTH)
     #imgmaps[maparea] = []
+    
     # Draw scale bar
     m100 = int(100 / (m[R] - m[L]) * img.size[0])
     draw.line([10, TEXTSIZE*3, 10, TEXTSIZE*2], fill='#000000', width=LINEWIDTH)
@@ -273,11 +217,8 @@ def prospecting_image(request, name):
     label = "100m"
     draw.text([10 + (m100 - draw.textsize(label)[0]) / 2, TEXTSIZE/2], label, fill='#000000', font=myFont)
 
-    ml = MapLocations()
-    for p in ml.points():
-        surveypoint, number,  point_type, label = p
-        plot(surveypoint, number,  point_type, label, name, draw, img)
-
+    # Draw the circles for known points 
+    # Northing, Easting, Diameter - but N&E are swapped re database
     for (N, E, D, num) in [(35975.37,    83018.21,    100,   "177"),    # Calculated from bearings
                            (35350.00,    81630.00,    50,    "71"),    # From Auer map
                            (36025.00,    82475.00,    50,    "146"),    # From mystery map
@@ -297,8 +238,66 @@ def prospecting_image(request, name):
         draw.ellipse([lo[0]+2, lo[1]+2, hi[0]-2, hi[1]-2], outline=areacolours[maparea])
         draw.rectangle([lpos[0],lpos[1]-TEXTSIZE/2, lpos[0] + draw.textsize(name)[0], lpos[1]+TEXTSIZE/2], fill="#ffffff")
         draw.text((lpos[0], lpos[1]-TEXTSIZE/2), num, fill="#000000")
+        #print(f' CIRCLES - {num} {(N,E,D)}')
+  
+    # ml = MapLocations()
+    # for p in ml.points():
+        # surveypoint, number,  point_type, label = p
+        # print(f'{surveypoint}, {number},  {point_type}, {label}')
+        # plot(surveypoint, number,  True, label, name, draw, img)
+    # print(f'{name},\n{draw},\n{img}')
+    
+    ents = Entrance.objects.all() # only has entrances and fixed points in it these days, 
+    # but there are only 11 Entrances with northing, easting and a useable tag!
+    D = 50
+    for e in ents:
+        try:
+            E, N = e.easting, e.northing
+            if e.tag_station:
+                st = e.tag_station
+            elif e.exact_station:
+                st = e.exact_station
+            elif e.exact_station:
+                st = e.exact_station
+            else:
+                # print(f' No tag    - {e.name}   ')
+                continue
+            if not e.northing:
+                continue
+            lo = mungecoord(N-D, E+D, st, img)
+            hi = mungecoord(N+D, E-D, st, img)
+            lpos = mungecoord(N-D, E, st, img)
+            
+            draw.ellipse([lo,hi], outline="#000000")
+            draw.ellipse([lo[0]+1, lo[1]+1, hi[0]-1, hi[1]-1], outline="#ffffff")
+            draw.ellipse([lo[0]+2, lo[1]+2, hi[0]-2, hi[1]-2], outline="#ffffff")
+            draw.rectangle([lpos[0],lpos[1]-TEXTSIZE/2, lpos[0] + draw.textsize(st)[0], lpos[1]+TEXTSIZE/2], fill="#ffffff")
+            draw.text((lpos[0], lpos[1]-TEXTSIZE/2), num, fill="#000000")
+     
+            # draw.ellipse([(x-CIRCLESIZE,y-CIRCLESIZE),(x+CIRCLESIZE,y+CIRCLESIZE)], fill="red", outline="blue")
+            # draw.rectangle([(x+CIRCLESIZE, y-TEXTSIZE/2), (x+CIRCLESIZE*2+draw.textsize(shortnumber)[0], y+TEXTSIZE/2)], fill="#ffffff")
+            # draw.text((x+CIRCLESIZE * 1.5,y-TEXTSIZE/2), shortnumber, fill="#000000")
+            #print(f' SUCCESS - {st} {(E, N)}   ')
+        except:
+            #print(f' FAIL    - {st}  {(E, N)} ')
+            pass
+
     response = HttpResponse(content_type = "image/png")
     del draw
     img.save(response, "PNG")
-    
-    return response
\ No newline at end of file
+    return response
+
+# def plot(surveypoint, number,  point_type, label, mapcode, draw, img):
+    # try:
+        # ss = SurvexStation.objects.lookup(surveypoint)
+        # E, N = ss.x, ss.y
+        # shortnumber = number.replace("—","")
+        # (x,y) = list(map(int, mungecoord(E, N, mapcode, img)))
+        # imgmaps[maparea].append( [x-4, y-SIZE/2, x+4+draw.textsize(shortnumber)[0], y+SIZE/2, shortnumber, label] )
+        # draw.rectangle([(x+CIRCLESIZE, y-TEXTSIZE/2), (x+CIRCLESIZE*2+draw.textsize(shortnumber)[0], y+TEXTSIZE/2)], fill="#ffffff")
+        # draw.text((x+CIRCLESIZE * 1.5,y-TEXTSIZE/2), shortnumber, fill="#000000")
+        # draw.ellipse([(x-CIRCLESIZE,y-CIRCLESIZE),(x+CIRCLESIZE,y+CIRCLESIZE)], fill=COL_TYPES[point_type], outline="#000000")
+        # print(f' SUCCESS - YES {surveypoint}, {number},  {point_type}, {label}')
+    # except:
+        # print(f'         - NO  {surveypoint}, {number},  {point_type}, {label}')
+        # pass
diff --git a/parsers/survex.py b/parsers/survex.py
index af154b7..a073686 100644
--- a/parsers/survex.py
+++ b/parsers/survex.py
@@ -12,11 +12,10 @@ from django.utils.timezone import get_current_timezone
 from django.utils.timezone import make_aware
 
 import troggle.settings as settings
-import troggle.core.models.caves as models_caves
+from troggle.core.models.caves import Entrance, QM
 from troggle.core.utils import get_process_memory, chaosmonkey
 from troggle.parsers.people import GetPersonExpeditionNameLookup
 from troggle.parsers.logbooks import GetCaveLookup
-from troggle.core.views.prospect import MapLocations
 from troggle.core.models.troggle import DataIssue, Expedition
 from troggle.core.models.survex import SurvexPersonRole, ScansFolder, SurvexDirectory, SurvexFile, SurvexBlock, SurvexStation
 
@@ -32,6 +31,46 @@ debugprint = False # Turns on debug printout for just one *include file
 debugprinttrigger = "!"
 # debugprinttrigger = "caves-1623/40/old/EisSVH"
 
+class MapLocations(object):
+    p = [
+        ("laser.0_7", "BNase", "Reference", "Bräuning Nase laser point"),
+        ("226-96", "BZkn", "Reference", "Bräuning Zinken trig point"),
+        ("vd1","VD1","Reference", "VD1 survey point"),
+        ("laser.kt114_96","HSK","Reference", "Hinterer Schwarzmooskogel trig point"), 
+        ("2000","Nipple","Reference", "Nipple (Weiße Warze)"),
+        ("3000","VSK","Reference", "Vorderer Schwarzmooskogel summit"),
+        ("topcamp", "OTC", "Reference", "Old Top Camp"),
+        ("laser.0", "LSR0", "Reference", "Laser Point 0"),
+        ("laser.0_1", "LSR1", "Reference", "Laser Point 0/1"),
+        ("laser.0_3", "LSR3", "Reference", "Laser Point 0/3"),
+        ("laser.0_5", "LSR5", "Reference", "Laser Point 0/5"),
+        ("225-96", "BAlm", "Reference", "Bräuning Alm trig point")
+    ]
+    def points(self):
+        for ent in Entrance.objects.all():
+            if ent.best_station():
+                try:
+                    k = ent.caveandentrance_set.all()[0].cave
+                except:
+                    message = " ! Failed to get Cave linked to Entrance:{} from:{} best:{}".format(ent.name, ent.filename, ent.best_station())
+                    DataIssue.objects.create(parser='entrances', message=message)
+                    print(message)
+                    raise
+                try:
+                    areaName = k.getArea().short_name
+                except:
+                    message = " ! Failed to get Area on cave '{}' linked to Entrance:{} from:{} best:{}".format(cave, ent.name, ent.filename, ent.best_station())
+                    DataIssue.objects.create(parser='entrances', message=message)
+                    print(message)
+                    raise
+                self.p.append((ent.best_station(), "%s-%s" % (areaName, str(ent)[5:]), ent.needs_surface_work(), str(ent)))
+        message = f" -  {len(self.p)} entrances linked to caves."
+        print(message)
+        return self.p
+
+    def __str__(self):
+        return "{} map locations".format(len(self.p))
+
 class SurvexLeg():
     """No longer a models.Model subclass, so no longer a database table
     """
@@ -468,7 +507,7 @@ class LoadingSurvex():
         # NB none of the SurveyStations are in the DB now, so if we want to link to aSurvexStation
         # we would have to create one. But that is not obligatory and no QMs loaded from CSVs have one
         try:
-            qm = models_caves.QM.objects.create(number=qm_no,
+            qm = QM.objects.create(number=qm_no,
                                             # nearest_station=a_survex_station_object, # can be null
                                               nearest_station_description=qm_resolve_station,
                                               nearest_station_name=qm_nearest,
@@ -747,7 +786,7 @@ class LoadingSurvex():
         path_match = re.search(r"caves-(\d\d\d\d)/(\d+|\d\d\d\d-?\w+-\d+)/", survexblock.survexfile.path)
         if path_match:
             pos_cave = '%s-%s' % (path_match.group(1), path_match.group(2))
-            cave = models_caves.getCaveByReference(pos_cave)
+            cave = getCaveByReference(pos_cave)
             if cave:
                 survexfile.cave = cave
 
@@ -1369,5 +1408,5 @@ def LoadPositions():
                         print(message)
                         DataIssue.objects.create(parser='survex', message=message)
                         raise
-    print(" - {} SurvexStation entrances found.".format(found))
+    print(" -  {} SurvexStation entrances found.".format(found))
 
diff --git a/templates/caveindex.html b/templates/caveindex.html
index b4c4ee3..d4443d4 100644
--- a/templates/caveindex.html
+++ b/templates/caveindex.html
@@ -28,6 +28,7 @@
 
   <p style="text-align:right">
     <a href="{% url "newcave" %}">New Cave</a><br>
+    <a href="{% url "newentrance" %}">New Entrance</a><br>
     <a href="/noinfo/cave-number-index">Cave Number Index - kept updated</a>
   </p>
  <h3>1623</h3>
diff --git a/urls.py b/urls.py
index 26eacbb..74e6737 100644
--- a/urls.py
+++ b/urls.py
@@ -120,8 +120,8 @@ trogglepatterns = [
     url(r'^(?P<karea>162\d)(?P<subpath>.*)$',     cavepage,     name="cavepage"), # shorthand /1623/264 BUT url links may break
 
 #   Entrances
-    url(r'^entrance/(?P<caveslug>[^/]+)/(?P<slug>[^/]+)/edit/', caves.editEntrance, name = "editentrance"),
-    url(r'^entrance/new/(?P<caveslug>[^/]+)/', caves.editEntrance, name = "newentrance"), # NOT WORKING
+    url(r'^entrance/new/$', caves.edit_entrance, name = "newentrance"), 
+    url(r'^entrance/(?P<caveslug>[^/]+)/(?P<slug>[^/]+)/edit/', caves.edit_entrance, name = "editentrance"),
     
 
     url(r'^statistics/?$',  statistics.stats, name="stats"),