Skip to content
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

Easier File and Image model fields usage #507

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 30 additions & 3 deletions docs/settings.rst
Expand Up @@ -6,7 +6,7 @@ Settings
``FILER_ENABLE_PERMISSIONS``
----------------------------

Activate the or not the Permission check on the files and folders before
Activate the or not the Permission check on the files and folders before
displaying them in the admin. When set to ``False`` it gives all the authorization
to staff members based on standard Django model permissions.

Expand Down Expand Up @@ -73,7 +73,7 @@ Public storage uses ``DEFAULT_FILE_STORAGE`` as default storage backend.

``UPLOAD_TO`` is the function to generate the path relative to the storage root. The
default generates a random path like ``1d/a5/1da50fee-5003-46a1-a191-b547125053a8/filename.jpg``. This
will be applied whenever a file is uploaded or moved between public (without permission checks) and
will be applied whenever a file is uploaded or moved between public (without permission checks) and
private (with permission checks) storages. Defaults to ``'filer.utils.generate_filename.randomized'``.


Expand Down Expand Up @@ -111,7 +111,7 @@ Defaults to ``20``
``FILER_SUBJECT_LOCATION_IMAGE_DEBUG``
--------------------------------------

Draws a red circle around to point in the image that was used to do the
Draws a red circle around to point in the image that was used to do the
subject location aware image cropping.

Defaults to ``False``
Expand Down Expand Up @@ -150,3 +150,30 @@ Number of simultaneous AJAX uploads. Defaults to 3.
If your database backend is SQLite it would be set to 1 by default. This allows
to avoid ``database is locked`` errors on SQLite during multiple simultaneous
file uploads.


``FILER_DEFAULT_FOLDER_GETTER``
-------------------------------

Path to a subclass of `filer.utils.folders.DefaultFolderGetter`.
Methods name of this subclass can be used as value for the
`default_folder_key` of ``FilerFileField`` and ``FilerImageField``.

e.g::

FILER_DEFAULT_FOLDER_GETTER = 'myapp.handlers.FolderGetter'

and in myapp/hanlers.py::

from filer.utils.folders import DefaultFolderGetter

class FolderGetter(DefaultFolderGetter):
@classmethod
def USER_OWN_FOLDER(cls, request):
if not request.user.is_authenticated():
return None
parent_kwargs = {
name: 'users_files',

}
return cls._get_or_create(name=user.username, owner=user, parent_kwargs=parent_kwargs)
57 changes: 57 additions & 0 deletions docs/usage.rst
Expand Up @@ -24,6 +24,28 @@ checksums.
company.logo.icons['64'] # or {{ company.logo.icons.64 }} in a template
company.logo.url # prints path to original image

``FileMimetypeValidator`` and preconfigured validators
------------------------------------------------------

``django-filer`` provides a validator to allow only files of some mimetypes.
``FileMimetypeValidator`` require a mimetypes list and allow wildcard for
subtypes. eg : `image/jpeg` allow only JPEG files, but if you want to allow all
image types, `image/*` is accepted.

Some preconfigured validators are set:

* `validate_audios` : allow all `audio/*` files
* `validate_images` : allow all `images/*` files
* `validate_videos` : allow all `video/*` files
* `validate_html5_audios` : allow most supported audio file format for web integration
(wav, mp3, mp4, ogg, webm, aac)
* `validate_html5_images` : allow most supported image file format for web integration
(jpeg, png, gif, svg)
* `validate_html5_videos` : allow most supported video file format for web integration
(mp4, ogv, webm)
* `validate_documents` : allow main document types
(odt, ods, odpn, doc, xls, ppt, pdf, csv)


``FilerFileField`` and ``FilerImageField``
------------------------------------------
Expand All @@ -33,6 +55,17 @@ The only difference is, that there is no need to declare what model we are
referencing (it is always ``filer.models.File`` for the ``FilerFileField`` and
``filer.models.Image`` for the ``FilerImageField``).

Those Fields have some specific and optionnal options :

* `default_folder_key` : specify the folder handler key which will return the
folder where files will be stored for direct upload, or the folder to open
when we use file lookup.
* `default_direct_upload_enabled` : Allow use to upload a file inside the
main form (without opening the files lookup popup). Default is False
to stay backward compatible.
* `default_file_lookup_enabled` : Allow user to choose (or add) files via
the file lookup popup (default is True)

Simple example ``models.py``::

from django.db import models
Expand Down Expand Up @@ -60,6 +93,30 @@ As with `django.db.models.ForeignKey`_ in general, you have to define a
non-clashing ``related_name`` if there are multiple ``ForeignKey`` s to the
same model.


Advanced exemple ``models.py``::

from django.db import models
from filer.fields.image import FilerImageField
from filer.fields.file import FilerFileField
from filer.validators import FileMimetypeValidator, validate_documents

class Company(models.Model):
name = models.CharField(max_length=255)
logo = FilerImageField(null=True, blank=True,
help_text='JPEG only',
default_direct_upload_enabled=True,
default_file_lookup_enabled=False,
default_folder_key='USERS_OWN_FOLDER',
validators=[FileMimetypeValidator(['image/jpeg',]),],
related_name="company_logo")
disclaimer = FilerFileField(null=True, blank=True,
default_direct_upload_enabled=True,
default_file_lookup_enabled=True,
default_folder_key='DOCUMENTS',
validators=[validate_documents,]
related_name="company_disclaimer")

templates
.........

