separate filename and filehash (#21)

* separate filename and filehash

* rename migration

* test url

* bump version

* update changelog

* test fail url

* update coveragerc

* add coveragerc

* EOFNL

* update covrc

* filename
This commit is contained in:
Tyrel Souza 2017-01-20 14:38:41 -05:00 committed by GitHub
parent e50dc36942
commit 09d721df0d
9 changed files with 84 additions and 21 deletions

View File

@ -11,3 +11,4 @@ exclude_lines =
ignore_errors = True ignore_errors = True
omit = omit =
tests/* tests/*
dbfilestorage/migrations/*

View File

@ -54,6 +54,7 @@ I will sign everything with 0x769A1BC78A2DDEE2
## CHANGELOG ## CHANGELOG
- 2017-01-20 [Tyrel Souza] Split filename and filehash.
- 2016-12-09 [Tyrel Souza] Add signing key to readme. - 2016-12-09 [Tyrel Souza] Add signing key to readme.
- 2016-12-09 [Tyrel Souza] Update Tests, add some cleanup. - 2016-12-09 [Tyrel Souza] Update Tests, add some cleanup.
- 2016-12-08 [Tyrel Souza] Add more documentation. - 2016-12-08 [Tyrel Souza] Add more documentation.

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-20 18:44
from __future__ import unicode_literals
from django.db import migrations, models
def copy_filehash(apps, schema_editor):
DBFile = apps.get_model('dbfilestorage', 'DBFile')
for dbf in DBFile.objects.all():
dbf.filehash = dbf.name
dbf.save()
def fix_filename(apps, schema_editor):
DBFile = apps.get_model('dbfilestorage', 'DBFile')
for dbf in DBFile.objects.all():
ext = dbf.content_type.split("/")[0]
dbf.name = "{name}.{ext}".format(
name=dbf.name,
ext=ext)
dbf.save()
class Migration(migrations.Migration):
dependencies = [
('dbfilestorage', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='dbfile',
name='filehash',
field=models.CharField(default='filehash', max_length=32, primary_key=True, serialize=False),
preserve_default=False,
),
migrations.AlterField(
model_name='dbfile',
name='name',
field=models.CharField(max_length=100),
),
migrations.RunPython(copy_filehash),
migrations.RunPython(fix_filename),
]

View File

@ -7,15 +7,16 @@ class DBFile(models.Model):
# This is kept as `name` and not something like `md5` because the file # This is kept as `name` and not something like `md5` because the file
# operations pass around `name` as the identifier, so it's kept the same # operations pass around `name` as the identifier, so it's kept the same
# to make sense. # to make sense.
name = models.CharField(max_length=100, primary_key=True) name = models.CharField(max_length=100)
filehash = models.CharField(max_length=32, primary_key=True)
# file data # file data
content_type = models.CharField(max_length=100) content_type = models.CharField(max_length=100)
b64 = models.TextField() b64 = models.TextField()
def __unicode__(self): def __unicode__(self):
return u"{name} <{content_type}>".format( return u"{filehash} <{content_type}>".format(
name=self.name, content_type=self.content_type) filehash=self.filehash, content_type=self.content_type)
def save(self, **kwargs): def save(self, **kwargs):
if self.content_type is None: if self.content_type is None:

View File

@ -1,6 +1,7 @@
import mimetypes import mimetypes
import logging import logging
import hashlib import hashlib
import os
from django.db.transaction import atomic from django.db.transaction import atomic
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
@ -50,16 +51,17 @@ class DBFileStorage(Storage):
ct = mimetypes.guess_type(name)[0] ct = mimetypes.guess_type(name)[0]
# After we get the mimetype by name potentially, mangle it. # After we get the mimetype by name potentially, mangle it.
name = hashlib.md5(read_data).hexdigest() filehash = hashlib.md5(read_data).hexdigest()
# create the file, or just return name if the exact file already exists # create the file, or just return name if the exact file already exists
if not DBFile.objects.filter(pk=name).exists(): if not DBFile.objects.filter(pk=name).exists():
the_file = DBFile( the_file = DBFile(
name=name, name=os.path.basename(name),
filehash=filehash,
content_type=ct, content_type=ct,
b64=b64) b64=b64)
the_file.save() the_file.save()
return name return filehash
def get_available_name(self, name, max_length=None): def get_available_name(self, name, max_length=None):
return name return name

View File

@ -1,4 +1,5 @@
from django.http import HttpResponse from django.http import HttpResponse, Http404
from django.db.models import Q
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from .models import DBFile from .models import DBFile
@ -12,7 +13,12 @@ def show_file(request, name):
:return HttpResponse: Rendered file :return HttpResponse: Rendered file
""" """
dbf = get_object_or_404(DBFile, pk=name) dbf = DBFile.objects.filter(Q(name=name)|Q(filehash=name))
return HttpResponse( if dbf.exists():
dbf.b64.decode('base64'), response = HttpResponse(
content_type=dbf.content_type) dbf[0].b64.decode('base64'),
content_type=dbf[0].content_type)
response['Content-Disposition'] = 'attachment; filename="{}"'.format(
dbf[0].name)
return response
raise Http404

View File

@ -54,9 +54,9 @@ author = u'Tyrel Souza'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = u'0.1.3' version = u'0.1.4'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = u'0.1.3' release = u'0.1.4'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@ -24,7 +24,7 @@ class CleanCommand(Command):
setup( setup(
name="django-dbfilestorage", name="django-dbfilestorage",
version="0.1.3", version="0.1.4",
description="Database backed file storage for testing.", description="Database backed file storage for testing.",
long_description="Database backed file storage for testing. Stores files as base64 encoded textfields.", long_description="Database backed file storage for testing. Stores files as base64 encoded textfields.",
author="Tyrel Souza", author="Tyrel Souza",

View File

@ -30,14 +30,14 @@ class DBFileTest(TestCase):
def test_upload(self): def test_upload(self):
""" Test that the file storage uploads and puts in DB Properly """ """ Test that the file storage uploads and puts in DB Properly """
self.assertTrue(DBFile.objects.filter(name=self.md5).exists()) self.assertTrue(DBFile.objects.filter(filehash=self.md5).exists())
def test_content_file(self): def test_content_file(self):
""" Test that this code works with ContentFile as well """ """ Test that this code works with ContentFile as well """
content_file = ContentFile(u"ΑΔΔGΕΝΕ") content_file = ContentFile(u"ΑΔΔGΕΝΕ")
content_file_md5 = hashlib.md5(u"ΑΔΔGΕΝΕ".encode('utf8')).hexdigest() content_file_md5 = hashlib.md5(u"ΑΔΔGΕΝΕ".encode('utf8')).hexdigest()
default_storage.save("unicode", content_file) default_storage.save("unicode", content_file)
unicode_file = DBFile.objects.get(name=content_file_md5) unicode_file = DBFile.objects.get(filehash=content_file_md5)
self.assertEqual(unicode(unicode_file), self.assertEqual(unicode(unicode_file),
"{} <application/octet-stream>".format(content_file_md5)) "{} <application/octet-stream>".format(content_file_md5))
@ -50,7 +50,7 @@ class DBFileTest(TestCase):
def test_equality(self): def test_equality(self):
""" Test that the DB entry matches what is expected from the file """ """ Test that the DB entry matches what is expected from the file """
with open(self.filepath, 'rb') as f: with open(self.filepath, 'rb') as f:
dbf = DBFile.objects.get(name=self.md5) dbf = DBFile.objects.get(filehash=self.md5)
self.assertEqual(dbf.b64.decode("base64"), f.read()) self.assertEqual(dbf.b64.decode("base64"), f.read())
self.assertEqual(dbf.content_type, 'image/jpeg') self.assertEqual(dbf.content_type, 'image/jpeg')
@ -66,9 +66,9 @@ class DBFileTest(TestCase):
def test_delete(self): def test_delete(self):
""" Test Deletion """ """ Test Deletion """
self.assertTrue(DBFile.objects.filter(name=self.md5).exists()) self.assertTrue(DBFile.objects.filter(filehash=self.md5).exists())
default_storage.delete(self.md5) default_storage.delete(self.md5)
self.assertFalse(DBFile.objects.filter(name=self.md5).exists()) self.assertFalse(DBFile.objects.filter(filehash=self.md5).exists())
# Also test that calling delete on something that doesn't exist, # Also test that calling delete on something that doesn't exist,
# errors silently # errors silently
self.assertFalse(DBFile.objects.filter(name="Nothing").exists()) self.assertFalse(DBFile.objects.filter(name="Nothing").exists())
@ -91,9 +91,18 @@ class DBFileTest(TestCase):
def test_view(self): def test_view(self):
client = Client() client = Client()
url = default_storage.url(self.md5) # check it works for both md5 and filename
for param in (self.md5, self.filename):
url = default_storage.url(param)
resp = client.get(url)
self.assertEqual(resp.status_code, 200)
def test_view_fails(self):
client = Client()
url = default_storage.url("failure")
resp = client.get(url) resp = client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 404)
def test_admin(self): def test_admin(self):
my_admin = User.objects.create_superuser( my_admin = User.objects.create_superuser(