dso_api.dynamic_api.views package

All views for the dynamically generated API, split by protocol type.

REST API

The REST API views are based on Django-Rest-Framework.

The viewsets handle the main API logic of the server. The relevant subclasses are dynamically generated from Amsterdam Schema files, just like the rest of the objects (such as serializers, filtersets and models).

To maintain readable code for the dynamic viewsets, all logic can be found in a normal Python class (namely the DynamicApiViewSet base class). The viewset_factory() function then generates a subclass for the specific model/endpoint. The same two-step logic can be found in the serializer and model layer of this application.

class dso_api.dynamic_api.views.DynamicApiViewSet(**kwargs)

Viewset for an API, that is DSO-compatible and dynamically generated. Each dynamically generated model in this server will receive a viewset.

Most of the DSO logic is implemented in the base class; DSOViewMixin.

dataset_id = None

The dataset ID is filled in by the factory

get_object() DynamicModel

An improved version of GenericAPIView.get_object() that supports temporal objects.

get_queryset() QuerySet

Get the list of items for this view. This must be an iterable, and may be a queryset. Defaults to using self.queryset.

This method should always be used rather than accessing self.queryset directly, as self.queryset gets evaluated only once, and those results are cached for all subsequent requests.

You may want to override this if you need to provide different querysets depending on the incoming request.

(Eg. return a list of items that is specific to the user)

initial(request, *args, **kwargs)

Runs anything that needs to occur prior to calling the method handler.

property lookup_field

Overwritten from GenericAPIView to filter on temporal identifier instead.

model: type[schematools.contrib.django.models.DynamicModel] = None

The model is filled in by the factory

property paginator

The paginator is disabled when a output format supports unlimited sizes.

table_id = None

The table ID is filled in by the factory

dso_api.dynamic_api.views.viewset_factory(model: type[schematools.contrib.django.models.DynamicModel]) type[dso_api.dynamic_api.views.api.DynamicApiViewSet]

Generate the viewset for a dynamic model.

This generates a class in-memory, as if the following code was written:

class CustomViewSet(DynamicApiViewSet):
    dataset_id = ...
    table_id = ...
    model = ...

    queryset = model.objects.all()
    serializer_class = serializer_factory(model)
    filterset_class = filterset_factory(model)
    authorization_grantor = "OIS"
    ordering_fields = ...

Internally, the serializer_factory(), function is called to generate those classes.

WFS API

The WFS views expose all geographic datasets as WFS endpoints and feature types.

The main logic of the WFS server can be found in the django-gisserver package. By overriding it’s WFSView class, our dynamic models can be introduced into the django-gisserver logic. Specifically, the following happens:

  • The FeatureType class is overwritten to implement permission checks.

  • The WFSView.get_feature_types() dynamically returns the feature types based on the datasets.

  • The query parameters ?expand and ?embed extend the server logic to provide object-embedding at the feature type level.

The WFS server itself doesn’t have any awareness of Amsterdam Schema, or the fact that model definitions are dynamically generated. It’s output and filter logic is purely based on the provided FeatureType definition. Hence, the server can act differently based on authorization logic - as we provide a different FeatureType definition in such case.

By making the dataset name part of the view kwargs, each individual dataset becomes a separate WFS server endpoint. The models of that dataset become WFS feature types.

class dso_api.dynamic_api.views.DatasetWFSIndexView(**kwargs)

Bases: APIIndexView

An overview of the available WFS endpoints. This makes sure a request to /wfs/ renders a reasonable index page.

class dso_api.dynamic_api.views.DatasetWFSView(**kwargs)

Bases: CheckPermissionsMixin, WFSView

A WFS view for a single dataset.

This extends the logic of django-gisserver to expose the dynamically generated as WFS FeatureType objects. When the ?extend=../?expand=.. parameters are given, a different set of feature types is generated so the WFS response contains complex nested objects.

Permission checks happen in 2 levels. First, only the accessible fields are exposed in the feature types. Second, the AuthenticatedFeatureType class extends the feature type logic to add permission-checking for table-level access.

This view is not constructed with factory-logic as we don’t need named-integration in the URLConf. Instead, we can resolve the ‘dataset’ via the URL kwargs.

get_embedded_fields(relation_name, model, pk_attr=None) list[Union[str, gisserver.features.FeatureField]]

Define which fields to embed as flattened fields.

get_expanded_fields(model) list[Union[str, gisserver.features.FeatureField]]

Define which fields to include in an expanded relation. This is a shorter list, as including a geometry has no use here. Relations are also avoided as these won’t be expanded anyway.

get_feature_fields(model, main_geometry_field_name) list[Union[str, gisserver.features.FeatureField]]

Define which fields should be exposed with the model.

Instead of opting for the “__all__” value of django-gisserver, provide an explicit list of fields so unauthorized fields are excluded.

get_feature_types() list[gisserver.features.FeatureType]

Generate map feature layers for all models that have geometry data.

get_index_context_data(**kwargs)

Context data for the HTML root page

get_service_description(service: str) ServiceDescription

Provide the (dynamically generated) service description.

index_template_name = 'dso_api/dynamic_api/wfs_dataset.html'

Template to render a HTML welcome page for non-OGC requests.

setup(request, *args, **kwargs)

Initial setup logic before request handling:

Resolve the current model or return a 404 instead.

xml_namespace = 'https://api.data.amsterdam.nl/v1/wfs/'

Define the namespace to use in the XML

class dso_api.dynamic_api.views.wfs.AuthenticatedFeatureType(queryset, *, wfs_view: DatasetWFSView, **kwargs)

Bases: FeatureType

Extended WFS feature type definition that also performs authentication.

__init__(queryset, *, wfs_view: DatasetWFSView, **kwargs)
Parameters
  • queryset – The queryset to retrieve the data.

  • fields – Define which fields to show in the WFS data. This can be a list of field names, or FeatureField objects.

  • display_field_name – Name of the field that’s used as general string representation.

  • geometry_field – Name of the geometry field to expose (default = auto detect).

  • name – Name, also used as XML tag name.

  • title – Used in WFS metadata.

  • abstract – Used in WFS metadata.

  • keywords – Used in WFS metadata.

  • crs – Used in WFS metadata.

  • other_crs – Used in WFS metadata.

  • metadata_url – Used in WFS metadata.

  • show_name_field – Whether to show the gml:name or the GeoJSON geometry_name field. Default is to show a field when name_field is given.

  • xml_prefix – The XML namespace prefix to use.

check_permissions(request)

Perform the access check for a particular request. This retrieves the accessed models, and relays further processing in the view.

When a related object returns a queryset, this hook allows extra filtering.

get_queryset() QuerySet

Return the queryset that is used as basis for this feature.