API

alliance_platform.server_choices.decorators.server_choices(fields=None, *, registration_class=None, registry=<alliance_platform.server_choices.field_registry.ServerChoicesRegistry object>, class_handler_registry=<alliance_platform.server_choices.class_handlers.registry.ClassHandlerRegistry object>, search_fields=None, perm=None, page_size=None, get_choices=None, get_record=None, get_records=None, get_label=None, filter_choices=None, label_field='label', value_field='key', serialize=None, **kwargs)[source]

Decorate a class and expose a API endpoint to lookup choices for a field

The most common use case for this is to provide AJAX lookups for autocomplete widgets.

For instance:

  • On a Serializer class expose the choices for a PrimaryKeyRelatedField

  • On a django Form expose choices for a ModelChoiceField

  • On a FilterSet expose choices for a ModelChoiceFilter

If fields isn’t specified creates choices using the registration_class.infer_fields static method. For a Serializer this takes all related fields, for a Form & FilterSet all ModelChoiceField.

The examples above all use to related fields as these can have a large a variable size list of choices - but any field with choices can be used with the same interface. For example if you had a very large static list of options that you wanted to serve dynamically with backend filtering & pagination server_choices can be used.

See the usage documentation for more details and examples.

Base Usage

# This example applies to  a serializer class but the same usage applies to both
# Forms and FilterSets

