Skip to content

Numerous improvements and refactorings #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 95 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
d5cd508
Modified database storage to fallback to file storage. Added management
chrisspen Feb 22, 2012
48bda65
Added data migration to auto-load existing files into the database.
chrisspen Feb 22, 2012
081c845
Fixed typo.
chrisspen Feb 22, 2012
80991a5
Added management command to handle bulk file deletions.
chrisspen Feb 23, 2012
478ff91
Updated version number.
chrisspen Feb 29, 2012
34a880e
Fixed bug in dump management command.
chrisspen Feb 29, 2012
ac76488
Updated version.
chrisspen Feb 29, 2012
f32d21b
Added option to load command to allow limiting file load by app model.
chrisspen Mar 2, 2012
a44a0df
Limited debugging output.
Apr 6, 2012
c35eb57
Fixed unittest command.
Apr 15, 2012
8bad5c3
Added feature to define custom user and group to set ownership with when
Jun 11, 2012
bc6a7e6
Updated version number.
Jun 11, 2012
63af3a3
Improved performance of database_files_dump by only exporting
Aug 6, 2012
249a1df
Added a dryrun option to the cleanup command. Fixed minor bug in content
Aug 20, 2012
0817d2f
Added a unittest for hashing functions.
Aug 20, 2012
cad337e
Fixing hashing functions to be consistent in Python2.6. Added management
Aug 21, 2012
a9b396a
Fixed management command.
Aug 21, 2012
3b73968
Added file hash caching on the filesystem.
Sep 19, 2012
57fe6c2
Updated version.
Sep 19, 2012
aaa95c6
Modified the default behavior to automatically export files from the
Sep 21, 2012
b407fa4
Updated version number.
Sep 21, 2012
76dfde2
Modified file dump command to update hash upon write and allow targeting
Nov 14, 2012
816f3ea
Added configurable file URL constructor.
May 9, 2013
40fd4cf
Updated version.
May 9, 2013
882b9a0
Improved performance of cleanup command.
Jul 16, 2013
1ed4ad7
Added view to automatically handle retrieving files from either the
Aug 28, 2013
44fed3c
Updated version.
Aug 28, 2013
a08a2ab
Fixed bug causing file to be unnecessarily re-written on every request.
Apr 28, 2014
9580dea
Added table name.
Apr 28, 2014
93ebddf
Updated ignore.
Apr 28, 2014
15c7499
Added test image Python markup.
Jun 18, 2014
77e22e9
Added test image.
Jun 18, 2014
7def8c9
Added support for Python3.
Jun 18, 2014
624bf58
Updated version.
Jun 18, 2014
802b86c
Repackaged. Fixed Python3 unittest due to South bug. Updated docs.
Jul 9, 2014
03e2d2b
Broadened exception handling for pypandoc.
Jul 27, 2014
a9b285c
Added error handling for cases where path is passed.
Dec 9, 2014
266bfec
Fixed error handling bug in setup.
Dec 9, 2014
8b6bda0
Added option to specify filename to cleanup.
Feb 24, 2015
4fdbcb4
Move South migrations to south_migrations.
richardxia Aug 2, 2015
452c827
Add Django-1.7-style migrations.
richardxia Aug 2, 2015
022ae02
Merge pull request #1 from richardxia/add_django_1.7_support
chrisspen Aug 11, 2015
7e8e5bc
Add south_migrations to distribution.
richardxia Aug 24, 2015
67a1c58
Merge pull request #2 from richardxia/fix_setup_packages
chrisspen Aug 29, 2015
71b42d2
Make Django 1.7 migrations compatible with Python3
rhunwicks Feb 4, 2016
96d8144
Update README.md to include instructions for changing urls.py
rhunwicks Feb 4, 2016
87eded9
Merge pull request #3 from rhunwicks/master
chrisspen Feb 6, 2016
32d45e2
Added support for Django<=1.10.1.
chrisspen Oct 11, 2016
59b8793
Merge branch 'master' of
chrisspen Oct 11, 2016
328691c
Updated docs.
chrisspen Oct 11, 2016
07f9190
the bytestring was breaking the migration.
Oct 14, 2016
68b9fa6
Merge pull request #4 from niranjanthilak/patch-1
chrisspen Oct 15, 2016
e5f7884
changed travis link
chrisspen Oct 15, 2016
0ecf0f7
fixed unittests for django>=1.7
chrisspen Oct 15, 2016
b5376b4
Fixed unittests for all supported Python+Django versions.
chrisspen Oct 15, 2016
737bab6
Fixed code formatting.
chrisspen Oct 16, 2016
de14a38
Fixed undefined variable.
chrisspen Oct 16, 2016
2f4cf7b
Cleaned up setup.
chrisspen Oct 16, 2016
ea49709
Updated shields.
chrisspen Jan 4, 2017
a4e74ac
Typo fix in README.
samorajp Feb 7, 2017
4e939b4
Merge pull request #5 from samorajp/patch-1
chrisspen Feb 7, 2017
50584c0
Pin south to latest version 1.0.2
pyup-bot Feb 7, 2017
c91d238
Merge pull request #6 from chrisspen/pyup-initial-update
chrisspen Feb 7, 2017
7ecee94
Drop Django 1.6 support
YamaoMaoen Aug 4, 2017
dc18273
Merge pull request #7 from YamaoMaoen/master
chrisspen Aug 4, 2017
a1e5643
Extended django version. Updated ignore
chrisspen Aug 4, 2017
3b5e92b
Merge branch 'master' of git+ssh://github.com/chrisspen/django-databa…
chrisspen Aug 4, 2017
39438ca
Updated travis config to include python 3.6
chrisspen Aug 4, 2017
a128887
Updated travis config to include python 3.6
chrisspen Aug 4, 2017
05420ed
Dropped Python 3.3 support.
chrisspen Aug 4, 2017
ba9828f
Updated version
chrisspen Aug 4, 2017
3f8dd2b
Refactored default settings initialization to work with Django 1.11.
chrisspen Nov 1, 2017
882a90e
trusty does not have 3.5 distribution
JordanReiter Mar 21, 2019
0e515f1
Python 3.4 is EOL & no longer available & pylint > 2.0 does not suppo…
JordanReiter Mar 21, 2019
1a564fd
return None to fix inconsistent-return-statements warning for pylint
Mar 25, 2019
ec38658
Merge pull request #24 from JordanReiter/patch-1
chrisspen Mar 25, 2019
e7934c1
Import location changed in Django 2.0+
JordanReiter Apr 11, 2019
6811b01
Merge pull request #27 from JordanReiter/patch-2
chrisspen Apr 11, 2019
034b007
Import location changed in Django 2.0+
JordanReiter Apr 11, 2019
3e43fe9
Merge pull request #28 from JordanReiter/patch-2
chrisspen Apr 16, 2019
63d908c
Django requirement
Apr 17, 2019
1d54005
convert bytes to str before saving to TextField
Apr 30, 2019
91c2abc
Upgraded tests
Apr 30, 2019
bab0e55
Merge pull request #29 from JordanReiter/patch-2
chrisspen May 7, 2019
58f32e8
Increased version.
chrisspen May 7, 2019
2b1f98e
Added yapf config.
chrisspen Sep 5, 2019
4ce86e3
Updated readme attachment in setup.py
chrisspen Sep 5, 2019
bc7b02f
Removed Python2.7 support from test suite.
chrisspen Sep 5, 2019
0ee1ba9
Removed Python2.7 support from test suite.
chrisspen Sep 5, 2019
da50f7d
Added test script.
chrisspen Sep 5, 2019
91d8470
Removed Python2.7 support from test suite.
chrisspen Sep 5, 2019
e7c3406
Removed Python2.7 support from test suite.
chrisspen Sep 5, 2019
9a44d11
Removed Python2.7 support from test suite.
chrisspen Sep 5, 2019
9b1939f
Removed Python2.7 support from test suite.
chrisspen Sep 5, 2019
818d558
Forced name methods to force string.
chrisspen Oct 28, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 61 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,61 @@
*.pyc
/build

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo

