django-gisserver 1.4.1__tar.gz → 2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {django-gisserver-1.4.1 → django-gisserver-2.0}/CHANGES.md +83 -1
  2. {django-gisserver-1.4.1/django_gisserver.egg-info → django-gisserver-2.0}/PKG-INFO +13 -3
  3. {django-gisserver-1.4.1 → django-gisserver-2.0}/README.md +6 -0
  4. {django-gisserver-1.4.1 → django-gisserver-2.0/django_gisserver.egg-info}/PKG-INFO +13 -3
  5. {django-gisserver-1.4.1 → django-gisserver-2.0}/django_gisserver.egg-info/SOURCES.txt +23 -10
  6. {django-gisserver-1.4.1 → django-gisserver-2.0}/django_gisserver.egg-info/requires.txt +2 -2
  7. django-gisserver-2.0/gisserver/__init__.py +1 -0
  8. django-gisserver-2.0/gisserver/compat.py +23 -0
  9. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/conf.py +7 -0
  10. django-gisserver-2.0/gisserver/db.py +149 -0
  11. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/exceptions.py +47 -9
  12. django-gisserver-2.0/gisserver/extensions/__init__.py +4 -0
  13. {django-gisserver-1.4.1/gisserver/parsers/fes20 → django-gisserver-2.0/gisserver/extensions}/functions.py +11 -5
  14. django-gisserver-2.0/gisserver/extensions/queries.py +261 -0
  15. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/features.py +267 -240
  16. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/geometries.py +34 -39
  17. django-gisserver-2.0/gisserver/management/commands/loadgeojson.py +291 -0
  18. django-gisserver-2.0/gisserver/operations/__init__.py +0 -0
  19. django-gisserver-2.0/gisserver/operations/base.py +262 -0
  20. django-gisserver-2.0/gisserver/operations/wfs20.py +578 -0
  21. django-gisserver-2.0/gisserver/output/__init__.py +35 -0
  22. django-gisserver-2.0/gisserver/output/base.py +268 -0
  23. django-gisserver-2.0/gisserver/output/csv.py +172 -0
  24. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/output/geojson.py +63 -72
  25. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/output/gml32.py +310 -281
  26. django-gisserver-1.4.1/gisserver/output/utils.py → django-gisserver-2.0/gisserver/output/iters.py +48 -4
  27. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/output/results.py +71 -30
  28. django-gisserver-2.0/gisserver/output/stored.py +143 -0
  29. django-gisserver-2.0/gisserver/output/utils.py +84 -0
  30. django-gisserver-2.0/gisserver/output/xmlschema.py +161 -0
  31. django-gisserver-2.0/gisserver/parsers/__init__.py +13 -0
  32. django-gisserver-2.0/gisserver/parsers/ast.py +320 -0
  33. django-gisserver-2.0/gisserver/parsers/fes20/__init__.py +28 -0
  34. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/parsers/fes20/expressions.py +89 -50
  35. django-gisserver-2.0/gisserver/parsers/fes20/filters.py +165 -0
  36. django-gisserver-2.0/gisserver/parsers/fes20/identifiers.py +112 -0
  37. django-gisserver-2.0/gisserver/parsers/fes20/lookups.py +144 -0
  38. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/parsers/fes20/operators.py +336 -128
  39. django-gisserver-2.0/gisserver/parsers/fes20/sorting.py +146 -0
  40. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/parsers/gml/__init__.py +12 -11
  41. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/parsers/gml/base.py +6 -3
  42. django-gisserver-2.0/gisserver/parsers/gml/geometries.py +134 -0
  43. django-gisserver-2.0/gisserver/parsers/ows/__init__.py +25 -0
  44. django-gisserver-2.0/gisserver/parsers/ows/kvp.py +190 -0
  45. django-gisserver-2.0/gisserver/parsers/ows/requests.py +158 -0
  46. django-gisserver-2.0/gisserver/parsers/query.py +175 -0
  47. django-gisserver-2.0/gisserver/parsers/values.py +66 -0
  48. django-gisserver-2.0/gisserver/parsers/wfs20/__init__.py +37 -0
  49. django-gisserver-2.0/gisserver/parsers/wfs20/adhoc.py +245 -0
  50. django-gisserver-2.0/gisserver/parsers/wfs20/base.py +143 -0
  51. django-gisserver-2.0/gisserver/parsers/wfs20/projection.py +103 -0
  52. django-gisserver-2.0/gisserver/parsers/wfs20/requests.py +482 -0
  53. django-gisserver-2.0/gisserver/parsers/wfs20/stored.py +192 -0
  54. django-gisserver-2.0/gisserver/parsers/xml.py +249 -0
  55. django-gisserver-2.0/gisserver/projection.py +357 -0
  56. django-gisserver-2.0/gisserver/static/gisserver/index.css +15 -0
  57. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/templates/gisserver/index.html +1 -1
  58. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/templates/gisserver/service_description.html +2 -2
  59. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +11 -11
  60. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/templates/gisserver/wfs/feature_field.html +2 -2
  61. django-gisserver-2.0/gisserver/templatetags/__init__.py +0 -0
  62. django-gisserver-2.0/gisserver/templatetags/gisserver_tags.py +30 -0
  63. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/types.py +375 -258
  64. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/views.py +206 -75
  65. {django-gisserver-1.4.1 → django-gisserver-2.0}/pyproject.toml +5 -4
  66. {django-gisserver-1.4.1 → django-gisserver-2.0}/setup.py +5 -4
  67. django-gisserver-1.4.1/gisserver/__init__.py +0 -1
  68. django-gisserver-1.4.1/gisserver/db.py +0 -146
  69. django-gisserver-1.4.1/gisserver/operations/base.py +0 -438
  70. django-gisserver-1.4.1/gisserver/operations/wfs20.py +0 -486
  71. django-gisserver-1.4.1/gisserver/output/__init__.py +0 -73
  72. django-gisserver-1.4.1/gisserver/output/base.py +0 -213
  73. django-gisserver-1.4.1/gisserver/output/csv.py +0 -176
  74. django-gisserver-1.4.1/gisserver/output/xmlschema.py +0 -122
  75. django-gisserver-1.4.1/gisserver/parsers/__init__.py +0 -13
  76. django-gisserver-1.4.1/gisserver/parsers/base.py +0 -149
  77. django-gisserver-1.4.1/gisserver/parsers/fes20/__init__.py +0 -24
  78. django-gisserver-1.4.1/gisserver/parsers/fes20/filters.py +0 -97
  79. django-gisserver-1.4.1/gisserver/parsers/fes20/identifiers.py +0 -94
  80. django-gisserver-1.4.1/gisserver/parsers/fes20/query.py +0 -275
  81. django-gisserver-1.4.1/gisserver/parsers/fes20/sorting.py +0 -73
  82. django-gisserver-1.4.1/gisserver/parsers/gml/geometries.py +0 -100
  83. django-gisserver-1.4.1/gisserver/parsers/tags.py +0 -102
  84. django-gisserver-1.4.1/gisserver/parsers/values.py +0 -40
  85. django-gisserver-1.4.1/gisserver/queries/__init__.py +0 -34
  86. django-gisserver-1.4.1/gisserver/queries/adhoc.py +0 -181
  87. django-gisserver-1.4.1/gisserver/queries/base.py +0 -146
  88. django-gisserver-1.4.1/gisserver/queries/stored.py +0 -205
  89. django-gisserver-1.4.1/gisserver/static/gisserver/index.css +0 -4
  90. django-gisserver-1.4.1/gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
  91. django-gisserver-1.4.1/gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
  92. django-gisserver-1.4.1/gisserver/templatetags/gisserver_tags.py +0 -10
  93. {django-gisserver-1.4.1 → django-gisserver-2.0}/LICENSE +0 -0
  94. {django-gisserver-1.4.1 → django-gisserver-2.0}/MANIFEST.in +0 -0
  95. {django-gisserver-1.4.1 → django-gisserver-2.0}/django_gisserver.egg-info/dependency_links.txt +0 -0
  96. {django-gisserver-1.4.1 → django-gisserver-2.0}/django_gisserver.egg-info/not-zip-safe +0 -0
  97. {django-gisserver-1.4.1 → django-gisserver-2.0}/django_gisserver.egg-info/top_level.txt +0 -0
  98. {django-gisserver-1.4.1/gisserver/operations → django-gisserver-2.0/gisserver/management}/__init__.py +0 -0
  99. {django-gisserver-1.4.1/gisserver/templatetags → django-gisserver-2.0/gisserver/management/commands}/__init__.py +0 -0
  100. {django-gisserver-1.4.1 → django-gisserver-2.0}/gisserver/templates/gisserver/wfs/feature_type.html +0 -0
  101. {django-gisserver-1.4.1 → django-gisserver-2.0}/setup.cfg +0 -0