# Register 2 fields and use the ``name`` field on them all when doing filtering
@server_choices(["trading_entity", "waste_types"], search_fields=["name"])
# Register project_status but don't do pagination
@server_choices(["project_status"], page_size=0)
# Register project_manager but override how choices are returned
@server_choices(["project_manager", get_choices=get_project_manager_choices]
class ProjectSerializer(ModelSerializer):
    ...
Parameters:
  • fields (Iterable[str] | None) – List of fields to support. If not specified creates choices for all related fields on the serializer.

  • registration_class (type[ServerChoiceFieldRegistration] | None) – The class to use for registration

  • registry (ServerChoicesRegistry) – The registry in which to register the supported fields.

  • class_handler_registry (ClassHandlerRegistry) – The registry to search for a handler for the decorated class

  • label_field (str | None) – What to name the label field on the serialized object (defaults to “label”). If you override serialize this may be ignored.

  • value_field (str | None) – What to name the value field on the serialized object (defaults to “key”). If you override serialize this may be ignored.

  • search_fields (list[str] | None) – A list of fields to use in default filtering. Each field will be searched using icontains and OR’d together. For more complicated filtering pass filter_choices

  • perm (str | Iterable[str] | Callable[[HttpRequest], bool] | None) – The permission name to check when accessing choices for this field. See ServerChoicesView. If not specified uses resolve_perm_name() with action of create (we default to ‘create’ on the model the relation is _from_ as otherwise you’d be prevented from saving the record at all). The perm can only be inferred when using a ModelSerializer, ModelForm or FilterSet. If using a plain Serializer or Form you must provide perm. A list can also be provided in which case all perms listed would be checked (ie. like permission_required)

  • page_size (int | None) – The number of results to return in each API call. Defaults to 20. If set to 0 then no pagination is used.

  • get_choices (QuerySet | Callable[[ServerChoiceFieldRegistration, HttpRequest], QuerySet | list[tuple[str | int, str]]] | None) – Override how choices are generated. Passed this instance and the current request. This can return any iterable (eg. a queryset, list of key/value tuples)

  • get_record (Callable[[ServerChoiceFieldRegistration, str, HttpRequest], Model | tuple[str | int, str]] | None) – Override how a single record is looked up. Passed this instance, the pk of record to return and the current request. Note that no individual permission checks are done - perm is checked once by default.

  • get_records (Callable[[ServerChoiceFieldRegistration, list[str], HttpRequest], QuerySet | Sequence[tuple[str | int, str]]] | None) – Override how a multiple records are looked up. Passed this instance, a list of pks to return and the current request. Note that no individual permission checks are done - perm is checked once by default.

  • get_label (Callable[[ServerChoiceFieldRegistration, Model | tuple[str | int, str]], str] | None) – Override how the label for a record is returned. By default just calls str on the record. Note that this will also be called if the choices is a list of tuple and will receive the tuple representation of a choice (eg. (key, label))

  • filter_choices (Callable[[ServerChoiceFieldRegistration, QuerySet | list[tuple[str | int, str]], HttpRequest], QuerySet | list[tuple[str | int, str]]] | None) – Override how choices are filtered. Passed this instance, the choices to filter and the current request.

  • serialize (Callable[[QuerySet | Sequence[tuple[str | int, str]] | Model | tuple[str | int, str], HttpRequest], list | dict] | None) – Override how record(s) are serialized. Passed this instance, the item or items to serialize, and the current request. Where possible use self.label_field and self.value_field as the name of the fields on returned data (if applicable - if using a complete custom return shape then ignore). This allows codegen to generate frontend code that knows what to expect from ServerChoicesView.

  • empty_label – Label to use for empty option. Specify None to disable the empty option. This will be inferred from the field if possible otherwise will be None.

  • **kwargs – Any extra kwargs are passed through directly to registration_class

class alliance_platform.server_choices.class_handlers.rest_framework.SerializerServerChoiceFieldRegistration(*, decorated_class, perm=None, **kwargs)

Registration for DRF Serializer classes

You usually don’t need to instantiate this manually - call server_choices() instead

Parameters:
  • serializer – The serializer this registration is for

  • field – The field on serializer this registration is for

  • class_name – The registered class name. This is used to index into server_choices_registry

  • field_name – The name of the field

  • **kwargs – See ServerChoiceFieldRegistration

  • decorated_class (type[serializers.Serializer])

  • perm (str | Iterable[str] | Callable[[HttpRequest], bool])

filter_choices(choices, request)

Given some choices returned by get_choices, filter then based on current request.

Default implementation works as follows

  1. Looks for search terms in the ‘keywords’ query param. This is split into individual words and each word must exist somewhere in the fields searched.

  2. If choices is a QuerySet and search_fields is set then each of those fields is filtered and OR’d

  3. If choices is a QuerySet and search_fields is not set then it is an error

  4. If choices is a list of key/label tuples then each label is searched with the keywords

Parameters:
  • choices (QuerySet | list[tuple[str | int, str]])

  • request (HttpRequest)

Return type:

QuerySet | list[tuple[str | int, str]]

classmethod get_available_fields(serializer_cls)

Return the available fields on decorated_cls. Should return a dict mapping field_name to field instance

get_choices(request)

Return the available choices for this field. Can return a queryset or list of key/label tuples.

Parameters:

request (HttpRequest)

Return type:

QuerySet | list[tuple[str | int, str]]

get_record(pk, request)

Return the matching record for the specified primary key.

Raises ObjectDoesNotExist if not found

Parameters:
  • pk (str)

  • request (HttpRequest)

Return type:

Model | tuple[str | int, str]

get_records(pks, request)

Return the matching records for the specified primary keys.

If any record is not found it is omitted from the return value.

Parameters:
  • pks (list[str])

  • request (HttpRequest)

Return type:

QuerySet | Sequence[tuple[str | int, str]]

classmethod infer_fields(field_mapping)

Given field_mapping return the names of the fields that should have choices generated for them

This is only used when no explicit list of fields is provided

Return type:

Iterable[str]

page_size: int

The number of results returned by the API at a time. Set to 0 to disable pagination (not recommended).

serialize(item_or_items, request)

Serialize the specified item(s)

Must handle either 1 item or an iterable of items

The default implementation returns a list of dicts with a key and label field named according to self.value_field and self.label_field.

  • If an iterable of Model is passed then the key is set to .pk and label to the __str__()

  • If an iterable of 2-tuples is passed then the first element is used as key and second as label (eg. standard field choices in django)

Parameters:
  • item_or_items (QuerySet | Sequence[tuple[str | int, str]] | Model | tuple[str | int, str])

  • request (HttpRequest)

Return type:

list | dict

classmethod should_handle_class_for_registration(decorated_class)

Given the class to which the server_choices decorator is applied, determine whether this registration class should be used to handle it. Will be called by ClassHandlersRegistry

class alliance_platform.server_choices.class_handlers.form.FormServerChoiceFieldRegistration(*, decorated_class, perm=None, model=None, **kwargs)

Registration for django Form classes.

You usually don’t need to instantiate this manually - call server_choices() instead

Parameters:
  • form – The model class this registration is for

  • field – The field on form this registration is for

  • class_name – The registered class name. This is used to index into server_choices_registry.

  • field_name – The name of the field

  • **kwargs – See ServerChoiceFieldRegistration

  • decorated_class (type[Form])

  • perm (str | Iterable[str] | Callable[[HttpRequest], bool])

filter_choices(choices, request)

Given some choices returned by get_choices, filter then based on current request.

Default implementation works as follows

  1. Looks for search terms in the ‘keywords’ query param. This is split into individual words and each word must exist somewhere in the fields searched.

  2. If choices is a QuerySet and search_fields is set then each of those fields is filtered and OR’d

  3. If choices is a QuerySet and search_fields is not set then it is an error

  4. If choices is a list of key/label tuples then each label is searched with the keywords

Parameters:
  • choices (QuerySet | list[tuple[str | int, str]])

  • request (HttpRequest)

Return type:

QuerySet | list[tuple[str | int, str]]

classmethod get_available_fields(form_cls)

Return the available fields on decorated_cls. Should return a dict mapping field_name to field instance

Parameters:

form_cls (Form)

get_choices(request)

Return the available choices for this field. Can return a queryset or list of key/label tuples.

Parameters:

request (HttpRequest)

Return type:

QuerySet | list[tuple[str | int, str]]

get_record(pk, request)

Return the matching record for the specified primary key.

Raises ObjectDoesNotExist if not found

Parameters:
  • pk (str)

  • request (HttpRequest)

Return type:

Model | tuple[str | int, str]

get_records(pks, request)

Return the matching records for the specified primary keys.

If any record is not found it is omitted from the return value.

Parameters:
  • pks (list[str])

  • request (HttpRequest)

Return type:

QuerySet | Sequence[tuple[str | int, str]]

classmethod infer_fields(field_mapping)

Given field_mapping return the names of the fields that should have choices generated for them

This is only used when no explicit list of fields is provided

Return type:

Iterable[str]

page_size: int

The number of results returned by the API at a time. Set to 0 to disable pagination (not recommended).

serialize(item_or_items, request)

Forms always serialize data as strings (eg. from query string)

Forcing this avoids type mismatches when dealing with data on page load (which is a string) vs from the API (which could be an int)

Parameters:

request (HttpRequest)

classmethod should_handle_class_for_registration(decorated_class)

Given the class to which the server_choices decorator is applied, determine whether this registration class should be used to handle it. Will be called by ClassHandlersRegistry

class alliance_platform.server_choices.class_handlers.form.ServerChoicesSelectWidget(registration, *args, **kwargs)

Form widget that renders the React ServerChoicesSelectWidget component

This widget is attached to fields by FormFieldServerChoiceRegistration - you don’t need to manually set it.

See ServerChoicesSelectWidget.tsx for frontend implementation.

Parameters:

registration (ServerChoiceFieldRegistration)

class alliance_platform.server_choices.class_handlers.django_filters.FilterSetServerChoiceFieldRegistration(*, decorated_class, field, class_name, field_name, perm=None, model=None, **kwargs)

Registration for FilterSet classes

As FilterSet uses django Form Fields underneath the only difference is how we extract the fields from the FilterSet.

You usually don’t need to instantiate this manually - call server_choices() instead

Parameters:
  • decorated_class (type[FilterSet])

  • class_name (str)

  • field_name (str)

  • perm (str | Iterable[str] | Callable[[HttpRequest], bool])

filter_choices(choices, request)

Given some choices returned by get_choices, filter then based on current request.

Default implementation works as follows

  1. Looks for search terms in the ‘keywords’ query param. This is split into individual words and each word must exist somewhere in the fields searched.

  2. If choices is a QuerySet and search_fields is set then each of those fields is filtered and OR’d

  3. If choices is a QuerySet and search_fields is not set then it is an error

  4. If choices is a list of key/label tuples then each label is searched with the keywords

Parameters:
  • choices (QuerySet | list[tuple[str | int, str]])

  • request (HttpRequest)

Return type:

QuerySet | list[tuple[str | int, str]]

classmethod get_available_fields(filterset_cls)

Return the available fields on decorated_cls. Should return a dict mapping field_name to field instance

Parameters:

filterset_cls (type[FilterSet])

get_choices(request)

Return the available choices for this field. Can return a queryset or list of key/label tuples.

Parameters:

request (HttpRequest)

Return type:

QuerySet | list[tuple[str | int, str]]

get_record(pk, request)

Return the matching record for the specified primary key.

Raises ObjectDoesNotExist if not found

Parameters:
  • pk (str)

  • request (HttpRequest)

Return type:

Model | tuple[str | int, str]

get_records(pks, request)

Return the matching records for the specified primary keys.

If any record is not found it is omitted from the return value.

Parameters:
  • pks (list[str])

  • request (HttpRequest)

Return type:

QuerySet | Sequence[tuple[str | int, str]]

classmethod infer_fields(field_mapping)

Given field_mapping return the names of the fields that should have choices generated for them

This is only used when no explicit list of fields is provided

Return type:

Iterable[str]

page_size: int

The number of results returned by the API at a time. Set to 0 to disable pagination (not recommended).

serialize(item_or_items, request)

Forms always serialize data as strings (eg. from query string)

Forcing this avoids type mismatches when dealing with data on page load (which is a string) vs from the API (which could be an int)

Parameters:

request (HttpRequest)

classmethod should_handle_class_for_registration(decorated_class)

Given the class to which the server_choices decorator is applied, determine whether this registration class should be used to handle it. Will be called by ClassHandlersRegistry

alliance_platform.server_choices.views.ServerChoicesView(**kwargs)[source]

View to handle serving all registered server choices

See server_choices() for documentation on how choices are registered.

Permission checks are done based on the perm argument on the registration.

This view should be added to urlpatterns and will then be used for any server choices:

path("api/server-choices/", ServerChoicesView.as_view())

Server choices can optionally use a custom registry. If this is used then a view needs to be registered for each registry.

urlpatterns += [
    # Handles the default registry
    path("api/server-choices/", ServerChoicesView.as_view()),
    # Handles a custom registry called CustomChoicesRegistry
    path("api/server-choices/", ServerChoicesView.as_view({"registry": CustomChoicesRegistry })),
]

When the view is called it expects class_name and field_name query params. These are used to lookup the registration. Optionally pk or pks can also be passed to retrieve the values for the specified key or keys (eg. when initially rendering an existing field that has a value you need the associated label).

The return value is serialized according to serialize()

class alliance_platform.server_choices.field_registry.ServerChoicesRegistry(name)[source]
class alliance_platform.server_choices.field_registry.ServerChoiceFieldRegistration(*, field, decorated_class, class_name, field_name, search_fields=None, perm=None, page_size=None, get_choices=None, get_record=None, get_records=None, get_label=None, filter_choices=None, model=None, label_field='label', value_field='key', empty_label=None, serialize=None, supports_server_search=None, source_class_name=None)[source]
Parameters:
  • decorated_class (ClassType)

  • class_name (str)

  • field_name (str)

  • search_fields (list[str] | None)

  • perm (str | Iterable[str] | Callable[[HttpRequest], bool])

  • page_size (int)

  • label_field (str)

  • value_field (str)

  • empty_label (str | None)

  • supports_server_search (bool)

  • source_class_name (str | None)

filter_choices(choices, request)[source]

Given some choices returned by get_choices, filter then based on current request.

Default implementation works as follows

  1. Looks for search terms in the ‘keywords’ query param. This is split into individual words and each word must exist somewhere in the fields searched.

  2. If choices is a QuerySet and search_fields is set then each of those fields is filtered and OR’d

  3. If choices is a QuerySet and search_fields is not set then it is an error

  4. If choices is a list of key/label tuples then each label is searched with the keywords

Parameters:
  • choices (QuerySet | list[tuple[str | int, str]])

  • request (HttpRequest)

Return type:

QuerySet | list[tuple[str | int, str]]

classmethod get_available_fields(decorated_cls)[source]

Return the available fields on decorated_cls. Should return a dict mapping field_name to field instance

Parameters:

decorated_cls (ClassType)

Return type:

dict[str, Any]

classmethod infer_fields(field_mapping)[source]

Given field_mapping return the names of the fields that should have choices generated for them

This is only used when no explicit list of fields is provided

Parameters:

field_mapping (dict[str, Any])

Return type:

Iterable[str]

page_size: int[source]

The number of results returned by the API at a time. Set to 0 to disable pagination (not recommended).

serialize(item_or_items, request)[source]

Serialize the specified item(s)

Must handle either 1 item or an iterable of items

The default implementation returns a list of dicts with a key and label field named according to self.value_field and self.label_field.

  • If an iterable of Model is passed then the key is set to .pk and label to the __str__()

  • If an iterable of 2-tuples is passed then the first element is used as key and second as label (eg. standard field choices in django)

Parameters:
  • item_or_items (QuerySet | Sequence[tuple[str | int, str]] | Model | tuple[str | int, str])

  • request (HttpRequest)

Return type:

list | dict

classmethod should_handle_class_for_registration(decorated_class)[source]

Given the class to which the server_choices decorator is applied, determine whether this registration class should be used to handle it. Will be called by ClassHandlersRegistry

Parameters:

decorated_class (ClassType)

Return type:

bool

class alliance_platform.server_choices.class_handlers.registry.ClassHandlerRegistry(name)

Registry for handlers for classes which can be decorated using server_choices().

A default registry is instantiated that supports Django forms, as well as DRF serializers and django-filter FilterSets if available. New classes can be supported by importing the default registry and calling register().

Checking the registry can be bypassed by passing registration_class directly to the server_choices decorator.

register(class_handler)

Add a handler to the list of backends which are looked up to handle decorated classes. Note that class handlers are checked in reverse order, and checking stops when a suitable class is found. This is based on the idea that devs will be registering their own class handlers after the default handlers, and will want theirs to take priority.

Parameters:

class_handler (type[ServerChoiceFieldRegistration])

class alliance_platform.server_choices.field_registry.ClassType

Anything that can be registered: Serializer, Form, FilterSet