You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

320 lines
11 KiB

import logging
import posixpath
import re
from functools import partial
from django.db import models
from django.utils.html import strip_tags
from django.utils.text import Truncator
from django.utils.translation import ugettext_lazy as _
from imagefield.fields import ImageField, PPOIField
from shared.utils.text import html_to_text, sanitized_html
from shared.utils.models.slugs import DowngradingSlugField, slugify
from .conf import UPLOAD_TO, USE_TRANSLATABLE_FIELDS
if USE_TRANSLATABLE_FIELDS:
from content_plugins.fields import TranslatableCleansedRichTextField
from shared.multilingual.utils.fields import TranslatableCharField
from shared.multilingual.utils import i18n_fields
else:
TranslatableCharField = models.CharField
from content_plugins.fields import TranslatableCleansedRichTextField
def i18n_fields(field_name, languages=None):
return [field_name]
logger = logging.getLogger(__name__)
class MediaCategoryManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related("parent")
class MediaCategory(models.Model):
name = models.CharField(_("name"), blank=True, max_length=200)
parent = models.ForeignKey(
'self', blank=True, null=True,
on_delete=models.CASCADE,
related_name='children', limit_choices_to={'parent__isnull': True},
verbose_name=_("Übergeordnet"))
slug = models.SlugField(_('slug'), max_length=150)
objects = MediaCategoryManager()
class Meta:
verbose_name = _("Working Folder")
verbose_name_plural = _("Working Folders")
ordering = ['parent__name', 'name']
def __str__(self):
if self.parent_id:
return '%s - %s' % (self.parent.name, self.name)
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
save.alters_data = True
def path_list(self):
if self.parent is None:
return [self]
p = self.parent.path_list()
p.append(self)
return p
def path(self):
return ' - '.join((f.name for f in self.path_list()))
class Gallery(models.Model):
internal_name = models.CharField(_("Internal Name"), max_length=500,
help_text=_("Internal use only, not publicly visible."))
name = TranslatableCharField(_("Name"), max_length=200, null=True, blank=True,
help_text=_("Publicly visible name."))
slug = models.SlugField(_("Slug"), null=True, blank=True)
credits = TranslatableCharField(_("Credits"), null=True, blank=True, max_length=500)
caption = TranslatableCleansedRichTextField(_("Caption"),
null=True, blank=True, config_name='caption')
is_public = models.BooleanField(_("Active"), default=False)
order_index = models.PositiveIntegerField(_("Order Index"), default=0)
# background_color = models.ForeignKey('site_pages.Color', on_delete=models.PROTECT,
# null=True, blank=True,
# verbose_name=_("Background color"),
# help_text=_("The background color is used until the first image is loaded."))
images = models.ManyToManyField('Image', blank=True,
verbose_name=_("Images"),
through='ImageGalleryRel')
class Meta:
verbose_name = _("Image Gallery")
verbose_name_plural = _("Image Galleries")
ordering = i18n_fields('name')
def __str__(self):
return self.internal_name or self.name or self.slug
def public_images(self):
return self.images.filter(is_public=True)
def filename_to_slug(instance, field):
n, e = posixpath.splitext(instance.file.name)
return slugify(n)
class FileTypeMixin(models.Model):
type = models.CharField(_('file type'),
max_length=12, editable=False, choices=())
filetypes = []
filetypes_dict = {}
class Meta:
abstract = True
@classmethod
def register_filetypes(cls, *types):
cls.filetypes[0:0] = types
choices = [t[0:2] for t in cls.filetypes]
cls.filetypes_dict = dict(choices)
cls._meta.get_field('type').choices = choices[:]
def determine_file_type(self, name):
for type_key, type_name, type_test in self.filetypes:
if type_test(name):
return type_key
return self.filetypes[-1][0]
def save(self, *args, **kwargs):
self.type = self.determine_file_type(self.file.name)
super().save(*args, **kwargs)
save.alters_data = True
class DeleteOldFileMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.file:
self._original_file_name = self.file.name
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# User uploaded a new file. Try to get rid of the old file in
# storage, to avoid having orphaned files hanging around.
if getattr(self, '_original_file_name', None):
if self.file.name != self._original_file_name:
self.delete_mediafile(self._original_file_name)
def delete_mediafile(self, name=None):
if name is None:
name = self.file.name
try:
self.file.storage.delete(name)
except Exception as e:
logger.warn("Cannot delete media file %s: %s" % (name, e))
class MediaRole(models.Model):
id_text = models.CharField(primary_key=True, max_length=20,
help_text=_("Dieser Wert wird in der Programmierung benutzt und darf nicht verändert werden."))
name = TranslatableCharField(_("name"), max_length=200)
class Meta:
verbose_name = _("Art")
verbose_name_plural = _("Arten")
ordering = i18n_fields('name')
def __str__(self):
return self.name
class MediaBaseManager(models.Manager):
def public_objects(self):
return self.get_queryset().filter(is_public=True)
class MediaBase(DeleteOldFileMixin, models.Model):
created = models.DateTimeField(_("Hochgeladen"), auto_now_add=True)
modified = models.DateTimeField(_("Geändert"), auto_now=True)
is_public = models.BooleanField(_("Veröffentlicht"), default=True,
help_text=_("Nur als \"öffentlich sichtbar\" markierte Mediendaten werden öffentlich angezeigt."))
# file = models.FileField(_("Datei"))
file_size = models.IntegerField(_("file size"),
blank=True, null=True, editable=False)
slug = DowngradingSlugField(blank=True,
populate_from=filename_to_slug, unique_slug=True)
role = models.ForeignKey(MediaRole, on_delete=models.PROTECT,
verbose_name=_("Typ"),
null=True, blank=True)
name = TranslatableCharField(_("Name"), max_length=200, null=True, blank=True)
caption = TranslatableCleansedRichTextField(_("Bildunterschrift"), blank=True,
config_name='caption',
cleanse=partial(sanitized_html, config_name='caption'))
credits = TranslatableCharField(_("Credits"), max_length=500, null=True, blank=True)
copyright = TranslatableCharField(_('Rechteinhaber/in'), max_length=2000, blank=True)
categories = models.ManyToManyField(MediaCategory,
verbose_name=_("Arbeitsmappe"), blank=True)
categories.category_filter = True
objects = MediaBaseManager()
class Meta:
abstract = True
def __str__(self):
return self.name or strip_tags(self.caption) or posixpath.basename(self.file.name)
def save(self, *args, **kwargs):
if not self.name:
if self.caption:
self.name = Truncator(html_to_text(self.caption)).chars(200, truncate='')
else:
self.name = Truncator(posixpath.basename(self.file.name)).chars(200, truncate='')
if self.file:
try:
self.file_size = self.file.size
except (OSError, IOError, ValueError) as e:
logger.error("Unable to read file size for %s: %s" % (self, e))
super().save(*args, **kwargs)
save.alters_data = True
class Image(MediaBase):
file = ImageField(
_("image"),
max_length=200,
upload_to=UPLOAD_TO,
width_field="image_width",
height_field="image_height",
ppoi_field="image_ppoi",
blank=True,
)
image_width = models.PositiveIntegerField(
_("image width"), blank=True, null=True, editable=False)
image_height = models.PositiveIntegerField(
_("image height"), blank=True, null=True, editable=False)
image_ppoi = PPOIField(_("primary point of interest"))
type = 'image'
class Meta:
verbose_name = _("Bild")
verbose_name_plural = _("Bilder")
ordering = ['imagegalleryrel__position']
#
# Accessors to GIF images
# FIXME ImageKit should leave alone GIF images in the first place
# TODO Need more robust method to get image type
def gif_gallery_image_thumbnail(self, image_spec_name='gallery_image_thumbnail'):
# Return gif image URLs without converting.
name, ext = posixpath.splitext(self.file.name)
if ext == '.gif':
return self.file
else:
return getattr(self, image_spec_name)
def gif_lightbox_image(self):
return self.gif_gallery_image_thumbnail(image_spec_name='lightbox_image')
class ImageGalleryRel(models.Model):
image = models.ForeignKey(Image, on_delete=models.CASCADE)
gallery = models.ForeignKey(Gallery, models.CASCADE)
position = models.PositiveIntegerField(default=0)
class Meta:
verbose_name = _("Bild")
verbose_name_plural = _("Bilder")
ordering = ['position']
class Download(FileTypeMixin, MediaBase):
file = models.FileField(_("Datei"),
upload_to=UPLOAD_TO)
class Meta:
verbose_name = _("Download")
verbose_name_plural = _("Downloads")
ordering = i18n_fields('name')
def get_display_name(self):
return self.name or posixpath.basename(self.file.name)
File = Download
Download.register_filetypes(
# Should we be using imghdr.what instead of extension guessing?
('image', _('Image'), lambda f: re.compile(
r'\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?)$', re.IGNORECASE).search(f)),
('video', _('Video'), lambda f: re.compile(
r'\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv|flv)$',
re.IGNORECASE).search(f)),
('audio', _('Audio'), lambda f: re.compile(
r'\.(au|mp3|m4a|wma|oga|ram|wav)$', re.IGNORECASE).search(f)),
('pdf', _('PDF document'), lambda f: f.lower().endswith('.pdf')),
('swf', _('Flash'), lambda f: f.lower().endswith('.swf')),
('txt', _('Text'), lambda f: f.lower().endswith('.txt')),
('rtf', _('Rich Text'), lambda f: f.lower().endswith('.rtf')),
('zip', _('Zip archive'), lambda f: f.lower().endswith('.zip')),
('doc', _('Microsoft Word'), lambda f: re.compile(
r'\.docx?$', re.IGNORECASE).search(f)),
('xls', _('Microsoft Excel'), lambda f: re.compile(
r'\.xlsx?$', re.IGNORECASE).search(f)),
('ppt', _('Microsoft PowerPoint'), lambda f: re.compile(
r'\.pptx?$', re.IGNORECASE).search(f)),
('other', _('Binary'), lambda f: True), # Must be last
)