276 lines
8.9 KiB
Python
276 lines
8.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# 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 subprocess
|
|
import os
|
|
import re
|
|
import threading
|
|
|
|
from django.conf import settings
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import models
|
|
from django.utils.encoding import python_2_unicode_compatible
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.utils.encoding import force_text
|
|
from django.utils.text import slugify
|
|
from django.utils.http import urlquote
|
|
from django.utils.html import format_html
|
|
from django.dispatch import receiver
|
|
from django.db.models.signals import post_save, post_delete
|
|
from django.core.files.storage import default_storage
|
|
|
|
from sorl.thumbnail import get_thumbnail, delete
|
|
from sorl.thumbnail.conf import settings as thumbnail_settings
|
|
|
|
from jsonfield import JSONField
|
|
|
|
from . import utils, managers
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Origin(models.Model):
|
|
label = models.CharField(_('Label'), max_length=80)
|
|
slug = models.SlugField(_('Slug'))
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.slug:
|
|
self.slug = slugify(self.label)
|
|
return super(Origin, self).save(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
return self.label
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
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')
|
|
document = models.ForeignKey(
|
|
'Document',
|
|
related_name='user_documents',
|
|
verbose_name=_('document'))
|
|
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)
|
|
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 urlquote(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('[/\.+-]', '-', self.document.mime_type))
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Validation(models.Model):
|
|
'''Validation of a document as special kind for an user,
|
|
the data field contains metadata extracted from the document.
|
|
'''
|
|
user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
verbose_name=_('user'))
|
|
content_hash = models.CharField(
|
|
max_length=128,
|
|
verbose_name=_('content hash'),
|
|
blank=True,
|
|
null=True)
|
|
origin = models.ForeignKey(
|
|
Origin,
|
|
verbose_name=_('origin'),
|
|
null=True)
|
|
document_type = models.CharField(max_length=256, verbose_name=_('document type'))
|
|
data = JSONField(null=True, verbose_name=_('data'))
|
|
start = models.DateField(verbose_name=_('start date'))
|
|
end = models.DateField(verbose_name=_('end date'))
|
|
creator = models.CharField(max_length=256, verbose_name=_('creator'))
|
|
created = models.DateTimeField(verbose_name=_('creation date'))
|
|
|
|
@property
|
|
def document_type_schema(self):
|
|
return utils.get_document_type_schema(settings, self.document_type) or {}
|
|
|
|
@property
|
|
def metadata(self):
|
|
return self.document_type_schema.get('metadata', [])
|
|
|
|
def display(self):
|
|
template = self.document_type_schema.get('display_template', '')
|
|
if template:
|
|
try:
|
|
return force_text(template.format(**self.data))
|
|
except KeyError:
|
|
pass
|
|
l = []
|
|
for meta_field in self.metadata:
|
|
l.append(_(u'%(label)s: %(value)s')
|
|
% {
|
|
'label': meta_field['label'],
|
|
'value': self.data.get(meta_field['varname'], ''),
|
|
})
|
|
return force_text(u'; '.join(l))
|
|
display.short_description = _('description')
|
|
|
|
@property
|
|
def user_document(self):
|
|
if self.content_hash:
|
|
try:
|
|
return UserDocument.objects.get(document__content_hash=self.content_hash)
|
|
except UserDocument.DoesNotExist:
|
|
pass
|
|
|
|
def __str__(self):
|
|
return self.display()
|
|
|
|
|
|
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.file.name) or ''
|
|
super(Document, self).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: # sorl-thumbnail can crash in unexpected ways
|
|
return None
|
|
try:
|
|
# check file exists and is readable
|
|
default_storage.open(thumbnail.name)
|
|
return thumbnail
|
|
except IOError:
|
|
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}
|
|
|
|
@classmethod
|
|
def occupancy_for_user(cls, user):
|
|
return float(sum(document.content.size for document in cls.objects.filter(user_documents__user=user).distinct()))
|
|
|
|
def __unicode__(self):
|
|
return u'%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)
|