Expand Down
68 changes: 46 additions & 22 deletions filer/admin/clipboardadmin.py
Expand Up @@ -8,11 +8,12 @@

from filer import settings as filer_settings
from filer.models import Folder, Clipboard, ClipboardItem, Image
from filer.utils.compatibility import DJANGO_1_4
from filer.utils.compatibility import DJANGO_1_4, get_model
from filer.utils.files import (
handle_upload, handle_request_files_upload, UploadException,
)
from filer.utils.loader import load_object
from filer.validators import FileMimetypeValidator


NO_FOLDER_ERROR = "Can't find folder to upload. Please refresh and try again"
Expand Down Expand Up @@ -53,7 +54,16 @@ def get_urls(self):
url(r'^operations/upload/(?P<folder_id>[0-9]+)/$',
ajax_upload,
name='filer-ajax_upload'),
url(r'^operations/upload/no_folder/$',
url((r'^operations/upload/'
r'(?P<folder_key>\w*)/'
r'(?P<related_field>\w+.\w+.\w+)/'
r'$'),
ajax_upload,
name='filer-ajax_upload'),
url((r'^operations/upload/(?P<folder_key>\w*)/$'),
ajax_upload,
name='filer-ajax_upload'),
url((r'^operations/upload/no_folder/$'),
ajax_upload,
name='filer-ajax_upload'),
)
Expand All @@ -72,34 +82,48 @@ def get_model_perms(self, *args, **kwargs):


@csrf_exempt
def ajax_upload(request, folder_id=None):
def ajax_upload(request, folder_id=None, folder_key=None, related_field=None):
"""
Receives an upload from the uploader. Receives only one file at a time.
"""
mimetype = "application/json" if request.is_ajax() else "text/html"
content_type_key = 'mimetype' if DJANGO_1_4 else 'content_type'
response_params = {content_type_key: mimetype}
folder = None
if folder_id:
try:
# Get folder
folder = Folder.objects.get(pk=folder_id)
except Folder.DoesNotExist:
return HttpResponse(json.dumps({'error': NO_FOLDER_ERROR}),
**response_params)

# check permissions
if folder and not folder.has_add_children_permission(request):
return HttpResponse(
json.dumps({'error': NO_PERMISSIONS_FOR_FOLDER}),
**response_params)
mimetypes = []
try:
if related_field:
related_field = related_field.split('.')
if len(related_field) != 3:
raise UploadException("Related field is not valid.")
try:
model = get_model(related_field[0], related_field[1])
field = model._meta.get_field_by_name(related_field[2])[0]
except:
raise UploadException("Related field is not valid.")
for validator in field.validators:
if isinstance(validator, FileMimetypeValidator):
mimetypes += validator.mimetypes
folder = None
if folder_id:
try:
# Get folder
folder = Folder.objects.get(pk=folder_id)
except Folder.DoesNotExist:
return HttpResponse(json.dumps({'error': NO_FOLDER_ERROR}),
**response_params)
elif folder_key and folder_key != 'no_folder':
from filer.utils.folders import get_default_folder_getter
folder = get_default_folder_getter().get(folder_key, request)

# check permissions
if folder and not folder.has_add_children_permission(request):
raise UploadException(NO_PERMISSIONS_FOR_FOLDER)
if len(request.FILES) == 1:
# dont check if request is ajax or not, just grab the file
upload, filename, is_raw = handle_request_files_upload(request)
upload, filename, is_raw = handle_request_files_upload(request, mimetypes)
else:
# else process the request as usual
upload, filename, is_raw = handle_upload(request)
upload, filename, is_raw = handle_upload(request, mimetypes)
# TODO: Deprecated/refactor
# Get clipboad
# clipboard = Clipboard.objects.get_or_create(user=request.user)[0]
Expand Down Expand Up @@ -175,14 +199,14 @@ def ajax_upload(request, folder_id=None):
**response_params)
else:
form_errors = '; '.join(['%s: %s' % (
field,
', '.join(errors)) for field, errors in list(
fieldname,
', '.join(errors)) for fieldname, errors in list(
uploadform.errors.items())
])
raise UploadException(
"AJAX request not valid: form invalid '%s'" % (
form_errors,))
except UploadException as e:
return HttpResponse(json.dumps({'error': str(e)}),
status=500,
# status=500, we know what's going on and must display it to the user
**response_params)
9 changes: 8 additions & 1 deletion filer/admin/folderadmin.py
Expand Up @@ -205,7 +205,9 @@ def get_urls(self):
url(r'^(?P<folder_id>\d+)/list/$',
self.admin_site.admin_view(self.directory_listing),
name='filer-directory_listing'),

url(r'^(?P<folder_key>[a-zA-Z][a-zA-Z0-9_]*)/list/$',
self.admin_site.admin_view(self.directory_listing_by_key),
name='filer-directory_listing_by_key'),
url(r'^(?P<folder_id>\d+)/make_folder/$',
self.admin_site.admin_view(views.make_folder),
name='filer-directory_listing-make_folder'),
Expand All @@ -227,6 +229,11 @@ def get_urls(self):
return url_patterns

# custom views
def directory_listing_by_key(self, request, folder_key):
from filer.utils.folders import get_default_folder_getter
folder = get_default_folder_getter().get(folder_key, request)
return self.directory_listing(request, folder.pk)

def directory_listing(self, request, folder_id=None, viewtype=None):
clipboard = tools.get_user_clipboard(request.user)
if viewtype == 'images_with_missing_data':
Expand Down