# Django Permissify
Django Permissify is a reusable Django app that provides a robust and flexible way to manage object-level permissions. It allows you to assign permissions to users and groups on a per-object basis, enhancing the default Django permissions system.
## Features
- Assign permissions to users, groups (and optionally roles) on a per-object basis.
- Manage roles and permissions through Django admin.
- Integrates seamlessly with Django's authentication system.
- Provides management commands for adding and removing roles.
- Integrates with Django Rest Framework (DRF) to provide object-level permissions for API views.
-
## Installation
1. Install the package using pip:
```bash
pip install django-permissify
```
2. Add `permissify` to your `INSTALLED_APPS` in your Django settings:
```python
INSTALLED_APPS = [
...
'permissify',
]
```
3. Add the custom authentication backend to your `AUTHENTICATION_BACKENDS`:
```python
AUTHENTICATION_BACKENDS = [
'permissify.backends.ObjectPermissionModelBackend',
...
]
```
4. Run the migrations to create the necessary database tables:
```bash
python manage.py migrate
```
## Optional Configurations
- If you want to use the custom User model provided by this app, which includes roles, set the **AUTH_USER_MODEL** in your `settings.py` to `permissify.User`:
```python
AUTH_USER_MODEL = 'permissify.User'
```
Please refer to [Django's documentation](https://docs.djangoproject.com/en/5.0/topics/auth/customizing/#changing-to-a-custom-user-model-mid-project) to determine if this change is suitable for your project.
## Usage
### Granting and Revoking Permissions
#### Granting a Permission
```python
from permissify.shortcuts import grant_perm
from django.contrib.auth.models import Permission
# Grant a permission to a user
grant_perm(user, '<app_label>.<permission>_<model_name>')
# Grant a permission to a user for a specific object
grant_perm(user, '<app_label>.<permission>_<model_name>', obj)
# Pass the permission instance directly
perm = Permission.objects.get(pk=<...>)
grant_perm(user, perm)
# or for object permission only: grant_perm(user, perm, obj)
# Grant all permissions to an object
grant_perm(user, '*', obj)
grant_perm(user, '__all__', obj)
# Grant all permissions of an app
grant_perm(user, '<app_label>.*')
grant_perm(user, '<app_label>.__all__')
# Grant all permissions of a model (including future ones)
grant_perm(user, '<app_label>.*_<model_name>')
# or: grant_perm(user, '<app_label>.*_<model_name>', obj) for an object-level permission
```
#### Revoking a Permission
```python
from permissify.shortcuts import revoke_perm
from django.contrib.auth.models import Permission
# Revoke a permission from a user
revoke_perm(user, '<app_label>.<permission>_<model_name>')
# Revoke a permission from a user for a specific object
revoke_perm(user, '<app_label>.<permission>_<model_name>', obj)
# Pass the permission instance directly
perm = Permission.objects.get(pk=<...>)
revoke_perm(user, perm) # or for object permission only: revoke_perm(user, perm, obj)
# Revoke all permissions of an object
revoke_perm(user, '*', obj)
revoke_perm(user, '__all__', obj)
# Revoke all permissions of an app
revoke_perm(user, '<app_label>.*')
revoke_perm(user, '<app_label>.__all__')
# Revoke all permissions of a model (including future ones)
revoke_perm(user, '<app_label>.*_<model_name>')
# or: revoke_perm(user, '<app_label>.*_<model_name>', obj) for an object-level permission
```
#### Granting or Revoking Permissions Through a Group or Role
The same logic for granting or revoking permissions can be applied to groups or roles. Use the default Django method to check permissions with `user.has_perm(...)`.
##### Granting / Revoking Through a Group
```python
from permissify.shortcuts import grant_perm, revoke_perm
from django.contrib.auth import get_user_model
from permissify.models import Group
User = get_user_model()
foo = User.objects.create_user(username="foo", email="foo@example.com", password="foo")
bar = User.objects.create_user(username="bar", email="bar@example.com", password="bar")
group = Group.objects.create(name="pro-membership")
foo.groups.add(group)
grant_perm(group, "<app_label>.<permission>_<model_name>")
# Refer to Django documentation to understand why you need to refresh the user instance
# after granting or revoking a permission:
# https://docs.djangoproject.com/en/5.0/topics/auth/default/#permission-caching
foo = User.objects.get(pk=foo.pk)
assert foo.has_perm("<app_label>.<permission>_<model_name>")
# Some code logic ...
revoke_perm(group, "<app_label>.<permission>_<model_name>")
foo = User.objects.get(pk=foo.pk)
assert not foo.has_perm("<app_label>.<permission>_<model_name>")
```
##### Granting / Revoking Through a Role
If you swapped your authentication model to use `permissify.User`, you can also manage roles.
```python
from permissify.shortcuts import grant_perm, revoke_perm
from django.contrib.auth import get_user_model
from permissify.models import Role
User = get_user_model()
foo = User.objects.create_user(username="foo", email="foo@example.com", password="foo")
bar = User.objects.create_user(username="bar", email="bar@example.com", password="bar")
role = Role.objects.create(name="editor")
foo.roles.add(role)
grant_perm(role, "<app_label>.<permission>_<model_name>")
# Refresh the user instance after granting or revoking a permission:
# https://docs.djangoproject.com/en/5.0/topics/auth/default/#permission-caching
foo = User.objects.get(pk=foo.pk)
assert foo.has_perm("<app_label>.<permission>_<model_name>")
# Some code logic ...
revoke_perm(role, "<app_label>.<permission>_<model_name>")
foo = User.objects.get(pk=foo.pk)
assert not foo.has_perm("<app_label>.<permission>_<model_name>")
```
###### NOTE
If you cannot swap your model to `permissify.User` but still want to include roles in your project, you can add the `RolePermissionMixin` to your user model.
Ensure that you don't already have a "roles" field in your User model, as this will be used for the `ManyToManyField` relationship to the `Role` model.
For example:
```python
# foo/models.py
from django.contrib.auth import models as auth_models
class FooUser(ExternalAppUser, ComplexStuffUser, auth_models.AbstractUser):
# some stuff...
...
# settings.py
AUTH_USER_MODEL = 'foo.FooUser'
```
To include roles without swapping, update your current model to include `RolePermissionMixin` and run migrations to include relationships to roles.
```python
# foo/models.py
from django.contrib.auth import models as auth_models
from permissify.models import RolePermissionMixin
# Pay close attention to the order of inheritance
class FooUser(ExternalAppUser, ComplexStuffUser, RolePermissionMixin, auth_models.AbstractUser):
# some stuff...
...
```
### Using with Django Rest Framework (DRF)
Django Permissify provides object-level permissions for API `viewsets` in DRF through its permission classes.
#### Permission Classes
- `PermissifyObjectPermissions`: Ensures that the user has the required object-level permissions
- `PermissifyObjectPermissionsOrAnonReadOnly`: Allows read-only access for anonymous users, but requires object-level permissions for other actions.
```python
# views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets, status
from rest_framework.response import Response
from permissify.drf.permissions import PermissifyObjectPermissions, PermissifyObjectPermissionsOrAnonReadOnly
from myapp.serializers import UserSerializer
User = get_user_model()
class MockViewSet(viewsets.ModelViewSet):
permission_classes = [PermissifyObjectPermissions]
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request, *args, **kwargs):
return Response({'detail': 'success'}, status=status.HTTP_200_OK)
class MockViewAnonReadOnlySet(viewsets.ModelViewSet):
permission_classes = [PermissifyObjectPermissionsOrAnonReadOnly]
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request, *args, **kwargs):
return Response({'detail': 'success'}, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
return Response({'detail': 'success'}, status=status.HTTP_201_CREATED)
```
### Management Commands
#### Adding a Role
```bash
python manage.py add_role <role_name> --permissions [<app_label.permission_codename>, ...]
```
### Removing a Role
```bash
python manage.py remove_role <role_name>
```
#### Why Use a Role Model Instead of Just the Group Model if They Are Identical Tables?
##### Separate Logical Concerns of Groups vs Roles
**Groups:** Typically used to manage permissions for users with common access needs. For example, an "editors" group might have permission to edit articles. A "users" group can comment on the editors' posts. A "Moderator" role, for example, can validate both posts and comments, crossing both groups. Instead of a "moderators" group, it's more semantically correct to have a "Moderator" role that can validate editors' posts and users' comments.
**Roles:** Often represent a user's position or function within an organization, such as "Manager" or "Employee". Roles encapsulate a set of permissions tied to a specific job function.
##### Additional Model for Other Types of Permissions
Having a separate Role model allows for more granular control over permissions. For instance, in a subscription-based application, groups can determine if a user has paid for a subscription (e.g., a group of "pro" users), while roles manage specific permissions within the application (e.g., editor, contributor, moderator).
##### Business Enterprises Preference
Many enterprises prefer the concept of roles because it aligns more closely with organizational structures. Roles define job functions and responsibilities, making it easier to manage permissions based on an employee's role within the company.
##### Flexibility and Extensibility
Using a separate Role model provides flexibility to extend the model with additional fields and methods specific to roles. This includes custom logic for role-based access control, additional metadata, or integration with other systems.
---
Thank you for reading!
If you like this library, you can support me by [buying me a coffee](https://www.buymeacoffee.com/dmp593).
Raw data
{
"_id": null,
"home_page": "https://github.com/dmp593/django-permissify",
"name": "django-permissify",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.10",
"maintainer_email": null,
"keywords": "Django, Permissions, Object Permissions",
"author": "Daniel Pinto",
"author_email": "dmp593@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/3a/aa/253dc29ae7a1c96ab6ba2b0e470e433634a0e908c6865c3e1ea8901d64b0/django_permissify-1.0.6.tar.gz",
"platform": null,
"description": "# Django Permissify\n\nDjango Permissify is a reusable Django app that provides a robust and flexible way to manage object-level permissions. It allows you to assign permissions to users and groups on a per-object basis, enhancing the default Django permissions system.\n\n## Features\n\n- Assign permissions to users, groups (and optionally roles) on a per-object basis.\n- Manage roles and permissions through Django admin.\n- Integrates seamlessly with Django's authentication system.\n- Provides management commands for adding and removing roles.\n- Integrates with Django Rest Framework (DRF) to provide object-level permissions for API views.\n- \n## Installation\n\n1. Install the package using pip:\n\n ```bash\n pip install django-permissify\n ``` \n\n2. Add `permissify` to your `INSTALLED_APPS` in your Django settings:\n\n ```python\n INSTALLED_APPS = [\n ...\n 'permissify',\n ]\n ```\n\n3. Add the custom authentication backend to your `AUTHENTICATION_BACKENDS`:\n\n ```python\n AUTHENTICATION_BACKENDS = [\n 'permissify.backends.ObjectPermissionModelBackend',\n ...\n ]\n ```\n\n4. Run the migrations to create the necessary database tables:\n\n ```bash\n python manage.py migrate\n ```\n\n## Optional Configurations\n\n- If you want to use the custom User model provided by this app, which includes roles, set the **AUTH_USER_MODEL** in your `settings.py` to `permissify.User`:\n\n ```python\n AUTH_USER_MODEL = 'permissify.User'\n ```\n\nPlease refer to [Django's documentation](https://docs.djangoproject.com/en/5.0/topics/auth/customizing/#changing-to-a-custom-user-model-mid-project) to determine if this change is suitable for your project.\n\n## Usage\n\n### Granting and Revoking Permissions\n\n#### Granting a Permission\n\n```python\nfrom permissify.shortcuts import grant_perm\nfrom django.contrib.auth.models import Permission\n\n# Grant a permission to a user\ngrant_perm(user, '<app_label>.<permission>_<model_name>')\n\n# Grant a permission to a user for a specific object\ngrant_perm(user, '<app_label>.<permission>_<model_name>', obj)\n\n# Pass the permission instance directly\nperm = Permission.objects.get(pk=<...>)\ngrant_perm(user, perm)\n# or for object permission only: grant_perm(user, perm, obj)\n\n# Grant all permissions to an object\ngrant_perm(user, '*', obj)\ngrant_perm(user, '__all__', obj)\n\n# Grant all permissions of an app\ngrant_perm(user, '<app_label>.*')\ngrant_perm(user, '<app_label>.__all__')\n\n# Grant all permissions of a model (including future ones)\ngrant_perm(user, '<app_label>.*_<model_name>')\n# or: grant_perm(user, '<app_label>.*_<model_name>', obj) for an object-level permission\n```\n\n#### Revoking a Permission\n\n```python\nfrom permissify.shortcuts import revoke_perm\nfrom django.contrib.auth.models import Permission\n\n# Revoke a permission from a user\nrevoke_perm(user, '<app_label>.<permission>_<model_name>')\n\n# Revoke a permission from a user for a specific object\nrevoke_perm(user, '<app_label>.<permission>_<model_name>', obj)\n\n# Pass the permission instance directly\nperm = Permission.objects.get(pk=<...>)\nrevoke_perm(user, perm) # or for object permission only: revoke_perm(user, perm, obj)\n\n# Revoke all permissions of an object\nrevoke_perm(user, '*', obj)\nrevoke_perm(user, '__all__', obj)\n\n# Revoke all permissions of an app\nrevoke_perm(user, '<app_label>.*')\nrevoke_perm(user, '<app_label>.__all__')\n\n# Revoke all permissions of a model (including future ones)\nrevoke_perm(user, '<app_label>.*_<model_name>')\n# or: revoke_perm(user, '<app_label>.*_<model_name>', obj) for an object-level permission\n```\n\n#### Granting or Revoking Permissions Through a Group or Role\n\nThe same logic for granting or revoking permissions can be applied to groups or roles. Use the default Django method to check permissions with `user.has_perm(...)`.\n\n##### Granting / Revoking Through a Group\n\n```python\nfrom permissify.shortcuts import grant_perm, revoke_perm\nfrom django.contrib.auth import get_user_model\nfrom permissify.models import Group\n\nUser = get_user_model()\n\nfoo = User.objects.create_user(username=\"foo\", email=\"foo@example.com\", password=\"foo\")\nbar = User.objects.create_user(username=\"bar\", email=\"bar@example.com\", password=\"bar\")\n\ngroup = Group.objects.create(name=\"pro-membership\")\n\nfoo.groups.add(group)\n\ngrant_perm(group, \"<app_label>.<permission>_<model_name>\")\n\n# Refer to Django documentation to understand why you need to refresh the user instance\n# after granting or revoking a permission:\n# https://docs.djangoproject.com/en/5.0/topics/auth/default/#permission-caching\nfoo = User.objects.get(pk=foo.pk)\n\nassert foo.has_perm(\"<app_label>.<permission>_<model_name>\")\n\n# Some code logic ...\n\nrevoke_perm(group, \"<app_label>.<permission>_<model_name>\")\nfoo = User.objects.get(pk=foo.pk)\n\nassert not foo.has_perm(\"<app_label>.<permission>_<model_name>\")\n```\n\n##### Granting / Revoking Through a Role\n\nIf you swapped your authentication model to use `permissify.User`, you can also manage roles.\n\n```python\nfrom permissify.shortcuts import grant_perm, revoke_perm\nfrom django.contrib.auth import get_user_model\nfrom permissify.models import Role\n\nUser = get_user_model()\n\nfoo = User.objects.create_user(username=\"foo\", email=\"foo@example.com\", password=\"foo\")\nbar = User.objects.create_user(username=\"bar\", email=\"bar@example.com\", password=\"bar\")\n\nrole = Role.objects.create(name=\"editor\")\n\nfoo.roles.add(role)\n\ngrant_perm(role, \"<app_label>.<permission>_<model_name>\")\n\n# Refresh the user instance after granting or revoking a permission:\n# https://docs.djangoproject.com/en/5.0/topics/auth/default/#permission-caching\nfoo = User.objects.get(pk=foo.pk)\n\nassert foo.has_perm(\"<app_label>.<permission>_<model_name>\")\n\n# Some code logic ...\n\nrevoke_perm(role, \"<app_label>.<permission>_<model_name>\")\nfoo = User.objects.get(pk=foo.pk)\n\nassert not foo.has_perm(\"<app_label>.<permission>_<model_name>\")\n```\n\n###### NOTE\n\nIf you cannot swap your model to `permissify.User` but still want to include roles in your project, you can add the `RolePermissionMixin` to your user model.\n\nEnsure that you don't already have a \"roles\" field in your User model, as this will be used for the `ManyToManyField` relationship to the `Role` model.\n\nFor example:\n\n```python\n# foo/models.py\n\nfrom django.contrib.auth import models as auth_models\n\nclass FooUser(ExternalAppUser, ComplexStuffUser, auth_models.AbstractUser):\n # some stuff...\n ...\n\n# settings.py\nAUTH_USER_MODEL = 'foo.FooUser'\n```\n\nTo include roles without swapping, update your current model to include `RolePermissionMixin` and run migrations to include relationships to roles.\n\n```python\n# foo/models.py\n\nfrom django.contrib.auth import models as auth_models\nfrom permissify.models import RolePermissionMixin\n\n# Pay close attention to the order of inheritance\nclass FooUser(ExternalAppUser, ComplexStuffUser, RolePermissionMixin, auth_models.AbstractUser):\n # some stuff...\n ...\n```\n\n\n### Using with Django Rest Framework (DRF)\n\nDjango Permissify provides object-level permissions for API `viewsets` in DRF through its permission classes.\n\n#### Permission Classes\n\n- `PermissifyObjectPermissions`: Ensures that the user has the required object-level permissions\n- `PermissifyObjectPermissionsOrAnonReadOnly`: Allows read-only access for anonymous users, but requires object-level permissions for other actions.\n\n\n```python\n# views.py\n\nfrom django.contrib.auth import get_user_model\nfrom rest_framework import viewsets, status\nfrom rest_framework.response import Response\nfrom permissify.drf.permissions import PermissifyObjectPermissions, PermissifyObjectPermissionsOrAnonReadOnly\nfrom myapp.serializers import UserSerializer\n\n\nUser = get_user_model()\n\n\nclass MockViewSet(viewsets.ModelViewSet):\n permission_classes = [PermissifyObjectPermissions]\n queryset = User.objects.all()\n serializer_class = UserSerializer\n\n def list(self, request, *args, **kwargs):\n return Response({'detail': 'success'}, status=status.HTTP_200_OK)\n\n \nclass MockViewAnonReadOnlySet(viewsets.ModelViewSet):\n permission_classes = [PermissifyObjectPermissionsOrAnonReadOnly]\n queryset = User.objects.all()\n serializer_class = UserSerializer\n\n def list(self, request, *args, **kwargs):\n return Response({'detail': 'success'}, status=status.HTTP_200_OK)\n\n def create(self, request, *args, **kwargs):\n return Response({'detail': 'success'}, status=status.HTTP_201_CREATED)\n```\n\n\n### Management Commands\n\n#### Adding a Role\n\n```bash\npython manage.py add_role <role_name> --permissions [<app_label.permission_codename>, ...]\n```\n\n### Removing a Role\n\n```bash\npython manage.py remove_role <role_name>\n```\n\n#### Why Use a Role Model Instead of Just the Group Model if They Are Identical Tables?\n\n##### Separate Logical Concerns of Groups vs Roles\n\n**Groups:** Typically used to manage permissions for users with common access needs. For example, an \"editors\" group might have permission to edit articles. A \"users\" group can comment on the editors' posts. A \"Moderator\" role, for example, can validate both posts and comments, crossing both groups. Instead of a \"moderators\" group, it's more semantically correct to have a \"Moderator\" role that can validate editors' posts and users' comments.\n\n**Roles:** Often represent a user's position or function within an organization, such as \"Manager\" or \"Employee\". Roles encapsulate a set of permissions tied to a specific job function.\n\n##### Additional Model for Other Types of Permissions\n\nHaving a separate Role model allows for more granular control over permissions. For instance, in a subscription-based application, groups can determine if a user has paid for a subscription (e.g., a group of \"pro\" users), while roles manage specific permissions within the application (e.g., editor, contributor, moderator).\n\n##### Business Enterprises Preference\n\nMany enterprises prefer the concept of roles because it aligns more closely with organizational structures. Roles define job functions and responsibilities, making it easier to manage permissions based on an employee's role within the company.\n\n##### Flexibility and Extensibility\n\nUsing a separate Role model provides flexibility to extend the model with additional fields and methods specific to roles. This includes custom logic for role-based access control, additional metadata, or integration with other systems.\n\n---\n\nThank you for reading!\n\nIf you like this library, you can support me by [buying me a coffee](https://www.buymeacoffee.com/dmp593).",
"bugtrack_url": null,
"license": "MIT",
"summary": "Django Object Permissions",
"version": "1.0.6",
"project_urls": {
"Homepage": "https://github.com/dmp593/django-permissify",
"Repository": "https://github.com/dmp593/django-permissify"
},
"split_keywords": [
"django",
" permissions",
" object permissions"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "40d10e25db3c215fc34929dbda84d686cf82471d675602e6397d775f50765674",
"md5": "68419dcf58e6032f7e43229a0b14dbae",
"sha256": "c114603f9de0d9980064e78112f20f3873c0b739c6216c17d587326fe2bd146f"
},
"downloads": -1,
"filename": "django_permissify-1.0.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "68419dcf58e6032f7e43229a0b14dbae",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.10",
"size": 14781,
"upload_time": "2024-07-11T10:19:00",
"upload_time_iso_8601": "2024-07-11T10:19:00.517082Z",
"url": "https://files.pythonhosted.org/packages/40/d1/0e25db3c215fc34929dbda84d686cf82471d675602e6397d775f50765674/django_permissify-1.0.6-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "3aaa253dc29ae7a1c96ab6ba2b0e470e433634a0e908c6865c3e1ea8901d64b0",
"md5": "8ffbad2aee91813f3b194646bb437136",
"sha256": "ccfb2444ae163620ed4bba7d8916641376c2c4f80a59ecd0406fea1d136face4"
},
"downloads": -1,
"filename": "django_permissify-1.0.6.tar.gz",
"has_sig": false,
"md5_digest": "8ffbad2aee91813f3b194646bb437136",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.10",
"size": 14005,
"upload_time": "2024-07-11T10:19:02",
"upload_time_iso_8601": "2024-07-11T10:19:02.853080Z",
"url": "https://files.pythonhosted.org/packages/3a/aa/253dc29ae7a1c96ab6ba2b0e470e433634a0e908c6865c3e1ea8901d64b0/django_permissify-1.0.6.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-11 10:19:02",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "dmp593",
"github_project": "django-permissify",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "django-permissify"
}