Customization

APIS is designed to be easily customizable. This section describes how you can customize the views and the templates of the views that are shipped with APIS by injecting classes and methods that are then automatically used by the generic views. The core of the logic described here is based on the apis_core.generic. It provides generic CRUD views and API views for all models that are configured to use it. To make a model use the generic functionality, it has to inherit from apis_core.generic.abc.GenericModel. In standard APIS those models are

The apis_core.history module also uses the generic views for its models. This means that you can use the generic views for the historical models of your own ontology. E.g. if you have a model Person in your your_app.models module, you can use the generic views for it. /apis/your_app.historicalperson/ will be the URL for the list view of the historical model. /apis/api/your_app.historicalperson/ for the API view.

If you want to use the generic app for your own model, simple make your model inherit from apis_core.generic.abc.GenericModel.

List views

The list views consist of a django-filters filterset on the left and a django-tables table of results on the right side.

The default filterset used is apis_core.generic.filtersets.GenericFilterSet. You can override the filterset for you models by defining a custom filterset class in your_app.filtersets. The filterset has to be named <Modelname>FilterSet, so if you have a model Person in your app myproject, the view looks for the filterset myproject.filtersets.PersonFilterSet. You can inherit from apis_core.generic.filtersets.GenericFilterSet and add your customzations.

The default table used is apis_core.generic.tables.GenericTable. You can override the table for your models by defining a custom table class in your_app.tables. The table class has to be named <Modelname>Table, so if you have a model Person in your app myproject, the view looks for the table class myproject.tables.PersonTable. You can inherit from apis_core.generic.tables.GenericTable and add your customizations. apis_core.generic.tables.GenericTable also contains a handful of useful django table columns that you might want to use.

Your table can also contain a paginate_by attribute, which is then used by the list view to determines the number of items per page, or None for no pagination (which is the default).

The base queryset that is used in the listview, which is then filtered using the django-filters filter, is model.objects.all() - but you can override the queryset by creating a custom queryset for your model in your_app.querysets. The queryset function has to be named <Modelname>ListViewQueryset, so if you have a model Person in your app myproject, the view looks for the queryset myproject.querysets.PersonListViewQueryset.

List view templates

The list view looks for templates using the _list.html suffix. It uses the generic/generic_list.html template as fallback, but you can use a custom template using your model name, so if your model is myproject.Person then you can use the myproject/person_list.html template to override the generic template.

Create and Update views

The create and update view use the form apis_core.generic.forms.GenericModelForm by default. You can override the form it uses by creating a custom form in your_app.forms. The form class has to be named <Modelname>Form, so if you have a model Person in your app myproject, the view looks for the form class myproject.forms.PersonForm.

Create and update view templates

The create and update views looks for templates using the _form.html suffix. It uses the generic/generic_form.html template as fallback, but you can use a custom template using your model name, so if your model is myproject.Person then you can use the myproject/person_form.html template to override the generic template.

Autocomplete views

The autocomplete views filter your model instances based on a query string provided. By default, the autocomplete views use apis_core.generic.helpers.generate_search_filter() to filter the model queryset. You can override the queryset by creating a custom queryset for your model in your_app.querysets. The queryset function has to be named <Modelname>AutocompleteQueryset, so if you have a model Person in your app myproject, the view looks for the queryset myproject.querysets.PersonAutocompleteQueryset.

The results of the autocomplete view can be themed using templates. The autocomplete view looks for templates using the autocomplete_result.html suffix, if no such template is found, the string representation of the result is used. The autocomplete view uses the same template search function as for other templates, so if you have a model myproject.Person then you can use the myproject/person_autocomplete_result.html template.

The results of the autocomplete view can be extended with additional results coming from another source (an external API or another queryset). The view looks for this function in your_app.querysets and it has to be named <Modelname>ExternalAutocomplete, so if you have a model Person in your app myproject, the view looks for the function in myproject.querysets.PersonExternalAutocomplete.

Lets say you have an app called myapp with a models.py

class Person(models.Model):
     name = models.CharField(max_length=255)

then the respective autocomplete class should reside in myapp.querysets and has to be called PersonExternalAutocomplete.

class PersonExternalAutocomplete:
    def extract_results(data):
        ... do something with the data
        return data

    def get_results(self, q):
        with urllib.request.urlopen(f"https://some.uri.tld/search?q={q}") as f:
            data = extract_results(json.loads(f.read()))
            return results
        return {}

The class has to have a get_results method that receives a query as the first parameter and returns a result in the format, the django-autocomplete-light module uses- this is a dict with the keys “id”, “text” and “selected_text”.

Import view

The import view uses the form apis_core.generic.forms.GenericImportForm by default. You can override the form it uses by creating a custom form in your_app.forms. The form class has to be named <ModelName>ImportForm, so if you have a model Person in your app myproject, the view looks for the form class myproject.forms.PersonImportForm.

Import view template

The import view looks for templates using the _import.html suffix. It uses the generic/generic_import.html template as fallback, but you can use a custom template using your model name, so if your model is myproject.Person then you can use the myproject/person_import.html template to override the generic template.

Class, method and template lookup

As mentioned above, APIS tries to find the correct class or method to override the ones the generic one ships. This is done using apis_core.generic.helpers.first_match_via_mro(). The method does not only look for possible overrides using the name of the model itself, but also using all the parent models following the full inheritance chain. So if all your models inherit from MyAbstractModel, you can for example create an override table for all your models by creating a myproject.tables.MyAbstractModelTable.

Importing data from external resources

APIS provides the structure for easily importing data from external resources. One main component for this are Importer classes. They always belong to a Django model, reside in the same app as the Django model in the importers module and are named after the Django model. So if you have an app called myapp with a models.py

class Person(models.Model):
     name = models.CharField(max_length=255)

then the respective importer should reside in myapp.importers and has to be called PersonImporter.

An importer takes two arguments to instantiate: an uri and a model. The importers task is then to create a model instance from this URI, usually by fetching data from the URI, parsing it and extracting the needed fields. The instance should then be returned by the create_instance method of the importer. There is apis_core.generic.importers.GenericModelImporter which you can inherit from. It is used by default of no other importer is defined for the model and it tries to do the right thing out of the box: it first looks if there is an RDF configuration for the URI and if that fails tries to parse the URI response as json.

To use this logic in forms, there is apis_core.generic.forms.fields.ModelImportChoiceField which is based on django.forms.ModelChoiceField. It checks if the passed value starts with http and if so, it uses the importer that fits the model and uses it to create the model instance.