From c83567c95e8be3c7df5add4249f365263f07072d Mon Sep 17 00:00:00 2001 From: TehomCD Date: Sun, 1 Mar 2020 01:07:00 -0500 Subject: [PATCH] Add filter for aliases and permissions --- evennia/web/api/README.md | 17 ++++++------ evennia/web/api/filters.py | 48 +++++++++++++++++++++++++++++++--- evennia/web/api/permissions.py | 10 +++---- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/evennia/web/api/README.md b/evennia/web/api/README.md index 7b564a9edc..3f1b26ce07 100644 --- a/evennia/web/api/README.md +++ b/evennia/web/api/README.md @@ -1,6 +1,6 @@ # Evennia API -## What's an API? +## Synopsis An API, or [Application Programming Interface][wiki-api], is a way of establishing rules through which external services can use your program. In web development, it's often that case that the 'frontend' of a web app is written in HTML and Javascript @@ -8,7 +8,6 @@ and communicates with the 'backend' server through an API so that it can retriev information to populate web pages or process actions when users click buttons on a web page. - The API contained within the web/api/ package is an implementation of the [Django Rest Framework][drf]. It provides tools to allow you to quickly process requests for resources and generate responses. URLs, called endpoints, are @@ -24,7 +23,7 @@ that largely automate the process of serializing your in-game objects into JSON representations for sending them to a client, or for turning a JSON string into a model for updating or creating it. -## What is it for? +## Motivations For Using An API Having an API can allow you to have richer interactions with client applications. For example, suppose you want to allow players to send and receive in-game messages from @@ -35,7 +34,7 @@ displays it on the page. You also provide a form to let them send messages, wher submit button uses AJAX to make a POST request to that endpoint, sending along the JSON data from the form, and then returns the response of the results. This works, but then a tech-savvy player might ask if they can have their own application that -will retrieve messages periodically for their own computer. By having a [REST][REST] API that +will retrieve messages periodically for their own computer. By having a [REST][rest] API that they can use, they can create client applications of their own to retrieve or change data. @@ -88,8 +87,8 @@ a request to update the object with the specified data you pass along. In most cases, you won't be making API requests to the backend with python, but with Javascript from your frontend application. There are many Javascript libraries which are meant to make this process -easier for requests from the frontend, such as [AXIOS][AXIOS], or using -the native [Fetch][Fetch]. +easier for requests from the frontend, such as [AXIOS][axios], or using +the native [Fetch][fetch]. [wiki-api]: https://en.wikipedia.org/wiki/Application_programming_interface [drf]: https://www.django-rest-framework.org/ @@ -97,7 +96,7 @@ the native [Fetch][Fetch]. [crud]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete [serializers]: https://www.django-rest-framework.org/api-guide/serializers/ [ajax]: https://en.wikipedia.org/wiki/Ajax_(programming) -[REST]: https://en.wikipedia.org/wiki/Representational_state_transfer +[rest]: https://en.wikipedia.org/wiki/Representational_state_transfer [requests]: https://requests.readthedocs.io/en/master/ -[AXIOS]: https://github.com/axios/axios -[Fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API \ No newline at end of file +[axios]: https://github.com/axios/axios +[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API \ No newline at end of file diff --git a/evennia/web/api/filters.py b/evennia/web/api/filters.py index df580ba2d2..6888b5fbb1 100644 --- a/evennia/web/api/filters.py +++ b/evennia/web/api/filters.py @@ -1,27 +1,67 @@ +""" +FilterSets allow clients to specify querystrings that will determine the data +that is retrieved in GET requests. By default, Django Rest Framework uses the +'django-filter' package as its backend. Django-filter also has a section in its +documentation specifically regarding DRF integration. + +https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html +""" +from django.db.models import Q from django_filters.rest_framework.filterset import FilterSet +from django_filters.filters import CharFilter from evennia.objects.models import ObjectDB from evennia.accounts.models import AccountDB from evennia.scripts.models import ScriptDB + +class TagTypeFilter(CharFilter): + """ + This class lets you create different filters for tags of a specified db_tagtype. + """ + tag_type = None + + def filter(self, qs, value): + return qs.filter(Q(db_tags__db_tagtype=self.tag_type) & Q(db_tags__db_key=value)) + + +class AliasFilter(TagTypeFilter): + """A filter for objects by their aliases (tags with a tagtype of 'alias'""" + tag_type = "alias" + + +class PermissionFilter(TagTypeFilter): + """A filter for objects by their permissions (tags with a tagtype of 'permission'""" + tag_type = "permission" + + SHARED_FIELDS = ["db_key", "db_typeclass_path", "db_tags__db_key", "db_tags__db_category"] -class ObjectDBFilterSet(FilterSet): +class BaseTypeclassFilterSet(FilterSet): + """A parent class with filters for aliases and permissions""" + alias = AliasFilter(lookup_expr="iexact") + permission = PermissionFilter(lookup_expr="iexact") + + +class ObjectDBFilterSet(BaseTypeclassFilterSet): + """This adds filters for ObjectDB instances - characters, rooms, exits, etc""" class Meta: model = ObjectDB fields = SHARED_FIELDS + ["db_location__db_key", "db_home__db_key", "db_location__id", "db_home__id"] -class AccountDBFilterSet(FilterSet): +class AccountDBFilterSet(BaseTypeclassFilterSet): + """This adds filters for Account objects""" class Meta: model = AccountDB fields = SHARED_FIELDS + ["username", "db_is_connected", "db_is_bot"] -class ScriptDBFilterSet(FilterSet): +class ScriptDBFilterSet(BaseTypeclassFilterSet): + """This adds filters for Script objects""" class Meta: model = ScriptDB fields = SHARED_FIELDS + ["db_desc", "db_obj__db_key", "db_obj__id", "db_account__id", - "db_account__username", "db_is_active", "db_persistent"] + "db_account__username", "db_is_active", "db_persistent", "db_interval"] diff --git a/evennia/web/api/permissions.py b/evennia/web/api/permissions.py index e56cd1cf23..dd1a52c952 100644 --- a/evennia/web/api/permissions.py +++ b/evennia/web/api/permissions.py @@ -10,11 +10,11 @@ class EvenniaPermission(permissions.BasePermission): view, we'll check a corresponding Evennia access/lock check. """ # subclass this to change these permissions - MINIMUM_LIST_PERMISSION = settings.REST_FRAMEWORK["DEFAULT_LIST_PERMISSION"] - MINIMUM_CREATE_PERMISSION = settings.REST_FRAMEWORK["DEFAULT_CREATE_PERMISSION"] - view_locks = settings.REST_FRAMEWORK["DEFAULT_VIEW_LOCKS"] - destroy_locks = settings.REST_FRAMEWORK["DEFAULT_DESTROY_LOCKS"] - update_locks = settings.REST_FRAMEWORK["DEFAULT_UPDATE_LOCKS"] + MINIMUM_LIST_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_LIST_PERMISSION", "builder") + MINIMUM_CREATE_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_CREATE_PERMISSION", "builder") + view_locks = settings.REST_FRAMEWORK.get("DEFAULT_VIEW_LOCKS", ["examine"]) + destroy_locks = settings.REST_FRAMEWORK.get("DEFAULT_DESTROY_LOCKS", ["delete"]) + update_locks = settings.REST_FRAMEWORK.get("DEFAULT_UPDATE_LOCKS", ["control", "edit"]) def has_permission(self, request, view): """