Changelog¶
2.0.0rc1¶
First release candidate of django-oapif 2.0. It adds GeoArrow output and JSON-FG curves, fixes a permission check on delete, and changes several behaviours that clients or projects may rely on: read upgrading from 1.x before trying it, and from 2.0.0-beta1 for curves.
pip only installs it when asked for:
Please report anything that breaks in the issues.
Security¶
- Deleting a feature now requires the delete permission. 1.x checked the view permission, so anyone who could read a feature could also delete it. Upgrade, or check your permissions on 1.x if deleting matters to you.
- The extent of a collection no longer covers the features its
get_queryset()hides. 1.x and the 2.0 betas computed it from every row of the model, so a collection serving each user their own features gave them the extent of everyone's, whose corners can be the coordinates of someone else's feature. Upgrade if that matters to you. - A
POSTno longer overwrites the feature whose id it carries. 1.x and the 2.0 betas saved it over that feature when the primary key has no default, like theidDjango gives a model, so anyone who could add features could change any of them, even those they could not see. It is now refused with a409 Conflict, instead of a server error when the key has a default, such as a UUID. Upgrade, or on 1.x give the add permission only to users who may change every feature. - A foreign key can no longer reference a feature that
get_queryset()hides from the user. 1.x and the 2.0 betas accepted the key of any row of the referenced model, so a user could link a feature to someone else's, in a collection serving each user their own features, and tell from a201or a422which keys exist. Upgrade if that matters to you.
Upgrading from 2.0.0-beta1¶
- Curves are no longer served in
geometry, which GeoJSON does not allow, and which GeoJSON readers such as QGIS dropped. A request for them in GeoJSON is refused with a406, which links them in JSON-FG, with the curves inplace, and in GeoJSON, linearized: ask for one or the other withprofile=jsonfgorlinearize=true. QGIS 3 asks for the linearized curves with?linearize=truein the URL of its connection. The features without curves are unchanged. See curves.
Upgrading from 1.x¶
Requirements¶
- Django 5.2 or newer is required (was 5.0). 5.2, 6.0 and 6.1 are tested.
- The PostgreSQL driver is no longer installed with django-oapif, which used to pull in
psycopg2-binary. Install one yourself if your project does not already, preferably psycopg 3:
API behaviour¶
- A
crs,bbox-crsorContent-Crsthat the collection does not list in itscrsis now rejected with400, instead of being reprojected to. By default a collection lists CRS84 and its storage CRS. OverrideOapifCollection.supported_crs()to offer more. - The spatial extent of a collection is now always given in CRS84, instead of in its storage CRS.
- Feature responses are sent as
application/geo+jsoninstead ofapplication/json, with aContent-Crsheader.GETresponses also carryVary: Accept. - Items follow the model's
Meta.ordering, with the primary key as tie breaker, so that pages no longer overlap or skip features. A collection that setsorderingkeeps exactly that ordering. - Properties come in the order the fields are declared, instead of in an arbitrary order.
PUTandPATCHignore a primary key in the body: they always update the feature in the URL.- The conformance declaration drops the HTML and OpenAPI 3.0 classes, which the API did not meet, and declares the OpenAPI 3.1 class of Features Part 1 1.1, matching the document actually served.
Code that subclasses OapifCollection¶
query()now annotates_oapif_geometrywith WKB, instead of a GeoJSON dictionary. This only matters if you overridequery()or read that annotation.- The CRS a collection offers now come from two new hooks,
storage_crs()andsupported_crs(). get_queryset()also gives the extent of a collection, so/collectionscalls it too, for every collection the user may view: one that fails for a user, an anonymous one for instance, now fails their list of collections too, and not only the items of that collection.get_queryset()also limits the rows a foreign key to its model may reference, soPOST,PUTandPATCHcall it too, for the collections the key references: one that fails for a user now fails their writes of such a key too, and not only the items of that collection.
New¶
- Filtering:
/itemstakes a CQL2 textfilter, as defined by Features Part 3, and each collection describes the properties it can be filtered on at/queryables. QGIS sends the filter of a layer to the server this way, instead of filtering every feature itself. Comparisons,LIKE,BETWEEN,IN,CASEIand the spatial functions are supported. This adds pygeofilter to the requirements. - GeoArrow:
/itemsand/items/{featureId}return an Apache Arrow stream, with GeoArrow WKB geometries, when the request'sAcceptheader prefersapplication/vnd.apache.arrow.stream. Paging links come in aLinkheader and the total inOGC-NumberMatched. The collection links to this encoding of its items. This needs the newarrowextra (pip install "django-oapif[arrow]"); without it such requests get a406. See GeoArrow. - Curves: CircularString, CompoundCurve, CurvePolygon, MultiCurve and MultiSurface columns are served in JSON-FG, the curves in
place, or in GeoJSON linearized by PostGIS, in the CRS asked for, with thelinearizeparameter and thelinearization_toleranceof the collection. GeoJSON refuses them otherwise, with a406that links both, and so it does the pages of aGeometryFieldcolumn that have a curve. A CircularString of more than 5 arcs, the most JSON-FG allows, is served as a CompoundCurve of its arcs, and accepted back in that form. Writing curves needs GEOS 3.13 and a Django whose GEOS bindings support curves, which no Django release has yet; otherwise writing one returns501. - JSON-FG: the
profilequery parameter, or theprofileofapplication/geo+jsonin theAcceptheader, asks for the features of any collection in JSON-FG (jsonfg), or in GeoJSON (rfc7946), the default. The responses link their profile in aLinkheader, and the pages of items their other pages and their number of features in the headers too, where QGIS reads them in JSON-FG. A collection links its items in JSON-FG, as QGIS 4.2 finds them, and the conformance declaration lists the classes of JSON-FG and the profile query parameter. - JSON-FG input:
POST,PUTandPATCHaccept aplace, which is stored instead of thegeometryfallback, and acoordRefSys, which must match theContent-Crs. A curve column sent a GeoJSON geometry, such as a linearized curve, answers that the curve goes inplace. - HTML: browsers get an HTML page of every resource but the API definition, with a map of the features, which the JSON links with the
alternaterelation.f=htmlandf=jsonchoose the encoding instead of theAcceptheader. The landing page links Swagger UI with theservice-docrelation. - PolyhedralSurface and TIN geometries are served as MultiPolygons, and Triangles as Polygons.
- CRS URIs are accepted with
https://, and aContent-Crsis accepted in angle brackets, the way the responses send it. - The items
limitis published undercomponents/parametersin the OpenAPI document, which is where QGIS reads the page size from. - Feature collections carry the
bboxof the page. - The schema gives the choices of a field as the
enumof its property, and the bounds of a number, from its validators and the range of its column, as itsminimumandmaximum. The validators of a string give itsminLength,maxLengthandpattern. URLs, emails, files, and IP addresses of a single version have theirformat. - The schema gives the primary key the
idrole, and a foreign key thereferencerole, with the collections it references inx-ogc-collectionId. The conformance declaration lists the feature references class of Part 5.
Fixes¶
- Unknown properties in
POSTandPUTare always rejected. After aGET, they used to be dropped silently. - Invalid geometries, such as polygons with unclosed rings, and errors raised by validators now return
422, instead of a server error. - A write that leaves a feature out of the rows of
get_queryset(), such as one moved out of the area of the user, now returns a201with itsLocationfor aPOST, and a204for aPUTorPATCH, without the feature. It used to be saved and answered with a server error, which a client could take for a failure and send again, or, withATOMIC_REQUESTS, refused with one. - LineString coordinates are checked against the dimension of the column, like those of the other geometry types.
- Geometry fields declared with
dim=3accept and serve Z coordinates. POST,PUTandPATCHno longer fail with a server error when a foreign key references a model that has noobjectsmanager.- A foreign key to another field than the primary key, with
to_field, is written as it is served: the value of that field. It was looked up as a primary key, so the value read was refused, or linked the row whose primary key it is. - An empty point,
POINT EMPTY, is served as{"type": "Point", "coordinates": []}, which RFC 7946 lets readers take for no geometry, instead of failing/itemsand/items/{featureId}with a server error. Writes take it too, as they take an empty LineString. - The schema of a collection is served, and linked from the collection, as
application/schema+json, as Part 5 requires, instead ofapplication/json. - Decimals are described in the schema as the strings they are served as. They had no type, and QGIS made no field of them.
- Read-only properties are marked
readOnlyin the schema, so QGIS no longer lets them be edited, only to have the write rejected. - The schema leaves out the fields of
exclude, which it described although they are neither served nor written. - The conformance declaration lists the returnables and receivables class of Part 5, which the collections already met by linking their schema.
- The
$idof a schema is its URI without the query parameters of the request, such asf=json. - The schema gives the position of each property, which QGIS orders the fields of a layer by, instead of alphabetically.
- The members a collection has no value for, such as its
descriptionorextent, are left out instead of beingnull, which OGC API - Features does not allow. /collectionsand/collections/{collectionId}no longer fail with a server error when the model of a collection has noobjectsmanager./collectionsand/collections/{collectionId}no longer fail with a server error when the features of a collection stored in a CRS other than EPSG:4326 lie on one horizontal or vertical line, such as two points side by side, as they did in the 2.0 betas.- The links of the collections of
/collectionskeepcollections/in their path when the request has a query, such as an API key orf=json: they answered404, and GDAL found no feature. Those of a collection whose id ends incollectionsno longer have it twice. - A
bbox, and the geometries of afilter, in a CRS other than the storage one, such as CRS84, are reprojected along their edges. Only their corners were, so across Switzerland a box missed features up to 2.6 km inside its edges, and returned others as far outside them.
Performance¶
- GeoJSON is serialized directly by pydantic, instead of being validated again by django-ninja and then written with
json.dumps. - Geometries are read from the database as WKB, and the bbox of a page is computed from them as they are read.
- Collection extents are computed in the storage CRS, and only their outline is reprojected, instead of every geometry.
- The HTML page of
/collectionsno longer computes the extent of every collection, which it does not show: a query each, about 45 ms for a table of 200,000 rows. - The documentation now has a deployment page. Use persistent database connections: PostGIS prepares each reprojection once per connection, which costs about 130 ms, so without them every request in a CRS other than the storage one pays that again.
1.x and earlier¶
The releases before 2.0 are listed on GitHub.