Repository: GeotrekCE/Geotrek-admin

Issue:
Intervention with geometry null fails at edition
```

  File "/home/adminpnm/Geotrek-0.20.2/lib/eggs/django_floppyforms-1.0-py2.7.egg/floppyforms/widgets.py", line 93, in render
    context = self.get_context(name, value, attrs=attrs or {}, **kwargs)

  File "/home/adminpnm/Geotrek-0.20.2/geotrek/core/widgets.py", line 67, in get_context
    topologyjson = value.serialize()

  File "/home/adminpnm/Geotrek-0.20.2/geotrek/core/models.py", line 362, in serialize
    return TopologyHelper.serialize(self)

  File "/home/adminpnm/Geotrek-0.20.2/geotrek/core/helpers.py", line 149, in serialize
    geom = topology.geom_as_point()

  File "/home/adminpnm/Geotrek-0.20.2/geotrek/core/models.py", line 271, in geom_as_point
    assert geom, 'Topology point is None'

```



Discussion:

Resolving patch:
diff --git a/geotrek/core/models.py b/geotrek/core/models.py
--- a/geotrek/core/models.py
+++ b/geotrek/core/models.py
@@ -6,7 +6,7 @@
 from django.contrib.gis.db import models
 from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
-from django.contrib.gis.geos import LineString, Point
+from django.contrib.gis.geos import fromstr, LineString, Point
 
 from mapentity.models import MapEntityMixin
 from mapentity.helpers import is_file_newer, convertit_download
@@ -281,6 +281,11 @@ def add_path(self, path, start=0.0, end=1.0, order=0, reload=True):
                                              start_position=start,
                                              end_position=end,
                                              order=order)
+
+        if self.deleted:
+            self.deleted = False
+            self.save(update_fields=['deleted'])
+
         # Since a trigger modifies geom, we reload the object
         if reload:
             self.reload()
@@ -301,6 +306,8 @@ def mutate(self, other, delete=True):
         self.geom = other.geom
         self.save()
         PathAggregation.objects.filter(topo_object=self).delete()
+        # The previous operation has put deleted = True (in triggers)
+        self.deleted = False
         aggrs = other.aggregations.all()
         # A point has only one aggregation, except if it is on an intersection.
         # In this case, the trigger will create them, so ignore them here.
@@ -335,12 +342,18 @@ def save(self, *args, **kwargs):
         # but they can be changed at DB level. Since Django write all fields
         # to DB anyway, it is important to update it before writting
         if self.pk:
-            tmp = self.__class__.objects.get(pk=self.pk)
-            self.length = tmp.length
+            existing = self.__class__.objects.get(pk=self.pk)
+            self.length = existing.length
             # In the case of points, the geom can be set by Django. Don't override.
-            if (self.ispoint() and self.geom is None) or \
-               (not self.ispoint() and tmp.geom is not None):
-                self.geom = tmp.geom
+            point_geom_not_set = self.ispoint() and self.geom is None
+            geom_already_in_db = not self.ispoint() and existing.geom is not None
+            if (point_geom_not_set or geom_already_in_db):
+                self.geom = existing.geom
+        else:
+            if not self.deleted and self.geom is None:
+                # We cannot have NULL geometry. So we use an empty one,
+                # it will be computed or overwritten by triggers.
+                self.geom = fromstr('POINT (0 0)')
 
         if not self.kind:
             if self.KIND == "TOPOLOGYMIXIN":


Test patch:
diff --git a/geotrek/core/tests/topology.py b/geotrek/core/tests/topology.py
--- a/geotrek/core/tests/topology.py
+++ b/geotrek/core/tests/topology.py
@@ -1,6 +1,7 @@
+import json
+
 from django.test import TestCase
 from django.conf import settings
-import json
 from django.contrib.gis.geos import Point, LineString
 
 from geotrek.common.utils import dbnow, almostequal
@@ -10,6 +11,13 @@
 
 
 class TopologyTest(TestCase):
+
+    def test_geom_null_is_safe(self):
+        t = TopologyFactory.create()
+        t.geom = None
+        t.save()
+        self.assertNotEqual(t.geom, None)
+
     def test_dates(self):
         t1 = dbnow()
         e = TopologyFactory.build(no_path=True)
@@ -68,6 +76,17 @@ def test_delete(self):
         self.assertNotIn(topology, Topology.objects.existing())
         self.assertEqual(len(path.topology_set.existing()), 0)
 
+    def test_deleted_when_all_path_are_deleted(self):
+        topology = TopologyFactory.create()
+        self.assertFalse(topology.deleted)
+
+        paths = path = topology.paths.all()
+        for path in paths:
+            path.delete()
+
+        topology.reload()
+        self.assertTrue(topology.deleted)
+
     def test_mutate(self):
         topology1 = TopologyFactory.create(no_path=True)
         self.assertEqual(len(topology1.paths.all()), 0)
@@ -635,16 +654,14 @@ def test_deserialize_serialize(self):
         path = PathFactory.create(geom=LineString((1, 1), (2, 2), (2, 0)))
         before = TopologyFactory.create(offset=1, no_path=True)
         before.add_path(path, start=0.5, end=0.5)
-        # Reload from DB
-        before = Topology.objects.get(pk=before.pk)
 
         # Deserialize its serialized version !
         after = Topology.deserialize(before.serialize())
-        # Reload from DB
-        after = Topology.objects.get(pk=after.pk)
 
         self.assertEqual(len(before.paths.all()), len(after.paths.all()))
-        self.assertTrue(almostequal(before.aggregations.all()[0].start_position,
-                                    after.aggregations.all()[0].start_position))
-        self.assertTrue(almostequal(before.aggregations.all()[0].end_position,
-                                    after.aggregations.all()[0].end_position))
+        start_before = before.aggregations.all()[0].start_position
+        end_before = before.aggregations.all()[0].end_position
+        start_after = after.aggregations.all()[0].start_position
+        end_after = after.aggregations.all()[0].end_position
+        self.assertTrue(almostequal(start_before, start_after), '%s != %s' % (start_before, start_after))
+        self.assertTrue(almostequal(end_before, end_after), '%s != %s' % (end_before, end_after))
diff --git a/geotrek/trekking/tests/tests.py b/geotrek/trekking/tests/tests.py
--- a/geotrek/trekking/tests/tests.py
+++ b/geotrek/trekking/tests/tests.py
@@ -261,10 +261,8 @@ def test_poi_geojson_translation(self):
         poi.name_fr = "Chapelle"
         poi.name_it = "Capela"
         poi.save()
-        PathAggregationFactory.create(topo_object=trek, path=p1,
-                                      start_position=0.5)
-        PathAggregationFactory.create(topo_object=poi, path=p1,
-                                      start_position=0.6, end_position=0.6)
+        trek.add_path(p1, start=0.5)
+        poi.add_path(p1, start=0.6, end=0.6)
         # Check that it applies to GeoJSON also :
         self.assertEqual(len(trek.pois), 1)
         poi = trek.pois[0]