Skip to main content

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


Installation

  1. Install the package: pip install django-docmgr

  2. Add the app to INSTALLED_APPS:

   INSTALLED_APPS = [
       ...,
       "docmgr",
       # Optional if you use DRF integration
       # "rest_framework",
   ]

Admin quick start (Django Admin)

  1. Include the classic URLconf (optional, only if you use the provided views): path("docmgr/", include("docmgr.urls")),

  2. 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)

  1. Install DRF if you haven't: pip install djangorestframework

  2. Add rest_framework to INSTALLED_APPS.

  3. 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)),
   ]
  1. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_docmgr-0.7.0.tar.gz (21.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_docmgr-0.7.0-py3-none-any.whl (28.5 kB view details)

Uploaded Python 3

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

Hashes for django_docmgr-0.7.0.tar.gz
Algorithm Hash digest
SHA256 39f320bc40a37014fd5d438ce6dba94f01b1023a03bf26f4f818358794704dc6
MD5 7ae13a01cd10b6d3bbf33f75c09b3bcd
BLAKE2b-256 fd39a6ee5e648a9b29745933a25598010ea123ebcfe987dbad81926e3d1ac00f

See more details on using hashes here.

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

Hashes for django_docmgr-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 81de19d58d55447920d66a7dfff92dd8b95c6d26568749392deda69d60558ddd
MD5 c7d060554c8d061a7cb6fa35ce67a5b5
BLAKE2b-256 036a00755791a925a855dc13281b027f9e8bcc4810529ba8d9db16206742c8bc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page