@@ -1,3 +1,85 @@
1
+ # 2024-04-28 (2.0)
2
+
3
+ * Added support for XML POST requests.
4
+ * Added support for geometry elements on child nodes.
5
+ * Added support for Django 5 `GeneratedField` fields as geometry field.
6
+ * Added `WFSView.check_permissions()` API to simplify permission checking.
7
+ * Added `WFSView.xml_namespace_aliases` attribute to configure custom namespace prefixes.
8
+ * Added example app and docker-compose setup for testing.
9
+ * Added `GISSERVER_EXTRA_OUTPUT_FORMATS` setting to define additional output formats.
10
+ * Added `GISSERVER_GET_FEATURE_OUTPUT_FORMATS` setting to override the default output formats.
11
+ * Added `CRS84` and `WEB_MERCATOR` constants in `gisserver.geometries`.
12
+ * Improved debugging by adding debug-level logging and better error messages.
13
+ * Improved font styling somewhat for browsable HTML pages.
14
+ * Fixed XML namespace support (e.g. handling `<ValueReference>ns0:tagname</ValueReference>`).
15
+ * Fixed bugs found by CITE compliance testing:
16
+ * Support `<fes:PropertyIsLike>` for array elements.
17
+ * Support `<fes:PropertyIsNil>` for geometry elements.
18
+ * Support `resulttype=hits` with `count` arguments.
19
+ * Fixed `ArrayField` detection when `django.contrib.postgres` is not in `INSTALLED_APPS`.
20
+ * Fixed `main_geometry_element` detection.
21
+ * Fixed swapped X/Y coordinates for systems that use a different axis-ordering (e.g. EPSG:3879 for Finland).
22
+ * Fixed GeoJSON CRS value to be `CRS84` (urn:ogc:def:crs:OGC::CRS84) instead of WGS84 (urn:ogc:def:crs:EPSG::4326).
23
+ * Fixed applying the `CRS.backend` when the client requests a custom coordinate system using `srsName`.
24
+ * Fixed rendering JSON exceptions during streaming errors.
25
+ * Fixed internal XML Schema; use proper model CamelCasing for class names (doesn't affect requests).
26
+ * Fixed internal XML schema; remove unneeded inheritance from `gml:AbstractFeatureType` for nested elements.
27
+ * Fixed CI testing.
28
+ * Confirmed support for Python 3.13.
29
+
30
+ This release has a lot of API changes, renamed and moved classes,
31
+ which was needed to implement POST support and XML namespace handling.
32
+
33
+ This won't affect most projects as they use the basic `FeatureType` functionality.
34
+ For implementations that have taken full advantage of our architecture,
35
+ the notable API changes are:
36
+
37
+ * Configuration:
38
+ * `WFSOperation.output_formats` still works, but `get_output_formats()` is preferred.
39
+ * `WFSOperation.parameters` is replaced by ``get_parameters()`` and only lists parameters that need to be mentioned in `GetCapabilities`.
40
+ * The `Parameter` class only exposes choices, parsing happens in `gisserver.parsers.wfs20` now.
41
+ * `FeatureType.xml_namespace` now defines which namespace the feature exists in (defaults to `WFSView.xml_namespace`).
42
+ * Overiding and extending queries:
43
+ * `FeatureType.get_extent()` was replaced with ``GmlBoundedByElement.get_value()``.
44
+ * `gisserver.operations`: renamed `WFSMethod` -> `WFSOperation`.
45
+ * `gisserver.extensions.functions` now tracks filter function registration.
46
+ * `gisserver.extensions.queries` now tracks stored query registration.
47
+ * The `StoredQuery` base class is now `StoredQueryImplementation` providing a `build_query()` method.
48
+ * The internal `CompiledQuery` moved to `gisserver.parsers.query` and receives a `feature_types` array now with a single element.
49
+ (This change reflects the WFS spec and allowing to potentially implement JOIN queries later).
50
+ * Request parsing:
51
+ * `WFSView.ows_request` and `request.ows_request` both provide access to the parsed WFS request.
52
+ * `WFSOperation.parser_class` allows to define a custom parser for a additional WFS operations.
53
+ * The WFS request parsing moved to `gisserver.parsers.wfs20`, which builds an Abstract Syntax Tree (AST) of the XML request.
54
+ * The GET request (KVP format) parsing is now a special-case of the XML-based parsing classes.
55
+ * Output formats:
56
+ * `gisserver.output` no longer exposes the auto-switching DB/non-DB rendering aliases, as `get_output_formats()` can do that easier.
57
+ * `OutputRenderer` only provides the basic XML aliasing, a new `CollectionOutputRenderer` base class provides the collection logic.
58
+ * `OutputRenderer.xml_namespaces` allows defining XML namespace aliases that construct default `xmlns` attributes.
59
+ * All rendering parameters (e.g. output CRS) moved to the `FeatureProjection` logic.
60
+ * The `decorate_queryset()` logic is no longer a classmethod.
61
+ * Internal XSD schema elements:
62
+ * XML tag parsing is reworked for simplicity and namespace handling.
63
+ * Namespace aliases/prefixes are removed entirely, and resolved during rendering.
64
+ * `FeatureType.xml_name` now returns the full XML name, not the QName.
65
+ * `XsdTypes` use fully qualified XML names, not the QName.
66
+ * `XsdElement.xml_name` now returns the full XML name, not the QName.
67
+ * `XsdElement.is_geometry` was unneeded, use `XsdElement.type.is_geometry` now.
68
+ * `XsdElement.orm_path` points to the absolute path, and `XsdElement.local_orm_path` to the relative path.
69
+
70
+
71
+ # 2024-11-25 (1.5.0)
72
+
73
+ * Added `PROPERTYNAME` support
74
+ * Added rendering of `<wfs:truncatedResponse>` when errors happen during output streaming.
75
+ * Fixed accessing a feature with 3 geometry fields (fixed our PostgreSQL `ST_Union` syntax).
76
+ * Make sure Django 4.1+ won't do double prefetches on relations that we prefetch.
77
+ * Hide the erroneous output formats in `GetCapabilities` that are supported for FME.
78
+ * Bump requirements to non-vulnerable versions (of lxml and orjson).
79
+ * Improved documentation.
80
+ * Cleaned up code, removing some internal methods.
81
+ * Cleaned up leftover Python 3.7 compat code.
82
+
1
83
  # 2024-08-29 (1.4.1)
2
84
 
3
85
  * Fix 500 error when `model_attribute` points to a dotted-path.
@@ -197,7 +279,7 @@ for their work on this release.
197
279
  # 2020-06-29 (0.8.1)
198
280
 
199
281
  * Added unpaginated GeoJSON support (performance is good enough).
200
- * Added basic support for non-namespaced `FILTER` queries, and `PropertyName` tags (though being WFS2 attributes, clients still use them).
282
+ * Added basic support for non-namespaced `FILTER` queries, and `PropertyName` tags (though being WFS1 attributes, clients still use them).
201
283
  * Added extra strict check that `ValueReference`/`Literal`/`ResourceId` nodes don't have child nodes.
202
284
  * Fixed allowing filtering on unknown or undefined fields (XPaths are now resolved to known elements).
203
285
  * Optimized results streaming by automatically using a queryset-iterator if possible.
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-gisserver
3
- Version: 1.4.1
3
+ Version: 2.0
4
4
  Summary: Django speaking WFS 2.0 (exposing GeoDjango model fields)
5
5
  Home-page: https://github.com/amsterdam/django-gisserver
6
6
  Author: Diederik van der Boor
7
7
  Author-email: opensource@edoburu.nl
8
8
  License: Mozilla Public License 2.0
9
- Classifier: Development Status :: 4 - Beta
9
+ Platform: UNKNOWN
10
+ Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: Environment :: Web Environment
11
12
  Classifier: Intended Audience :: Developers
12
13
  Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
@@ -16,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.9
16
17
  Classifier: Programming Language :: Python :: 3.10
17
18
  Classifier: Programming Language :: Python :: 3.11
18
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
19
21
  Classifier: Framework :: Django
20
22
  Classifier: Framework :: Django :: 3.2
21
23
  Classifier: Framework :: Django :: 4.0
@@ -27,7 +29,7 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
27
29
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
28
30
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
31
  Requires: Django (>=3.2)
30
- Requires-Python: >=3.8
32
+ Requires-Python: >=3.9
31
33
  Description-Content-Type: text/markdown
32
34
  Provides-Extra: tests
33
35
  License-File: LICENSE
@@ -56,6 +58,12 @@ Django speaking WFS 2.0 to expose geo data.
56
58
 
57
59
  For more details, see: <https://django-gisserver.readthedocs.io/>
58
60
 
61
+ ## Test drive
62
+
63
+ * Run `docker compose up`.
64
+ * Open http://localhost:8000/ to see this works.
65
+ * Connect to http://localhost:8000/wfs/ in your GIS client (e.g. QGis) and zoom to Amsterdam.
66
+ * Add more data on the map using the Django admin, username and password are both "admin".
59
67
 
60
68
  ## Quickstart
61
69
 
@@ -158,3 +166,5 @@ software developed with public money is also publicly available.
158
166
 
159
167
  This package is initially developed by the City of Amsterdam, but the tools
160
168
  and concepts created in this project can be used in any city.
169
+
170
+
@@ -22,6 +22,12 @@ Django speaking WFS 2.0 to expose geo data.
22
22
 
23
23
  For more details, see: <https://django-gisserver.readthedocs.io/>
24
24
 
25
+ ## Test drive
26
+
27
+ * Run `docker compose up`.
28
+ * Open http://localhost:8000/ to see this works.
29
+ * Connect to http://localhost:8000/wfs/ in your GIS client (e.g. QGis) and zoom to Amsterdam.
30
+ * Add more data on the map using the Django admin, username and password are both "admin".
25
31
 
26
32
  ## Quickstart
27
33
 
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-gisserver
3
- Version: 1.4.1
3
+ Version: 2.0
4
4
  Summary: Django speaking WFS 2.0 (exposing GeoDjango model fields)
5
5
  Home-page: https://github.com/amsterdam/django-gisserver
6
6
  Author: Diederik van der Boor
7
7
  Author-email: opensource@edoburu.nl
8
8
  License: Mozilla Public License 2.0
9
- Classifier: Development Status :: 4 - Beta
9
+ Platform: UNKNOWN
10
+ Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: Environment :: Web Environment
11
12
  Classifier: Intended Audience :: Developers
12
13
  Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
@@ -16,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.9
16
17
  Classifier: Programming Language :: Python :: 3.10
17
18
  Classifier: Programming Language :: Python :: 3.11
18
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
19
21
  Classifier: Framework :: Django
20
22
  Classifier: Framework :: Django :: 3.2
21
23
  Classifier: Framework :: Django :: 4.0
@@ -27,7 +29,7 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
27
29
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
28
30
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
31
  Requires: Django (>=3.2)
30
- Requires-Python: >=3.8
32
+ Requires-Python: >=3.9
31
33
  Description-Content-Type: text/markdown
32
34
  Provides-Extra: tests
33
35
  License-File: LICENSE
@@ -56,6 +58,12 @@ Django speaking WFS 2.0 to expose geo data.
56
58
 
57
59
  For more details, see: <https://django-gisserver.readthedocs.io/>
58
60
 
61
+ ## Test drive
62
+
63
+ * Run `docker compose up`.
64
+ * Open http://localhost:8000/ to see this works.
65
+ * Connect to http://localhost:8000/wfs/ in your GIS client (e.g. QGis) and zoom to Amsterdam.
66
+ * Add more data on the map using the Django admin, username and password are both "admin".
59
67
 
60
68
  ## Quickstart
61
69
 
@@ -158,3 +166,5 @@ software developed with public money is also publicly available.
158
166
 
159
167
  This package is initially developed by the City of Amsterdam, but the tools
160
168
  and concepts created in this project can be used in any city.
169
+
170
+
@@ -11,13 +11,21 @@ django_gisserver.egg-info/not-zip-safe
11
11
  django_gisserver.egg-info/requires.txt
12
12
  django_gisserver.egg-info/top_level.txt
13
13
  gisserver/__init__.py
14
+ gisserver/compat.py
14
15
  gisserver/conf.py
15
16
  gisserver/db.py
16
17
  gisserver/exceptions.py
17
18
  gisserver/features.py
18
19
  gisserver/geometries.py
20
+ gisserver/projection.py
19
21
  gisserver/types.py
20
22
  gisserver/views.py
23
+ gisserver/extensions/__init__.py
24
+ gisserver/extensions/functions.py
25
+ gisserver/extensions/queries.py
26
+ gisserver/management/__init__.py
27
+ gisserver/management/commands/__init__.py
28
+ gisserver/management/commands/loadgeojson.py
21
29
  gisserver/operations/__init__.py
22
30
  gisserver/operations/base.py
23
31
  gisserver/operations/wfs20.py
@@ -26,35 +34,40 @@ gisserver/output/base.py
26
34
  gisserver/output/csv.py
27
35
  gisserver/output/geojson.py
28
36
  gisserver/output/gml32.py
37
+ gisserver/output/iters.py
29
38
  gisserver/output/results.py
39
+ gisserver/output/stored.py
30
40
  gisserver/output/utils.py
31
41
  gisserver/output/xmlschema.py
32
42
  gisserver/parsers/__init__.py
33
- gisserver/parsers/base.py
34
- gisserver/parsers/tags.py
43
+ gisserver/parsers/ast.py
44
+ gisserver/parsers/query.py
35
45
  gisserver/parsers/values.py
46
+ gisserver/parsers/xml.py
36
47
  gisserver/parsers/fes20/__init__.py
37
48
  gisserver/parsers/fes20/expressions.py
38
49
  gisserver/parsers/fes20/filters.py
39
- gisserver/parsers/fes20/functions.py
40
50
  gisserver/parsers/fes20/identifiers.py
51
+ gisserver/parsers/fes20/lookups.py
41
52
  gisserver/parsers/fes20/operators.py
42
- gisserver/parsers/fes20/query.py
43
53
  gisserver/parsers/fes20/sorting.py
44
54
  gisserver/parsers/gml/__init__.py
45
55
  gisserver/parsers/gml/base.py
46
56
  gisserver/parsers/gml/geometries.py
47
- gisserver/queries/__init__.py
48
- gisserver/queries/adhoc.py
49
- gisserver/queries/base.py
50
- gisserver/queries/stored.py
57
+ gisserver/parsers/ows/__init__.py
58
+ gisserver/parsers/ows/kvp.py
59
+ gisserver/parsers/ows/requests.py
60
+ gisserver/parsers/wfs20/__init__.py
61
+ gisserver/parsers/wfs20/adhoc.py
62
+ gisserver/parsers/wfs20/base.py
63
+ gisserver/parsers/wfs20/projection.py
64
+ gisserver/parsers/wfs20/requests.py
65
+ gisserver/parsers/wfs20/stored.py
51
66
  gisserver/static/gisserver/index.css
52
67
  gisserver/templates/gisserver/index.html
53
68
  gisserver/templates/gisserver/service_description.html
54
69
  gisserver/templates/gisserver/wfs/feature_field.html
55
70
  gisserver/templates/gisserver/wfs/feature_type.html
56
- gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml
57
71
  gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml
58
- gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml
59
72
  gisserver/templatetags/__init__.py
60
73
  gisserver/templatetags/gisserver_tags.py
@@ -1,12 +1,12 @@
1
1
  Django>=3.2
2
2
  defusedxml>=0.6.0
3
3
  lru_dict>=1.1.7
4
- orjson>=2.4.0
4
+ orjson>=3.9.15
5
5
 
6
6
  [tests]
7
7
  django-environ>=0.4.5
8
8
  psycopg2-binary>=2.8.4
9
- lxml>=4.5.0
9
+ lxml>=4.9.1
10
10
  pytest>=6.2.3
11
11
  pytest-django>=4.1.0
12
12
  pytest-cov>=2.11.1
@@ -0,0 +1 @@
1
+ __version__ = "2.0" # follows PEP440
@@ -0,0 +1,23 @@
1
+ """Compatability imports"""
2
+
3
+ import sys
4
+
5
+ import django
6
+ from django.conf import settings
7
+ from django.db import models
8
+
9
+ if (
10
+ "django.contrib.postgres" in settings.INSTALLED_APPS
11
+ or "django.contrib.postgres" in sys.modules
12
+ ):
13
+ from django.contrib.postgres.fields import ArrayField
14
+ else:
15
+ ArrayField = None
16
+
17
+ GeneratedField = models.GeneratedField if django.VERSION >= (5, 0) else None
18
+
19
+
20
+ __all__ = (
21
+ "ArrayField",
22
+ "GeneratedField",
23
+ )
@@ -28,6 +28,13 @@ GISSERVER_SUPPORTED_CRS_ONLY = getattr(settings, "GISSERVER_SUPPORTED_CRS_ONLY",
28
28
  # 0 = No counting, 1 = all pages, 2 = only for the first page.
29
29
  GISSERVER_COUNT_NUMBER_MATCHED = getattr(settings, "GISSERVER_COUNT_NUMBER_MATCHED", 1)
30
30
 
31
+ # -- output rendering
32
+
33
+ GISSERVER_EXTRA_OUTPUT_FORMATS = getattr(settings, "GISSERVER_EXTRA_OUTPUT_FORMATS", {})
34
+ GISSERVER_GET_FEATURE_OUTPUT_FORMATS = getattr(
35
+ settings, "GISSERVER_GET_FEATURE_OUTPUT_FORMATS", {}
36
+ )
37
+
31
38
  # -- max page size
32
39
 
33
40
  # Allow tuning the page size without having to override code.
@@ -0,0 +1,149 @@
1
+ """Internal module for additional GIS database functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from functools import lru_cache, reduce
7
+
8
+ from django.contrib.gis.db.models import functions
9
+ from django.db import connection, connections, models
10
+
11
+ from gisserver import conf
12
+ from gisserver.geometries import CRS
13
+ from gisserver.types import GeometryXsdElement
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class AsEWKT(functions.GeoFunc):
19
+ """Generate EWKT in the database (PostGIS tested only at the moment)."""
20
+
21
+ name = "AsEWKT"
22
+ output_field = models.TextField()
23
+
24
+ def __init__(self, field, precision=conf.GISSERVER_DB_PRECISION, **extra):
25
+ if connection.ops.spatial_version >= (3, 1):
26
+ super().__init__(field, precision, **extra)
27
+ else:
28
+ super().__init__(field, **extra)
29
+
30
+
31
+ class AsGML(functions.AsGML):
32
+ name = "AsGML"
33
+
34
+ def __init__(
35
+ self,
36
+ expression,
37
+ version=3,
38
+ precision=conf.GISSERVER_DB_PRECISION,
39
+ envelope=False,
40
+ **extra,
41
+ ):
42
+ # Note that Django's AsGml, version=2, precision=8
43
+ # the options is postgres-only.
44
+ super().__init__(expression, version, precision, **extra)
45
+ self.envelope = envelope
46
+
47
+ def as_postgresql(self, compiler, connection, **extra_context):
48
+ # Fill options parameter (https://postgis.net/docs/ST_AsGML.html)
49
+ options = 33 if self.envelope else 1 # 32 = bbox, 1 = long CRS urn
50
+ template = f"%(function)s(%(expressions)s, {options})"
51
+ return self.as_sql(compiler, connection, template=template, **extra_context)
52
+
53
+
54
+ class ST_Union(functions.Union):
55
+ name = "Union"
56
+ arity = None
57
+
58
+ def as_postgresql(self, compiler, connection, **extra_context):
59
+ # PostgreSQL can handle ST_Union(ARRAY[field names]), other databases don't.
60
+ if len(self.source_expressions) > 2:
61
+ extra_context["template"] = "%(function)s(ARRAY[%(expressions)s])"
62
+ return self.as_sql(compiler, connection, **extra_context)
63
+
64
+
65
+ def get_geometries_union(
66
+ expressions: list[str | functions.GeoFunc], using="default"
67
+ ) -> str | functions.Union:
68
+ """Generate a union of multiple geometry fields."""
69
+ if not expressions:
70
+ raise ValueError("Missing geometry fields for get_geometries_union()")
71
+
72
+ if len(expressions) == 1:
73
+ return next(iter(expressions)) # fastest in set data type
74
+ elif len(expressions) == 2:
75
+ return functions.Union(*expressions)
76
+ elif connections[using].vendor == "postgresql":
77
+ # postgres can handle multiple field names
78
+ return ST_Union(*expressions)
79
+ else:
80
+ # other databases do Union(Union(1, 2), 3)
81
+ return reduce(functions.Union, expressions)
82
+
83
+
84
+ def replace_queryset_geometries(
85
+ queryset: models.QuerySet,
86
+ geo_elements: list[GeometryXsdElement],
87
+ output_crs: CRS,
88
+ wrapper_func: type[functions.GeoFunc],
89
+ ) -> models.QuerySet:
90
+ """Replace the queryset geometry retrieval with a database-rendered version.
91
+
92
+ This uses absolute paths in the queryset, but can use relative paths for related querysets.
93
+ """
94
+ defer_names = []
95
+ as_geo_map = {}
96
+ for geo_element in geo_elements:
97
+ if geo_element.source is not None: # excludes GmlBoundedByElement
98
+ defer_names.append(geo_element.local_orm_path)
99
+ annotation_name = _as_annotation_name(geo_element.local_orm_path, wrapper_func)
100
+ as_geo_map[annotation_name] = wrapper_func(
101
+ get_db_geometry_target(geo_element, output_crs, use_relative_path=True)
102
+ )
103
+
104
+ if not defer_names:
105
+ return queryset
106
+
107
+ logger.debug(
108
+ "DB rendering: QuerySet for %s replacing %r with %r",
109
+ queryset.model._meta.label,
110
+ defer_names,
111
+ list(as_geo_map.keys()),
112
+ )
113
+ return queryset.defer(*defer_names).annotate(**as_geo_map)
114
+
115
+
116
+ def get_db_rendered_geometry(
117
+ instance: models.Model, geo_element: GeometryXsdElement, replacement: type[functions.GeoFunc]
118
+ ) -> str:
119
+ """Retrieve the database-rendered geometry.
120
+ This includes formatted EWKT or GML output, rendered by the database.
121
+ """
122
+ annotation_name = _as_annotation_name(geo_element.local_orm_path, replacement)
123
+ try:
124
+ return getattr(instance, annotation_name)
125
+ except AttributeError as e:
126
+ prefix = _as_annotation_name("", replacement)
127
+ available = ", ".join(key for key in instance.__dict__ if key.startswith(prefix)) or "none"
128
+ raise AttributeError(
129
+ f" DB annotation {instance._meta.label}.{annotation_name} not found. Found {available}."
130
+ ) from e
131
+
132
+
133
+ @lru_cache
134
+ def _as_annotation_name(name: str, func: type[functions.GeoFunc]) -> str:
135
+ """Escape an XML name to be used as annotation name."""
136
+ return f"_{func.__name__}_{name.replace('.', '_')}"
137
+
138
+
139
+ def get_db_geometry_target(
140
+ geo_element: GeometryXsdElement, output_crs: CRS, use_relative_path: bool = False
141
+ ) -> str | functions.Transform:
142
+ """Translate a GML geometry field into the proper expression for retrieving it from the database.
143
+ The path will be wrapped into a CRS Transform function if needed.
144
+ """
145
+ orm_path = geo_element.local_orm_path if use_relative_path else geo_element.orm_path
146
+ if geo_element.source_srid != output_crs.srid:
147
+ return functions.Transform(orm_path, srid=output_crs.srid)
148
+ else:
149
+ return orm_path
@@ -8,14 +8,37 @@ https://docs.opengeospatial.org/is/09-025r2/09-025r2.html#35
8
8
  https://docs.opengeospatial.org/is/09-025r2/09-025r2.html#411
9
9
  """
10
10
 
11
+ import logging
12
+ from contextlib import contextmanager
13
+
11
14
  from django.conf import settings
15
+ from django.http import HttpResponse
12
16
  from django.utils.html import format_html
13
17
 
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @contextmanager
22
+ def wrap_parser_errors(name: str, locator: str):
23
+ """Convert the value into a Python format.
24
+ This catches any typical exceptions and transforms them into an OWSException.
25
+ """
26
+ try:
27
+ yield
28
+ except ExternalParsingError as e:
29
+ raise OperationParsingFailed(
30
+ f"Unable to parse {name} argument: {e}", locator=locator
31
+ ) from None
32
+ except (TypeError, ValueError, NotImplementedError) as e:
33
+ # TypeError/ValueError are raised by most handlers for unexpected data
34
+ # The NotImplementedError can be raised by fes parsing.
35
+ raise InvalidParameterValue(f"Invalid {name} argument: {e}", locator=locator) from None
36
+
14
37
 
15
38
  class ExternalValueError(ValueError):
16
39
  """Raise a ValueError for external input.
17
40
  This helps to distinguish between internal bugs
18
- (e.g. unpacking values) and misformed external input.
41
+ (e.g. unpacking values) and malformed external input.
19
42
  """
20
43
 
21
44
 
@@ -32,18 +55,31 @@ class OWSException(Exception):
32
55
  version = "2.0.0"
33
56
  code = None
34
57
  text_template = None
58
+ debug_hint = True
35
59
 
36
- def __init__(self, locator, text=None, code=None, status_code=None):
60
+ def __init__(self, text=None, code=None, locator=None, status_code=None):
37
61
  text = text or self.text_template.format(code=self.code, locator=locator)
62
+ if (code and len(text) < len(code)) or (locator and len(text) < len(locator)):
63
+ raise ValueError(f"text/locator arguments are switched: {text!r}, locator={locator!r}")
64
+
38
65
  super().__init__(text)
39
66
  self.locator = locator
40
67
  self.text = text
41
- self.code = code or self.code
68
+ self.code = code or self.code or self.__class__.__name__
42
69
  self.status_code = status_code or self.status_code
43
70
 
71
+ def as_response(self):
72
+ logger.debug("Returning HTTP %d for %s: %s", self.status_code, self.code, self.text)
73
+ xml_body = self.as_xml()
74
+ return HttpResponse(
75
+ b'<?xml version="1.0" encoding="UTF-8"?>\n%b' % xml_body.encode("utf-8"),
76
+ content_type="text/xml; charset=utf-8",
77
+ status=self.status_code,
78
+ reason=self.reason,
79
+ )
80
+
44
81
  def as_xml(self):
45
82
  return format_html(
46
- '<?xml version="1.0" encoding="UTF-8"?>\n'
47
83
  "<ows:ExceptionReport"
48
84
  ' xmlns:ows="http://www.opengis.net/ows/1.1"'
49
85
  ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
@@ -51,16 +87,18 @@ class OWSException(Exception):
51
87
  ' http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd"'
52
88
  ' xml:lang="en-US"'
53
89
  ' version="2.0.0">\n'
54
- ' <ows:Exception exceptionCode="{code}" locator="{locator}">\n'
55
- " <ows:ExceptionText>{text}{debug}</ows:ExceptionText>\n"
90
+ ' <ows:Exception exceptionCode="{code}"{locator_attr}>\n\n'
91
+ " <ows:ExceptionText>{text}{debug}</ows:ExceptionText>\n\n"
56
92
  " </ows:Exception>\n"
57
- "</ows:ExceptionReport>",
93
+ "</ows:ExceptionReport>\n",
58
94
  code=self.code,
59
- locator=self.locator,
95
+ locator_attr=(
96
+ format_html(' locator="{locator}"', locator=self.locator) if self.locator else ""
97
+ ),
60
98
  text=self.text,
61
99
  debug=(
62
100
  ".\n\n(set GISSERVER_WRAP_FILTER_DB_ERRORS=False to see the Django error page)"
63
- if settings.DEBUG and self.status_code >= 500
101
+ if settings.DEBUG and self.status_code >= 500 and self.debug_hint
64
102
  else ""
65
103
  ),
66
104
  )
@@ -0,0 +1,4 @@
1
+ """Package that allows third party code to extend the server.
2
+
3
+ This allows registering extra stored procedures or filter functions.
4
+ """
@@ -22,8 +22,14 @@ FesFunctionBody = Union[models.Func, Callable[..., models.Func]]
22
22
 
23
23
  @dataclass(order=True)
24
24
  class FesFunction:
25
- """Wrapper that defines a fes function, with type descriptions.
26
- This is also used to provide metadata to the GetCapabilities call.
25
+ """A registered database function that can be used by ``<fes:Function name="...">`.
26
+
27
+ The :class:`~gisserver.parsers.fes20.expressions.Function` class will resolve
28
+ these registered functions by name, and call :meth:`build_query` to include them
29
+ in the database query. This will actually insert a Django ORM function in the query!
30
+
31
+ This wrapper class also provides the metadata and type descriptions of the function,
32
+ which is exposed in the ``GetCapabilities`` call.
27
33
  """
28
34
 
29
35
  #: Name of the function
@@ -50,7 +56,7 @@ class FesFunction:
50
56
 
51
57
 
52
58
  class FesFunctionRegistry:
53
- """Registry of functions to be callable by <fes:Function>.
59
+ """Registry of functions to be callable by ``<fes:Function>``.
54
60
 
55
61
  The registered functions should be capable of running an SQL function.
56
62
  """
@@ -97,12 +103,12 @@ class FesFunctionRegistry:
97
103
  return _wrapper
98
104
 
99
105
  def resolve_function(self, function_name) -> FesFunction:
100
- """Resole the function using it's name."""
106
+ """Resole the function using its name."""
101
107
  try:
102
108
  return self.functions[function_name]
103
109
  except KeyError:
104
110
  raise InvalidParameterValue(
105
- "filter", f"Unsupported function: {function_name}"
111
+ f"Unsupported function: {function_name}", locator="filter"
106
112
  ) from None
107
113
 
108
114