Skip to content

Add FileStorage abstraction for handling remote file storage #193

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 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ local_settings.py
build/
dist/
forms_builder/example_project/static/
.tox/
.cache/
.coverage
coverage.xml
junit*.xml
14 changes: 11 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
django-forms-builder
====================

** This package was forked to provide a `FILE_STORAGE` setting. **

The `file-storage` branch is the current branch for this.

Created by `Stephen McDonald <http://twitter.com/stephen_mcd>`_

A Django reusable app providing the ability for admin users to create
Expand Down Expand Up @@ -126,10 +130,11 @@ in the CSV export. By default these uploaded files are stored in an
obscured location under your project's ``MEDIA_ROOT`` directory but
ideally the should be stored somewhere inaccessible to the public. To
set the location where files are stored to be somewhere outside of your
project's ``MEDIA_ROOT`` directory you just need to define the
project's ``MEDIA_ROOT`` directory you just need to define the either the
``FORMS_BUILDER_UPLOAD_ROOT`` setting in your project's ``settings``
module. Its value should be an absolute path on the web server that
isn't accessible to the public.
module or the ``FORMS_BUILDER_FILE_STORAGE`` setting. The
``FORMS_BUILDER_UPLOAD_ROOT`` value should be an absolute path on the web
server that isn't accessible to the public.


Configuration
Expand All @@ -146,6 +151,9 @@ module.
will be added to the form field types. Defaults to ``()``
* ``FORMS_BUILDER_UPLOAD_ROOT`` - The absolute path where files will
be uploaded to. Defaults to ``None``
* ``FORMS_BUILDER_FILE_STORAGE`` - The class path of the Django storage class
to use for storing files. Defaults to ``None`` which uses the default file
storage.
* ``FORMS_BUILDER_USE_HTML5`` - Boolean controlling whether HTML5 form
fields are used. Defaults to ``True``
* ``FORMS_BUILDER_USE_SITES`` - Boolean controlling whether forms are
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 3 additions & 1 deletion forms_builder/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
__version__ = "0.13.0"
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
__version__ = "0.14.0b4"
22 changes: 15 additions & 7 deletions forms_builder/forms/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from future.builtins import bytes, open
import os

from csv import writer
from mimetypes import guess_type
Expand All @@ -18,7 +20,7 @@

from forms_builder.forms.forms import EntriesForm
from forms_builder.forms.models import Form, Field, FormEntry, FieldEntry
from forms_builder.forms.settings import CSV_DELIMITER, UPLOAD_ROOT
from forms_builder.forms.settings import CSV_DELIMITER, UPLOAD_ROOT, FILE_STORAGE
from forms_builder.forms.settings import USE_SITES, EDITABLE_SLUGS
from forms_builder.forms.utils import now, slugify

Expand All @@ -29,15 +31,17 @@
except ImportError:
XLWT_INSTALLED = False


fs = FileSystemStorage(location=UPLOAD_ROOT)
if UPLOAD_ROOT is not None:
fs = FileSystemStorage(location=UPLOAD_ROOT)
else:
fs = FILE_STORAGE()
form_admin_filter_horizontal = ()
form_admin_fieldsets = [
(None, {"fields": ("title", ("status", "login_required",),
("publish_date", "expiry_date",),
"intro", "button_text", "response", "redirect_url")}),
(_("Email"), {"fields": ("send_email", "email_from", "email_copies",
"email_subject", "email_message")}),]
"email_subject", "email_message")}), ]

