Code

Ticket #9364: handle_inherited_geofields.diff

File handle_inherited_geofields.diff, 10.1 KB (added by jbronn, 5 years ago)

Patch that allows GeoQuerySet methods to operate on inherited geometry fields; includes tests.

Line 
1Index: django/contrib/gis/db/models/query.py
2===================================================================
3--- django/contrib/gis/db/models/query.py       (revision 9286)
4+++ django/contrib/gis/db/models/query.py       (working copy)
5@@ -605,13 +605,19 @@
6         # set on the `GeoQuery` object of this queryset.
7         if aggregate: self.query.aggregate = True
8 
9-        # Is this operation going to be on a related geographic field?
10-        if not geo_field in self.model._meta.fields:
11+        opts = self.model._meta
12+        if not geo_field in opts.fields:
13+            # Is this operation going to be on a related geographic field?
14             # If so, it'll have to be added to the select related information
15             # (e.g., if 'location__point' was given as the field name).
16             self.query.add_select_related([field_name])
17             self.query.pre_sql_setup()
18             rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
19             return self.query._field_column(geo_field, rel_table)
20+        elif not geo_field in opts.local_fields:
21+            # This geographic field is inherited from another model, so we have to
22+            # use the db table for the _parent_ model instead.
23+            tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
24+            return self.query._field_column(geo_field, parent_model._meta.db_table)
25         else:
26             return self.query._field_column(geo_field)
27Index: django/contrib/gis/tests/geoapp/tests.py
28===================================================================
29--- django/contrib/gis/tests/geoapp/tests.py    (revision 9286)
30+++ django/contrib/gis/tests/geoapp/tests.py    (working copy)
31@@ -1,10 +1,10 @@
32 import os, unittest
33-from models import Country, City, State, Feature, MinusOneSRID
34+from models import Country, City, PennsylvaniaCity, State, Feature, MinusOneSRID
35 from django.contrib.gis import gdal
36 from django.contrib.gis.db.backend import SpatialBackend
37 from django.contrib.gis.geos import *
38 from django.contrib.gis.measure import Distance
39-from django.contrib.gis.tests.utils import no_oracle, no_postgis, oracle, postgis
40+from django.contrib.gis.tests.utils import no_oracle, no_postgis
41 
42 # TODO: Some tests depend on the success/failure of previous tests, these should
43 # be decoupled.  This flag is an artifact of this problem, and makes debugging easier;
44@@ -17,7 +17,7 @@
45     def test01_initial_sql(self):
46         "Testing geographic initial SQL."
47         if DISABLE: return
48-        if oracle:
49+        if SpatialBackend.oracle:
50             # Oracle doesn't allow strings longer than 4000 characters
51             # in SQL files, and I'm stumped on how to use Oracle BFILE's
52             # in PLSQL, so we set up the larger geometries manually, rather
53@@ -38,7 +38,7 @@
54         self.assertEqual(8, City.objects.count())
55 
56         # Oracle cannot handle NULL geometry values w/certain queries.
57-        if oracle: n_state = 2
58+        if SpatialBackend.oracle: n_state = 2
59         else: n_state = 3
60         self.assertEqual(n_state, State.objects.count())
61 
62@@ -147,7 +147,7 @@
63         ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo')
64         ptown2 = City.objects.gml(precision=9).get(name='Pueblo')
65 
66-        if oracle:
67+        if SpatialBackend.oracle:
68             # No precision parameter for Oracle :-/
69             import re
70             gml_regex = re.compile(r'<gml:Point srsName="SDO:4326" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="\." cs="," ts=" ">-104.60925199\d+,38.25500\d+ </gml:coordinates></gml:Point>')
71@@ -167,7 +167,7 @@
72 
73         # Asserting the result of the transform operation with the values in
74         #  the pre-transformed points.  Oracle does not have the 3084 SRID.
75-        if not oracle:
76+        if not SpatialBackend.oracle:
77             h = City.objects.transform(htown.srid).get(name='Houston')
78             self.assertEqual(3084, h.point.srid)
79             self.assertAlmostEqual(htown.x, h.point.x, prec)
80@@ -214,7 +214,7 @@
81         qs1 = City.objects.filter(point__disjoint=ptown.point)
82         self.assertEqual(7, qs1.count())
83 
84-        if not postgis:
85+        if not SpatialBackend.postgis:
86             # TODO: Do NULL columns bork queries on PostGIS?  The following
87             # error is encountered:
88             #  psycopg2.ProgrammingError: invalid memory alloc request size 4294957297
89@@ -231,7 +231,7 @@
90         # Seeing what cities are in Texas, should get Houston and Dallas,
91         #  and Oklahoma City because 'contained' only checks on the
92         #  _bounding box_ of the Geometries.
93-        if not oracle:
94+        if not SpatialBackend.oracle:
95             qs = City.objects.filter(point__contained=texas.mpoly)
96             self.assertEqual(3, qs.count())
97             cities = ['Houston', 'Dallas', 'Oklahoma City']
98@@ -259,7 +259,7 @@
99         self.assertEqual(0, len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
100 
101         # OK City is contained w/in bounding box of Texas.
102-        if not oracle:
103+        if not SpatialBackend.oracle:
104             qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
105             self.assertEqual(1, len(qs))
106             self.assertEqual('Texas', qs[0].name)
107@@ -272,7 +272,7 @@
108         wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
109 
110         # Oracle doesn't have SRID 3084, using 41157.
111-        if oracle:
112+        if SpatialBackend.oracle:
113             # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157)
114             # Used the following Oracle SQL to get this value:
115             #  SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL;
116@@ -287,7 +287,7 @@
117         # `SDO_OVERLAPBDYINTERSECT` operates differently from
118         # `ST_Intersects`, so contains is used instead.
119         nad_pnt = fromstr(nad_wkt, srid=nad_srid)
120-        if oracle:
121+        if SpatialBackend.oracle:
122             tx = Country.objects.get(mpoly__contains=nad_pnt)
123         else:
124             tx = Country.objects.get(mpoly__intersects=nad_pnt)
125@@ -329,7 +329,7 @@
126         self.assertEqual(True, 'Kansas' in state_names)
127 
128         # Saving another commonwealth w/a NULL geometry.
129-        if not oracle:
130+        if not SpatialBackend.oracle:
131             # TODO: Fix saving w/NULL geometry on Oracle.
132             State(name='Northern Mariana Islands', poly=None).save()
133 
134@@ -398,11 +398,11 @@
135             self.assertRaises(e, qs.count)
136 
137         # Relate works differently for the different backends.
138-        if postgis:
139+        if SpatialBackend.postgis:
140             contains_mask = 'T*T***FF*'
141             within_mask = 'T*F**F***'
142             intersects_mask = 'T********'
143-        elif oracle:
144+        elif SpatialBackend.oracle:
145             contains_mask = 'contains'
146             within_mask = 'inside'
147             # TODO: This is not quite the same as the PostGIS mask above
148@@ -417,7 +417,7 @@
149         self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
150 
151         # Testing intersection relation mask.
152-        if not oracle:
153+        if not SpatialBackend.oracle:
154             self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, intersects_mask)).name)
155             self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name)
156             self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
157@@ -479,7 +479,7 @@
158         "Testing the `centroid` GeoQuerySet method."
159         if DISABLE: return
160         qs = State.objects.exclude(poly__isnull=True).centroid()
161-        if oracle: tol = 0.1
162+        if SpatialBackend.oracle: tol = 0.1
163         else: tol = 0.000000001
164         for s in qs:
165             self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
166@@ -536,14 +536,14 @@
167         for c in City.objects.filter(point__isnull=False).num_geom():
168             # Oracle will return 1 for the number of geometries on non-collections,
169             # whereas PostGIS will return None.
170-            if postgis: self.assertEqual(None, c.num_geom)
171+            if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
172             else: self.assertEqual(1, c.num_geom)
173 
174     def test24_numpoints(self):
175         "Testing the `num_points` GeoQuerySet method."
176         if DISABLE: return
177         for c in Country.objects.num_points(): self.assertEqual(c.mpoly.num_points, c.num_points)
178-        if postgis:
179+        if SpatialBackend.postgis:
180             # Oracle cannot count vertices in Point geometries.
181             for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
182 
183@@ -558,6 +558,18 @@
184             self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
185             self.assertEqual(c.mpoly.union(geom), c.union)
186 
187+    def test26_inherited_geofields(self):
188+        "Test GeoQuerySet methods on inherited Geometry fields."
189+        # Creating a Pennsylvanian city.
190+        mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
191+
192+        # All transformation SQL will need to be performed on the
193+        # _parent_ table.
194+        qs = PennsylvaniaCity.objects.transform(32128)
195+       
196+        self.assertEqual(1, qs.count())
197+        for pc in qs: self.assertEqual(32128, pc.point.srid)
198+
199 from test_feeds import GeoFeedTest
200 from test_sitemaps import GeoSitemapTest
201 def suite():
202Index: django/contrib/gis/tests/geoapp/models.py
203===================================================================
204--- django/contrib/gis/tests/geoapp/models.py   (revision 9286)
205+++ django/contrib/gis/tests/geoapp/models.py   (working copy)
206@@ -16,6 +16,11 @@
207     objects = models.GeoManager()
208     def __unicode__(self): return self.name
209 
210+# This is an inherited model from City
211+class PennsylvaniaCity(City):
212+    county = models.CharField(max_length=30)
213+    objects = models.GeoManager() # TODO: This should be implicitly inherited.
214+
215 class State(models.Model):
216     name = models.CharField(max_length=30)
217     poly = models.PolygonField(null=null_flag) # Allowing NULL geometries here.