14 changed files with 256 additions and 45 deletions
@ -1,3 +1,18 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
||||||
|
from __future__ import unicode_literals |
||||||
|
# Erik Stein <code@classlibrary.net>, 2016 |
||||||
|
|
||||||
from django.contrib import admin |
from django.contrib import admin |
||||||
|
from django.db import models |
||||||
|
from django.utils.translation import ugettext_lazy as _ |
||||||
|
|
||||||
|
from .models import MediaAsset |
||||||
|
|
||||||
|
|
||||||
|
@admin.register(MediaAsset) |
||||||
|
class MediaAssetAdmin(admin.ModelAdmin): |
||||||
|
list_display = ['name', 'get_original_path'] |
||||||
|
|
||||||
# Register your models here. |
def get_original_path(self, obj): |
||||||
|
return obj.original_file.name |
||||||
|
get_original_path.short_description = _("original path") |
||||||
|
@ -0,0 +1,110 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
||||||
|
from __future__ import unicode_literals |
||||||
|
# Erik Stein <code@classlibrary.net>, 2016 |
||||||
|
|
||||||
|
import os |
||||||
|
from django import forms |
||||||
|
from django.contrib.admin.widgets import AdminFileWidget |
||||||
|
from django.db import models |
||||||
|
from django.db.models.fields.files import FileDescriptor |
||||||
|
from django.utils.html import conditional_escape |
||||||
|
from django.utils.text import slugify |
||||||
|
|
||||||
|
from ..storages import ProtectedMediaAssetStorage |
||||||
|
|
||||||
|
|
||||||
|
# TODO Define the central storage somewhere more central |
||||||
|
ORIGINALS_STORAGE = ProtectedMediaAssetStorage() |
||||||
|
# TODO Move filename max length to storage |
||||||
|
FILENAME_MAX_LENGTH = 255 |
||||||
|
|
||||||
|
|
||||||
|
# TOD Move get_upload_path to utils |
||||||
|
def get_upload_path(instance, filename): |
||||||
|
""" |
||||||
|
Returns /<uuid_hex>/original/<slugified_filename.ext> |
||||||
|
where |
||||||
|
- uuid is taken from instance, |
||||||
|
- filename is slugified and shortened to a max length including the extension. |
||||||
|
""" |
||||||
|
name, ext = os.path.splitext(filename) |
||||||
|
name = slugify(name) |
||||||
|
name = name[:(FILENAME_MAX_LENGTH - len(ext))] |
||||||
|
filename = "%s%s" % (name, ext) |
||||||
|
return os.path.join( |
||||||
|
instance.get_uuid(), |
||||||
|
instance.STORAGE.ORIGINAL_FILE_PREFIX, |
||||||
|
filename |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_UPLOAD_TO = get_upload_path |
||||||
|
|
||||||
|
|
||||||
|
class MediaAssetFileWidget(AdminFileWidget): |
||||||
|
""" |
||||||
|
Widget which understands ProtectedMediaAssetStorage |
||||||
|
(knows that the url property is not relevant), also |
||||||
|
does not provide a link to the original file. |
||||||
|
# TODO Add admin access to the original file, if permissions apply |
||||||
|
# TODO Add permission "allowed to view original file" |
||||||
|
""" |
||||||
|
|
||||||
|
template_with_initial = ( |
||||||
|
'%(initial_text)s: %(initial)s ' |
||||||
|
'%(clear_template)s<br />%(input_text)s: %(input)s' |
||||||
|
) |
||||||
|
|
||||||
|
def is_initial(self, value): |
||||||
|
# Checks for 'name' instead of 'url' property |
||||||
|
return bool(value and hasattr(value, 'name')) |
||||||
|
|
||||||
|
def get_template_substitution_values(self, value): |
||||||
|
# Does not use value.url |
||||||
|
return { |
||||||
|
'initial': conditional_escape(value), |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class MediaAssetFileDescriptor(FileDescriptor): |
||||||
|
# TODO Assign metadata fields like width/height |
||||||
|
|
||||||
|
def __set__(self, instance, value): |
||||||
|
previous_file = instance.__dict__.get(self.field.name) |
||||||
|
super(MediaAssetFileDescriptor, self).__set__(instance, value) |
||||||
|
|
||||||
|
# TODO Check if previous_file is actually None in our case; remove comment |
||||||
|
# To prevent recalculating image dimensions when we are instantiating |
||||||
|
# an object from the database (bug #11084), only update dimensions if |
||||||
|
# the field had a value before this assignment. Since the default |
||||||
|
# value for FileField subclasses is an instance of field.attr_class, |
||||||
|
# previous_file will only be None when we are called from |
||||||
|
# Model.__init__(). The ImageField.update_dimension_fields method |
||||||
|
# hooked up to the post_init signal handles the Model.__init__() cases. |
||||||
|
# Assignment happening outside of Model.__init__() will trigger the |
||||||
|
# update right here. |
||||||
|
if previous_file is not None: |
||||||
|
pass # self.field.update_dimension_fields(instance, force=True) |
||||||
|
|
||||||
|
|
||||||
|
class MediaAssetFormField(forms.FileField): |
||||||
|
widget = MediaAssetFileWidget |
||||||
|
|
||||||
|
|
||||||
|
class MediaAssetField(models.FileField): |
||||||
|
# The descriptor to use for accessing the attribute off of the class. |
||||||
|
description_class = MediaAssetFileDescriptor |
||||||
|
|
||||||
|
def _init__(self, verbose_name=None, name=None, upload_to='', storage=None, *args, **kwargs): |
||||||
|
kwargs['max_length'] = kwargs.get('max_length', FILENAME_MAX_LENGTH) |
||||||
|
kwargs['upload_to'] = kwargs.get('upload_to', DEFAULT_UPLOAD_TO) |
||||||
|
kwargs['storage'] = kwargs.get('storage', ORIGINALS_STORAGE) |
||||||
|
super(MediaAssetField, self).__init__(verbose_name, name, **kwargs) |
||||||
|
|
||||||
|
def formfield(self, **kwargs): |
||||||
|
defaults = { |
||||||
|
'form_class': MediaAssetFormField, |
||||||
|
'max_length': self.max_length |
||||||
|
} |
||||||
|
defaults.update(kwargs) |
||||||
|
return super(MediaAssetField, self).formfield(**defaults) |
@ -0,0 +1,8 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
||||||
|
from __future__ import unicode_literals |
||||||
|
# Erik Stein <code@classlibrary.net>, 2016 |
||||||
|
|
||||||
|
|
||||||
|
class FileField(Field): |
||||||
|
widget = ClearableFileInput |
||||||
|
|
@ -0,0 +1,27 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
||||||
|
# Generated by Django 1.9.4 on 2016-03-24 12:34 |
||||||
|
from __future__ import unicode_literals |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
import main.medialibrary.models |
||||||
|
import main.storages |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
initial = True |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.CreateModel( |
||||||
|
name='MediaAsset', |
||||||
|
fields=[ |
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||||
|
('uuid_hex', models.CharField(editable=False, max_length=32)), |
||||||
|
('original_file', models.FileField(storage=main.storages.ProtectedMediaAssetStorage(), upload_to=main.medialibrary.fields.get_upload_path)), |
||||||
|
('name', models.CharField(max_length=50, verbose_name='name')), |
||||||
|
], |
||||||
|
), |
||||||
|
] |
@ -0,0 +1,20 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
||||||
|
# Generated by Django 1.9.4 on 2016-04-24 10:33 |
||||||
|
from __future__ import unicode_literals |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('medialibrary', '0001_initial'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AddField( |
||||||
|
model_name='mediaasset', |
||||||
|
name='simple_file', |
||||||
|
field=models.FileField(blank=True, null=True, upload_to=b''), |
||||||
|
), |
||||||
|
] |
@ -0,0 +1,21 @@ |
|||||||
|
# -*- coding: utf-8 -*- |
||||||
|
# Generated by Django 1.9.4 on 2016-04-25 08:11 |
||||||
|
from __future__ import unicode_literals |
||||||
|
|
||||||
|
from django.db import migrations |
||||||
|
import main.medialibrary.fields |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('medialibrary', '0002_mediaasset_simple_file'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AlterField( |
||||||
|
model_name='mediaasset', |
||||||
|
name='original_file', |
||||||
|
field=main.medialibrary.fields.MediaAssetField(upload_to=b'', verbose_name='original file'), |
||||||
|
), |
||||||
|
] |
@ -0,0 +1,25 @@ |
|||||||
|
# # -*- coding: utf-8 -*- |
||||||
|
# from __future__ import unicode_literals |
||||||
|
# # Erik Stein <code@classlibrary.net>, 2016 |
||||||
|
|
||||||
|
# import os |
||||||
|
# from django.utils.text import slugify |
||||||
|
|
||||||
|
|
||||||
|
# def get_upload_path(instance, filename): |
||||||
|
# """ |
||||||
|
# Returns /<uuid_hex>/original/<slugified_filename.ext> |
||||||
|
# where |
||||||
|
# - uuid is taken from instance, |
||||||
|
# - filename is slugified and shortened to a max length including the extension. |
||||||
|
# """ |
||||||
|
# name, ext = os.path.splitext(filename) |
||||||
|
# name = slugify(name) |
||||||
|
# name = name[:(FILENAME_MAX_LENGTH - len(ext))] |
||||||
|
# filename = "%s%s" % (name, ext) |
||||||
|
# return os.path.join( |
||||||
|
# instance.get_uuid(), |
||||||
|
# instance.STORAGE.ORIGINAL_FILE_PREFIX, |
||||||
|
# filename |
||||||
|
# ) |
||||||
|
|
Loading…
Reference in new issue