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
Formexpose choices for aModelChoiceFieldOn a
FilterSetexpose choices for aModelChoiceFilter
If
fieldsisn’t specified creates choices using theregistration_class.infer_fieldsstatic method. For aSerializerthis takes all related fields, for aForm&FilterSetallModelChoiceField.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_choicescan 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
serializethis may be ignored.value_field (str | None) – What to name the value field on the serialized object (defaults to “key”). If you override
serializethis may be ignored.search_fields (list[str] | None) – A list of fields to use in default filtering. Each field will be searched using
icontainsand OR’d together. For more complicated filtering passfilter_choicesperm (str | Iterable[str] | Callable[[HttpRequest], bool] | None) – The permission name to check when accessing choices for this field. See
ServerChoicesView. If not specified usesresolve_perm_name()with action ofcreate(we default to ‘create’ on the model the relation is _from_ as otherwise you’d be prevented from saving the record at all). Thepermcan only be inferred when using aModelSerializer,ModelFormorFilterSet. If using a plainSerializerorFormyou must provideperm. A list can also be provided in which case all perms listed would be checked (ie. likepermission_required)page_size (int | None) – The number of results to return in each API call. Defaults to 20. If set to
0then 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 -
permis 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 -
permis 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
stron 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
Noneto disable the empty option. This will be inferred from the field if possible otherwise will beNone.**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
serializerthis registration is forclass_name – The registered class name. This is used to index into
server_choices_registryfield_name – The name of the field
**kwargs – See
ServerChoiceFieldRegistrationdecorated_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
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.
If choices is a QuerySet and
search_fieldsis set then each of those fields is filtered and OR’dIf choices is a QuerySet and
search_fieldsis not set then it is an errorIf 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_mappingreturn the names of the fields that should have choices generated for themThis 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
.pkand 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
formthis registration is forclass_name – The registered class name. This is used to index into
server_choices_registry.field_name – The name of the field
**kwargs – See
ServerChoiceFieldRegistrationdecorated_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
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.
If choices is a QuerySet and
search_fieldsis set then each of those fields is filtered and OR’dIf choices is a QuerySet and
search_fieldsis not set then it is an errorIf 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_mappingreturn the names of the fields that should have choices generated for themThis 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
ServerChoicesSelectWidgetcomponentThis 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
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.
If choices is a QuerySet and
search_fieldsis set then each of those fields is filtered and OR’dIf choices is a QuerySet and
search_fieldsis not set then it is an errorIf 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_mappingreturn the names of the fields that should have choices generated for themThis 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
permargument on the registration.This view should be added to
urlpatternsand 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_nameandfield_namequery params. These are used to lookup the registration. Optionallypkorpkscan 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.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
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.
If choices is a QuerySet and
search_fieldsis set then each of those fields is filtered and OR’dIf choices is a QuerySet and
search_fieldsis not set then it is an errorIf 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_mappingreturn the names of the fields that should have choices generated for themThis 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
.pkand 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_classdirectly to theserver_choicesdecorator.- 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