194 lines
6.7 KiB
Python
194 lines
6.7 KiB
Python
# fargo - document box
|
|
# Copyright (C) 2016-2019 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import base64
|
|
import hashlib
|
|
import os
|
|
import re
|
|
from urllib.parse import quote
|
|
|
|
from django.conf import settings
|
|
from django.core.files.storage import default_storage
|
|
from django.db import models
|
|
from django.db.models.signals import post_delete
|
|
from django.dispatch import receiver
|
|
from django.urls import reverse
|
|
from django.utils.html import format_html
|
|
from django.utils.text import slugify
|
|
from django.utils.translation import gettext_lazy as _
|
|
from sorl.thumbnail import delete, get_thumbnail
|
|
from sorl.thumbnail.conf import settings as thumbnail_settings
|
|
|
|
from . import managers, utils
|
|
|
|
|
|
def slug_truncate(label, length=256):
|
|
slug = slugify(label)
|
|
if len(slug) < length:
|
|
return slug
|
|
return slug[: length - 5] + '-%4s' % hashlib.md5(label.encode()).hexdigest()[:4]
|
|
|
|
|
|
class Origin(models.Model):
|
|
label = models.TextField(_('Label'))
|
|
slug = models.SlugField(_('Slug'), max_length=256)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.slug:
|
|
self.slug = slug_truncate(self.label)
|
|
return super().save(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
return self.label
|
|
|
|
|
|
class UserDocument(models.Model):
|
|
'''Document uploaded by an user or an agent'''
|
|
|
|
user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
verbose_name=_('user'),
|
|
related_name='user_documents',
|
|
on_delete=models.CASCADE,
|
|
)
|
|
document = models.ForeignKey(
|
|
'Document', related_name='user_documents', verbose_name=_('document'), on_delete=models.CASCADE
|
|
)
|
|
filename = models.CharField(verbose_name=_('filename'), max_length=512)
|
|
created = models.DateTimeField(verbose_name=_('creation date'), auto_now_add=True)
|
|
origin = models.ForeignKey(Origin, verbose_name=_('origin'), null=True, on_delete=models.CASCADE)
|
|
deletable_by_user = models.BooleanField(verbose_name=_('deletable by user'), default=True)
|
|
title = models.CharField(verbose_name=_('title'), max_length=200, blank=True)
|
|
description = models.TextField(verbose_name=_('description'), blank=True)
|
|
expiration_date = models.DateField(verbose_name=_('expiration date'), blank=True, null=True)
|
|
|
|
class Meta:
|
|
verbose_name = _('user document')
|
|
verbose_name_plural = _('user documents')
|
|
ordering = ('-created', 'user')
|
|
unique_together = ('user', 'filename', 'document', 'origin', 'deletable_by_user')
|
|
|
|
@property
|
|
def filename_encoded(self):
|
|
return quote(self.filename, safe='')
|
|
|
|
def __str__(self):
|
|
return self.title or self.filename
|
|
|
|
def get_download_url(self):
|
|
return reverse('download', kwargs={'pk': self.id, 'filename': self.filename_encoded})
|
|
|
|
@property
|
|
def thumbnail_image(self):
|
|
thumbnail = self.document.thumbnail
|
|
if not thumbnail:
|
|
return ''
|
|
src = reverse('thumbnail', kwargs={'pk': self.id, 'filename': self.filename_encoded})
|
|
return {'src': src, 'width': thumbnail.width, 'height': thumbnail.height}
|
|
|
|
@property
|
|
def css_classes(self):
|
|
if not self.document.mime_type:
|
|
return ''
|
|
return 'mime-%s mime-%s' % (
|
|
self.document.mime_type.split('/')[0],
|
|
re.sub(r'[/\.+-]', '-', self.document.mime_type),
|
|
)
|
|
|
|
|
|
class Document(models.Model):
|
|
'''Content indexed documents'''
|
|
|
|
content_hash = models.CharField(primary_key=True, max_length=128, verbose_name=_('content hash'))
|
|
content = models.FileField(upload_to='uploads/', max_length=300, verbose_name=_('file'))
|
|
mime_type = models.CharField(max_length=256, blank=True)
|
|
creation_date = models.DateTimeField(auto_now_add=True)
|
|
|
|
objects = managers.DocumentManager()
|
|
|
|
def save(self, *args, **kwargs):
|
|
'''Create content_hash if new'''
|
|
if not self.content_hash:
|
|
self.content_hash = utils.sha256_of_file(self.content)
|
|
if not self.mime_type:
|
|
self.mime_type = utils.get_mime_type(self.content) or ''
|
|
if self.content.name and len(self.content.name) > 200:
|
|
file_root, file_ext = os.path.splitext(self.content.name)
|
|
file_root, file_ext = file_root[:150], file_ext[:50]
|
|
self.content.name = file_root + file_ext
|
|
super().save(*args, **kwargs)
|
|
|
|
@property
|
|
def thumbnail(self):
|
|
if not (self.mime_type.startswith('image/') or self.mime_type == 'application/pdf'):
|
|
return None
|
|
try:
|
|
thumbnail = get_thumbnail(self.content, '200x200')
|
|
except Exception: # sorl-thumbnail can crash in unexpected ways
|
|
return None
|
|
try:
|
|
# check file exists and is readable
|
|
default_storage.open(thumbnail.name)
|
|
return thumbnail
|
|
except OSError:
|
|
pass
|
|
return None
|
|
|
|
@property
|
|
def thumbnail_data_url(self):
|
|
thumbnail = self.thumbnail
|
|
if not thumbnail:
|
|
return ''
|
|
|
|
mime_type = 'image/' + thumbnail_settings.THUMBNAIL_FORMAT.lower()
|
|
return 'data:%s;base64,%s' % (mime_type, base64.b64encode(thumbnail.read()))
|
|
|
|
@property
|
|
def thumbnail_img_tag(self):
|
|
thumbnail = self.thumbnail
|
|
if not thumbnail:
|
|
return ''
|
|
|
|
return format_html(
|
|
'<img width="{}" height="{}" src="{}"/>',
|
|
thumbnail.width,
|
|
thumbnail.height,
|
|
self.thumbnail_data_url,
|
|
)
|
|
|
|
@property
|
|
def thumbnail_image(self):
|
|
thumbnail = self.thumbnail
|
|
if not thumbnail:
|
|
return ''
|
|
return {'src': self.thumbnail_data_url, 'width': thumbnail.width, 'height': thumbnail.height}
|
|
|
|
def __unicode__(self):
|
|
return '%s %s' % (os.path.basename(self.content.name), self.content_hash[:6])
|
|
|
|
class Meta:
|
|
verbose_name = _('document')
|
|
verbose_name_plural = _('documents')
|
|
ordering = ('creation_date',)
|
|
|
|
|
|
@receiver(post_delete, sender=Document)
|
|
def delete_file(sender, instance, **kwargs):
|
|
if instance.content:
|
|
if os.path.isfile(instance.content.path):
|
|
os.remove(instance.content.path)
|
|
delete(instance.content)
|