Erik Stein 9 years ago
parent
commit
5e86ffb7b4
  1. 0
      README.rst
  2. 0
      assetkit/pkgmeta.py
  3. 17
      example_site/main/medialibrary/admin.py
  4. 2
      example_site/main/medialibrary/apps.py
  5. 110
      example_site/main/medialibrary/fields.py
  6. 8
      example_site/main/medialibrary/forms.py
  7. 27
      example_site/main/medialibrary/migrations/0001_initial.py
  8. 20
      example_site/main/medialibrary/migrations/0002_mediaasset_simple_file.py
  9. 21
      example_site/main/medialibrary/migrations/0003_auto_20160425_0811.py
  10. 44
      example_site/main/medialibrary/models.py
  11. 25
      example_site/main/medialibrary/utils.py
  12. 8
      example_site/main/settings.py
  13. 14
      example_site/main/storages.py
  14. 5
      example_site/main/urls.py

0
README.rst

0
assetkit/pkgmeta.py

17
example_site/main/medialibrary/admin.py

@ -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")

2
example_site/main/medialibrary/apps.py

@ -4,4 +4,4 @@ from django.apps import AppConfig
class MedialibraryConfig(AppConfig): class MedialibraryConfig(AppConfig):
name = 'medialibrary' name = 'main.medialibrary'

110
example_site/main/medialibrary/fields.py

@ -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)

8
example_site/main/medialibrary/forms.py

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2016
class FileField(Field):
widget = ClearableFileInput

27
example_site/main/medialibrary/migrations/0001_initial.py

@ -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')),
],
),
]

20
example_site/main/medialibrary/migrations/0002_mediaasset_simple_file.py

@ -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''),
),
]

21
example_site/main/medialibrary/migrations/0003_auto_20160425_0811.py

@ -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'),
),
]

44
example_site/main/medialibrary/models.py

@ -1,49 +1,33 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import uuid import uuid
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ..storages import ProtectedMediaAssetStorage from ..medialibrary.fields import MediaAssetField
# TODO Define the central storage somewhere more central
originals_storage = ProtectedMediaAssetStorage()
class UUIDMixin(models.Model):
uuid_hex = models.CharField(max_length=32, null=False, editable=False)
class Meta:
abstract = True
# TODO Move this to special FileField class # TODO Make auto-initializing UUID-field
def get_upload_path(instance, filename): def get_uuid(self):
""" if not self.uuid_hex:
Returns /<uuid_hex>/original/<slugified_filename.ext> self.uuid_hex = uuid.uuid4().hex
where return str(uuid.UUID(self.uuid_hex))
- uuid is taken from instance,
- filename is slugified and shortened to a max length of 255 characters including the extension.
"""
MAX_LENGTH = 255
name, ext = os.path.splitext(filename)
name = slugify(name)
name = name[:(MAX_LENGTH - len(ext))]
filename = "%s%s" % (name, ext)
return os.path.join(instance.get_uuid(), instance.storage.ORIGINAL_FILE_PREFIX, filename)
@python_2_unicode_compatible @python_2_unicode_compatible
class MediaAsset(models.Model): class MediaAsset(UUIDMixin, models.Model):
uuid_hex = models.CharField(max_length=32, null=False, editable=False) original_file = MediaAssetField(_("original file"))
original_file = models.FileField(upload_to=get_upload_path,
storage=originals_storage
)
name = models.CharField(_('name'), max_length=50) name = models.CharField(_('name'), max_length=50)
simple_file = models.FileField(null=True, blank=True)
def __str__(self): def __str__(self):
return self.name return self.name
# TODO Make auto-initialized UUID-field
def get_uuid(self):
if not self.uuid_hex:
self.uuid_hex = uuid.uuid4().hex
return uuid.UUID(self.uuid_hex)

25
example_site/main/medialibrary/utils.py

@ -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
# )

8
example_site/main/settings.py

@ -32,8 +32,10 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'imagekit', 'imagekit',
'main.medialibrary', 'main.medialibrary.apps.MedialibraryConfig',
'main', # 'main',
'django_extensions',
] ]
MIDDLEWARE_CLASSES = [ MIDDLEWARE_CLASSES = [
@ -127,7 +129,7 @@ STATIC_URL = '/static/'
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
STATIC_ROOT = os.path.join(ENV_DIR, 'var', 'static') STATIC_ROOT = os.path.join(ENV_DIR, 'var', 'static')
# All media uploads should be protected: MEDIA_ROOT = os.path.join(ENV_DIR, 'var', 'media') MEDIA_ROOT = os.path.join(ENV_DIR, 'var', 'public')
PROTECTED_MEDIA_ASSETS_ROOT = os.path.join(ENV_DIR, 'var', 'protected') PROTECTED_MEDIA_ASSETS_ROOT = os.path.join(ENV_DIR, 'var', 'protected')

14
example_site/main/storages.py

@ -23,12 +23,11 @@ class ProtectedMediaAssetStorage(FileSystemStorage):
ORIGINAL_FILE_PREFIX = ORIGINAL_FILE_PREFIX ORIGINAL_FILE_PREFIX = ORIGINAL_FILE_PREFIX
def __init__(self, location=None, file_permissions_mode=None, def __init__(self, location=None, base_url=None, **kwargs):
directory_permissions_mode=None):
if location is None: if location is None:
location = getattr(settings, 'PROTECTED_MEDIA_ASSETS_ROOT', settings.PROTECTED_MEDIA_ASSETS_ROOT) location = getattr(settings, 'PROTECTED_MEDIA_ASSETS_ROOT', settings.PROTECTED_MEDIA_ASSETS_ROOT)
super(ProtectedMediaAssetStorage, self).__init__(self, location=location, file_permissions_mode=None, super(ProtectedMediaAssetStorage, self).__init__(location=location, base_url=None, **kwargs)
directory_permissions_mode=None) self.base_url = None
def delete(self, name): def delete(self, name):
super(ProtectedMediaAssetStorage, self).delete(name) super(ProtectedMediaAssetStorage, self).delete(name)
@ -45,10 +44,9 @@ class ProtectedMediaAssetStorage(FileSystemStorage):
return os.path.getsize(self.path(name)) return os.path.getsize(self.path(name))
def url(self, name): def url(self, name):
if self.base_url is None: raise ValueError("This file storage is not accessible via a URL.")
raise ValueError("This file is not accessible via a URL.")
# TODO ? @deconstructible # TODO ? @deconstructible
class # class
return urljoin(self.base_url, ORIGINAL_FILE_PREFIX, filepath_to_uri(name)) # return urljoin(self.base_url, ORIGINAL_FILE_PREFIX, filepath_to_uri(name))

5
example_site/main/urls.py

@ -10,10 +10,11 @@ from django.utils.translation import ugettext_lazy as _
urlpatterns = [ urlpatterns = [
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
# url(r'^', include('private_media.urls')), # url(r'^', include('private_media.urls')),
] ]
# Never used: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # For unprotected uploads:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Loading…
Cancel
Save