A pluggable Django app to reference documents (files) in models.
Project description
django-docmgr
A small, pluggable Django app to store and reference uploaded documents (files) from any model.
This release expands the project with optional Django REST Framework (DRF) integration so it can be used comfortably in API-centric projects without relying solely on Django Admin. The DRF layer is secure by default and simple to wire into existing routers.
Features
- Django Admin integration with preview of uploaded files.
- Document model with robust, slugified upload paths and configurable directory structure.
- Generic relation support: attach a document to any model via ContentType + object_id.
- Classic Django views for thumbnail/default file via django-downloadview.
- Optional DRF module (serializer + viewset) providing secure CRUD + download endpoints.
Requirements
- Python 3.10+
- Django 4.2+
- django-braces (>=1.4 recommended)
- django-downloadview (>=2.4.0 recommended)
- Optional: djangorestframework (if you use the API integration)
Prerequisites
- Ensure Django’s contenttypes framework is installed and active. See https://docs.djangoproject.com/en/5.2/ref/contrib/contenttypes/
Installation
-
Install the package:
pip install django-docmgr -
Add the app to INSTALLED_APPS:
INSTALLED_APPS = [
...,
"docmgr",
# Optional if you use DRF integration
# "rest_framework",
]
Admin quick start (Django Admin)
-
Include the classic URLconf (optional, only if you use the provided views): path("docmgr/", include("docmgr.urls")),
-
Use docmgr in your Admin with predefined inlines (includes image preview support):
from docmgr.models import Document
from docmgr.admin import DocumentAdmin, DocumentStackedInline, DocumentTabularInline
class MyDocumentInline(DocumentTabularInline):
pass
class MyModelAdmin(DocumentAdmin):
inlines = [MyDocumentInline]
Settings
Minimal settings (all optional):
- DOCMGR_UPLOAD_PATH: Base directory where files are stored (default: BASE_DIR / "files_docmgr"). If not set, DocMgr will create a "files_docmgr" directory in your project root. The given path does not need to live under MEDIA_ROOT.
- DOCMGR_UPLOAD_STRUCTURE: Subdirectory structure under the base path. One of:
- "year" (default) -> YYYY/
- "year_month" -> YYYY/MM/
- "date" or "date_iso" -> YYYY-MM-DD/
DRF-related optional settings:
-
DOCMGR_DRF_PERMISSION_CLASSES: list[str] Defaults to [ "rest_framework.permissions.IsAuthenticated", "rest_framework.permissions.DjangoModelPermissions", ]. Provide dotted paths to DRF permission classes you want to enforce on the viewset.
-
DOCMGR_DRF_THROTTLE_SCOPE: str Defaults to "docmgr". Used with DRF's ScopedRateThrottle.
-
DOCMGR_OBJECT_PERMISSION_HANDLER: str Optional. Dotted path to a class or function that implements object-level permissions based on the document's referenced object.
Quick start (DRF)
-
Install DRF if you haven't: pip install djangorestframework
-
Add rest_framework to INSTALLED_APPS.
-
Wire the viewset to your router (urls.py):
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from docmgr.drf import DocumentViewSet
router = DefaultRouter()
router.register(r"documents", DocumentViewSet, basename="document")
urlpatterns = [
path("api/", include(router.urls)),
]
- Ensure model permissions are in place. By default, the API requires authentication and Django model permissions (view/add/change/delete) for docmgr.Document. Grant users/groups the appropriate perms in your auth backend or via migrations/fixtures.
API reference (DRF)
Base path below assumes /api/documents/.
-
List: GET /api/documents/ Optional filtering by related object: ?content_type=<id|app_label.model>&object_id=
-
Retrieve: GET /api/documents/{uuid}/
-
Create (upload): POST /api/documents/ Content-Type: multipart/form-data Fields:
- docfile: file (required)
- description: string (optional)
- content_type: integer id OR "app_label.model" (optional)
- object_id: integer (optional)
-
Update: PUT/PATCH /api/documents/{uuid}/ You can update description, and optionally re-upload docfile.
-
Delete: DELETE /api/documents/{uuid}/
-
Download file contents: GET /api/documents/{uuid}/download/?attachment=1 If attachment=0/false, most browsers will try to display inline.
Attaching documents to your models
You can link a Document to any model using content_type + object_id (GenericForeignKey):
-
On upload, pass content_type and object_id. Example content_type values:
- 9 (ContentType pk)
- "blog.post" (app_label.model in lowercase)
-
To list all documents for a specific object: GET /api/documents/?content_type=blog.post&object_id=123
Permissions and throttling
- Permissions default to IsAuthenticated + DjangoModelPermissions.
- Throttling is opt-in. DocumentViewSet only enables throttling if your DRF settings define a rate for the configured scope (DOCMGR_DRF_THROTTLE_SCOPE, default "docmgr") in DEFAULT_THROTTLE_RATES. If no rate is present, throttling is disabled and no error is raised.
Customizing permissions
Provide your own permission classes via settings:
DOCMGR_DRF_PERMISSION_CLASSES = [
"rest_framework.permissions.IsAuthenticated",
"rest_framework.permissions.DjangoModelPermissions",
# Example: "yourapp.permissions.OwnerOrReadOnly"
]
Pluggable Object-Level Permissions
You can restrict access to a document based on the permissions of the object it references (content_type and object_id). This is useful when documents should inherit the access rights of their "parent" object.
Option A: Class-based Handler
Create a class that implements the ObjectPermissionHandler protocol.
# your_app/permissions.py
from typing import Any, Union
from django.contrib.auth.models import AnonymousUser, User
from django.db.models import QuerySet
from docmgr.models import Document
class MyObjectPermissionHandler:
def has_object_permission(self, request, view, obj: Document) -> bool:
user = request.user
action = 'view' if request.method in SAFE_METHODS else 'change'
# Get the referenced object
target = obj.object_id
if not target:
return False # Or True, depending on your policy
# Implement your logic (e.g., check if user can view the target)
if action == "view":
return user.has_perm("your_app.view_target", target)
return user.has_perm("your_app.change_target", target)
Option B: Function-based Handler
You can also use a simple function if you don't need filter_queryset or want a more lightweight approach. Note that functions only support the has_object_permission check; if you need custom queryset filtering, you must use a class.
# your_app/permissions.py
from django.contrib.auth.models import User
from docmgr.models import Document
def my_permission_check(user: User, obj: Document, action: str) -> bool:
target = obj.content_object
if not target:
return True
return user.has_perm(f"your_app.{action}_target", target)
Configuration
Configure the handler in your settings.py:
# For class-based:
DOCMGR_OBJECT_PERMISSION_HANDLER = "your_app.permissions.MyObjectPermissionHandler"
# For function-based:
# DOCMGR_OBJECT_PERMISSION_HANDLER = "your_app.permissions.my_permission_check"
The DefaultDocumentPermission will automatically call the handler after standard model permission checks. DocumentViewSet.get_queryset will also call filter_queryset (if using a class-based handler) to ensure users only see documents they are allowed to access.
Enabling throttling (optional)
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"docmgr": "50/min",
},
}
Note on "No default throttle rate set for 'docmgr' scope" If you enable ScopedRateThrottle but forget to add a rate for the "docmgr" scope in DEFAULT_THROTTLE_RATES, DRF will normally raise that error. The viewset avoids this by activating throttling only when a rate exists. To throttle docmgr endpoints, add the rate as shown above.
Security considerations and best practices
- Keep DOCMGR_UPLOAD_PATH outside STATIC_ROOT and ensure your web server does not serve it publicly without authentication.
- Rely on Django/DRF permissions to protect listing, retrieving, and downloading of documents.
- Use throttling to prevent abusive uploads/downloads.
- Filenames are slugified; upload subfolders are controlled by DOCMGR_UPLOAD_STRUCTURE to avoid traversal issues.
- For production downloads, consider serving via django-downloadview or signed URLs if needed. The DRF download action uses FileResponse and honors permissions.
Examples
curl upload (attach to object blog.post #123):
curl -X POST
-H "Authorization: Bearer "
-F "docfile=@/path/to/photo.jpg"
-F "description=A sample file"
-F "content_type=blog.post"
-F "object_id=123"
http://localhost:8000/api/documents/
curl list for given object:
curl -H "Authorization: Bearer "
"http://localhost:8000/api/documents/?content_type=blog.post&object_id=123"
curl download inline:
curl -L -H "Authorization: Bearer "
"http://localhost:8000/api/documents//download/?attachment=0" -o -
Python (requests) upload:
files = {"docfile": open("/path/file.pdf", "rb")}
data = {"description": "My doc", "content_type": "shop.order", "object_id": 42}
r = requests.post("http://localhost:8000/api/documents/", files=files, data=data, headers={"Authorization": "Bearer <token>"})
r.raise_for_status()
print(r.json())
FAQ
- Is DRF required? No. docmgr works without DRF. The DRF integration lives in docmgr.drf and is only imported if you use it.
- Can I store files in cloud storage? Yes. Configure DEFAULT_FILE_STORAGE or provide a custom storage in your project; docmgr uses a FileField and respects Django's storage backends.
- How do I restrict access so users see only their own files? Provide a custom permission and/or override get_queryset in your own subclass of DocumentViewSet.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_docmgr-0.7.0.tar.gz.
File metadata
- Download URL: django_docmgr-0.7.0.tar.gz
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"TUXEDO OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39f320bc40a37014fd5d438ce6dba94f01b1023a03bf26f4f818358794704dc6
|
|
| MD5 |
7ae13a01cd10b6d3bbf33f75c09b3bcd
|
|
| BLAKE2b-256 |
fd39a6ee5e648a9b29745933a25598010ea123ebcfe987dbad81926e3d1ac00f
|
File details
Details for the file django_docmgr-0.7.0-py3-none-any.whl.
File metadata
- Download URL: django_docmgr-0.7.0-py3-none-any.whl
- Upload date:
- Size: 28.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"TUXEDO OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81de19d58d55447920d66a7dfff92dd8b95c6d26568749392deda69d60558ddd
|
|
| MD5 |
c7d060554c8d061a7cb6fa35ce67a5b5
|
|
| BLAKE2b-256 |
036a00755791a925a855dc13281b027f9e8bcc4810529ba8d9db16206742c8bc
|