Package: django-seriously
… wait what? no seriously, why isn’t that part of Django/DRF?Opinionated collection ofDjangoandDjango REST frameworktools that came in handy time and again.AdminItemActionAllow triggering context-aware custom admin operations in model list views.admin_navigation_linkAllow 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 likefirst_nameandlast_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.TokenAuthenticationWhen OAuth2 adds too much complexity, DRF’s TokenAuthentication is too simple, anddjango-rest-knoxdoes not quite fit the permissioning.No plain passwords in database (PBKDF2, i.e. hashed and salted)Enabled for permission scopingEasy (one-time-view) token creation in Django adminBaseModel(abstract model)Reusable base model with automaticcreated_at,updated_atfields.Primary key is a random UUID (uuid4).Ensure validation logic (full_clean()) always runs, not just in a subset of cases.AppSettingsA settings container with defaults and string importing inspired by DRF’sAPISettingsLicenseProvided byT. Franzel,Licensed under 3-Clause BSD.RequirementsPython >= 3.6Django >= 3.0Django REST Framework (optional)Installation$pipinstalldjango-seriouslyDemoShowcasingAdminItemAction,admin_navigation_link,MinimalUserandTokenAuthenticationUsageAdminItemAction# admin.pyfromdjango_seriously.utils.adminimportAdminItemActionclassUserAdminAction(AdminItemAction[User]):model_cls=Useractions=[("reset_invitation","Reset Invitation"),]@classmethoddefis_actionable(cls,obj:User,action:str)->bool:# check whether action should be shown for this itemifaction=="reset_invitation":returnis_user_resettable_check(obj)# your codereturnFalsedefperform_action(self,obj:User,action:str)->Any:# perform the action on the itemifaction=="reset_invitation":perform_your_resetting(obj)# your codeobj.save()@admin.register(User)classUserAdmin(ModelAdmin):# insert item actions into a list view columnlist_display=(...,"admin_actions")defadmin_actions(self,obj:User):returnUserAdminAction.action_markup(obj)# urls.pyfromdjango_seriously.utils.adminimportAdminItemActionurlpatterns=[...# item actions must precede regular admin endpointspath("admin/",AdminItemAction.urls()),path("admin/",admin.site.urls),]admin_navigation_link# admin.pyfromdjango_seriously.utils.adminimportadmin_navigation_link@admin.register(Article)classArticleAdmin(ModelAdmin):# insert item actions into a list view columnlist_display=('id',"name","author_link")defauthor_link(self,obj:Article):returnadmin_navigation_link(obj.author,obj.author.name)TokenAuthentication# settings.pyINSTALLED_APPS=[...# only required if auth token is not extended by you'django_seriously.authtoken',...]SERIOUSLY_SETTINGS={"AUTH_TOKEN_SCOPES":["test-scope","test-scope2"]}# views.pyfromdjango_seriously.authtoken.authenticationimportTokenAuthentication,TokenHasScopeclassTestViewSet(viewsets.ModelViewSet):...permission_classes=[TokenHasScope]authentication_classes=[TokenAuthentication]required_scopes=['test-scope']MinimalUser# models.pyfromdjango_seriously.minimaluser.modelsimportMinimalAbstractUserfromdjango_seriously.utils.modelsimportBaseModel# BaseModel is optional but adds useful uuid, created_at, updated_atclassUser(BaseModel,MinimalAbstractUser):pass# admin.pyfromdjango_seriously.minimaluser.adminimportMinimalUserAdmin@admin.register(User)classUserAdmin(MinimalUserAdmin):pass