This API was first introduced in this github issue
The simplified API is a simple task oriented API which simplifies the most common needs. It is a API which lends itself naturally to be exposed as a RESTful web service API. This API is the public interface for devilry in any language and platform:
- RESTful web services
- javascript
- serverside python
- client libraries using the RESTful web API.
This means that a developer who has played around with the javascript in the Devilry templates, can use the same API if he or she wants to develop a serverside addon or a command-line script using the official devilry python bindings.
The devilry core is very powerful and flexible. This unfortunatly makes it a bit complex. The simplified API makes all the information provided by the core available through a much more simple API. Instead of the recursive database API provided by the core, developers get a API where they can say things like:
You can access all data in the devilry core via the simplified API, but it is limited to the most common ways of accessing this data. This means that you have to use the core directly if you wish to make complex queries which is not needed by a common task. To put it simple: It is not an API for doing things that are not supported by the devilry web interface.
The code for a simplified class might look like this:
@simplified_modelapi
class Subject(object):
class Meta:
model = models.Subject
resultfields = FieldSpec('id', 'short_name', 'long_name')
searchfields = FieldSpec('short_name', 'long_name')
methods = ['search', 'read']
@classmethod
def create_searchqryset(cls, user, **kwargs):
return cls._meta.model.published_where_is_examiner(user)
@classmethod
def read_authorize(cls, user, obj):
if not cls._meta.model.published_where_is_examiner(user).filter(id=obj.id):
raise PermissionDenied()
This is the API an examiner would use to access a subject in Devilry. Every Simplified API class has to have the fields model, resultfields, searchfields and methods. Depending on what methods you define you will also need to implement some @classmethods in your Simplified class. Lets run through the details:
class Meta:
...
This class contains all the required fields. Lets look at what thiese fields do.
model defines the database model to be used:
class Meta:
model = models.Subject
...
resultfields defines the fields you will get access to when you search/read from the model using this API:
class Meta:
...
resultfields = FieldSpec(...)
...
searchfields defines what fields in the model will be used when searching for this object:
class Meta:
...
searchfields = FieldSpec(...)
...
And methods define what methods will be created for this class:
class Meta:
...
methods = [...]
Now let look at the required methods for class Subject.
@classmethod
def create_searchqryset(cls, user, **kwargs):
return cls._meta.model.published_where_is_examiner(user)
runs a query in the database for the information user has access to with the parameters given in kwargs and returns this queryset. This is needed for all database-lookups.
@classmethod
def read_authorize(cls, user, obj):
if not cls._meta.model.published_where_is_examiner(user).filter(id=obj.id):
raise PermissionDenied()
simply checkes that user has access to the information he/she/it tries to access. This is needed for the read method we defined in methods.
Now its time to start using this API. If you look at the methods field you will se that the defined methods are read and search, and if we look at the searchfields we can search based on short_name and long_name. Say we have a subject with short_name = ‘dev10’ and another subject with short_name = ‘dev11’. And we have a user with username = ‘bob’ as an examiner in both subjects.
from simplified import Subject
examinerbob = User.objects.get(username="bob")
qrywrap = Subject.search(examinerbob, query="dev1")
Then qrywrap would contain the two subjects dev10 and dev11, since both short_name’s match our query. However, of we run:
from simplified import Subject
examinerbob = User.objects.get(username="bob")
qrywrap = Subject.search(examinerbob, query="10")
our query will only match dev10, and qrywrap will only contain the subject dev10.
Every function and class in the public API is made available directly in devilry.simplified.NAME.
Bases: exceptions.Exception
Base class for Simplified exceptions.
Bases: devilry.simplified.exceptions.SimplifiedException
Signals that a user tries to access something they are not permitted to access.
Bases: devilry.simplified.exceptions.SimplifiedException
Raised when search() does not return exactly the number of results specified in the exact_number_of_results parameter.
Bases: devilry.simplified.exceptions.SimplifiedException
Raised when trying to set an invalid username.
Bases: devilry.simplified.exceptions.SimplifiedException
Raised when an invalid filter is given to devilry.simplified.SimplifiedModelApi.search().
Bases: object
Wrapper around a django QuerySet.
This object can be iterated and indexed:
print resultdct[0]['myfield']
for resultdct in qryresultwrapper:
print resultdct['myfield']
The underlying django queryset provides access to far more data than what we usually require. This list contains the fields provided when methods in this class convert the model instances in the wrapped django queryset into a dict.
The fields that were searched when generating this result.
The django queryset which this object wraps to provide security. Should not be used unless the public methods of this class fails to provide the required functionality. Any use of _insecure_django_qryset is considered a security risc.
The total number of matches before slicing (before applying start and limit).
Shortcut for _insecure_django_qryset.count().
Bases: object
Specifies and groups fields for search and read results.
Parameters: |
|
---|
Returns a list of the keys in additional_fieldgroups.
Get all fields in always_available_fields and in additional_fieldgroups.
Get the fields in always_available_fields and all fields in any of the fieldgroups.
Parameters: | fieldgroups – Tuple/list of fieldgroups. Fieldgroups are keys in additional_fieldgroups (see __init__). |
---|
Iterate over all fieldnames in this FieldSpec.
Get all fieldgroups containing fields belonging to the current table.
Fields not belonging to the current table are any field containing __.
Get all fields belonging to the current table.
Fields not belonging to the current table are any field containing __.
Decorator which creates a simplified API for a Django model.
The cls must have an inner class named Meta with the following attributes:
- model
- Then Django model. Required.
- methods
A list of supported CRUD+S methods. Required. Legal values are:
- create
- read
- update
- delete
- search
- resultfields
- A FieldSpec which defines what fields to return from read() and search(). Required.
- searchfields
- A FieldSpec which defines what fields to search in search(). Required.
- editablefields
A list of fields that are editable. If this is not specified, it defaults to resultfields.localfields_aslist() with the primary key field removed if it is a AutoField.
Only fields in this set may be given as field_values to update() or create(). Furthermore, these fields are used to generate forms in Low level RESTful web service API.
- editable_fieldgroups
- A list of result field groups which should at least contain all fields in editablefields. Defaults to resultfields.localfieldgroups_aslist()
- filters
- A devilry.simplified.FilterSpecs limiting the possible filters to perform. Defaults to an empty FilterSpec.
The cls must have the following methods for handling permissions:
- read_authorize
- Authorization method used for each call to read().
- write_authorize
- Authorization method used for each call to any CRUD+S method except for read().
- create_searchqryset
- Method used to create the queryset filtered in search(). Required if "search" is in Meta.methods.
The decorator adds the following attributes to cls:
- _meta
- Alias for the Meta class (above).
- supports_create
- Boolean variable: is 'create' in _meta.methods.
- supports_read
- Boolean variable: is 'read' in _meta.methods.
- supports_update
- Boolean variable: is 'update' in _meta.methods.
- supports_delete
- Boolean variable: is 'delete' in _meta.methods.
Bases: object
Base class for all simplified APIs.
Create the given object.
Parameters: | user – Django user object. |
---|---|
Field_values : | The values to set on the given object. |
Returns: | The primary key of the newly created object. |
Throws PermissionDenied: | |
If the given user does not have permission to edit this object, if the object does not exist, or if any of the field_values is not in cls._meta.editablefields. |
Create search queryset.
Returns: | A Django QuerySet. |
---|
Create many.
This does the same as calling create() many times, except that it does it all in a single transaction. This means that the database rolls back the changes unless they all succeed.
Param : | list_of_field_values List of field_values dicts (see create()). |
---|---|
Returns: | List of the primary keys of the created objects. |
Delete the given object. If the object is_empty() it can be deleted by any user with write_authorize(), if not then only a Superuser may delete the object
Parameters: |
|
---|---|
Returns: | The primary key of the deleted object. |
Throws PermissionDenied: | |
If the given user does not have permission to delete this object, if the object does not exist, or if the user does not have permission to recursively delete this objects and all its children. |
Delete many.
This does the same as calling delete() many times, except that it does it all in a single transaction. This means that the database rolls back the changes unless they all succeed.
Param : | list_of_pks List of primary-keys/ids of the objects to delete. |
---|---|
Returns: | List of the primary keys of the deleted objects. |
Can be overridden to change fieldgroups before they are sent into the QryResultWrapper. For example, if certain fieldgroups contain senstive data for anonymous assignments, we can add those fieldgroups if a filter for a specific assignment is provided in filters and that assignment is not anonymous.
Returns: | (result_fieldgroups, search_fieldgroups) |
---|
Check if the given obj is empty. Defaults to returning False.
Can be implemented in subclasses to enable superadmins to recursively delete() the obj.
Invoked after the obj has been saved.
Override this to set custom/generated values on obj after it has been validated and saved. The default does nothing.
Invoked after the user has been authorize by write_authorize() and before obj.full_clean() in create() and update().
Override this to set custom/generated values on obj before it is validated and saved. The default does nothing.
Read the requested item and return a dict with the fields specified in Meta.resultfields and additional fields specified in result_fieldgroups.
Parameters: |
|
---|---|
Throws PermissionDenied: | |
If the given user does not have permission to view this object, or if the object does not exist. |
Check if the given user has read permission on the given obj.
Defaults to cls.write_authorize(user, obj)
Raises PermissionDenied: | |
---|---|
If the given user do not have read permission on the given obj. |
Search for objects.
Parameters: |
|
---|---|
Returns: | The result of the search. |
Return type: | QryResultWrapper |
Update the given object.
Parameters: |
|
---|---|
Param : | field_values: The values to set on the given object. |
Returns: | The primary key of the updated object. |
Throws PermissionDenied: | |
If the given user does not have permission to edit this object, if the object does not exist, or if any of the field_values is not in cls._meta.editablefields. |
Update many.
This does the same as calling update() many times, except that it does it all in a single transaction. This means that the database rolls back the changes unless they all succeed.
Param : | list_of_field_values List of field_values dicts (see update()). Each dict _must_ have a _pk_ key that maps to the primary-key/id value. |
---|---|
Returns: | List of the primary keys of the updated objects. |
Check if the given user has write (update/create) permission on the given obj.
Defaults to raising :exc:NotImplementedError
Raises PermissionDenied: | |
---|---|
If the given user do not have write permission on the given obj. |
Bases: object
Container of FilterSpec and ForeignFilterSpec.
Iterate over all fieldnames in this FilterSpecs. Used in @simplified_modelapi to validate the fields in this FilterSpecs. Note that PatternFilterSpec are not validated.
Validate the given filters and translate them into a Django query.
Parameters: | filters – A list of filters on the following format: [{'field': 'myfieldname', 'comp': '>', 'value': 30},
{'field': 'myotherfieldname', 'comp': '=', 'value': 'myname'}]
|
---|---|
Throws FilterValidationError: | |
If any of the filters are not in the filterspecs. |
Bases: object
Specifies that a specific field can be filtered, and what comp it can use.
Parameters: |
|
---|
Bases: object
Specify FilterSpec for foreign tables without having to type __ for each table. Instead of:
FilterSpecs(FilterSpec('parentnode__parentnode__short_name'),
FilterSpec('parentnode__parentnode__longname'))
We can do:
FilterSpecs(ForeignFilterSpec('parentnode__parentnode',
FilterSpec('short_name'),
FilterSpec('long_name'))
Bases: devilry.simplified.filterspec.FilterSpec
Pattern based field spec.
NOTE: Should only be used when _really_ required, since it is less secure and slows down filter validation (which is done on each search()).
General purpose utilities used by the simplified API. If any functions here proves useful outside this module, they should me moved to devilry.utils.
Convert the given django model instance into a dict where each fieldname in fieldnames is a key.
Parameters: |
|
---|