... wait what? no seriously, why isn't that part of Django/DRF?
Opinionated collection of Django and Django REST framework tools that came in handy time and again.
AdminItemAction
- Allow triggering context-aware custom admin operations in model list views.
admin_navigation_link
- Allow navigation from the admin list view to other related models via links.
MinimalUser
(abstract model)- Bare minimum user model ready for customization.
- Removes the username and auxiliary fields like
first_name
andlast_name
. - Allow creating users without a valid password (unusable password)
- Abstract since its highly recommended to subclass the user model anyway.
ValidatedJSONField
(model field)- validate the structure of JSON fields with Pydantic models.
TokenAuthentication
- When OAuth2 adds too much complexity, DRF's TokenAuthentication is too simple, and django-rest-knox does not quite fit the permissioning.
- No plain passwords in database (PBKDF2, i.e. hashed and salted)
- Enabled for permission scoping
- Easy (one-time-view) token creation in Django admin
BaseModel
(abstract model)- Reusable base model with automatic
created_at
,updated_at
fields. - Primary key is a random UUID (
uuid4
). - Ensure validation logic (
full_clean()
) always runs, not just in a subset of cases.
- Reusable base model with automatic
AppSettings
- A settings container with defaults and string importing inspired by DRF's
APISettings
- A settings container with defaults and string importing inspired by DRF's
Provided by T. Franzel, Licensed under 3-Clause BSD.
- Python >= 3.6
- Django >= 3.0
- Django REST Framework (optional)
$ pip install django-seriously
Showcasing AdminItemAction
, admin_navigation_link
, MinimalUser
and TokenAuthentication
# admin.py
from django_seriously.utils.admin import AdminItemAction
class UserAdminAction(AdminItemAction[User]):
model_cls = User
actions = [
("reset_invitation", "Reset Invitation"),
]
@classmethod
def is_actionable(cls, obj: User, action: str) -> bool:
# check whether action should be shown for this item
if action == "reset_invitation":
return is_user_resettable_check(obj) # your code
return False
def perform_action(self, obj: User, action: str) -> Any:
# perform the action on the item
if action == "reset_invitation":
perform_your_resetting(obj) # your code
obj.save()
@admin.register(User)
class UserAdmin(ModelAdmin):
# insert item actions into a list view column
list_display = (..., "admin_actions")
def admin_actions(self, obj: User):
return UserAdminAction.action_markup(obj)
# urls.py
from django_seriously.utils.admin import AdminItemAction
urlpatterns = [
...
# item actions must precede regular admin endpoints
path("admin/", AdminItemAction.urls()),
path("admin/", admin.site.urls),
]
# admin.py
from django_seriously.utils.admin import admin_navigation_link
@admin.register(Article)
class ArticleAdmin(ModelAdmin):
# insert item actions into a list view column
list_display = ('id', "name", "author_link")
def author_link(self, obj: Article):
return admin_navigation_link(obj.author, obj.author.name)
# settings.py
INSTALLED_APPS = [
...
# only required if auth token is not extended by you
'django_seriously.authtoken',
...
]
SERIOUSLY_SETTINGS = {
"AUTH_TOKEN_SCOPES": ["test-scope", "test-scope2"]
}
# views.py
from django_seriously.authtoken.authentication import TokenAuthentication, TokenHasScope
class TestViewSet(viewsets.ModelViewSet):
...
permission_classes = [TokenHasScope]
authentication_classes = [TokenAuthentication]
required_scopes = ['test-scope']
# models.py
from django_seriously.minimaluser.models import MinimalAbstractUser
from django_seriously.utils.models import BaseModel
# BaseModel is optional but adds useful uuid, created_at, updated_at
class User(BaseModel, MinimalAbstractUser):
pass
# admin.py
from django_seriously.minimaluser.admin import MinimalUserAdmin
@admin.register(User)
class UserAdmin(MinimalUserAdmin):
pass