{ "info": { "author": "Pablo Escodebar", "author_email": "escodebar@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6" ], "description": "django-rest-framework-rules\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``django-rest-framework-rules`` aims to seamlessly integrate ``rules`` - a tiny but powerful app providing object-level permissions - into the Django REST framework.\n\nParts of the original `django-rules documentation`_ were used to improve the reading experience of this document.\n\nTable of Contents\n=================\n\n- `Requirements`_\n- `Run tests`_\n- `How to install`_\n- `Using rest_framework_rules`_\n\n - `PermissionRequiredMixin with the APIView and GenericAPIView`_\n - `PermissionRequiredMixin with the ViewSet and GenericViewSet`_\n - `permission_required decorator with APIView and ViewSet methods`_\n - `using list_route and detail_route decorator with the PermissionRequiredMixin`_\n\n- `Changelog`_\n- `Licence`_\n\nRequirements\n============\n\nThis package was made to be used with ``Django``, ``django-rules`` and the ``django-rest-framework``.\n\nBuild status\n============\n\n.. image:: https://travis-ci.org/escodebar/django-rest-framework-rules.svg?branch=master\n\nRun tests\n=========\n\n.. code:: bash\n\n $ git clone http://github.com/escodebar/django-rest-framework-rules.git\n $ cd django-rest-framework-rules\n $ python3 -m venv . && source bin/activate\n $ pip install -r requirements_test.txt\n $ (django-rest-framework-rules) ./runtests.sh\n\nHow to install\n==============\n\nUsing pip:\n\n.. code:: bash\n\n $ pip install django-rest-framework-rules\n\nUsing rest_framework_rules\n==========================\n\n``rest_framework_rules`` aims to integrate ``rules`` permission system into the Django REST framework.\n\n ``rules`` is based on the idea that you maintain a dict-like object that maps string keys used as identifiers of some kind, to callables, called *predicates*.\n Predicates can do pretty much anything with the given arguments, but must always return True if the condition they check is true, False otherwise.\n (Read more about rules in the `django-rules documentation - Using Rules`_.)\n\nFor a better illustration of the usage of ``rest_framework_rules`` let's assume the following setup:\nWe are proud owners of an app (climb-app!) which allows routesetters to advertise their newly created routes / boulders.\nThe climbers can use the app to review the routes / boulders and propose solutions for these.\n\nGiven such an application, one could think of the following permissions:\n\nRoutesetters may\n- create new boulders\n- retrieve boulders' details\n- update their boulders\n- delete their boulders\n- retrieve their boulders' reviews\n- retrieve their boulders' solutions\n\nClimbers may\n- retrieve boulders' details\n- create boulder reviews\n- retrieve boulders' reviews\n- update their reviews\n- delete their reviews\n- create a boulder solution\n- retrieve boulders' solutions\n- update their boulder solutions\n- delete their boulder solutions\n\nLet's define some predicates and the beforementioned permissions (this code usually resides in ``rules.py`` in your application folder).\n\n.. code:: python\n\n from climb_app.models import Climber, RouteSetter\n import rules\n \n @rules.predicate\n def is_a_climber(user):\n return Climber.objects.filter(user=user).exists()\n\n @rules.predicate\n def is_a_routesetter(user):\n return RouteSetter.objects.filter(user=user).exists()\n\n @rules.predicate\n def is_related_to_routesetters_boulder(user, content=None):\n if content is None or not hasattr(content, 'boulder'):\n return False\n return content.boulder.routesetter == user\n\n @rules.predicate\n def object_is_none(user, obj=None):\n return obj is None\n\n @rules.predicate\n def is_author(user, content):\n if not hasattr(content, 'author'):\n return False\n return content.author == user\n\n rules.add_perm('climb_app.create_boulder', is_a_routesetter)\n rules.add_perm('climb_app.retrieve_boulder', is_a_climber | is_a_routesetter & is_author)\n rules.add_perm('climb_app.update_boulder', is_a_routesetter & is_author)\n rules.add_perm('climb_app.delete_boulder', is_a_routesetter & is_author)\n rules.add_perm('climb_app.retrieve_reviews', is_a_routesetter)\n rules.add_perm('climb_app.retrieve_climbers', is_a_routesetter)\n\n rules.add_perm('climb_app.create_climber_content', is_a_climber)\n rules.add_perm('climb_app.retrieve_climber_content',\n (is_a_climber |\n is_a_routesetter & is_related_to_routesetters_boulder |\n is_a_routesetter & object_is_none))\n rules.add_perm('climb_app.update_climber_content', is_a_climber & is_author)\n rules.add_perm('climb_app.delete_climber_content', is_a_climber & is_author)\n\nPermissionRequiredMixin with the APIView and GenericAPIView\n-----------------------------------------------------------\n\nThe ``PermissionRequiredMixin`` allows to define a required permission name (``permission_required``).\nThis permission name (or list of such) is needed by the request's user to access the methods of the view.\n\nI could think of the following use case within climb-app!\nLet's allow climbers to *check* boulders once they were able to solve them.\n(This is basically adding a solution without data.)\n\n.. code:: python\n\n from climb_app.models import Boulder, Solution\n from rest_framework.response import Response\n from rest_framework.views import APIView\n from rest_framework_rules.mixins import PermissionRequiredMixin\n\n class CheckmarkBoulderView(PermissionRequiredMixin, APIView):\n permission_required = 'climb_app.create_climber_content'\n\n def get(self, request, *args, **kwargs):\n solution, created = Solution.objects.get_or_create(\n user=request.user,\n boulder=Boulder.objects.get(pk=kwargs.get('boulder_pk')),\n )\n if created:\n return Response(status=204)\n return Response(status=304)\n\nWhen used with a ``GenericAPIView``, the ``PermissionRequiredMixin`` allows to define an ``object_permission_required`` (defaults to ``permission_required`` if not set).\nThis permission (or list of permissions) is required by the request's user to call the ``get_object`` method of the view.\n\nIn the context of climb-app! this could be used with the views for retrieving and updating boulder reviews:\n\n.. code:: python\n\n from climb_app.models import Review\n from climb_app.serializer import ReviewSerializer\n from rest_framework.generics import GenericAPIView\n from rest_framework.response import Response\n from rest_framework_rules.mixins import PermissionRequiredMixin\n\n class RetrieveReviewView(PermissionRequiredMixin, GenericAPIView):\n permission_required = 'climb_app.retrieve_climber_content'\n queryset = Review.objects.all()\n\n def get(self, request, *args, **kwargs):\n review = self.get_object()\n serializer = ReviewSerializer(review)\n return Response(data=serializer.data)\n\n class CreateOrUpdateReviewView(PermissionRequiredMixin, GenericAPIView):\n object_permission_required = 'climb_app.update_climber_content'\n permission_required = 'climb_app.create_climber_content'\n queryset = Review.objects.all()\n\n def post(self, request, *args, **kwargs):\n solution, created = Review.objects.get_or_create(\n user=request.user,\n boulder=Boulder.objects.get(pk=kwargs.get('boulder_pk')),\n )\n if created:\n return Response(status=204)\n return Response(status=304)\n\n def put(self, request, *args, **kwargs):\n review = self.get_object()\n # update review...\n return Response(status=204)\n\nPermissionRequiredMixin with the ViewSet and GenericViewSet\n-----------------------------------------------------------\n\nThe ``PermissionRequiredMixin`` can be used as well with ``ViewSet`` and ``GenericViewSet``.\nThe user need to have the ``permission_required`` to call actions of a viewset and ``object_permission_required`` (which defaults to ``permission_required`` if not set) to call ``get_object``.\n\nLet's use this in climb app! to allow routesetters to create, delete boulders and list the reviews of their boulders.\n\n.. code:: python\n\n from climb_app.models import Boulder, Review\n from climb_app.serializers import BoulderSerializer, ReviewSerializer\n from rest_framework.response import Response\n from rest_framework.viewsets import GenericViewSet, ViewSet\n from rest_framework_rules.mixins import PermissionRequiredMixin\n\n class ReviewViewSet(PermissionRequiredMixin, ViewSet):\n permission_required = 'climb_app.retrieve_reviews'\n\n def list(self, request):\n queryset = Review.objects.filter(boulder__author=request.user)\n serializer = ReviewSerializer(queryset, many=True)\n return Response(serializer.data)\n\n class BoulderViewSet(PermissionRequiredMixin, GenericViewSet):\n object_permission_required = 'climb_app.delete_boulder'\n permission_required = 'climb_app.create_boulder'\n\n def create(self, request, *args, **kwargs):\n # create boulders...\n return Response(status=204)\n\n def destroy(self, request, *args, **kwargs):\n instance = self.get_object()\n instance.delete()\n return Response(status=204)\n\npermission_required decorator with APIView and ViewSet methods\n--------------------------------------------------------------\n\nPermissions can be set using the ``permission_required`` decorator.\nThe required permission(s) is passed as first argument to the decorator.\nThe decorator also has an optional ``fn`` argument, which is either the context object itself or a callable returning the context object.\nThe arguments passed to the context object callable are the same as the ones of the decorated method.\n\n.. code:: python\n\n from climb_app.models import Boulder, Solution\n from rest_framework.response import Response\n from rest_framework.views import APIView\n from rest_framework_rules.decorators import permission_required\n\n class CheckmarkBoulderView(APIView):\n\n @permission_required('climb_app.create_climber_content')\n def get(self, request, *args, **kwargs):\n solution, created = Solution.objects.get_or_create(\n user=request.user,\n boulder=Boulder.objects.get(pk=kwargs.get('boulder_pk')),\n )\n if created:\n return Response(status=204)\n return Response(status=304)\n\n\n class BoulderViewSet(ViewSet):\n\n @permission_required('climb_app.access_method')\n def create(self, request):\n # create boulder...\n return Response(status=204)\n\n @permission_required(\n 'someapp.access_method',\n fn=lambda request, pk: Boulder.objects.get(pk=pk))\n def destroy(self, request, pk):\n boulder = Boulder.objects.get(pk=pk)\n boulder.delete()\n return Response(status=204)\n\n\nusing list_route and detail_route decorator with the PermissionRequiredMixin\n----------------------------------------------------------------------------\n\n``rest_framework`` provides the decorators ``list_route`` and ``detail_route`` to define custom routes in viewsets.\nThese can be used as well with ``django-rest-framework-rules`` under the condition, that the ``ViewSet`` is mixed with the ``PermissionRequiredMixin``.\n\nLet's add some custom routes to the ``BoulderViewSet`` defined in climb-app! to allow routesetter to retrieve all reviews of a boulder and list all climbers which have solved the routesetter's boulders.\n\n.. code:: python\n\n from rest_framework.decorators import list_route, detail_route\n from rest_framework.response import Response\n from rest_framework.viewsets import ViewSet\n from rest_framework_rules.mixins import PermissionRequiredMixin\n\n class BoulderViewSet(PermissionRequiredMixin, GenericViewSet):\n object_permission_required = 'climb_app.delete_boulder'\n permission_required = 'climb_app.create_boulder'\n\n def create(self, request, *args, **kwargs):\n # create boulders...\n return Response(status=204)\n\n def destroy(self, request, *args, **kwargs):\n instance = self.get_object()\n instance.delete()\n return Response(status=204)\n\n @detail_route(methods=['get'], permission_required='climb_app.retrieve_reviews')\n def reviews(self, request, pk):\n boulder = self.get_object()\n queryset = (Review.objects\n .filter(boulder=boulder)\n .order_by('created'))\n serializer = ReviewSerializer(queryset=queryset, many=True)\n return Response(serializer.data)\n\n @list_route(methods=['get'], permission_required='climb_app.retrieve_climbers')\n def climbers(self, request):\n queryset = Climber.objects.filter(solution__boulder__author=request.user).distinct()\n serializer = ClimberSerializer(queryset=queryset, many=True)\n return Response(serializer.data)\n\nChangelog\n=========\n\n``v1.0.0`` - 2018/05/15\n - Dropped python 2.7 support.\n\n``v0.1.1`` - 2017/11/17\n - Improve README and package setup.\n\n``v0.1.0`` - 2017/11/13\n - Implemented PermissionRequiredMdddixin, permission_required decorator and the django rest framework integration tests.\n\n``v0.0.1`` - 2017/10/30\n - Forked from https://github.com/dfunckt/django-rules\n\n\nLicence\n=======\n\n``django-rest-framework-rules`` is distributed under the MIT licence.\n\nCopyright (c) 2017 Pablo Escodebar\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n.. _django-rules documentation: https://github.com/dfunckt/django-rules/blob/7688fdac68e7de6832f28f7b96ebf1f98f32f3c8/README.rst\n.. _django-rules documentation - Using Rules: https://github.com/dfunckt/django-rules/blob/7688fdac68e7de6832f28f7b96ebf1f98f32f3c8/README.rst#using-rules\n\n", "description_content_type": "", "docs_url": null, "download_url": "https://github.com/escodebar/django-rest-framework-rules/archive/v1.0.0.tar.gz", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/escodebar/django-rest-framework-rules", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "django-rest-framework-rules", "package_url": "https://pypi.org/project/django-rest-framework-rules/", "platform": "", "project_url": "https://pypi.org/project/django-rest-framework-rules/", "project_urls": { "Download": "https://github.com/escodebar/django-rest-framework-rules/archive/v1.0.0.tar.gz", "Homepage": "https://github.com/escodebar/django-rest-framework-rules" }, "release_url": "https://pypi.org/project/django-rest-framework-rules/1.0.0/", "requires_dist": null, "requires_python": "", "summary": "Django REST framework integration for django-rules", "version": "1.0.0" }, "last_serial": 3872451, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "f82eb37ad725c94bec18912a91cb057a", "sha256": "4acbc2fdb4a5e563f7c074244b55e176d096a6d6da85b43b4d730c23428fe73f" }, "downloads": -1, "filename": "django-rest-framework-rules-0.1.0.tar.gz", "has_sig": false, "md5_digest": "f82eb37ad725c94bec18912a91cb057a", "packagetype": "sdist", "python_version": "source", "requires_python": ">=2.7, >=3.5.*, <4", "size": 15935, "upload_time": "2017-11-23T20:37:42", "url": "https://files.pythonhosted.org/packages/1c/05/d73a8ef12684645661461687469e3a600107733ea519feeee51d3c4e821c/django-rest-framework-rules-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "727a984cdec9107df56436f1cc975289", "sha256": "c592306f5125bb0fdf990bc694d0f39f6f6a447de5636b4567d59987372eb2f7" }, "downloads": -1, "filename": "django_rest_framework_rules-0.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "727a984cdec9107df56436f1cc975289", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": ">=2.7, >=3.5.*, <4", "size": 11419, "upload_time": "2017-11-23T20:37:40", "url": "https://files.pythonhosted.org/packages/97/67/18c121512f74d65fa386ca56a844df4b72c091565c96413b83553def5b1d/django_rest_framework_rules-0.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "81f5be95abf809bd9e38b60116acf0e9", "sha256": "c20923f0ccb9f083b81e1a8dbf078fdfbd01ee8b572a21b22d12d07cef07a885" }, "downloads": -1, "filename": "django-rest-framework-rules-0.1.1.tar.gz", "has_sig": false, "md5_digest": "81f5be95abf809bd9e38b60116acf0e9", "packagetype": "sdist", "python_version": "source", "requires_python": ">=2.7, >=3.5.*, <4", "size": 15940, "upload_time": "2017-11-23T20:37:44", "url": "https://files.pythonhosted.org/packages/cc/41/8ab136e5d38197de8bb8ccca2221c5b98b7c3fc142e083a54ac4590c32c4/django-rest-framework-rules-0.1.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "8364d46fa1fe48c7f137ddb6673d53a5", "sha256": "a789bd15fcdf9f15ac8c9f999102c148a8c91f7feeb6bc9aec537ea675bcd36a" }, "downloads": -1, "filename": "django-rest-framework-rules-1.0.0.tar.gz", "has_sig": false, "md5_digest": "8364d46fa1fe48c7f137ddb6673d53a5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16061, "upload_time": "2018-05-17T14:00:30", "url": "https://files.pythonhosted.org/packages/82/7c/f700cd29e46cd04f9ff9017f29bbf5e865b1c5f3f5464274d5d2ef3aa759/django-rest-framework-rules-1.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "8364d46fa1fe48c7f137ddb6673d53a5", "sha256": "a789bd15fcdf9f15ac8c9f999102c148a8c91f7feeb6bc9aec537ea675bcd36a" }, "downloads": -1, "filename": "django-rest-framework-rules-1.0.0.tar.gz", "has_sig": false, "md5_digest": "8364d46fa1fe48c7f137ddb6673d53a5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16061, "upload_time": "2018-05-17T14:00:30", "url": "https://files.pythonhosted.org/packages/82/7c/f700cd29e46cd04f9ff9017f29bbf5e865b1c5f3f5464274d5d2ef3aa759/django-rest-framework-rules-1.0.0.tar.gz" } ] }