django-cabinet - Media library for Django

Version 0.7.6-2-g6d316bc

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. The folder tree intentionally uses an adjacency list model without any query optimization strategies (such as nested sets or recursive CTEs) so that no dependencies are necessary.

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.

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.utils.translation import ugettext_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

    # 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.

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 django.utils.translation import ugettext_lazy as _

from cabinet.admin import FileAdmin as _FileAdmin

from .models import File

@admin.register(File)
class FileAdmin(_FileAdmin):
    # list_display / list_display_links are probably fine if you
    # built on top of AbstractFile and added caption/copyright
    # fields, otherwise there is additional work to do.

    # You **have** to override this, or else you won't see the
    # pdf_file field of existing files:
    def get_fieldsets(self, request, obj=None):
        if obj and obj.image_file.name:
            return [(None, {'fields': (
                'folder', 'image_file', 'image_ppoi', 'caption',
                'image_alt_text', 'copyright',
            )})]

        elif obj and obj.pdf_file.name:
            return [(None, {'fields': (
                'folder', 'pdf_file', 'caption', 'copyright',
            )})]

        elif obj and obj.download_file.name:
            return [(None, {'fields': (
                'folder', 'download_file', 'caption', 'copyright',
            )})]

        else:
            return [
                (None, {'fields': (
                    'folder', 'caption', 'copyright',
                )}),
                (_('Image'), {'fields': (
                    'image_file', 'image_alt_text',
                )}),
                (_('PDF'), {'fields': (
                    'pdf_file',
                )}),
                (_('Download'), {'fields': (
                    'download_file',
                )}),
            ]

Last, add CABINET_FILE_MODEL = 'yourapp.File' to your Django settings.

Change log

Next version

  • 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.

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.