# Mr Developer
.mr.developer.cfg
.project
.pydevproject
.settings

# Rope
.ropeproject

# Django stuff:
*.log
*.pot

# Sphinx documentation
docs/_build/

# Local virtualenvs used for testing.
/.env*

# PIP install version files.
/=*
*.geany
*.out
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
repos:
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.28.0
hooks:
- id: yapf
args: [--in-place, --parallel, --recursive]
13 changes: 13 additions & 0 deletions .style.yapf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[style]
BASED_ON_STYLE = pep8
COLUMN_LIMIT = 160
COALESCE_BRACKETS = true
DEDENT_CLOSING_BRACKETS = true
BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF = true
SPACES_BEFORE_COMMENT = 1
SPLIT_COMPLEX_COMPREHENSION = true
SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET = false
SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT = 10
CONTINUATION_INDENT_WIDTH = 4
INDENT_WIDTH = 4
CONTINUATION_ALIGN_STYLE = SPACE
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# https://docs.travis-ci.com/user/reference/overview/
dist: bionic
sudo: required
language: python
python:
- "3.6"
install:
- sudo add-apt-repository -y ppa:deadsnakes/ppa
- sudo apt-get -yq update
- sudo apt-get -yq install python3.5 python3.5-dev python3.6 python3.6-dev python3.7 python3.7-dev
- pip install -r pip-requirements-test.txt
script:
- ./pep8.sh
- tox
1 change: 1 addition & 0 deletions .yapfignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
database_files/migrations/*
5 changes: 5 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
recursive-include database_files/tests/fixtures *
recursive-include database_files/tests/media *
include pip-requirements-min-django.txt
include pip-requirements.txt
include pip-requirements-test.txt
112 changes: 90 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,108 @@
django-database-files
=====================
Django Database Files 3000
==========================

django-database-files is a storage system for Django that stores uploaded files
in the database.
[![](https://img.shields.io/pypi/v/django-database-files-3000.svg)](https://pypi.python.org/pypi/django-database-files-3000) [![Build Status](https://img.shields.io/travis/chrisspen/django-database-files-3000.svg?branch=master)](https://travis-ci.org/chrisspen/django-database-files-3000) [![](https://pyup.io/repos/github/chrisspen/django-database-files-3000/shield.svg)](https://pyup.io/repos/github/chrisspen/django-database-files-3000)

WARNING: It is generally a bad idea to serve static files from Django,
but there are some valid use cases. If your Django app is behind a caching
reverse proxy and you need to scale your application servers, it may be
simpler to store files in the database.

Requires:

* Django 1.1
This is a storage system for Django that stores uploaded
files in the database. Files can be served from the database
(usually a bad idea), the file system, or a CDN.

Installation
------------

$ python setup.py install
Simply install via pip with:

pip install django-database-files-3000

Usage
-----

In ``settings.py``, add ``database_files`` to your ``INSTALLED_APPS`` and add this line:
In `settings.py`, add `database_files` to your `INSTALLED_APPS` and add
this line:

DEFAULT_FILE_STORAGE = 'database_files.storage.DatabaseStorage'

Although ``upload_to`` is a required argument on ``FileField``, it is not used for
storing files in the database. Just set it to a dummy value:
Note, the `upload_to` parameter is still used to synchronize the files stored
in the database with those on the file system, so new and existing fields
should still have a value that makes sense from your base media directory.

If you're using South, the initial model migrations will scan through all
existing models for `FileFields` or `ImageFields` and will automatically
load them into the database.

If for any reason you want to re-run this bulk import task, run:

python manage.py database_files_load

Additionally, if you want to export all files in the database back to the file
system, run:

python manage.py database_files_dump

Note, that when a field referencing a file is cleared, the corresponding file
in the database and on the file system will not be automatically deleted.
To delete all files in the database and file system not referenced by any model
fields, run:

python manage.py database_files_cleanup

Settings
-------

* `DB_FILES_AUTO_EXPORT_DB_TO_FS` = `True`|`False` (default `True`)

If true, when a file is uploaded or read from the database, a copy will be
exported to your media directory corresponding to the FileField's upload_to
path, just as it would with the default Django file storage.

If false, the file will only exist in the database.

* `DATABASE_FILES_URL_METHOD` = `URL_METHOD_1`|`URL_METHOD_2` (default `URL_METHOD_1`)

Defines the method to use when rendering the web-accessible URL for a file.

If `URL_METHOD_1`, assumes all files have been exported to the filesystem and
uses the path corresponding to your `settings.MEDIA_URL`.

If `URL_METHOD_2`, uses the URL bound to the `database_file` view
to dynamically lookup and serve files from the filesystem or database.

In this case, you will also need to updates your `urls.py` to include the view
that serves the files:

urlpatterns = patterns('',
# ... the rest of your URLconf goes here ...

# Serve Database Files directly
url(r'', include('database_files.urls')),
)

Development
-----------

Tests require the Python development headers to be installed, which you can install on Ubuntu with:

sudo apt-get install python-dev python3-dev python3.4-dev

To run unittests across multiple Python versions, install:

sudo apt-get install python3.4-minimal python3.4-dev python3.5-minimal python3.5-dev

Note, you may need to enable an [additional repository](https://launchpad.net/~fkrull/+archive/ubuntu/deadsnakes) to provide these packages.

To run all [tests](http://tox.readthedocs.org/en/latest/):

upload = models.FileField(upload_to='not required')
export TESTNAME=; tox

All your ``FileField`` and ``ImageField`` files will now be stored in the
database.
To run tests for a specific environment (e.g. Python 2.7 with Django 1.4):

export TESTNAME=; tox -e py27-django15

Test suite
----------
To run a specific test:

export TESTNAME=.test_adding_file; tox -e py27-django15

$ ./run_tests.sh
To build and deploy a versioned package to PyPI, verify [all unittests are passing](https://travis-ci.org/chrisspen/django-database-files), and then run:

python setup.py sdist
python setup.py sdist upload
2 changes: 2 additions & 0 deletions database_files/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VERSION = (1, 0, 6)
__version__ = '.'.join(map(str, VERSION))
Empty file.
Empty file.
81 changes: 81 additions & 0 deletions database_files/management/commands/database_files_cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from __future__ import print_function

import os
from optparse import make_option

from django.conf import settings
from django.core.files.storage import default_storage
from django.core.management.base import BaseCommand, CommandError
from django.db.models import FileField, ImageField, get_models

from database_files.models import File


class Command(BaseCommand):
args = ''
help = 'Deletes all files in the database that are not referenced by ' + \
'any model fields.'
option_list = BaseCommand.option_list + (
make_option('--dryrun',
action='store_true',
dest='dryrun',
default=False,
help='If given, only displays the names of orphaned files ' + \
'and does not delete them.'),
make_option('--filenames',
default='',
help='If given, only files with these names will be checked'),
)

def handle(self, *args, **options):
tmp_debug = settings.DEBUG
settings.DEBUG = False
names = set()
dryrun = options['dryrun']
filenames = set(_.strip() for _ in options['filenames'].split(',') if _.strip())
try:
for model in get_models():
print('Checking model %s...' % (model,))
for field in model._meta.fields:
if not isinstance(field, (FileField, ImageField)):
continue
# Ignore records with null or empty string values.
q = {'%s__isnull' % field.name: False}
xq = {field.name: ''}
subq = model.objects.filter(**q).exclude(**xq)
subq_total = subq.count()
subq_i = 0
for row in subq.iterator():
subq_i += 1
if subq_i == 1 or not subq_i % 100:
print('%i of %i' % (subq_i, subq_total))
f = getattr(row, field.name)
if f is None:
continue
if not f.name:
continue
names.add(f.name)

# Find all database files with names not in our list.
print('Finding orphaned files...')
orphan_files = File.objects.exclude(name__in=names)
if filenames:
orphan_files = orphan_files.filter(name__in=filenames)
orphan_files = orphan_files.only('name', 'size')
total_bytes = 0
orphan_total = orphan_files.count()
orphan_i = 0
print('Deleting %i orphaned files...' % (orphan_total,))
for f in orphan_files.iterator():
orphan_i += 1
if orphan_i == 1 or not orphan_i % 100:
print('%i of %i' % (orphan_i, orphan_total))
total_bytes += f.size
if dryrun:
print('File %s is orphaned.' % (f.name,))
else:
print('Deleting orphan file %s...' % (f.name,))
default_storage.delete(f.name)
print('%i total bytes in orphan files.' % total_bytes)
finally:
settings.DEBUG = tmp_debug
20 changes: 20 additions & 0 deletions database_files/management/commands/database_files_dump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os
from optparse import make_option

from django.core.management.base import BaseCommand, CommandError

from database_files.models import File


class Command(BaseCommand):
option_list = BaseCommand.option_list + (
# make_option('-w', '--overwrite', action='store_true',
# dest='overwrite', default=False,
# help='If given, overwrites any existing files.'),
)
help = 'Dumps all files in the database referenced by FileFields ' + \
'or ImageFields onto the filesystem in the directory specified by ' + \
'MEDIA_ROOT.'

def handle(self, *args, **options):
File.dump_files(verbose=True)
Loading