django-cabinet - Media library for Django

Version 0.14.3

django-cabinet is a media library for Django implemented while trying to write as little code as possible to keep maintenance at a minimum. At the time of writing the projects consists of less than 1000 lines of code (excluding tests), but still offers hierarchical folders, downloads, images with primary point of interest (courtesy of django-imagefield) and drag-drop uploading of files directly into the folder view.

Screenshots

_images/root-folders.png

List of root folders

_images/files-and-folders.png

Folder view with subfolders and files

_images/image-ppoi.png

File details with primary point of interest

Installation

  • pip install django-cabinet

  • Add cabinet and imagefield to your INSTALLED_APPS

  • Maybe replace the file model by setting CABINET_FILE_MODEL, but the default should be fine for most uses.

High-level overview

django-cabinet comes with two concrete models, cabinet.File and cabinet.Folder.

Folders can be nested into a hierarchy.

Files by default have two file fields, one for images based on django-imagefield and one for downloads, a standard Django FileField. Exactly one field has to be filled in. Files can only be added to folders; files can never exist in the root folder.

Using cabinet files in your models

The recommended way to use cabinet files in your models is by using cabinet.fields.CabinetForeignKey. When using raw_id_fields for the file foreign key you automatically get an improved widget which 1. allows directly uploading files inline without having to open the raw_id_fields popup and 2. also displays small thumbnails for image files.

Using django-cabinet as a CKEditor filebrowser

django-cabinet has built-in support for being used as a CKEditor 4 file manager. Currently, it only supports browsing files or images. Directly uploading files isn’t supported since the file browser API does not know about cabinet’s folders and folders are required for adding files to cabinet.

The values for using django-cabinet as a file and image browser follow (assuming you’re using the default file model):

CKEDITOR.config.filebrowserBrowseUrl = "/admin/cabinet/file/?_popup=1";
CKEDITOR.config.filebrowserImageUrl = "/admin/cabinet/file/?_popup=1&file_type=image_file";

Replacing the file model

The file model is swappable, and should be easy to replace if you require additional or different functionality. If, for example, you’d want to move all PDFs to a different storage, build on the following example code.

First, models.py:

from django.db import models
from django.db.models import signals
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _

from cabinet.base import AbstractFile, ImageMixin, DownloadMixin

class PDFMixin(models.Model):
    pdf_file = models.FileField(
        _('pdf'),
        upload_to=...,
        storage=...,
        blank=True,
    )

    class Meta:
        abstract = True
        verbose_name = _("PDF")
        verbose_name_plural = _("PDFs")

    # Cabinet requires a accept_file method on all mixins which
    # have a file field:
    def accept_file(self, value):
        if value.name.lower().endswith('.pdf'):
            self.pdf_file = value
            return True

class File(AbstractFile, ImageMixin, PDFMixin, DownloadMixin):
    FILE_FIELDS = ['image_file', 'pdf_file', 'download_file']

    # Add caption and copyright, makes FileAdmin reuse easier.
    caption = models.CharField(
        _('caption'),
        max_length=1000,
        blank=True,
    )
    copyright = models.CharField(
        _('copyright'),
        max_length=1000,
        blank=True,
    )

    # Add additional fields if you want to.

# If files should be automatically deleted (this is also the case when
# using the default file model):
@receiver(signals.post_delete, sender=File)
def delete_files(sender, instance, **kwargs):
    instance.delete_files()

Next, admin.py:

# You do not have to build on top of FileAdmin, but if there's no
# good reason not to it's probably much less work. If not, you
# still should take a long look at cabinet.base_admin.FileAdminBase

from django.contrib import admin

from cabinet.admin import FileAdmin

from .models import File

# The default should probably work fine.
admin.site.register(File, FileAdmin)

Then, add CABINET_FILE_MODEL = 'yourapp.File' to your Django settings and at last you’re ready for running ./manage.py makemigrations and ./manage.py migrate.

Change log

Next version

  • Added a Turkish translation. Thanks @mcihad!

  • Added the extra_context argument to our changelist_view. Thanks @underdoeg!

0.14 (2024-02-02)

  • Added Django 5.0, Python 3.12.

  • Updated the pre-commit hooks and added ESLint again (it was inadvertently dropped).

  • Dropped the unique constraint on folder name per subfolder. Duplicate names are a minor issue compared to the integrity errors which happen while loading fixtures.

0.13 (2023-07-21)

  • Added Django 4.1 and 4.2 to the CI matrix.

  • Added Python 3.11.

  • Switched to ruff and hatchling.

  • Documented that you should add a post_delete handler yourself when replacing the default file model.

  • Fixed a problem where the folder filter stopped working in the upcoming Django 5.0.

0.12 (2022-09-19)

  • Fixed a bug where mixins couldn’t be used as base classes of a custom file model.

  • Changed the file admin to never collapse the advanced fieldset by default.

  • Switched from Travis CI to GitHub actions.

  • Fixed a regression where the CKEditor plugin’s forms.Media declaration was missing the jQuery dependency.

  • Specified the default AutoField for django-cabinet to avoid migrations.

  • Added an additional safeguard against unhandleable images.

  • Fixed the default_app_config deprecation warning.

  • Raised the minimum versions of Python to 3.8 and Django to 3.2.

