diff --git a/fmartingrcom/apps/blog/admin.py b/fmartingrcom/apps/blog/admin.py index 7cbff8d..da7399b 100644 --- a/fmartingrcom/apps/blog/admin.py +++ b/fmartingrcom/apps/blog/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import Entry, Tag +from .models import Entry, Tag, Attachment from ckeditor.widgets import CKEditorWidget from django.utils.translation import ugettext as _ from reversion.admin import VersionAdmin @@ -15,6 +15,7 @@ class EntryAdminForm(forms.ModelForm): model = Entry fields = ('title', 'slug', 'draft', 'date', 'tags', 'content') + # # ENTRY # @@ -80,3 +81,12 @@ class TagAdmin(VersionAdmin): pass admin.site.register(Tag, TagAdmin) + + +# +# ATTACHMENT +# +class AttachmentAdmin(VersionAdmin): + pass + +admin.site.register(Attachment, AttachmentAdmin) diff --git a/fmartingrcom/apps/blog/migrations/0003_attachment.py b/fmartingrcom/apps/blog/migrations/0003_attachment.py new file mode 100644 index 0000000..a7081a5 --- /dev/null +++ b/fmartingrcom/apps/blog/migrations/0003_attachment.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-21 19:29 +from __future__ import unicode_literals + +from django.db import migrations, models +import fmartingrcom.apps.blog.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0002_auto_20160313_1051'), + ] + + operations = [ + migrations.CreateModel( + name='Attachment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('filename', models.CharField(max_length=256)), + ('sha1', models.CharField(max_length=40)), + ('mimetype', models.CharField(blank=True, max_length=256, null=True)), + ('file', models.FileField(upload_to=fmartingrcom.apps.blog.models.attachment_upload_to)), + ('creation_date', models.DateTimeField(auto_now_add=True)), + ('modification_date', models.DateTimeField(auto_now=True)), + ], + options={ + 'verbose_name': 'Attachment', + }, + ), + ] diff --git a/fmartingrcom/apps/blog/models.py b/fmartingrcom/apps/blog/models.py index 54bc4dc..ffcd624 100644 --- a/fmartingrcom/apps/blog/models.py +++ b/fmartingrcom/apps/blog/models.py @@ -1,12 +1,26 @@ # -*- coding: utf-8 -*- from datetime import datetime +import logging +import mimetypes +import os +import re +import shutil +import subprocess from django.db import models from django.conf import settings +from django.core.files import File from django.utils.timezone import utc from django.core.urlresolvers import reverse +from django.dispatch.dispatcher import receiver +from django.db.models.signals import pre_delete + +from fmartingrcom.utils import sha1_checksum + import mistune +logger = logging.getLogger() + # # ENTRY @@ -76,3 +90,106 @@ class Tag(models.Model): class Meta: app_label = 'blog' ordering = ['name'] + + +# +# ATTACHMENT +# +def attachment_upload_to(instance, filename): + return os.path.join('{}_.{}'.format( + instance.media_path, instance.extension + )) + + +class Attachment(models.Model): + filename = models.CharField(max_length=256) + sha1 = models.CharField(max_length=40) + mimetype = models.CharField(max_length=256, blank=True, null=True) + file = models.FileField(upload_to=attachment_upload_to) + + creation_date = models.DateTimeField(auto_now_add=True) + modification_date = models.DateTimeField(auto_now=True) + + @property + def extension(self): + return self.file.url.split('.')[-1] + + @property + def url(self): + return self.file.url + + @property + def media_path(self): + p1 = self.sha1[0:2] + p2 = self.sha1[2:4] + return 'attachment/{}/{}/{}/'.format(p1, p2, self.sha1) + + @staticmethod + def upload_file(handler, filename=None): + """ + Given a certain file handler returns an `Image` objects + handler can be: + + django.core.files.File instance __or subclass of__ (UploadedFile) + + string with an absolute path to an image + """ + FLAGS = 'rw+' + + # File Path + if isinstance(handler, str): + f = File(open(handler, FLAGS)) + elif issubclass(handler.__class__, File) or isinstance(handler, File): + # Re-read with correct flags + f = handler + f.mode = FLAGS + else: + # I give up! + f = handler + + if not filename: + filename = f.name.split('/')[-1] + + sha1 = sha1_checksum(f) + + # Check if file exists + try: + obj = Attachment.objects.get(sha1=sha1) + except Attachment.DoesNotExist: + obj = Attachment() + obj.file = f + obj.sha1 = sha1 + obj.filename = filename + obj.mimetype = Attachment.get_mimetype(f, obj.filename) + obj.save() + + return obj + + @staticmethod + def get_mimetype(handler, filename): + re_mime_validate = re.compile('\w+/\w+(; \w+=[^;]+)*') + + # subprocess + p = subprocess.Popen(('file', '--brief', '--mime-type', '-'), + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + stdout, stderr = p.communicate(handler.read()) + if re_mime_validate.match(stdout): + mimetype = stdout.split()[0] + else: + # by python + mime, encoding = mimetypes.guess_type(filename) + mimetype = mime if mime else None + + # rewind file descriptor + handler.file.seek(0) + + return mimetype + + class Meta: + verbose_name = 'Attachment' + + +@receiver(pre_delete, sender=Attachment) +def clean_files_on_delete(sender, instance, **kwargs): + try: + shutil.rmtree('/'.join(instance.file.path.split('/')[:-1])) + except Exception as error: + logger.warning(error, exc_info=True) diff --git a/fmartingrcom/utils.py b/fmartingrcom/utils.py new file mode 100644 index 0000000..4359358 --- /dev/null +++ b/fmartingrcom/utils.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +import hashlib + + +def sha1_checksum(handler): + f = open(handler.file.name, 'rb') + checksum = hashlib.sha1(f.read()) + return checksum.hexdigest()