if EDITABLE_SLUGS:
form_admin_fieldsets.append(
Expand Down Expand Up @@ -190,10 +194,14 @@ def file_view(self, request, field_entry_id):
"""
model = self.fieldentry_model
field_entry = get_object_or_404(model, id=field_entry_id)
path = join(fs.location, field_entry.value)
if hasattr(fs, 'location'):
path = join(fs.location, field_entry.value)
else:
path = field_entry.value
response = HttpResponse(content_type=guess_type(path)[0])
f = open(path, "r+b")
response["Content-Disposition"] = "attachment; filename=%s" % f.name
f = fs.open(path, "rb")
filename = os.path.basename(field_entry.value)
response["Content-Disposition"] = "attachment; filename=%s" % filename
response.write(f.read())
f.close()
return response
Expand Down
7 changes: 4 additions & 3 deletions forms_builder/forms/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from future.builtins import int, range, str

Expand All @@ -8,7 +9,6 @@
import django
from django import forms
from django.forms.extras import SelectDateWidget
from django.core.files.storage import default_storage
from django.core.urlresolvers import reverse
from django.template import Template
from django.utils.safestring import mark_safe
Expand All @@ -20,9 +20,10 @@
from forms_builder.forms.utils import now, split_choices


fs = default_storage
if settings.UPLOAD_ROOT is not None:
fs = default_storage.__class__(location=settings.UPLOAD_ROOT)
fs = settings.FILE_STORAGE(location=settings.UPLOAD_ROOT)
else:
fs = settings.FILE_STORAGE()


##############################
Expand Down
6 changes: 5 additions & 1 deletion forms_builder/forms/settings.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from django.core.files.storage import get_storage_class

if not ("django.contrib.sites" in settings.INSTALLED_APPS):
raise ImproperlyConfigured("django.contrib.sites is required")
Expand All @@ -23,6 +24,9 @@
# The absolute path where files will be uploaded to.
UPLOAD_ROOT = getattr(settings, "FORMS_BUILDER_UPLOAD_ROOT", None)

# The File Storage class to use, uses default file storage if None
FILE_STORAGE = get_storage_class(getattr(settings, "FORMS_BUILDER_FILE_STORAGE", None))

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following on from our discussion - perhaps this could default to a storage class that exists in this app, that merely uses UPLOAD_ROOT inside its init, and raises an error there if the setting isn't defined. That way all the access to the storage class can happen directly via FORMS_BUILDER_FILE_STORAGE.

# Boolean controlling whether HTML5 form fields are used.
USE_HTML5 = getattr(settings, "FORMS_BUILDER_USE_HTML5", True)

Expand Down
116 changes: 45 additions & 71 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,77 +5,51 @@
from setuptools import setup, find_packages


exclude = ["forms_builder/example_project/dev.db",
"forms_builder/example_project/local_settings.py"]
exclude = dict([(e, None) for e in exclude])
for e in exclude:
if e.endswith(".py"):
try:
os.remove("%sc" % e)
except:
pass
try:
with open(e, "r") as f:
exclude[e] = (f.read(), os.stat(e))
os.remove(e)
except Exception:
pass
version = __import__('forms_builder').__version__

if sys.argv[:2] == ["setup.py", "bdist_wheel"]:
# Remove previous build dir when creating a wheel build,
# since if files have been removed from the project,
# they'll still be cached in the build dir and end up
# as part of the build, which is unexpected.
try:
if sys.argv[-1] == 'publish':
if os.path.exists("build"):
rmtree("build")
except:
pass
os.system('python setup.py bdist_wheel upload -r natgeo')
print("You probably want to also tag the version now:")
print(" python setup.py tag")
sys.exit()
elif sys.argv[-1] == 'tag':
cmd = "git tag -a %s -m 'version %s';git push --tags" % (version, version)
os.system(cmd)
sys.exit()

try:
setup(
name = "django-forms-builder",
version = __import__("forms_builder").__version__,
author = "Stephen McDonald",
author_email = "[email protected]",
description = ("A Django reusable app providing the ability for "
"admin users to create their own forms and report "
"on their collected data."),
long_description = open("README.rst").read(),
license = "BSD",
url = "http://github.com/stephenmcd/django-forms-builder",
zip_safe = False,
include_package_data = True,
packages = find_packages(),
install_requires = [
"sphinx-me >= 0.1.2",
"unidecode",
"django-email-extras >= 0.2",
"django >= 1.8, < 1.11",
"future <= 0.15.0",
],
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Framework :: Django",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Internet :: WWW/HTTP :: Site Management",
]
)
finally:
for e in exclude:
if exclude[e] is not None:
data, stat = exclude[e]
try:
with open(e, "w") as f:
f.write(data)
os.chown(e, stat.st_uid, stat.st_gid)
os.chmod(e, stat.st_mode)
except:
pass
setup(
name="django-forms-builder",
version=__import__("forms_builder").__version__,
author="Stephen McDonald",
author_email="[email protected]",
description=("A Django reusable app providing the ability for "
"admin users to create their own forms and report "
"on their collected data."),
long_description=open("README.rst").read(),
license="BSD",
url="http://github.com/stephenmcd/django-forms-builder",
zip_safe=False,
include_package_data=True,
packages=find_packages(exclude=['*example*', ]),
install_requires=[
"unidecode",
"django-email-extras >= 0.2",
"future >= 0.16.0",
],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Framework :: Django",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Internet :: WWW/HTTP :: Site Management",
]
)
21 changes: 21 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[tox]
envlist = py{27,36}-django{19,110,111}

[testenv]
commands = pytest --cov=forms_builder \
--cov-report term-missing \
--cov-report xml \
--junitxml=junit-{envname}.xml \
--ds=example_project.settings {posargs}
setenv =
PYTHONPATH={toxinidir}:{toxinidir}/example_project
deps =
pytest
pytest-cov
pytest-django
django19: Django<1.10
django110: Django<1.11
django111: Django<2.0

[pytest]
python_files = tests.py **/tests.py **/tests/*.py **/tests.py