0.11 (2020-09-12)

  • Verified compatibility with Django 3.1 and Python 3.8 (no changes necessary).

  • Removed skipIf statements from the testsuite still referencing Django < 1.11.

  • Switched the testsuite to using sqlite3 instead of PostgreSQL. django-tree-queries supports different databases

  • Dropped Python 3.4 from the testsuite.

  • Updated ESLint and prettier used to format and check the CSS/JavaScript code.

  • Added isort.

0.10 (2019-12-19)

  • Changed files and folders to reuse more of django-tree-queries.

  • Made our inline upload JavaScript specify its dependency on django.jQuery.

  • Verified compatibility with Django 3.0.

  • Changed the abstract file model to protect files against cascading folder deletions.

0.9 (2019-04-15)

  • Changed CabinetForeignKey to reference the configured file model by default.

  • Limited the maximum width of the inline folder select widget.

  • Added tests for the CabinetForeignKey.

  • Hardened the file upload route a bit.

  • Removed a leftover call to versatileimagefields’ delete_all_created_images function.

  • Improved test coverage, mainly by actually writing more tests.

  • Changed reverse call sites to explicitly specify current_app.

  • Implemented optional autoselection of the last visited folder by explicitly specifying ?folder__id__exact=last.

  • Made CabinetForeignKey automatically open the last folder for new files.

  • Dropped compatibility with Django 1.8 again.

  • Made our JS files’ dependency on django.jQuery explicit.

  • Raised the length of file fields from 100 to 1000.

0.8 (2018-12-14)

  • Fix a problem where newer Django versions would crash because of a missing inline_admin_formsets variable in the admin change form context.

  • Fixed the folder hierarchy loop detection to not enter an infinite loop itself.

  • Fixed the breadcrumbs parent folder links.

  • Also prevented root folders with same name.

  • Added django-tree-queries for helping manage the folder tree.

  • Made search only search the current folder and its descendants.

  • Changed OverwriteMixin to only overwrite files once as intended.

  • Fixed a crash when moving several files at once with newer Django versions.

  • Reinstated the PPOI functionality in the default file admin interface.

  • Added a cabinet.fields.CabinetForeignKey drop-in replacement which extends the raw_id_fields administration interface with a direct upload facility.

  • Added configuration to make running prettier and ESLint easy.

  • Added compatibility with Django 1.8 so that migrating files from prehistoric django-filer versions gets easier.

  • Added more visible UI to upload several files at once.

  • Added timestamps to folders and files.

  • Added support for using django-cabinet as a CKEditor filebrowser.

  • Changed FileAdmin.get_fieldsets to automatically generate fitting fieldsets using the file mixins’ verbose name and editable fields.

  • Added a filter for only showing files of a certain type.

  • Improved test coverage a bit and updated the documentation after actually using a swappable file model in a project.

  • Fixed a crash when an invalid primary key was specified as a query parameter in the admin changelist.

  • Modified responses when adding or editing files to always redirect to the containing folder instead of the root folder.

  • Fixed a possible crash when setting _overwrite to true but uploading no new file.

0.7 (2018-03-28)

  • Switched the image field from django-versatileimagefield to django-imagefield. The latter uses the same database layout as the former, but there are differences when it comes to image generation and generating thumbnail URLs.

0.6 (2018-03-18)

  • Changed admin_details to not include superfluous <br> tags.

  • Changed the accept_file methods on file mixins to return bools and not raise exceptions.

  • Fixed the OverwriteMixin to call delete_files so that e.g. the imagefield gets a chance of removing stale thumbnails.

  • Dropped the useless AbstractFile, and renamed AbstractFileBase to AbstractFile.

  • Added a guide on how to swap out the file model.

  • Added a hint to the files changelist that drag-drop upload is possible.

  • Disabled the drag-drop upload on the root folder (which would not have worked anyway, because files cannot be added to the root folder).

  • Added unify so that only one quoting style is used in the code.

  • Changed the order of accept_file methods called to the order of FILE_FIELDS instead of the MRO (resp. the classes where the file fields are defined initially).

  • Fixed the double saves in OverwriteMixin, and hopefully avoided edge case-y problems with delete_files.

0.5 (2018-03-13)

  • Made the folder CRUD functionality preserve query parameters so that raw_id_fields popups work seamlessly.

  • Fixed the changelist to not crash when images are broken.

  • Changed the admin fieldsets to only show fields related to one file type when a cabinet file is filled in already.

  • Fixed a bug where adding subfolders would succeed, but redirect to the root folder.

  • Added an admin action for moving multiple files at once to a different folder.

0.4 (2017-07-04)

  • Made file model mixins determine themselves whether they can accept an upload or not.

  • Refactoring and code cleanups.

  • Tweaked the file list a bit.

0.3 (2017-06-21)

  • Added upload progress (only files, not bytes).

  • Implemented cleaning of storage when deleting and replacing files.

0.2 (2017-06-21)

  • Allow replacing files remotely.

  • Added caption, copyright and alt text fields.

  • Also show folder breadcrumbs when adding files.

  • Drag-drop upload of files directly into the folder view.

0.1 (2017-06-20)

  • Initial public version.