From 0d2e82cb9cec32c96e6133da988f05c3a7cb0fa9 Mon Sep 17 00:00:00 2001 From: Slava Date: Sat, 29 Oct 2016 19:30:47 +0300 Subject: [PATCH 1/9] pep8 --- .gitignore | 1 + filebrowser/base.py | 84 +++--- filebrowser/conf.py | 2 +- filebrowser/decorators.py | 5 +- filebrowser/fields.py | 37 ++- filebrowser/forms.py | 53 +++- filebrowser/functions.py | 284 +++++++++--------- .../management/commands/version_generator.py | 32 +- filebrowser/settings.py | 151 +++++++--- filebrowser/templatetags/fb_csrf.py | 13 +- filebrowser/templatetags/fb_pagination.py | 30 +- filebrowser/templatetags/fb_tags.py | 39 +-- filebrowser/templatetags/fb_versions.py | 98 ++++-- filebrowser/views.py | 58 ++-- 14 files changed, 524 insertions(+), 363 deletions(-) diff --git a/.gitignore b/.gitignore index 0762cf3..de659b8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ dist/ django_media_manager.egg-info example/example/local_settings.py .DS_Store +.idea/ diff --git a/filebrowser/base.py b/filebrowser/base.py index 5c5d6cb..f63623b 100755 --- a/filebrowser/base.py +++ b/filebrowser/base.py @@ -1,25 +1,15 @@ # coding: utf-8 # imports -import os, re, datetime -from time import gmtime, strftime - -# django imports -from django.conf import settings +import os +import re +import datetime +from PIL import Image # filebrowser imports -from filebrowser.settings import * +from filebrowser.settings import SAVE_FULL_URL, ADMIN_THUMBNAIL from filebrowser.conf import fb_settings -from filebrowser.functions import get_file_type, url_join, is_selectable, get_version_path - -# PIL import -if STRICT_PIL: - from PIL import Image -else: - try: - from PIL import Image - except ImportError: - import Image +from filebrowser.functions import get_file_type, url_join, get_version_path class FileObject(object): @@ -30,21 +20,29 @@ class FileObject(object): """ def __init__(self, path): - ''' - `os.path.split` Split the pathname path into a pair, (head, tail) where tail is the last pathname component and head is everything leading up to that. The tail part will never contain a slash; if path ends in a slash, tail will be empty. If there is no slash in path, head will be empty. If path is empty, both head and tail are empty. - ''' + """ + `os.path.split` Split the pathname path into a pair, (head, tail) + where tail is the last pathname component and head is everything + leading up to that. The tail part will never contain a slash; + if path ends in a slash, tail will be empty. + If there is no slash in path, head will be empty. + If path is empty, both head and tail are empty. + """ self.path = path - self.url_rel = path.replace("\\","/") + self.url_rel = path.replace("\\", "/") self.head, self.filename = os.path.split(path) - self.filename_lower = self.filename.lower() # important for sorting - self.filetype = get_file_type(self.filename) # strange if file no extension then this folder + # important for sorting + self.filename_lower = self.filename.lower() + # strange if file no extension then this folder + self.filetype = get_file_type(self.filename) def _filesize(self): """ Filesize. """ - path = (self.path) - if os.path.isfile(os.path.join(fb_settings.MEDIA_ROOT, path)) or os.path.isdir(os.path.join(fb_settings.MEDIA_ROOT, path)): + path = self.path + if os.path.isfile(os.path.join(fb_settings.MEDIA_ROOT, path)) or \ + os.path.isdir(os.path.join(fb_settings.MEDIA_ROOT, path)): return os.path.getsize(os.path.join(fb_settings.MEDIA_ROOT, path)) return "" filesize = property(_filesize) @@ -53,8 +51,11 @@ def _date(self): """ Date. """ - if os.path.isfile(os.path.join(fb_settings.MEDIA_ROOT, self.path)) or os.path.isdir(os.path.join(fb_settings.MEDIA_ROOT, self.path)): - return os.path.getmtime(os.path.join(fb_settings.MEDIA_ROOT, self.path)) + if os.path.isfile(os.path.join(fb_settings.MEDIA_ROOT, self.path)) or \ + os.path.isdir(os.path.join(fb_settings.MEDIA_ROOT, self.path)): + return os.path.getmtime( + os.path.join(fb_settings.MEDIA_ROOT, self.path) + ) return "" date = property(_date) @@ -96,7 +97,7 @@ def _path_relative_directory(self): """ Path relative to initial directory. """ - directory_re = re.compile(r'^({0})'.format((fb_settings.DIRECTORY))) + directory_re = re.compile(r'^({0})'.format(fb_settings.DIRECTORY)) value = directory_re.sub('', self.path) return u"{0}".format(value) path_relative_directory = property(_path_relative_directory) @@ -109,7 +110,7 @@ def _url_full(self): """ Full URL including MEDIA_URL. """ - return (url_join(fb_settings.MEDIA_URL, self.url_rel)) + return url_join(fb_settings.MEDIA_URL, self.url_rel) url_full = property(_url_full) def _url_save(self): @@ -127,18 +128,23 @@ def _url_thumbnail(self): Thumbnail URL. """ if self.filetype == "Image": - return u"{0}".format(url_join(fb_settings.MEDIA_URL, get_version_path(self.path, ADMIN_THUMBNAIL))) + return "{0}".format( + url_join( + fb_settings.MEDIA_URL, + get_version_path(self.path, ADMIN_THUMBNAIL) + ) + ) else: return "" url_thumbnail = property(_url_thumbnail) def url_admin(self): if self.filetype_checked == "Folder": - directory_re = re.compile(r'^({0})'.format((fb_settings.DIRECTORY))) + directory_re = re.compile(r'^({0})'.format(fb_settings.DIRECTORY)) value = directory_re.sub('', self.path) - return u"{0}".format(value) + return "{0}".format(value) else: - return u"{0}".format(url_join(fb_settings.MEDIA_URL, self.path)) + return "{0}".format(url_join(fb_settings.MEDIA_URL, self.path)) def _dimensions(self): """ @@ -146,9 +152,11 @@ def _dimensions(self): """ if self.filetype == 'Image': try: - im = Image.open(os.path.join(fb_settings.MEDIA_ROOT, self.path)) + im = Image.open( + os.path.join(fb_settings.MEDIA_ROOT, self.path) + ) return im.size - except: + except IOError: pass else: return False @@ -195,12 +203,10 @@ def _is_empty(self): is_empty = property(_is_empty) def __repr__(self): - return (self.url_save) + return self.url_save def __str__(self): - return (self.url_save) + return self.url_save def __unicode__(self): - return (self.url_save) - - + return self.url_save diff --git a/filebrowser/conf.py b/filebrowser/conf.py index 2fd3ded..c516a90 100755 --- a/filebrowser/conf.py +++ b/filebrowser/conf.py @@ -1,6 +1,6 @@ - from filebrowser import settings + class FileBrowserSettings(object): """ Proxy for file browser settings defined at module level diff --git a/filebrowser/decorators.py b/filebrowser/decorators.py index 792bc33..2d2c9a1 100755 --- a/filebrowser/decorators.py +++ b/filebrowser/decorators.py @@ -21,9 +21,6 @@ def decorator(request, *args, **kwargs): session_data = engine.SessionStore(request.POST.get('session_key')) user_id = session_data['_auth_user_id'] # will return 404 if the session ID does not resolve to a valid user - User = get_user_model() - request.user = get_object_or_404(User, pk=user_id) + request.user = get_object_or_404(get_user_model(), pk=user_id) return function(request, *args, **kwargs) return decorator - - diff --git a/filebrowser/fields.py b/filebrowser/fields.py index 5dd321a..0383dbf 100755 --- a/filebrowser/fields.py +++ b/filebrowser/fields.py @@ -4,15 +4,16 @@ import os # django imports -from django.db import models from django import forms from django.forms.widgets import Input -from django.db.models.fields import Field, CharField +from django.db.models.fields import Field from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ # filebrowser imports -from filebrowser.settings import * +from filebrowser.settings import ( + URL_FILEBROWSER_MEDIA, ADMIN_THUMBNAIL, DEBUG, EXTENSIONS +) from filebrowser.base import FileObject from filebrowser.conf import fb_settings from filebrowser.functions import url_to_path @@ -46,8 +47,9 @@ def render(self, name, value, attrs=None): if value is None: value = "" final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) - final_attrs[ - 'search_icon'] = URL_FILEBROWSER_MEDIA + 'img/filebrowser_icon_show.gif' + final_attrs['search_icon'] = os.path.join( + URL_FILEBROWSER_MEDIA, 'img/filebrowser_icon_show.gif' + ) final_attrs['directory'] = self.directory final_attrs['extensions'] = self.extensions final_attrs['format'] = self.format @@ -86,7 +88,7 @@ def clean(self, value): if value == '': return value file_extension = os.path.splitext(value)[1].lower() - if self.extensions and not file_extension in self.extensions: + if self.extensions and file_extension not in self.extensions: raise forms.ValidationError( self.error_messages['extension'] % {'ext': file_extension, 'allowed': ", ".join( @@ -111,17 +113,20 @@ def get_db_prep_value(self, value, connection, prepared=False): return None return str(value) - def get_manipulator_field_objs(self): - return [oldforms.TextField] + # FIXME: recheck or need it + # @staticmethod + # def get_manipulator_field_objs(): + # return [oldforms.TextField] def get_internal_type(self): return "CharField" def formfield(self, **kwargs): - attrs = {} - attrs["directory"] = self.directory - attrs["extensions"] = self.extensions - attrs["format"] = self.format + attrs = { + "directory": self.directory, + "extensions": self.extensions, + "format": self.format + } defaults = { 'form_class': FileBrowseFormField, 'widget': FileBrowseWidget(attrs=attrs), @@ -131,11 +136,3 @@ def formfield(self, **kwargs): } defaults.update(kwargs) return super(FileBrowseField, self).formfield(**defaults) - - -try: - from south.modelsinspector import add_introspection_rules - - add_introspection_rules([], ["^filebrowser\.fields\.FileBrowseField"]) -except: - pass diff --git a/filebrowser/forms.py b/filebrowser/forms.py index e958e2f..f572e1e 100755 --- a/filebrowser/forms.py +++ b/filebrowser/forms.py @@ -1,38 +1,50 @@ # coding: utf-8 # imports -import re, os +import re +import os # django imports from django import forms -from django.forms.formsets import BaseFormSet from django.utils.translation import ugettext as _ # filebrowser imports -from filebrowser.settings import MAX_UPLOAD_SIZE, FOLDER_REGEX +from filebrowser.settings import FOLDER_REGEX from filebrowser.functions import convert_filename alnum_name_re = re.compile(FOLDER_REGEX) + class MakeDirForm(forms.Form): """ Form for creating Folder. """ - + dir_name = forms.CharField( + widget=forms.TextInput( + attrs={'class': 'vTextField', 'max_length': 50, 'min_length': 3} + ), + label=_('Name'), + help_text=_('Only letters, numbers, underscores, ' + 'spaces and hyphens are allowed.'), + required=True + ) + def __init__(self, path, *args, **kwargs): self.path = path super(MakeDirForm, self).__init__(*args, **kwargs) - - dir_name = forms.CharField(widget=forms.TextInput(attrs=dict({ 'class': 'vTextField' }, max_length=50, min_length=3)), label=_(u'Name'), help_text=_(u'Only letters, numbers, underscores, spaces and hyphens are allowed.'), required=True) def clean_dir_name(self): if self.cleaned_data['dir_name']: - # only letters, numbers, underscores, spaces and hyphens are allowed. + # only letters, numbers, underscores, + # spaces and hyphens are allowed. if not alnum_name_re.search(self.cleaned_data['dir_name']): - raise forms.ValidationError(_(u'Only letters, numbers, underscores, spaces and hyphens are allowed.')) + raise forms.ValidationError( + _('Only letters, numbers, underscores, ' + 'spaces and hyphens are allowed.') + ) # Folder must not already exist. if os.path.isdir(os.path.join(self.path, convert_filename(self.cleaned_data['dir_name']))): - raise forms.ValidationError(_(u'The Folder already exists.')) + raise forms.ValidationError(_('The Folder already exists.')) return convert_filename(self.cleaned_data['dir_name']) @@ -40,24 +52,33 @@ class RenameForm(forms.Form): """ Form for renaming Folder/File. """ - + name = forms.CharField( + widget=forms.TextInput( + attrs={'class': 'vTextField', 'max_length': 50, 'min_length': 3} + ), + label=_('New Name'), + help_text=_('Only letters, numbers, underscores, ' + 'spaces and hyphens are allowed.'), + required=True + ) + def __init__(self, path, file_extension, *args, **kwargs): self.path = path self.file_extension = file_extension super(RenameForm, self).__init__(*args, **kwargs) - name = forms.CharField(widget=forms.TextInput(attrs=dict({ 'class': 'vTextField' }, max_length=50, min_length=3)), label=_(u'New Name'), help_text=_('Only letters, numbers, underscores, spaces and hyphens are allowed.'), required=True) - def clean_name(self): if self.cleaned_data['name']: - # only letters, numbers, underscores, spaces and hyphens are allowed. + # only letters, numbers, underscores, + # spaces and hyphens are allowed. if not alnum_name_re.search(self.cleaned_data['name']): - raise forms.ValidationError(_(u'Only letters, numbers, underscores, spaces and hyphens are allowed.')) + raise forms.ValidationError( + _('Only letters, numbers, underscores, ' + 'spaces and hyphens are allowed.') + ) # folder/file must not already exist. if os.path.isdir(os.path.join(self.path, convert_filename(self.cleaned_data['name']))): raise forms.ValidationError(_(u'The Folder already exists.')) elif os.path.isfile(os.path.join(self.path, convert_filename(self.cleaned_data['name']) + self.file_extension)): raise forms.ValidationError(_(u'The File already exists.')) return convert_filename(self.cleaned_data['name']) - - diff --git a/filebrowser/functions.py b/filebrowser/functions.py index 5a810c0..b28111e 100755 --- a/filebrowser/functions.py +++ b/filebrowser/functions.py @@ -1,42 +1,38 @@ # coding: utf-8 # imports -import os, re, decimal, itertools -from time import gmtime, strftime, localtime, mktime, time -# from urllib import parse +import os +import re +import itertools +from time import gmtime, strftime, localtime, time +from PIL import Image, ImageFile # django imports -from django.utils.translation import ugettext as _ -from django.utils.safestring import mark_safe -from django.core.files import File from django.core.files.storage import default_storage from django.utils.encoding import smart_str # filebrowser imports -from filebrowser.settings import * +from filebrowser.settings import ( + ADMIN_VERSIONS, VERSIONS_BASEDIR, DEBUG, URL_FILEBROWSER_MEDIA, + PATH_FILEBROWSER_MEDIA, URL_TINYMCE, PATH_TINYMCE, EXTENSIONS, + SELECT_FORMATS, VERSIONS, ADMIN_THUMBNAIL, PREVIEW_VERSION, + MAX_UPLOAD_SIZE, CONVERT_FILENAME, STRICT_PIL, IMAGE_MAXBLOCK +) from filebrowser.conf import fb_settings import sys _ver = sys.version_info -# PIL import -if STRICT_PIL: - from PIL import Image -else: - try: - from PIL import Image - except ImportError: - import Image - def url_to_path(value): """ Change URL to PATH. - Value has to be an URL relative to MEDIA URL or a full URL (including MEDIA_URL). - + Value has to be an URL relative to MEDIA URL or a full URL + (including MEDIA_URL). + Returns a PATH relative to MEDIA_ROOT. """ - + mediaurl_re = re.compile(r'^({0})'.format(fb_settings.MEDIA_URL)) value = mediaurl_re.sub('', value) return value @@ -49,7 +45,7 @@ def path_to_url(value): Return an URL relative to MEDIA_ROOT. """ - + mediaroot_re = re.compile(r'^({0})'.format(fb_settings.MEDIA_ROOT)) value = mediaroot_re.sub('', value) return url_join(fb_settings.MEDIA_URL, value) @@ -61,7 +57,7 @@ def dir_from_url(value): URL has to be an absolute URL including MEDIA_URL or an URL relative to MEDIA_URL. """ - + mediaurl_re = re.compile(r'^({0})'.format(fb_settings.MEDIA_URL)) value = mediaurl_re.sub('', value) directory_re = re.compile(r'^({0})'.format(fb_settings.DIRECTORY)) @@ -77,28 +73,32 @@ def get_version_path(value, version_prefix): version_filename = filename + version_prefix + ext Returns a path relative to MEDIA_ROOT. """ - + if os.path.isfile(smart_str(os.path.join(fb_settings.MEDIA_ROOT, value))): path, filename = os.path.split(value) filename, ext = os.path.splitext(filename) # check if this file is a version of an other file - # to return filename_.ext instead of filename__.ext + # to return filename_.ext instead of + # filename__.ext tmp = filename.split("_") if tmp[len(tmp)-1] in ADMIN_VERSIONS: - # it seems like the "original" is actually a version of an other original + # it seems like the "original" + # is actually a version of an other original # so we strip the suffix (aka. version_perfix) new_filename = filename.replace("_" + tmp[len(tmp)-1], "") # check if the version exists when we use the new_filename if os.path.isfile(smart_str(os.path.join(fb_settings.MEDIA_ROOT, path, new_filename + "_" + version_prefix + ext))): - # our "original" filename seem to be filename_ construct + # our "original" filename seem to be + # filename_ construct # so we replace it with the new_filename filename = new_filename - # if a VERSIONS_BASEDIR is set we need to strip it from the path - # or we get a //... construct + # if a VERSIONS_BASEDIR is set we need to strip it + # from the path or we get + # a //... construct if VERSIONS_BASEDIR != "": path = path.replace(VERSIONS_BASEDIR + "/", "") - + version_filename = filename + "_" + version_prefix + ext return os.path.join(VERSIONS_BASEDIR, path, version_filename) else: @@ -108,25 +108,34 @@ def get_version_path(value, version_prefix): def sort_by_attr(seq, attr): """ Sort the sequence of objects by object's attribute - + Arguments: - seq - the list or any sequence (including immutable one) of objects to sort. + seq - the list or any sequence (including immutable one) + of objects to sort. attr - the name of attribute to sort by - + Returns: the sorted list of objects. """ import operator - + # Use the "Schwartzian transform" # Create the auxiliary list of tuples where every i-th tuple has form - # (seq[i].attr, i, seq[i]) and sort it. The second item of tuple is needed not - # only to provide stable sorting, but mainly to eliminate comparison of objects + # (seq[i].attr, i, seq[i]) and sort it. + # The second item of tuple is needed not only to provide stable sorting, + # but mainly to eliminate comparison of objects # (which can be expensive or prohibited) in case of equal attribute values. if _ver >= (3, 0): - intermed = map(None, map(getattr, seq, (attr,)*len(seq)), itertools.zip_longest(range(len(seq)), seq)) - # intermed.sort() + intermed = map( + None, + map( + getattr, + seq, + (attr,)*len(seq) + ), + itertools.zip_longest(range(len(seq)), seq) + ) try: intermed = sorted(intermed) # does this actually DO anything? @@ -135,7 +144,9 @@ def sort_by_attr(seq, attr): except TypeError: return seq else: - intermed = map(None, map(getattr, seq, (attr,)*len(seq)), range(len(seq)), seq) + intermed = map( + None, map(getattr, seq, (attr,)*len(seq)), range(len(seq)), seq + ) intermed.sort() return map(operator.getitem, intermed, (-1,) * len(intermed)) @@ -144,7 +155,7 @@ def url_join(*args): """ URL join routine. """ - + if args[0].startswith("http://"): url = "http://" elif args[0].startswith("https://"): @@ -167,8 +178,10 @@ def get_path(path): """ Get Path. """ - - if path.startswith('.') or os.path.isabs(path) or not os.path.isdir(os.path.join(fb_settings.MEDIA_ROOT, fb_settings.DIRECTORY, path)): + + if path.startswith('.') or \ + os.path.isabs(path) or \ + not os.path.isdir(os.path.join(fb_settings.MEDIA_ROOT, fb_settings.DIRECTORY, path)): return None return path @@ -177,10 +190,18 @@ def get_file(path, filename): """ Get File. """ - - converted_path = smart_str(os.path.join(fb_settings.MEDIA_ROOT, fb_settings.DIRECTORY, path, filename)) - - if not os.path.isfile(converted_path) and not os.path.isdir(converted_path): + + converted_path = smart_str( + os.path.join( + fb_settings.MEDIA_ROOT, + fb_settings.DIRECTORY, + path, + filename + ) + ) + + if not os.path.isfile(converted_path) and \ + not os.path.isdir(converted_path): return None return filename @@ -189,30 +210,38 @@ def get_breadcrumbs(query, path): """ Get breadcrumbs. """ - + breadcrumbs = [] dir_query = "" if path: for item in path.split(os.sep): - dir_query = os.path.join(dir_query,item) - breadcrumbs.append([item,dir_query]) + dir_query = os.path.join(dir_query, item) + breadcrumbs.append([item, dir_query]) return breadcrumbs -def get_filterdate(filterDate, dateTime): +def get_filterdate(filter_date, date_time): """ Get filterdate. """ - + returnvalue = '' - dateYear = strftime("%Y", gmtime(dateTime)) - dateMonth = strftime("%m", gmtime(dateTime)) - dateDay = strftime("%d", gmtime(dateTime)) - if filterDate == 'today' and int(dateYear) == int(localtime()[0]) and int(dateMonth) == int(localtime()[1]) and int(dateDay) == int(localtime()[2]): returnvalue = 'true' - elif filterDate == 'thismonth' and dateTime >= time()-2592000: returnvalue = 'true' - elif filterDate == 'thisyear' and int(dateYear) == int(localtime()[0]): returnvalue = 'true' - elif filterDate == 'past7days' and dateTime >= time()-604800: returnvalue = 'true' - elif filterDate == '': returnvalue = 'true' + date_year = strftime("%Y", gmtime(date_time)) + date_month = strftime("%m", gmtime(date_time)) + date_day = strftime("%d", gmtime(date_time)) + if filter_date == 'today' and \ + int(date_year) == int(localtime()[0]) and \ + int(date_month) == int(localtime()[1]) and \ + int(date_day) == int(localtime()[2]): + returnvalue = 'true' + elif filter_date == 'thismonth' and date_time >= time() - 2592000: + returnvalue = 'true' + elif filter_date == 'thisyear' and int(date_year) == int(localtime()[0]): + returnvalue = 'true' + elif filter_date == 'past7days' and date_time >= time()-604800: + returnvalue = 'true' + elif filter_date == '': + returnvalue = 'true' return returnvalue @@ -220,32 +249,33 @@ def get_settings_var(): """ Get settings variables used for FileBrowser listing. """ - - settings_var = {} - # Main - settings_var['DEBUG'] = DEBUG - settings_var['MEDIA_ROOT'] = fb_settings.MEDIA_ROOT - settings_var['MEDIA_URL'] = fb_settings.MEDIA_URL - settings_var['DIRECTORY'] = fb_settings.DIRECTORY - # FileBrowser - settings_var['URL_FILEBROWSER_MEDIA'] = URL_FILEBROWSER_MEDIA - settings_var['PATH_FILEBROWSER_MEDIA'] = PATH_FILEBROWSER_MEDIA - # TinyMCE - settings_var['URL_TINYMCE'] = URL_TINYMCE - settings_var['PATH_TINYMCE'] = PATH_TINYMCE - # Extensions/Formats (for FileBrowseField) - settings_var['EXTENSIONS'] = EXTENSIONS - settings_var['SELECT_FORMATS'] = SELECT_FORMATS - # Versions - settings_var['VERSIONS_BASEDIR'] = VERSIONS_BASEDIR - settings_var['VERSIONS'] = VERSIONS - settings_var['ADMIN_VERSIONS'] = ADMIN_VERSIONS - settings_var['ADMIN_THUMBNAIL'] = ADMIN_THUMBNAIL - settings_var['PREVIEW_VERSION'] = PREVIEW_VERSION - # FileBrowser Options - settings_var['MAX_UPLOAD_SIZE'] = MAX_UPLOAD_SIZE - # Convert Filenames - settings_var['CONVERT_FILENAME'] = CONVERT_FILENAME + + settings_var = { + # Main + 'DEBUG': DEBUG, + 'MEDIA_ROOT': fb_settings.MEDIA_ROOT, + 'MEDIA_URL': fb_settings.MEDIA_URL, + 'DIRECTORY': fb_settings.DIRECTORY, + # FileBrowser + 'URL_FILEBROWSER_MEDIA': URL_FILEBROWSER_MEDIA, + 'PATH_FILEBROWSER_MEDIA': PATH_FILEBROWSER_MEDIA, + # TinyMCE + 'URL_TINYMCE': URL_TINYMCE, + 'PATH_TINYMCE': PATH_TINYMCE, + # Extensions/Formats (for FileBrowseField) + 'EXTENSIONS': EXTENSIONS, + 'SELECT_FORMATS': SELECT_FORMATS, + # Versions + 'VERSIONS_BASEDIR': VERSIONS_BASEDIR, + 'VERSIONS': VERSIONS, + 'ADMIN_VERSIONS': ADMIN_VERSIONS, + 'ADMIN_THUMBNAIL': ADMIN_THUMBNAIL, + 'PREVIEW_VERSION': PREVIEW_VERSION, + # FileBrowser Options + 'MAX_UPLOAD_SIZE': MAX_UPLOAD_SIZE, + # Convert Filenames + 'CONVERT_FILENAME': CONVERT_FILENAME, + } return settings_var @@ -253,7 +283,7 @@ def handle_file_upload(path, file): """ Handle File Upload. """ - + file_path = os.path.join(path, file.name) uploadedfile = default_storage.save(file_path, file) return uploadedfile @@ -263,10 +293,10 @@ def get_file_type(filename): """ Get file type as defined in EXTENSIONS. """ - + file_extension = os.path.splitext(filename)[1].lower() file_type = '' - for k,v in EXTENSIONS.items(): + for k, v in EXTENSIONS.items(): for extension in v: if file_extension == extension.lower(): file_type = k @@ -277,10 +307,10 @@ def is_selectable(filename, selecttype): """ Get select type as defined in FORMATS. """ - + file_extension = os.path.splitext(filename)[1].lower() select_types = [] - for k,v in SELECT_FORMATS.items(): + for k, v in SELECT_FORMATS.items(): for extension in v: if file_extension == extension.lower(): select_types.append(k) @@ -292,29 +322,30 @@ def version_generator(value, version_prefix, force=None): Generate Version for an Image. value has to be a serverpath relative to MEDIA_ROOT. """ - - # PIL's Error "Suspension not allowed here" work around: - # s. http://mail.python.org/pipermail/image-sig/1999-August/000816.html - if STRICT_PIL: - from PIL import ImageFile - else: - try: - from PIL import ImageFile - except ImportError: - import ImageFile - ImageFile.MAXBLOCK = IMAGE_MAXBLOCK # default is 64k - + ImageFile.MAXBLOCK = IMAGE_MAXBLOCK # default is 64k + try: im = Image.open(smart_str(os.path.join(fb_settings.MEDIA_ROOT, value))) version_path = get_version_path(value, version_prefix) - absolute_version_path = smart_str(os.path.join(fb_settings.MEDIA_ROOT, version_path)) + absolute_version_path = smart_str( + os.path.join(fb_settings.MEDIA_ROOT, version_path) + ) version_dir = os.path.split(absolute_version_path)[0] if not os.path.isdir(version_dir): os.makedirs(version_dir) os.chmod(version_dir, 0o775) - version = scale_and_crop(im, VERSIONS[version_prefix]['width'], VERSIONS[version_prefix]['height'], VERSIONS[version_prefix]['opts']) + version = scale_and_crop( + im, + VERSIONS[version_prefix]['width'], + VERSIONS[version_prefix]['height'], + VERSIONS[version_prefix]['opts'] + ) try: - version.save(absolute_version_path, quality=90, optimize=(os.path.splitext(version_path)[1].lower() != '.gif')) + version.save( + absolute_version_path, + quality=90, + optimize=(os.path.splitext(version_path)[1].lower() != '.gif') + ) except IOError: version.save(absolute_version_path, quality=90) return version_path @@ -327,55 +358,32 @@ def scale_and_crop(im, width, height, opts): Scale and Crop. """ - x, y = [float(v) for v in im.size] + x, y = [float(v) for v in im.size] if width: xr = float(width) else: - xr = float(x*height/y) + xr = float(x * height / y) if height: yr = float(height) else: - yr = float(y*width/x) + yr = float(y * width / x) if 'crop' in opts: - r = max(xr/x, yr/y) + r = max(xr / x, yr / y) else: - r = min(xr/x, yr/y) + r = min(xr / x, yr / y) if r < 1.0 or (r > 1.0 and 'upscale' in opts): - im = im.resize((int(x*r), int(y*r)), resample=Image.ANTIALIAS) + im = im.resize((int(x * r), int(y * r)), resample=Image.ANTIALIAS) if 'crop' in opts: - x, y = [float(v) for v in im.size] - ex, ey = (x-min(x, xr))/2, (y-min(y, yr))/2 + x, y = [float(v) for v in im.size] + ex, ey = (x - min(x, xr)) / 2, (y - min(y, yr)) / 2 if ex or ey: - im = im.crop((int(ex), int(ey), int(x-ex), int(y-ey))) + im = im.crop((int(ex), int(ey), int(x - ex), int(y - ey))) return im - - # if 'crop' in opts: - # if 'top_left' in opts: - # #draw cropping box from upper left corner of image - # box = (0, 0, int(min(x, xr)), int(min(y, yr))) - # im = im.resize((int(x), int(y)), resample=Image.ANTIALIAS).crop(box) - # elif 'top_right' in opts: - # #draw cropping box from upper right corner of image - # box = (int(x-min(x, xr)), 0, int(x), int(min(y, yr))) - # im = im.resize((int(x), int(y)), resample=Image.ANTIALIAS).crop(box) - # elif 'bottom_left' in opts: - # #draw cropping box from lower left corner of image - # box = (0, int(y-min(y, yr)), int(xr), int(y)) - # im = im.resize((int(x), int(y)), resample=Image.ANTIALIAS).crop(box) - # elif 'bottom_right' in opts: - # #draw cropping box from lower right corner of image - # (int(x-min(x, xr)), int(y-min(y, yr)), int(x), int(y)) - # im = im.resize((int(x), int(y)), resample=Image.ANTIALIAS).crop(box) - # else: - # ex, ey = (x-min(x, xr))/2, (y-min(y, yr))/2 - # if ex or ey: - # box = (int(ex), int(ey), int(x-ex), int(y-ey)) - # im = im.resize((int(x), int(y)), resample=Image.ANTIALIAS).crop(box) - # return im - + + scale_and_crop.valid_options = ('crop', 'upscale') @@ -383,7 +391,7 @@ def convert_filename(value): """ Convert Filename. """ - + if CONVERT_FILENAME: return value.replace(" ", "_").lower() else: diff --git a/filebrowser/management/commands/version_generator.py b/filebrowser/management/commands/version_generator.py index 9473134..0ba11b7 100755 --- a/filebrowser/management/commands/version_generator.py +++ b/filebrowser/management/commands/version_generator.py @@ -1,26 +1,29 @@ -from django.core.management.base import NoArgsCommand +from django.core.management.base import BaseCommand -class Command(NoArgsCommand): +from filebrowser.settings import EXTENSION_LIST, EXCLUDE, VERSIONS, EXTENSIONS +from filebrowser.conf import fb_settings + +import os +import re + + +class Command(BaseCommand): help = "(Re)Generate versions of Images" def handle_noargs(self, **options): - import os, re - from filebrowser.settings import EXTENSION_LIST, EXCLUDE, VERSIONS, EXTENSIONS - from filebrowser.conf import fb_settings - # Precompile regular expressions filter_re = [] for exp in EXCLUDE: - filter_re.append(re.compile(exp)) - for k,v in VERSIONS.items(): - exp = (r'_{0}.({1})').format(k, '|'.join(EXTENSION_LIST)) + filter_re.append(re.compile(exp)) + for k, v in VERSIONS.items(): + exp = r'_{0}.({1})'.format(k, '|'.join(EXTENSION_LIST)) filter_re.append(re.compile(exp)) path = os.path.join(fb_settings.MEDIA_ROOT, fb_settings.DIRECTORY) # walkt throu the filebrowser directory # for all/new files (except file versions itself and excludes) - for dirpath,dirnames,filenames in os.walk(path): + for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: filtered = False # no "hidden" files (stating with ".") @@ -34,12 +37,13 @@ def handle_noargs(self, **options): continue (tmp, extension) = os.path.splitext(filename) if extension in EXTENSIONS["Image"]: - self.createVersions(os.path.join(dirpath, filename)) - - def createVersions(self, path): + self.create_versions(os.path.join(dirpath, filename)) + + @staticmethod + def create_versions(path): print "generating versions for: ", path from filebrowser.settings import VERSIONS from filebrowser.functions import version_generator for version in VERSIONS: - #print " ", version + # print " ", version version_generator(path, version, True) diff --git a/filebrowser/settings.py b/filebrowser/settings.py index 2ea0fc1..a9caa3f 100755 --- a/filebrowser/settings.py +++ b/filebrowser/settings.py @@ -15,7 +15,10 @@ except ImportError: DEFAULT_URL_TINYMCE = getattr(settings, 'ADMIN_MEDIA_PREFIX', os.path.join(settings.STATIC_URL, 'admin/')) + "tinymce/jscripts/tiny_mce/" - DEFAULT_PATH_TINYMCE = os.path.join(settings.MEDIA_ROOT, 'admin/tinymce/jscripts/tiny_mce/') + DEFAULT_PATH_TINYMCE = os.path.join( + settings.MEDIA_ROOT, + 'admin/tinymce/jscripts/tiny_mce/' + ) # Set to True in order to see the FileObject when Browsing. DEBUG = getattr(settings, "FILEBROWSER_DEBUG", False) @@ -26,58 +29,118 @@ # Main FileBrowser Directory. This has to be a directory within MEDIA_ROOT. # Leave empty in order to browse all files under MEDIA_ROOT. -# DO NOT USE A SLASH AT THE BEGINNING, DO NOT FORGET THE TRAILING SLASH AT THE END. +# DO NOT USE A SLASH AT THE BEGINNING, +# DO NOT FORGET THE TRAILING SLASH AT THE END. DIRECTORY = getattr(settings, "FILEBROWSER_DIRECTORY", 'uploads/') # The URL/PATH to your filebrowser media-files. -URL_FILEBROWSER_MEDIA = getattr(settings, "FILEBROWSER_URL_FILEBROWSER_MEDIA", os.path.join(settings.STATIC_URL, 'filebrowser/')) -PATH_FILEBROWSER_MEDIA = getattr(settings, "FILEBROWSER_PATH_FILEBROWSER_MEDIA", os.path.join(settings.STATIC_ROOT, 'filebrowser/')) +URL_FILEBROWSER_MEDIA = getattr( + settings, + "FILEBROWSER_URL_FILEBROWSER_MEDIA", + os.path.join(settings.STATIC_URL, 'filebrowser/') +) +PATH_FILEBROWSER_MEDIA = getattr( + settings, + "FILEBROWSER_PATH_FILEBROWSER_MEDIA", + os.path.join(settings.STATIC_ROOT, 'filebrowser/') +) # The URL/PATH to your TinyMCE Installation. URL_TINYMCE = getattr(settings, "FILEBROWSER_URL_TINYMCE", DEFAULT_URL_TINYMCE) -PATH_TINYMCE = getattr(settings, "FILEBROWSER_PATH_TINYMCE", DEFAULT_PATH_TINYMCE) +PATH_TINYMCE = getattr( + settings, + "FILEBROWSER_PATH_TINYMCE", + DEFAULT_PATH_TINYMCE +) # Allowed Extensions for File Upload. Lower case is important. # Please be aware that there are Icons for the default extension settings. # Therefore, if you add a category (e.g. "Misc"), you won't get an icon. EXTENSIONS = getattr(settings, "FILEBROWSER_EXTENSIONS", { 'Folder': [''], - 'Image': ['.jpg','.jpeg','.gif','.png','.tif','.tiff'], - 'Video': ['.mov','.wmv','.mpeg','.mpg','.avi','.rm'], - 'Document': ['.pdf','.doc','.rtf','.txt','.xls','.csv'], - 'Audio': ['.mp3','.mp4','.wav','.aiff','.midi','.m4p'], - 'Code': ['.html','.py','.js','.css'] + 'Image': ['.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff'], + 'Video': ['.mov', '.wmv', '.mpeg', '.mpg', '.avi', '.rm'], + 'Document': ['.pdf', '.doc', '.rtf', '.txt', '.xls', '.csv'], + 'Audio': ['.mp3', '.mp4', '.wav', '.aiff', '.midi', '.m4p'], + 'Code': ['.html', '.py', '.js', '.css'] }) # Define different formats for allowed selections. # This has to be a subset of EXTENSIONS. -SELECT_FORMATS = getattr(settings, "FILEBROWSER_SELECT_FORMATS", { - 'File': ['Folder','Document',], - 'Image': ['Image'], - 'Media': ['Video','Sound'], - 'Document': ['Document'], - # for TinyMCE we can also define lower-case items - 'image': ['Image'], - 'file': ['Folder','Image','Document',], - 'media': ['Video','Sound'], -}) +SELECT_FORMATS = getattr( + settings, + "FILEBROWSER_SELECT_FORMATS", + { + 'File': ['Folder', 'Document'], + 'Image': ['Image'], + 'Media': ['Video', 'Sound'], + 'Document': ['Document'], + # for TinyMCE we can also define lower-case items + 'image': ['Image'], + 'file': ['Folder', 'Image', 'Document'], + 'media': ['Video', 'Sound'], + } +) # Directory to Save Image Versions (and Thumbnails). Relative to MEDIA_ROOT. # If no directory is given, versions are stored within the Image directory. # VERSION URL: VERSIONS_BASEDIR/original_path/originalfilename_versionsuffix.extension VERSIONS_BASEDIR = getattr(settings, 'FILEBROWSER_VERSIONS_BASEDIR', '') # Versions Format. Available Attributes: verbose_name, width, height, opts -VERSIONS = getattr(settings, "FILEBROWSER_VERSIONS", { - 'fb_thumb': {'verbose_name': 'Admin Thumbnail', 'width': 60, 'height': 60, 'opts': 'crop upscale'}, - 'thumbnail': {'verbose_name': 'Thumbnail (140px)', 'width': 140, 'height': '', 'opts': ''}, - 'small': {'verbose_name': 'Small (300px)', 'width': 300, 'height': '', 'opts': ''}, - 'medium': {'verbose_name': 'Medium (460px)', 'width': 460, 'height': '', 'opts': ''}, - 'big': {'verbose_name': 'Big (620px)', 'width': 620, 'height': '', 'opts': ''}, - 'cropped': {'verbose_name': 'Cropped (60x60px)', 'width': 60, 'height': 60, 'opts': 'crop'}, - 'croppedthumbnail': {'verbose_name': 'Cropped Thumbnail (140x140px)', 'width': 140, 'height': 140, 'opts': 'crop'}, -}) +VERSIONS = getattr( + settings, + "FILEBROWSER_VERSIONS", + { + 'fb_thumb': { + 'verbose_name': 'Admin Thumbnail', + 'width': 60, + 'height': 60, + 'opts': 'crop upscale' + }, + 'thumbnail': { + 'verbose_name': 'Thumbnail (140px)', + 'width': 140, + 'height': '', + 'opts': '' + }, + 'small': { + 'verbose_name': 'Small (300px)', + 'width': 300, + 'height': '', + 'opts': '' + }, + 'medium': { + 'verbose_name': 'Medium (460px)', + 'width': 460, + 'height': '', + 'opts': '' + }, + 'big': { + 'verbose_name': 'Big (620px)', + 'width': 620, + 'height': '', + 'opts': '' + }, + 'cropped': { + 'verbose_name': 'Cropped (60x60px)', + 'width': 60, + 'height': 60, + 'opts': 'crop' + }, + 'croppedthumbnail': { + 'verbose_name': 'Cropped Thumbnail (140x140px)', + 'width': 140, + 'height': 140, + 'opts': 'crop' + }, + } +) # Versions available within the Admin-Interface. -ADMIN_VERSIONS = getattr(settings, 'FILEBROWSER_ADMIN_VERSIONS', ['thumbnail','small', 'medium','big']) +ADMIN_VERSIONS = getattr( + settings, + 'FILEBROWSER_ADMIN_VERSIONS', + ['thumbnail', 'small', 'medium', 'big'] +) # Which Version should be used as Admin-thumbnail. ADMIN_THUMBNAIL = getattr(settings, 'FILEBROWSER_ADMIN_THUMBNAIL', 'fb_thumb') # Preview Version @@ -98,7 +161,11 @@ EXTENSION_LIST = [] for exts in EXTENSIONS.values(): EXTENSION_LIST += exts -EXCLUDE = getattr(settings, 'FILEBROWSER_EXCLUDE', (r'_(%(exts)s)_.*_q\d{1,3}\.(%(exts)s)' % {'exts': ('|'.join(EXTENSION_LIST))},)) +EXCLUDE = getattr( + settings, + 'FILEBROWSER_EXCLUDE', + (r'_(%(exts)s)_.*_q\d{1,3}\.(%(exts)s)' % {'exts': ('|'.join(EXTENSION_LIST))},) +) # Max. Upload Size in Bytes. MAX_UPLOAD_SIZE = getattr(settings, "FILEBROWSER_MAX_UPLOAD_SIZE", 10485760) # Convert Filename (replace spaces and convert to lowercase) @@ -109,14 +176,26 @@ LIST_PER_PAGE = getattr(settings, "FILEBROWSER_LIST_PER_PAGE", 50) # Default Sorting # Options: date, filesize, filename_lower, filetype_checked -DEFAULT_SORTING_BY = getattr(settings, "FILEBROWSER_DEFAULT_SORTING_BY", "date") +DEFAULT_SORTING_BY = getattr( + settings, + "FILEBROWSER_DEFAULT_SORTING_BY", + "date" +) # Sorting Order: asc, desc -DEFAULT_SORTING_ORDER = getattr(settings, "FILEBROWSER_DEFAULT_SORTING_ORDER", "desc") +DEFAULT_SORTING_ORDER = getattr( + settings, + "FILEBROWSER_DEFAULT_SORTING_ORDER", + "desc" +) # regex to clean dir names before creation (no use!) # SECURITY re for test name new dirs or file name # FILE_AND_DIRS_NAME_REGEXP -FOLDER_REGEX = getattr(settings, "FILEBROWSER_FOLDER_REGEX", r'^[ \w-][ \w.-]*$') +FOLDER_REGEX = getattr( + settings, + "FILEBROWSER_FOLDER_REGEX", + r'^[ \w-][ \w.-]*$' +) # EXTRA TRANSLATION STRINGS # The following strings are not availabe within views or templates @@ -127,7 +206,5 @@ _('Audio') _('Code') -#Suit Template +# Suit Template SUIT_TEMPLATE = getattr(settings, "FILEBROWSER_SUIT_TEMPLATE", None) - - diff --git a/filebrowser/templatetags/fb_csrf.py b/filebrowser/templatetags/fb_csrf.py index 9ecaa4b..be5bfda 100755 --- a/filebrowser/templatetags/fb_csrf.py +++ b/filebrowser/templatetags/fb_csrf.py @@ -13,18 +13,25 @@ def render(self, context): csrf_token = context.get('csrf_token', None) if csrf_token: if csrf_token == 'NOTPROVIDED': - return mark_safe(u"") + return mark_safe("") else: - return mark_safe(u"
".format(csrf_token)) + return mark_safe("
" + "" + "
".format(csrf_token)) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning from django.conf import settings if settings.DEBUG: import warnings - warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") + warnings.warn("A {% csrf_token %} was used in a template, " + "but the context did not provide the value. " + "This is usually caused by not " + "using RequestContext.") return u'' + def fb_csrf_token(parser, token): return CsrfTokenNode() register.tag(fb_csrf_token) diff --git a/filebrowser/templatetags/fb_pagination.py b/filebrowser/templatetags/fb_pagination.py index 1385897..a7eceaa 100755 --- a/filebrowser/templatetags/fb_pagination.py +++ b/filebrowser/templatetags/fb_pagination.py @@ -7,6 +7,7 @@ DOT = '.' + def _template(): if fb_settings.SUIT_TEMPLATE: path = 'suit/' @@ -15,7 +16,10 @@ def _template(): return path -@register.inclusion_tag(_template() + 'include/paginator.html', takes_context=True) + +@register.inclusion_tag( + _template() + 'include/paginator.html', takes_context=True +) def pagination(context): page_num = context['page'].number-1 paginator = context['p'] @@ -23,8 +27,8 @@ def pagination(context): if not paginator.num_pages or paginator.num_pages == 1: page_range = [] else: - ON_EACH_SIDE = 3 - ON_ENDS = 2 + on_each_side = 3 + on_ends = 2 # If there are 10 or fewer pages, display links to every page. # Otherwise, do some fancy @@ -35,16 +39,22 @@ def pagination(context): # links at either end of the list of pages, and there are always # ON_EACH_SIDE links at either end of the "current page" link. page_range = [] - if page_num > (ON_EACH_SIDE + ON_ENDS): - page_range.extend(range(0, ON_EACH_SIDE - 1)) + if page_num > (on_each_side + on_ends): + page_range.extend(range(0, on_each_side - 1)) page_range.append(DOT) - page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1)) + page_range.extend( + range(page_num - on_each_side, page_num + 1) + ) else: page_range.extend(range(0, page_num + 1)) - if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1): - page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1)) + if page_num < (paginator.num_pages - on_each_side - on_ends - 1): + page_range.extend( + range(page_num + 1, page_num + on_each_side + 1) + ) page_range.append(DOT) - page_range.extend(range(paginator.num_pages - ON_ENDS, paginator.num_pages)) + page_range.extend( + range(paginator.num_pages - on_ends, paginator.num_pages) + ) else: page_range.extend(range(page_num + 1, paginator.num_pages)) @@ -54,5 +64,3 @@ def pagination(context): 'results_var': context['results_var'], 'query': context['query'], } - - diff --git a/filebrowser/templatetags/fb_tags.py b/filebrowser/templatetags/fb_tags.py index a273128..1fcdb21 100755 --- a/filebrowser/templatetags/fb_tags.py +++ b/filebrowser/templatetags/fb_tags.py @@ -10,9 +10,9 @@ register = template.Library() - - -@register.inclusion_tag('filebrowser/include/_response.html', takes_context=True) +@register.inclusion_tag( + 'filebrowser/include/_response.html', takes_context=True +) def query_string(context, add=None, remove=None): """ Allows the addition and removal of query string parameters. @@ -30,7 +30,7 @@ def query_string(context, add=None, remove=None): remove = string_to_list(remove) params = context['query'].copy() response = get_query_string(params, add, remove) - return {'response': response } + return {'response': response} def query_helper(query, add=None, remove=None): @@ -49,8 +49,10 @@ def get_query_string(p, new_params=None, remove=None): Add and remove query parameters. From `django.contrib.admin`. """ - if new_params is None: new_params = {} - if remove is None: remove = [] + if new_params is None: + new_params = {} + if remove is None: + remove = [] for r in remove: try: p.pop(r) @@ -80,7 +82,8 @@ def string_to_dict(string): string += ',' for arg in string.split(','): arg = arg.strip() - if arg == '': continue + if arg == '': + continue kw, val = arg.split('=', 1) kwargs[kw] = val return kwargs @@ -100,7 +103,8 @@ def string_to_list(string): string += ',' for arg in string.split(','): arg = arg.strip() - if arg == '': continue + if arg == '': + continue args.append(arg) return args @@ -134,22 +138,9 @@ def selectable(parser, token): try: tag, filetype, format = token.split_contents() except: - raise template.TemplateSyntaxError("{0} tag requires 2 arguments".format(token.contents.split()[0])) + raise template.TemplateSyntaxError( + "{0} tag requires 2 arguments".format(token.contents.split()[0]) + ) return SelectableNode(filetype, format) - register.tag(selectable) - -@register.simple_tag -def custom_admin_media_prefix(): - import django - if "1.4" in django.get_version(): - from django.conf import settings - return "".join([settings.STATIC_URL,"admin/"]) - else: - try: - from django.contrib.admin.templatetags import admin_media_prefix - except ImportError: - from django.contrib.admin.templatetags.adminmedia import admin_media_prefix - return admin_media_prefix() - diff --git a/filebrowser/templatetags/fb_versions.py b/filebrowser/templatetags/fb_versions.py index cfebe1f..bdeb440 100755 --- a/filebrowser/templatetags/fb_versions.py +++ b/filebrowser/templatetags/fb_versions.py @@ -1,18 +1,21 @@ # coding: utf-8 # imports -import os, re -from time import gmtime +import os +import re # django imports -from django.template import Library, Node, Variable, VariableDoesNotExist, TemplateSyntaxError -from django.conf import settings +from django.template import ( + Library, Node, Variable, VariableDoesNotExist, TemplateSyntaxError +) from django.utils.encoding import smart_str # filebrowser imports from filebrowser.settings import VERSIONS from filebrowser.conf import fb_settings -from filebrowser.functions import url_to_path, path_to_url, get_version_path, version_generator +from filebrowser.functions import ( + url_to_path, path_to_url, get_version_path, version_generator +) from filebrowser.base import FileObject register = Library() @@ -21,7 +24,8 @@ class VersionNode(Node): def __init__(self, src, version_prefix): self.src = Variable(src) - if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")): + if (version_prefix[0] == version_prefix[-1] and + version_prefix[0] in ('"', "'")): self.version_prefix = version_prefix[1:-1] else: self.version_prefix = None @@ -40,13 +44,19 @@ def render(self, context): except VariableDoesNotExist: return None try: - version_path = get_version_path(url_to_path(source), version_prefix) + version_path = get_version_path( + url_to_path(source), version_prefix + ) if not os.path.isfile(smart_str(os.path.join(fb_settings.MEDIA_ROOT, version_path))): # create version - version_path = version_generator(url_to_path(source), version_prefix) + version_path = version_generator( + url_to_path(source), version_prefix + ) elif os.path.getmtime(smart_str(os.path.join(fb_settings.MEDIA_ROOT, url_to_path(source)))) > os.path.getmtime(smart_str(os.path.join(fb_settings.MEDIA_ROOT, version_path))): # recreate version if original image was updated - version_path = version_generator(url_to_path(source), version_prefix, force=True) + version_path = version_generator( + url_to_path(source), version_prefix, force=True + ) return path_to_url(version_path) except: return "" @@ -54,21 +64,30 @@ def render(self, context): def version(parser, token): """ - Displaying a version of an existing Image according to the predefined VERSIONS settings (see filebrowser settings). + Displaying a version of an existing Image according to the predefined + VERSIONS settings (see filebrowser settings). {% version field_name version_prefix %} Use {% version my_image 'medium' %} in order to display the medium-size version of an Image stored in a field name my_image. - version_prefix can be a string or a variable. if version_prefix is a string, use quotes. + version_prefix can be a string or a variable. + if version_prefix is a string, use quotes. """ try: tag, src, version_prefix = token.split_contents() except: - raise TemplateSyntaxError("{0} tag requires 2 arguments".format(token.contents.split()[0])) - if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")) and version_prefix.lower()[1:-1] not in VERSIONS: - raise TemplateSyntaxError("{0} tag received bad version_prefix {1}".format(tag, version_prefix)) + raise TemplateSyntaxError( + "{0} tag requires 2 arguments".format(token.contents.split()[0]) + ) + if (version_prefix[0] == version_prefix[-1] and + version_prefix[0] in ('"', "'")) and \ + version_prefix.lower()[1:-1] not in VERSIONS: + raise TemplateSyntaxError( + "{0} tag received bad version_prefix {1}".format(tag, + version_prefix) + ) return VersionNode(src, version_prefix) @@ -76,7 +95,8 @@ class VersionObjectNode(Node): def __init__(self, src, version_prefix, var_name): self.var_name = var_name self.src = Variable(src) - if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")): + if (version_prefix[0] == version_prefix[-1] and + version_prefix[0] in ('"', "'")): self.version_prefix = version_prefix[1:-1] else: self.version_prefix = None @@ -95,13 +115,19 @@ def render(self, context): except VariableDoesNotExist: return None try: - version_path = get_version_path(url_to_path(source), version_prefix) + version_path = get_version_path( + url_to_path(source), version_prefix + ) if not os.path.isfile(smart_str(os.path.join(fb_settings.MEDIA_ROOT, version_path))): # create version - version_path = version_generator(url_to_path(source), version_prefix) + version_path = version_generator( + url_to_path(source), version_prefix + ) elif os.path.getmtime(smart_str(os.path.join(fb_settings.MEDIA_ROOT, url_to_path(source)))) > os.path.getmtime(smart_str(os.path.join(fb_settings.MEDIA_ROOT, version_path))): # recreate version if original image was updated - version_path = version_generator(url_to_path(source), version_prefix, force=True) + version_path = version_generator( + url_to_path(source), version_prefix, force=True + ) context[self.var_name] = FileObject(version_path) except: context[self.var_name] = "" @@ -118,26 +144,35 @@ def version_object(parser, token): Use {% version_object my_image 'medium' as var %} in order to use 'var' as your context variable. - version_prefix can be a string or a variable. if version_prefix is a string, use quotes. + version_prefix can be a string or a variable. + if version_prefix is a string, use quotes. """ try: - #tag, src, version_prefix = token.split_contents() + # tag, src, version_prefix = token.split_contents() tag, arg = token.contents.split(None, 1) except: - raise TemplateSyntaxError("{0} tag requires arguments".format(token.contents.split()[0])) + raise TemplateSyntaxError( + "{0} tag requires arguments".format(token.contents.split()[0]) + ) m = re.search(r'(.*?) (.*?) as (\w+)', arg) if not m: raise TemplateSyntaxError("{0} tag had invalid arguments".format(tag)) src, version_prefix, var_name = m.groups() - if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")) and version_prefix.lower()[1:-1] not in VERSIONS: - raise TemplateSyntaxError("{0} tag received bad version_prefix {1}".format(tag, version_prefix)) + if (version_prefix[0] == version_prefix[-1] and + version_prefix[0] in ('"', "'")) and \ + version_prefix.lower()[1:-1] not in VERSIONS: + raise TemplateSyntaxError( + "{0} tag received bad version_prefix {1}".format(tag, + version_prefix) + ) return VersionObjectNode(src, version_prefix, var_name) class VersionSettingNode(Node): def __init__(self, version_prefix): - if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")): + if (version_prefix[0] == version_prefix[-1] and + version_prefix[0] in ('"', "'")): self.version_prefix = version_prefix[1:-1] else: self.version_prefix = None @@ -163,14 +198,19 @@ def version_setting(parser, token): try: tag, version_prefix = token.split_contents() except: - raise TemplateSyntaxError("{0} tag requires 1 argument".format(token.contents.split()[0])) - if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")) and version_prefix.lower()[1:-1] not in VERSIONS: - raise TemplateSyntaxError("{0} tag received bad version_prefix {1}".format(tag, version_prefix)) + raise TemplateSyntaxError( + "{0} tag requires 1 argument".format(token.contents.split()[0]) + ) + if (version_prefix[0] == version_prefix[-1] and + version_prefix[0] in ('"', "'")) and \ + version_prefix.lower()[1:-1] not in VERSIONS: + raise TemplateSyntaxError( + "{0} tag received bad version_prefix {1}".format(tag, + version_prefix) + ) return VersionSettingNode(version_prefix) register.tag(version) register.tag(version_object) register.tag(version_setting) - - diff --git a/filebrowser/views.py b/filebrowser/views.py index 5192c6c..16ef07e 100755 --- a/filebrowser/views.py +++ b/filebrowser/views.py @@ -1,12 +1,11 @@ # coding: utf-8 # general imports -import os, re -from time import gmtime, strftime +import os +import re # django imports from django.shortcuts import render, HttpResponse -from django.template import RequestContext as Context from django.http import HttpResponseRedirect, Http404 from django.contrib.admin.views.decorators import staff_member_required from django.views.decorators.cache import never_cache @@ -23,11 +22,16 @@ from django.contrib import messages # filebrowser imports -from filebrowser.settings import * +from filebrowser.settings import ( + EXCLUDE, VERSIONS, EXTENSION_LIST, EXTENSIONS, SELECT_FORMATS, + DEFAULT_SORTING_BY, DEFAULT_SORTING_ORDER, LIST_PER_PAGE +) from filebrowser.conf import fb_settings -from filebrowser.functions import path_to_url, sort_by_attr, get_path, \ - get_file, get_version_path, get_breadcrumbs, get_filterdate, \ - get_settings_var, handle_file_upload, convert_filename +from filebrowser.functions import ( + path_to_url, sort_by_attr, get_path, get_file, get_version_path, + get_breadcrumbs, get_filterdate, get_settings_var, handle_file_upload, + convert_filename +) from filebrowser.templatetags.fb_tags import query_helper from filebrowser.base import FileObject from filebrowser.decorators import flash_login_required @@ -37,7 +41,7 @@ for exp in EXCLUDE: filter_re.append(re.compile(exp)) for k, v in VERSIONS.items(): - exp = (r'_{0}.({1})').format(k, '|'.join(EXTENSION_LIST)) + exp = r'_{0}.({1})'.format(k, '|'.join(EXTENSION_LIST)) filter_re.append(re.compile(exp)) @@ -84,7 +88,8 @@ def browse(request): msg = _('The requested Folder does not exist.') messages.warning(request, message=msg) if directory is None: - # The DIRECTORY does not exist, raise an error to prevent eternal redirecting. + # The DIRECTORY does not exist, + # raise an error to prevent eternal redirecting. raise ImproperlyConfigured( _("Error finding Upload-Folder. Maybe it does not exist?")) redirect_url = reverse("fb_browse") + query_helper(query, "", "dir") @@ -116,9 +121,8 @@ def browse(request): # FILTER / SEARCH append = False - if fileobject.filetype == request.GET.get('filter_type', - fileobject.filetype) and get_filterdate( - request.GET.get('filter_date', ''), fileobject.date): + if fileobject.filetype == request.GET.get('filter_type', fileobject.filetype) and \ + get_filterdate(request.GET.get('filter_date', ''), fileobject.date): append = True if request.GET.get('q') and not re.compile( request.GET.get('q').lower(), re.M).search(file.lower()): @@ -135,8 +139,8 @@ def browse(request): results_var['delete_total'] += 1 elif fileobject.filetype == 'Folder' and fileobject.is_empty: results_var['delete_total'] += 1 - if _type and _type in SELECT_FORMATS and fileobject.filetype in \ - SELECT_FORMATS[_type]: + if _type and _type in SELECT_FORMATS and \ + fileobject.filetype in SELECT_FORMATS[_type]: results_var['select_total'] += 1 elif not _type: results_var['select_total'] += 1 @@ -162,8 +166,8 @@ def browse(request): p = Paginator(files, LIST_PER_PAGE) try: - page_nr = request.GET.get('p', '1') - except: + page_nr = int(request.GET.get('p', '1')) + except ValueError: page_nr = 1 try: page = p.page(page_nr) @@ -221,10 +225,10 @@ def mkdir(request): server_path = _check_access(request, path, _new_dir_name) try: # PRE CREATE SIGNAL - filebrowser_pre_createdir.send(sender=request, path=path, + filebrowser_pre_createdir.send(sender=request, + path=path, dirname=_new_dir_name) # CREATE FOLDER - print(server_path) os.mkdir(server_path) os.chmod(server_path, 0o775) # POST CREATE SIGNAL @@ -234,7 +238,8 @@ def mkdir(request): msg = _('The Folder {0} was successfully created.').format( _new_dir_name) messages.success(request, message=msg) - # on redirect, sort by date desc to see the new directory on top of the list + # on redirect, sort by date desc to see + # the new directory on top of the list # remove filter in order to actually _see_ the new folder # remove pagination redirect_url = reverse("fb_browse") + query_helper(query, @@ -243,10 +248,10 @@ def mkdir(request): return HttpResponseRedirect(redirect_url) except OSError as e: if e.errno == 13: - form.errors['dir_name'] = forms.util.ErrorList( + form.errors['dir_name'] = forms.utils.ErrorList( [_('Permission denied.')]) else: - form.errors['dir_name'] = forms.util.ErrorList( + form.errors['dir_name'] = forms.utils.ErrorList( [_('Error creating folder.')]) else: form = MakeDirForm(abs_path) @@ -270,8 +275,6 @@ def upload(request): Multipe File Upload. """ - from django.http import parse_cookie - # QUERY / PATH CHECK query = request.GET path = get_path(query.get('dir', '')) @@ -316,16 +319,17 @@ def _check_file(request): fb_uploadurl_re = re.compile(r'^.*({0})'.format(reverse("fb_upload"))) folder = fb_uploadurl_re.sub('', folder) - fileArray = {} + file_array = {} if request.method == 'POST': for k, v in request.POST.items(): if k != "folder": v = convert_filename(v) if os.path.isfile( smart_str(_check_access(request, folder, v))): - fileArray[k] = v + file_array[k] = v - return HttpResponse(json.dumps(fileArray)) + # TODO: change and test with JsonResponse + return HttpResponse(json.dumps(file_array)) # upload signals @@ -542,7 +546,7 @@ def rename(request): "filename") return HttpResponseRedirect(redirect_url) except OSError as e: - form.errors['name'] = forms.util.ErrorList([_('Error.')]) + form.errors['name'] = forms.utils.ErrorList([_('Error.')]) else: form = RenameForm(abs_path, file_extension) From 54155c273421145aa152e804ebedf529bb5f0b47 Mon Sep 17 00:00:00 2001 From: Slava Date: Sun, 30 Oct 2016 17:51:08 +0200 Subject: [PATCH 2/9] tests --- filebrowser/fields.py | 20 ++--- filebrowser/forms.py | 25 +++---- .../templates/filebrowser/makedir.html | 5 +- filebrowser/templates/filebrowser/rename.html | 33 +++++---- filebrowser/templates/suit/makedir.html | 4 +- filebrowser/templates/suit/rename.html | 6 +- filebrowser/templatetags/fb_csrf.py | 37 ---------- filebrowser/templatetags/fb_pagination.py | 2 +- filebrowser/tests/__init__.py | 0 filebrowser/tests/base.py | 42 +++++++++++ filebrowser/tests/test_fields.py | 30 ++++++++ filebrowser/tests/test_forms.py | 62 ++++++++++++++++ filebrowser/tests/test_templatetags.py | 73 +++++++++++++++++++ filebrowser/tests/test_views.py | 46 ++++++++++++ filebrowser/views.py | 39 ++++------ 15 files changed, 317 insertions(+), 107 deletions(-) delete mode 100755 filebrowser/templatetags/fb_csrf.py create mode 100644 filebrowser/tests/__init__.py create mode 100644 filebrowser/tests/base.py create mode 100644 filebrowser/tests/test_fields.py create mode 100644 filebrowser/tests/test_forms.py create mode 100644 filebrowser/tests/test_templatetags.py create mode 100644 filebrowser/tests/test_views.py diff --git a/filebrowser/fields.py b/filebrowser/fields.py index cf0fdcf..c179f75 100755 --- a/filebrowser/fields.py +++ b/filebrowser/fields.py @@ -38,10 +38,7 @@ def __init__(self, attrs=None): self.directory = attrs.get('directory', '') self.extensions = attrs.get('extensions', '') self.format = attrs.get('format', '') - if attrs is not None: - self.attrs = attrs.copy() - else: - self.attrs = {} + super(FileBrowseWidget, self).__init__(attrs) def render(self, name, value, attrs=None): if value is None: @@ -65,8 +62,6 @@ def render(self, name, value, attrs=None): class FileBrowseFormField(forms.CharField): - widget = FileBrowseWidget - default_error_messages = { 'extension': _( u'Extension %(ext)s is not allowed. Only %(allowed)s is allowed.'), @@ -78,9 +73,15 @@ def __init__(self, max_length=None, min_length=None, self.max_length, self.min_length = max_length, min_length self.directory = directory self.extensions = extensions - if format: - self.format = format or '' - self.extensions = extensions or EXTENSIONS.get(format) + self.format = format or '' + self.extensions = extensions or EXTENSIONS.get(format) + + attrs = { + "directory": self.directory, + "extensions": self.extensions, + "format": self.format + } + self.widget = FileBrowseWidget(attrs) super(FileBrowseFormField, self).__init__(*args, **kwargs) def clean(self, value): @@ -101,6 +102,7 @@ def __init__(self, *args, **kwargs): self.directory = kwargs.pop('directory', '') self.extensions = kwargs.pop('extensions', '') self.format = kwargs.pop('format', '') + kwargs['max_length'] = kwargs.get('max_length', 200) super(FileBrowseField, self).__init__(*args, **kwargs) def from_db_value(self, value, expression, connection, context): diff --git a/filebrowser/forms.py b/filebrowser/forms.py index f572e1e..8284b3f 100755 --- a/filebrowser/forms.py +++ b/filebrowser/forms.py @@ -34,17 +34,16 @@ def __init__(self, path, *args, **kwargs): super(MakeDirForm, self).__init__(*args, **kwargs) def clean_dir_name(self): - if self.cleaned_data['dir_name']: - # only letters, numbers, underscores, - # spaces and hyphens are allowed. - if not alnum_name_re.search(self.cleaned_data['dir_name']): - raise forms.ValidationError( - _('Only letters, numbers, underscores, ' - 'spaces and hyphens are allowed.') - ) - # Folder must not already exist. - if os.path.isdir(os.path.join(self.path, convert_filename(self.cleaned_data['dir_name']))): - raise forms.ValidationError(_('The Folder already exists.')) + # only letters, numbers, underscores, + # spaces and hyphens are allowed. + if not alnum_name_re.search(self.cleaned_data['dir_name']): + raise forms.ValidationError( + _('Only letters, numbers, underscores, ' + 'spaces and hyphens are allowed.') + ) + # Folder must not already exist. + if os.path.isdir(os.path.join(self.path, convert_filename(self.cleaned_data['dir_name']))): + raise forms.ValidationError(_('The Folder already exists.')) return convert_filename(self.cleaned_data['dir_name']) @@ -78,7 +77,7 @@ def clean_name(self): ) # folder/file must not already exist. if os.path.isdir(os.path.join(self.path, convert_filename(self.cleaned_data['name']))): - raise forms.ValidationError(_(u'The Folder already exists.')) + raise forms.ValidationError(_('The Folder already exists.')) elif os.path.isfile(os.path.join(self.path, convert_filename(self.cleaned_data['name']) + self.file_extension)): - raise forms.ValidationError(_(u'The File already exists.')) + raise forms.ValidationError(_('The File already exists.')) return convert_filename(self.cleaned_data['name']) diff --git a/filebrowser/templates/filebrowser/makedir.html b/filebrowser/templates/filebrowser/makedir.html index 6f56c99..8c65e15 100755 --- a/filebrowser/templates/filebrowser/makedir.html +++ b/filebrowser/templates/filebrowser/makedir.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} -{% load i18n admin_static fb_tags fb_csrf %} +{% load i18n admin_static fb_tags %} {% block extrastyle %} @@ -20,7 +20,8 @@ {% block content %}
-
{% fb_csrf_token %} + + {% csrf_token %} {% if form.errors %}

{% trans 'Please correct the following errors.' %}

{% endif %}
diff --git a/filebrowser/templates/filebrowser/rename.html b/filebrowser/templates/filebrowser/rename.html index e80636b..5d3c7ab 100755 --- a/filebrowser/templates/filebrowser/rename.html +++ b/filebrowser/templates/filebrowser/rename.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} -{% load i18n admin_static fb_tags fb_csrf %} +{% load i18n admin_static fb_tags %} {% block extrastyle %} @@ -20,22 +20,23 @@ {% block content %}
- {% fb_csrf_token %} -
- {% if form.errors %}

{% trans 'Please correct the following errors.' %}

{% endif %} -
-
- {% if form.name.errors %}
    {{ form.name.errors }}
{% endif %} - - {{ form.name }} - {% if file_extension %}{{ file_extension }}{% endif %} - {% if form.name.help_text %}

{{ form.name.help_text|safe }}

{% endif %} + + {% csrf_token %} +
+ {% if form.errors %}

{% trans 'Please correct the following errors.' %}

{% endif %} +
+
+ {% if form.name.errors %}
    {{ form.name.errors }}
{% endif %} + + {{ form.name }} + {% if file_extension %}{{ file_extension }}{% endif %} + {% if form.name.help_text %}

{{ form.name.help_text|safe }}

{% endif %} +
+
+
+ +
-
-
- -
-
{% endblock %} diff --git a/filebrowser/templates/suit/makedir.html b/filebrowser/templates/suit/makedir.html index 7a289eb..d66098c 100644 --- a/filebrowser/templates/suit/makedir.html +++ b/filebrowser/templates/suit/makedir.html @@ -1,6 +1,6 @@ {% extends "admin/base_site.html" %} {% load i18n admin_static admin_list admin_urls %} -{% load fb_tags fb_csrf %} +{% load fb_tags %} {% block extrastyle %} {{ block.super }} @@ -14,7 +14,7 @@ {% block content %}
- {% fb_csrf_token %} + {% csrf_token %}
diff --git a/filebrowser/templates/suit/rename.html b/filebrowser/templates/suit/rename.html index 5739829..a8f4e43 100644 --- a/filebrowser/templates/suit/rename.html +++ b/filebrowser/templates/suit/rename.html @@ -1,6 +1,6 @@ {% extends "admin/base_site.html" %} {% load i18n admin_static admin_list admin_urls %} -{% load fb_tags fb_csrf %} +{% load fb_tags %} {% block extrastyle %} {{ block.super }} @@ -18,7 +18,7 @@ {% block content %}
- {% fb_csrf_token %} + {% csrf_token %}
@@ -36,7 +36,7 @@
- +
{{ form.name }}{% if file_extension %}{{ file_extension }}{% endif %} diff --git a/filebrowser/templatetags/fb_csrf.py b/filebrowser/templatetags/fb_csrf.py deleted file mode 100755 index be5bfda..0000000 --- a/filebrowser/templatetags/fb_csrf.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding: utf-8 - -# django imports -from django.template import Node -from django.template import Library -from django.utils.safestring import mark_safe - -register = Library() - - -class CsrfTokenNode(Node): - def render(self, context): - csrf_token = context.get('csrf_token', None) - if csrf_token: - if csrf_token == 'NOTPROVIDED': - return mark_safe("") - else: - return mark_safe("
" - "" - "
".format(csrf_token)) - else: - # It's very probable that the token is missing because of - # misconfiguration, so we raise a warning - from django.conf import settings - if settings.DEBUG: - import warnings - warnings.warn("A {% csrf_token %} was used in a template, " - "but the context did not provide the value. " - "This is usually caused by not " - "using RequestContext.") - return u'' - - -def fb_csrf_token(parser, token): - return CsrfTokenNode() -register.tag(fb_csrf_token) diff --git a/filebrowser/templatetags/fb_pagination.py b/filebrowser/templatetags/fb_pagination.py index a7eceaa..c625cd2 100755 --- a/filebrowser/templatetags/fb_pagination.py +++ b/filebrowser/templatetags/fb_pagination.py @@ -21,7 +21,7 @@ def _template(): _template() + 'include/paginator.html', takes_context=True ) def pagination(context): - page_num = context['page'].number-1 + page_num = context['page'].number - 1 paginator = context['p'] if not paginator.num_pages or paginator.num_pages == 1: diff --git a/filebrowser/tests/__init__.py b/filebrowser/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filebrowser/tests/base.py b/filebrowser/tests/base.py new file mode 100644 index 0000000..8f49e0f --- /dev/null +++ b/filebrowser/tests/base.py @@ -0,0 +1,42 @@ +from django.test import TestCase +from django.contrib.auth import get_user_model + +from filebrowser.conf import fb_settings + +from PIL import Image, ImageDraw +import os + + +class BaseTestCase(TestCase): + """ + Base class with user creation, etc + """ + def setUp(self): + user_model = get_user_model() + self.user = user_model.objects.create_superuser('admin', + 'admin@example.com', + 'admin') + self.client.login(username='admin', password='admin') + + self.img = self.create_image() + self.image_name = 'test_file.jpg' + self.filename = os.path.join( + fb_settings.MEDIA_ROOT, fb_settings.DIRECTORY, self.image_name + ) + self.img.save(self.filename, 'JPEG') + + self.working_dir = os.path.join(fb_settings.MEDIA_ROOT, + fb_settings.DIRECTORY) + + + def tearDown(self): + self.client.logout() + os.remove(self.filename) + + def create_image(self, mode='RGB', size=(800, 600)): + image = Image.new(mode, size) + draw = ImageDraw.Draw(image) + x_bit, y_bit = size[0] // 10, size[1] // 10 + draw.rectangle((x_bit, y_bit * 2, x_bit * 7, y_bit * 3), 'red') + draw.rectangle((x_bit * 2, y_bit, x_bit * 3, y_bit * 8), 'red') + return image diff --git a/filebrowser/tests/test_fields.py b/filebrowser/tests/test_fields.py new file mode 100644 index 0000000..a2212da --- /dev/null +++ b/filebrowser/tests/test_fields.py @@ -0,0 +1,30 @@ +from django import forms +from django.db import connection + +from .base import BaseTestCase +from filebrowser.fields import FileBrowseFormField, FileBrowseField + + +class FieldsTests(BaseTestCase): + """ + Test fields and widgets + """ + def test_form_field(self): + class TestForm(forms.Form): + image = FileBrowseFormField(format='Image', extensions=['.jpg']) + + data = {'image': 'test.txt'} + form = TestForm(data) + self.assertFalse(form.is_valid()) + self.assertEquals( + form.errors['image'], + ['Extension .txt is not allowed. Only .jpg is allowed.'] + ) + + data = {'image': self.image_name} + form = TestForm(data) + self.assertTrue(form.is_valid()) + + def test_model_field(self): + f = FileBrowseField() + self.assertEqual(f.db_parameters(connection)['type'], 'varchar(200)') diff --git a/filebrowser/tests/test_forms.py b/filebrowser/tests/test_forms.py new file mode 100644 index 0000000..207394d --- /dev/null +++ b/filebrowser/tests/test_forms.py @@ -0,0 +1,62 @@ +from .base import BaseTestCase +from filebrowser.forms import MakeDirForm, RenameForm + +import os + + +class FormsTests(BaseTestCase): + """ + Test forms + """ + def test_make_dir_form(self): + data = {'dir_name': 'test_dir%$'} + form = MakeDirForm(self.working_dir, data) + self.assertFalse(form.is_valid()) + self.assertEquals( + form.errors['dir_name'], + ['Only letters, numbers, underscores, ' + 'spaces and hyphens are allowed.'] + ) + + data = {'dir_name': 'test_dir'} + form = MakeDirForm(self.working_dir, data) + self.assertTrue(form.is_valid()) + + os.makedirs(os.path.join(self.working_dir, 'test_dir')) + + data = {'dir_name': 'test_dir'} + form = MakeDirForm(self.working_dir, data) + self.assertFalse(form.is_valid()) + self.assertEquals( + form.errors['dir_name'], ['The Folder already exists.'] + ) + + os.rmdir(os.path.join(self.working_dir, 'test_dir')) + + def test_rename_form(self): + data = {'name': 'test_dir%$'} + file_extension = '' + form = RenameForm(self.working_dir, file_extension, data) + self.assertFalse(form.is_valid()) + self.assertEquals( + form.errors['name'], + ['Only letters, numbers, underscores, ' + 'spaces and hyphens are allowed.'] + ) + + os.makedirs(os.path.join(self.working_dir, 'test_dir')) + data = {'name': 'test_dir'} + file_extension = '' + form = RenameForm(self.working_dir, file_extension, data) + self.assertFalse(form.is_valid()) + self.assertEquals(form.errors['name'], ['The Folder already exists.']) + + os.rmdir(os.path.join(self.working_dir, 'test_dir')) + + file_name, file_extension = self.image_name.split('.') + data = {'name': file_name} + form = RenameForm( + self.working_dir, '.{0}'.format(file_extension), data + ) + self.assertFalse(form.is_valid()) + self.assertEquals(form.errors['name'], ['The File already exists.']) diff --git a/filebrowser/tests/test_templatetags.py b/filebrowser/tests/test_templatetags.py new file mode 100644 index 0000000..977accb --- /dev/null +++ b/filebrowser/tests/test_templatetags.py @@ -0,0 +1,73 @@ +from django.core.urlresolvers import reverse + +from .base import BaseTestCase +from filebrowser.conf import fb_settings +from filebrowser.functions import get_version_path + +import os + + +class FbCsrfTokenTests(BaseTestCase): + """ + Test template tags from fb_pagination + """ + def test_pagination(self): + response = self.client.get(reverse('fb_browse')) + self.assertEqual(response.status_code, 200) + + response = self.client.get('{0}?p=2'.format(reverse('fb_browse'))) + self.assertEqual(response.status_code, 200) + + +class FbTagsTests(BaseTestCase): + """ + Test template tags from fb_tags + """ + def test_query_string(self): + response = self.client.get('{0}?p=2'.format(reverse('fb_browse'))) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.wsgi_request.GET), 1) + + def test_selectable(self): + response = self.client.get('{0}?pop=3'.format(reverse('fb_browse'))) + needle = '' + self.assertInHTML(needle, response.content.decode('utf-8')) + + +class FbVersionsTests(BaseTestCase): + """ + Test template tags from fb_versions + """ + def test_version(self): + value = os.path.join(self.working_dir, self.image_name) + version_filename = get_version_path(value, fb_settings.ADMIN_THUMBNAIL) + self.assertTrue(os.path.isfile(version_filename)) + os.remove(version_filename) + + def test_version_object(self): + url = '{0}?filename={1}'.format(reverse('fb_versions'), + self.image_name) + self.client.get(url) + + for version in fb_settings.ADMIN_VERSIONS: + value = os.path.join(self.working_dir, self.image_name) + version_filename = get_version_path(value, version) + self.assertTrue(os.path.isfile(version_filename)) + os.remove(version_filename) + + def test_version_setting(self): + url = '{0}?filename={1}'.format(reverse('fb_versions'), + self.image_name) + response = self.client.get(url) + + for version in fb_settings.ADMIN_VERSIONS: + value = os.path.join(self.working_dir, self.image_name) + version_filename = get_version_path(value, version) + + version_prefix = fb_settings.VERSIONS[version]['verbose_name'] + needle = '{0}'.format(version_prefix) + self.assertInHTML(needle, response.content.decode('utf-8')) + + os.remove(version_filename) diff --git a/filebrowser/tests/test_views.py b/filebrowser/tests/test_views.py new file mode 100644 index 0000000..a9a7eef --- /dev/null +++ b/filebrowser/tests/test_views.py @@ -0,0 +1,46 @@ +from django.core.urlresolvers import reverse + +from .base import BaseTestCase +from filebrowser.conf import fb_settings +from filebrowser.functions import get_version_path + +import os + + +class ViewsTest(BaseTestCase): + """ + Test views + """ + def test_browse(self): + response = self.client.get(reverse('fb_browse')) + self.assertEqual(response.status_code, 200) + + value = os.path.join(self.working_dir, self.image_name) + version_filename = get_version_path(value, fb_settings.ADMIN_THUMBNAIL) + os.remove(version_filename) + + def test_mkdir(self): + response = self.client.get(reverse('fb_mkdir')) + self.assertEqual(response.status_code, 200) + + def test_upload(self): + response = self.client.get(reverse('fb_upload')) + self.assertEqual(response.status_code, 200) + + def test_rename(self): + response = self.client.get(reverse('fb_rename')) + self.assertEqual(response.status_code, 200) + + def test_delete(self): + os.makedirs(os.path.join(self.working_dir, 'test_dir')) + + response = self.client.get( + '{0}?filename=test_dir&filetype=Folder'.format( + reverse('fb_delete') + ) + ) + self.assertEqual(response.status_code, 302) + + def test_versions(self): + response = self.client.get(reverse('fb_versions')) + self.assertEqual(response.status_code, 200) diff --git a/filebrowser/views.py b/filebrowser/views.py index 16ef07e..de3b4e4 100755 --- a/filebrowser/views.py +++ b/filebrowser/views.py @@ -35,6 +35,7 @@ from filebrowser.templatetags.fb_tags import query_helper from filebrowser.base import FileObject from filebrowser.decorators import flash_login_required +from filebrowser.forms import MakeDirForm, RenameForm # Precompile regular expressions filter_re = [] @@ -67,6 +68,8 @@ def _template(): return path +@never_cache +@staff_member_required def browse(request): """ Browse Files/Directories. @@ -189,20 +192,17 @@ def browse(request): }) -browse = staff_member_required(never_cache(browse)) - # mkdir signals filebrowser_pre_createdir = Signal(providing_args=["path", "dirname"]) filebrowser_post_createdir = Signal(providing_args=["path", "dirname"]) +@never_cache +@staff_member_required def mkdir(request): """ Make Directory. """ - - from filebrowser.forms import MakeDirForm - # QUERY / PATH CHECK query = request.GET path = get_path(query.get('dir', '')) @@ -267,9 +267,8 @@ def mkdir(request): }) -mkdir = staff_member_required(never_cache(mkdir)) - - +@never_cache +@staff_member_required def upload(request): """ Multipe File Upload. @@ -304,9 +303,6 @@ def upload(request): }) -upload = staff_member_required(never_cache(upload)) - - @csrf_exempt @staff_member_required def _check_file(request): @@ -385,6 +381,8 @@ def _upload_file(request): filebrowser_post_delete = Signal(providing_args=["path", "filename"]) +@never_cache +@staff_member_required def delete(request): """ Delete existing File/Directory. @@ -439,7 +437,7 @@ def delete(request): return HttpResponseRedirect(redirect_url) except OSError as e: # todo: define error message - msg = unicode(e) + msg = str(e) else: try: # PRE DELETE SIGNAL @@ -459,7 +457,7 @@ def delete(request): return HttpResponseRedirect(redirect_url) except OSError as e: # todo: define error message - msg = unicode(e) + msg = str(e) if msg: messages.error(request, e) @@ -469,8 +467,6 @@ def delete(request): return HttpResponseRedirect(redirect_url) -delete = staff_member_required(never_cache(delete)) - # rename signals filebrowser_pre_rename = Signal( providing_args=["path", "filename", "new_filename"]) @@ -478,15 +474,14 @@ def delete(request): providing_args=["path", "filename", "new_filename"]) +@never_cache +@staff_member_required def rename(request): """ Rename existing File/Directory. Includes renaming existing Image Versions/Thumbnails. """ - - from filebrowser.forms import RenameForm - # QUERY / PATH CHECK query = request.GET path = get_path(query.get('dir', '')) @@ -562,9 +557,8 @@ def rename(request): }) -rename = staff_member_required(never_cache(rename)) - - +@never_cache +@staff_member_required def versions(request): """ Show all Versions for an Image according to ADMIN_VERSIONS. @@ -599,6 +593,3 @@ def versions(request): 'breadcrumbs_title': _(u'Versions for "{0}"').format(filename), 'is_popup': is_popup }) - - -versions = staff_member_required(never_cache(versions)) From ea1fb46a826feff53680076cc265288c52132c25 Mon Sep 17 00:00:00 2001 From: Slava Date: Sun, 30 Oct 2016 18:23:54 +0200 Subject: [PATCH 3/9] added cyrillic symbols support in directory name --- filebrowser/base.py | 2 +- filebrowser/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/filebrowser/base.py b/filebrowser/base.py index f63623b..fa2c061 100755 --- a/filebrowser/base.py +++ b/filebrowser/base.py @@ -99,7 +99,7 @@ def _path_relative_directory(self): """ directory_re = re.compile(r'^({0})'.format(fb_settings.DIRECTORY)) value = directory_re.sub('', self.path) - return u"{0}".format(value) + return value path_relative_directory = property(_path_relative_directory) def _url_relative(self): diff --git a/filebrowser/settings.py b/filebrowser/settings.py index a9caa3f..d9bf8fe 100755 --- a/filebrowser/settings.py +++ b/filebrowser/settings.py @@ -194,7 +194,7 @@ FOLDER_REGEX = getattr( settings, "FILEBROWSER_FOLDER_REGEX", - r'^[ \w-][ \w.-]*$' + r'^(?u)[ \w-][ \w.-]*$' ) # EXTRA TRANSLATION STRINGS From 8df303c868efb49abb53899c9b992ea3db9998bb Mon Sep 17 00:00:00 2001 From: Slava Date: Sun, 30 Oct 2016 18:27:34 +0200 Subject: [PATCH 4/9] function DRY --- filebrowser/fields.py | 12 +----------- filebrowser/functions.py | 9 +++++++++ filebrowser/views.py | 11 +---------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/filebrowser/fields.py b/filebrowser/fields.py index c179f75..92b4766 100755 --- a/filebrowser/fields.py +++ b/filebrowser/fields.py @@ -15,17 +15,7 @@ URL_FILEBROWSER_MEDIA, ADMIN_THUMBNAIL, DEBUG, EXTENSIONS ) from filebrowser.base import FileObject -from filebrowser.conf import fb_settings -from filebrowser.functions import url_to_path - - -def _template(): - if fb_settings.SUIT_TEMPLATE: - path = 'suit/' - else: - path = 'filebrowser/' - - return path +from filebrowser.functions import url_to_path, _template class FileBrowseWidget(Input): diff --git a/filebrowser/functions.py b/filebrowser/functions.py index b28111e..a38ca80 100755 --- a/filebrowser/functions.py +++ b/filebrowser/functions.py @@ -396,3 +396,12 @@ def convert_filename(value): return value.replace(" ", "_").lower() else: return value + + +def _template(): + if fb_settings.SUIT_TEMPLATE: + path = 'suit/' + else: + path = 'filebrowser/' + + return path diff --git a/filebrowser/views.py b/filebrowser/views.py index de3b4e4..cbeaf92 100755 --- a/filebrowser/views.py +++ b/filebrowser/views.py @@ -30,7 +30,7 @@ from filebrowser.functions import ( path_to_url, sort_by_attr, get_path, get_file, get_version_path, get_breadcrumbs, get_filterdate, get_settings_var, handle_file_upload, - convert_filename + convert_filename, _template ) from filebrowser.templatetags.fb_tags import query_helper from filebrowser.base import FileObject @@ -59,15 +59,6 @@ def _check_access(request, *path): return abs_path -def _template(): - if fb_settings.SUIT_TEMPLATE: - path = 'suit/' - else: - path = 'filebrowser/' - - return path - - @never_cache @staff_member_required def browse(request): From 5d9a69d475651b918198cbcf0f581748f35b620b Mon Sep 17 00:00:00 2001 From: Slava Date: Sun, 30 Oct 2016 20:01:23 +0200 Subject: [PATCH 5/9] show at admin panel --- filebrowser/__init__.py | 1 + filebrowser/apps.py | 50 +++++++++++++++++++++++++++++++++++++++++ filebrowser/settings.py | 6 +++++ 3 files changed, 57 insertions(+) create mode 100644 filebrowser/apps.py diff --git a/filebrowser/__init__.py b/filebrowser/__init__.py index e69de29..bef898c 100755 --- a/filebrowser/__init__.py +++ b/filebrowser/__init__.py @@ -0,0 +1 @@ +default_app_config = "filebrowser.apps.FilebrowserAppConfig" diff --git a/filebrowser/apps.py b/filebrowser/apps.py new file mode 100644 index 0000000..6812147 --- /dev/null +++ b/filebrowser/apps.py @@ -0,0 +1,50 @@ +from django.apps import AppConfig +from django.views.decorators.cache import never_cache +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ + +from filebrowser.conf import fb_settings + + +class FilebrowserAppConfig(AppConfig): + name = 'filebrowser' + + def ready(self): + from django.contrib import admin + from django.contrib.admin import sites + + class FilebrowserAdminSite(admin.AdminSite): + @never_cache + def index(self, request, extra_context=None): + resp = super(FilebrowserAdminSite, self).index(request, + extra_context) + app_dict = { + 'app_url': reverse('fb_browse'), + 'models': [ + { + 'admin_url': reverse('fb_browse'), + 'name': 'Browse', + 'add_url': None + }, + { + 'admin_url': reverse('fb_mkdir'), + 'name': _('New Folder'), + 'add_url': None + }, + { + 'admin_url': reverse('fb_upload'), + 'name': _('Upload'), + 'add_url': None + } + ], + 'has_module_perms': True, + 'name': _('Filebrowser'), + 'app_label': 'filebrowser' + } + resp.context_data['app_list'].append(app_dict) + return resp + + if fb_settings.SHOW_AT_ADMIN_PANEL: + fb = FilebrowserAdminSite() + admin.site = fb + sites.site = fb diff --git a/filebrowser/settings.py b/filebrowser/settings.py index d9bf8fe..aad4f84 100755 --- a/filebrowser/settings.py +++ b/filebrowser/settings.py @@ -208,3 +208,9 @@ # Suit Template SUIT_TEMPLATE = getattr(settings, "FILEBROWSER_SUIT_TEMPLATE", None) +# Show menu items at admin panel +SHOW_AT_ADMIN_PANEL = getattr( + settings, + "FILEBROWSER_SHOW_AT_ADMIN_PANEL", + False +) From 2b47229ab7583dec83b62f0b22a749aad65fd208 Mon Sep 17 00:00:00 2001 From: Slava Date: Sun, 6 Nov 2016 18:49:29 +0200 Subject: [PATCH 6/9] fix inlines with django 1.10 --- filebrowser/static/admin/js/inlines.js | 497 ++++++++++--------- filebrowser/templates/filebrowser/index.html | 2 +- 2 files changed, 257 insertions(+), 242 deletions(-) diff --git a/filebrowser/static/admin/js/inlines.js b/filebrowser/static/admin/js/inlines.js index 27dedf4..cceca06 100644 --- a/filebrowser/static/admin/js/inlines.js +++ b/filebrowser/static/admin/js/inlines.js @@ -1,3 +1,4 @@ +/*global DateTimeShortcuts, SelectFilter*/ /** * Django admin inlines * @@ -13,269 +14,283 @@ * * Licensed under the New BSD License * See: http://www.opensource.org/licenses/bsd-license.php - * - * Modified for django-media-manager by Yury Lapshinov (raagin) - * */ (function($) { - $.fn.formset = function(opts) { - var options = $.extend({}, $.fn.formset.defaults, opts); - var $this = $(this); - var $parent = $this.parent(); - var updateElementIndex = function(el, prefix, ndx) { - var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); - var replacement = prefix + "-" + ndx; - if ($(el).prop("for")) { - $(el).prop("for", $(el).prop("for").replace(id_regex, replacement)); - } - if (el.id) { - el.id = el.id.replace(id_regex, replacement); - } - if (el.name) { - el.name = el.name.replace(id_regex, replacement); - } - if ($(el).has('input[class="vFileBrowseField"]')) { - var a_fb_link = $('input.vFileBrowseField', el).next(); - if (a_fb_link.attr('href')) { - a_fb_link.attr('href', a_fb_link.attr('href').replace(id_regex, replacement)); - } - } - }; - var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off"); - var nextIndex = parseInt(totalForms.val(), 10); - var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off"); - // only show the add button if we are allowed to add more items, + 'use strict'; + $.fn.formset = function(opts) { + var options = $.extend({}, $.fn.formset.defaults, opts); + var $this = $(this); + var $parent = $this.parent(); + var updateElementIndex = function(el, prefix, ndx) { + var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); + var replacement = prefix + "-" + ndx; + if ($(el).prop("for")) { + $(el).prop("for", $(el).prop("for").replace(id_regex, replacement)); + } + if (el.id) { + el.id = el.id.replace(id_regex, replacement); + } + if (el.name) { + el.name = el.name.replace(id_regex, replacement); + } + if ($(el).has('input[class="vFileBrowseField"]')) { + var a_fb_link = $('input.vFileBrowseField', el).next(); + if (a_fb_link.attr('href')) { + a_fb_link.attr('href', a_fb_link.attr('href').replace(id_regex, replacement)); + } + } + }; + var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off"); + var nextIndex = parseInt(totalForms.val(), 10); + var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off"); + // only show the add button if we are allowed to add more items, // note that max_num = None translates to a blank string. - var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; - $this.each(function(i) { - $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); - }); - if ($this.length && showAddButton) { - var addButton; - if ($this.prop("tagName") == "TR") { - // If forms are laid out as table rows, insert the - // "add" button in a new table row: - var numCols = this.eq(-1).children().length; - $parent.append('' + options.addText + ""); - addButton = $parent.find("tr:last a"); - } else { - // Otherwise, insert it immediately after the last form: - $this.filter(":last").after('"); - addButton = $this.filter(":last").next().find("a"); - } - addButton.click(function(e) { - e.preventDefault(); - var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); - var template = $("#" + options.prefix + "-empty"); - var row = template.clone(true); - row.removeClass(options.emptyCssClass) - .addClass(options.formCssClass) - .attr("id", options.prefix + "-" + nextIndex); - if (row.is("tr")) { - // If the forms are laid out in table rows, insert - // the remove button into the last table cell: - row.children(":last").append('"); - } else if (row.is("ul") || row.is("ol")) { - // If they're laid out as an ordered/unordered list, - // insert an
  • after the last list item: - row.append('
  • ' + options.deleteText + "
  • "); - } else { - // Otherwise, just insert the remove button as the - // last child element of the form's container: - row.children(":first").append('' + options.deleteText + ""); - } - row.find("*").each(function() { - updateElementIndex(this, options.prefix, totalForms.val()); + var showAddButton = maxForms.val() === '' || (maxForms.val() - totalForms.val()) > 0; + $this.each(function(i) { + $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); }); - // Insert the new form when it has been fully edited - row.insertBefore($(template)); - // Update number of total forms - $(totalForms).val(parseInt(totalForms.val(), 10) + 1); - nextIndex += 1; - // Hide add button in case we've hit the max, except we want to add infinitely - if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { - addButton.parent().hide(); - } - // The delete button of each row triggers a bunch of other things - row.find("a." + options.deleteCssClass).click(function(e) { - e.preventDefault(); - // Remove the parent form containing this button: - var row = $(this).parents("." + options.formCssClass); - row.remove(); - nextIndex -= 1; - // If a post-delete callback was provided, call it with the deleted form: - if (options.removed) { - options.removed(row); - } - // Update the TOTAL_FORMS form count. - var forms = $("." + options.formCssClass); - $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); - // Show add button again once we drop below max - if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { - addButton.parent().show(); - } - // Also, update names and ids for all remaining form controls - // so they remain in sequence: - for (var i=0, formCount=forms.length; i' + options.addText + ""); + addButton = $parent.find("tr:last a"); + } else { + // Otherwise, insert it immediately after the last form: + $this.filter(":last").after('"); + addButton = $this.filter(":last").next().find("a"); + } + addButton.click(function(e) { + e.preventDefault(); + var template = $("#" + options.prefix + "-empty"); + var row = template.clone(true); + row.removeClass(options.emptyCssClass) + .addClass(options.formCssClass) + .attr("id", options.prefix + "-" + nextIndex); + if (row.is("tr")) { + // If the forms are laid out in table rows, insert + // the remove button into the last table cell: + row.children(":last").append('"); + } else if (row.is("ul") || row.is("ol")) { + // If they're laid out as an ordered/unordered list, + // insert an
  • after the last list item: + row.append('
  • ' + options.deleteText + "
  • "); + } else { + // Otherwise, just insert the remove button as the + // last child element of the form's container: + row.children(":first").append('' + options.deleteText + ""); + } + row.find("*").each(function() { + updateElementIndex(this, options.prefix, totalForms.val()); + }); + // Insert the new form when it has been fully edited + row.insertBefore($(template)); + // Update number of total forms + $(totalForms).val(parseInt(totalForms.val(), 10) + 1); + nextIndex += 1; + // Hide add button in case we've hit the max, except we want to add infinitely + if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) { + addButton.parent().hide(); + } + // The delete button of each row triggers a bunch of other things + row.find("a." + options.deleteCssClass).click(function(e1) { + e1.preventDefault(); + // Remove the parent form containing this button: + row.remove(); + nextIndex -= 1; + // If a post-delete callback was provided, call it with the deleted form: + if (options.removed) { + options.removed(row); + } + $(document).trigger('formset:removed', [row, options.prefix]); + // Update the TOTAL_FORMS form count. + var forms = $("." + options.formCssClass); + $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); + // Show add button again once we drop below max + if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) { + addButton.parent().show(); + } + // Also, update names and ids for all remaining form controls + // so they remain in sequence: + var i, formCount; + var updateElementCallback = function() { + updateElementIndex(this, options.prefix, i); + }; + for (i = 0, formCount = forms.length; i < formCount; i++) { + updateElementIndex($(forms).get(i), options.prefix, i); + $(forms.get(i)).find("*").each(updateElementCallback); + } + }); + // If a post-add callback was supplied, call it with the added form: + if (options.added) { + options.added(row); + } + $(document).trigger('formset:added', [row, options.prefix]); }); - } - }); - // If a post-add callback was supplied, call it with the added form: - if (options.added) { - options.added(row); } - }); - } - return this; - }; + return this; + }; - /* Setup plugin defaults */ - $.fn.formset.defaults = { - prefix: "form", // The form prefix for your django formset - addText: "add another", // Text for the add link - deleteText: "remove", // Text for the delete link - addCssClass: "add-row", // CSS class applied to the add link - deleteCssClass: "delete-row", // CSS class applied to the delete link - emptyCssClass: "empty-row", // CSS class applied to the empty row - formCssClass: "dynamic-form", // CSS class applied to each form in a formset - added: null, // Function called each time a new form is added - removed: null // Function called each time a form is deleted - }; + /* Setup plugin defaults */ + $.fn.formset.defaults = { + prefix: "form", // The form prefix for your django formset + addText: "add another", // Text for the add link + deleteText: "remove", // Text for the delete link + addCssClass: "add-row", // CSS class applied to the add link + deleteCssClass: "delete-row", // CSS class applied to the delete link + emptyCssClass: "empty-row", // CSS class applied to the empty row + formCssClass: "dynamic-form", // CSS class applied to each form in a formset + added: null, // Function called each time a new form is added + removed: null // Function called each time a form is deleted + }; - // Tabular inlines --------------------------------------------------------- - $.fn.tabularFormset = function(options) { - var $rows = $(this); - var alternatingRows = function(row) { - $($rows.selector).not(".add-row").removeClass("row1 row2") - .filter(":even").addClass("row1").end() - .filter(":odd").addClass("row2"); - }; + // Tabular inlines --------------------------------------------------------- + $.fn.tabularFormset = function(options) { + var $rows = $(this); + var alternatingRows = function(row) { + $($rows.selector).not(".add-row").removeClass("row1 row2") + .filter(":even").addClass("row1").end() + .filter(":odd").addClass("row2"); + }; - var reinitDateTimeShortCuts = function() { - // Reinitialize the calendar and clock widgets by force - if (typeof DateTimeShortcuts != "undefined") { - $(".datetimeshortcuts").remove(); - DateTimeShortcuts.init(); - } - }; + var reinitDateTimeShortCuts = function() { + // Reinitialize the calendar and clock widgets by force + if (typeof DateTimeShortcuts !== "undefined") { + $(".datetimeshortcuts").remove(); + DateTimeShortcuts.init(); + } + }; - var updateSelectFilter = function() { - // If any SelectFilter widgets are a part of the new form, - // instantiate a new SelectFilter instance for it. - if (typeof SelectFilter != 'undefined'){ - $('.selectfilter').each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix ); - }); - $('.selectfilterstacked').each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix ); - }); - } - }; + var updateSelectFilter = function() { + // If any SelectFilter widgets are a part of the new form, + // instantiate a new SelectFilter instance for it. + if (typeof SelectFilter !== 'undefined') { + $('.selectfilter').each(function(index, value) { + var namearr = value.name.split('-'); + SelectFilter.init(value.id, namearr[namearr.length - 1], false); + }); + $('.selectfilterstacked').each(function(index, value) { + var namearr = value.name.split('-'); + SelectFilter.init(value.id, namearr[namearr.length - 1], true); + }); + } + }; - var initPrepopulatedFields = function(row) { - row.find('.prepopulated_field').each(function() { - var field = $(this), - input = field.find('input, select, textarea'), - dependency_list = input.data('dependency_list') || [], - dependencies = []; - $.each(dependency_list, function(i, field_name) { - dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id')); + var initPrepopulatedFields = function(row) { + row.find('.prepopulated_field').each(function() { + var field = $(this), + input = field.find('input, select, textarea'), + dependency_list = input.data('dependency_list') || [], + dependencies = []; + $.each(dependency_list, function(i, field_name) { + dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id')); + }); + if (dependencies.length) { + input.prepopulate(dependencies, input.attr('maxlength')); + } + }); + }; + + $rows.formset({ + prefix: options.prefix, + addText: options.addText, + formCssClass: "dynamic-" + options.prefix, + deleteCssClass: "inline-deletelink", + deleteText: options.deleteText, + emptyCssClass: "empty-form", + removed: alternatingRows, + added: function(row) { + initPrepopulatedFields(row); + reinitDateTimeShortCuts(); + updateSelectFilter(); + alternatingRows(row); + } }); - if (dependencies.length) { - input.prepopulate(dependencies, input.attr('maxlength')); - } - }); + + return $rows; }; - $rows.formset({ - prefix: options.prefix, - addText: options.addText, - formCssClass: "dynamic-" + options.prefix, - deleteCssClass: "inline-deletelink", - deleteText: options.deleteText, - emptyCssClass: "empty-form", - removed: alternatingRows, - added: function(row) { - initPrepopulatedFields(row); - reinitDateTimeShortCuts(); - updateSelectFilter(); - alternatingRows(row); - } - }); + // Stacked inlines --------------------------------------------------------- + $.fn.stackedFormset = function(options) { + var $rows = $(this); + var updateInlineLabel = function(row) { + $($rows.selector).find(".inline_label").each(function(i) { + var count = i + 1; + $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); + }); + }; - return $rows; - }; + var reinitDateTimeShortCuts = function() { + // Reinitialize the calendar and clock widgets by force, yuck. + if (typeof DateTimeShortcuts !== "undefined") { + $(".datetimeshortcuts").remove(); + DateTimeShortcuts.init(); + } + }; - // Stacked inlines --------------------------------------------------------- - $.fn.stackedFormset = function(options) { - var $rows = $(this); - var updateInlineLabel = function(row) { - $($rows.selector).find(".inline_label").each(function(i) { - var count = i + 1; - $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); - }); - }; + var updateSelectFilter = function() { + // If any SelectFilter widgets were added, instantiate a new instance. + if (typeof SelectFilter !== "undefined") { + $(".selectfilter").each(function(index, value) { + var namearr = value.name.split('-'); + SelectFilter.init(value.id, namearr[namearr.length - 1], false); + }); + $(".selectfilterstacked").each(function(index, value) { + var namearr = value.name.split('-'); + SelectFilter.init(value.id, namearr[namearr.length - 1], true); + }); + } + }; - var reinitDateTimeShortCuts = function() { - // Reinitialize the calendar and clock widgets by force, yuck. - if (typeof DateTimeShortcuts != "undefined") { - $(".datetimeshortcuts").remove(); - DateTimeShortcuts.init(); - } - }; + var initPrepopulatedFields = function(row) { + row.find('.prepopulated_field').each(function() { + var field = $(this), + input = field.find('input, select, textarea'), + dependency_list = input.data('dependency_list') || [], + dependencies = []; + $.each(dependency_list, function(i, field_name) { + dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id')); + }); + if (dependencies.length) { + input.prepopulate(dependencies, input.attr('maxlength')); + } + }); + }; - var updateSelectFilter = function() { - // If any SelectFilter widgets were added, instantiate a new instance. - if (typeof SelectFilter != "undefined"){ - $(".selectfilter").each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix); - }); - $(".selectfilterstacked").each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix); + $rows.formset({ + prefix: options.prefix, + addText: options.addText, + formCssClass: "dynamic-" + options.prefix, + deleteCssClass: "inline-deletelink", + deleteText: options.deleteText, + emptyCssClass: "empty-form", + removed: updateInlineLabel, + added: function(row) { + initPrepopulatedFields(row); + reinitDateTimeShortCuts(); + updateSelectFilter(); + updateInlineLabel(row); + } }); - } - }; - var initPrepopulatedFields = function(row) { - row.find('.prepopulated_field').each(function() { - var field = $(this), - input = field.find('input, select, textarea'), - dependency_list = input.data('dependency_list') || [], - dependencies = []; - $.each(dependency_list, function(i, field_name) { - dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id')); - }); - if (dependencies.length) { - input.prepopulate(dependencies, input.attr('maxlength')); - } - }); + return $rows; }; - $rows.formset({ - prefix: options.prefix, - addText: options.addText, - formCssClass: "dynamic-" + options.prefix, - deleteCssClass: "inline-deletelink", - deleteText: options.deleteText, - emptyCssClass: "empty-form", - removed: updateInlineLabel, - added: (function(row) { - initPrepopulatedFields(row); - reinitDateTimeShortCuts(); - updateSelectFilter(); - updateInlineLabel(row); - }) + $(document).ready(function() { + $(".js-inline-admin-formset").each(function() { + var data = $(this).data(), + inlineOptions = data.inlineFormset; + switch(data.inlineType) { + case "stacked": + $(inlineOptions.name + "-group .inline-related").stackedFormset(inlineOptions.options); + break; + case "tabular": + $(inlineOptions.name + "-group .tabular.inline-related tbody tr").tabularFormset(inlineOptions.options); + break; + } + }); }); - - return $rows; - }; })(django.jQuery); diff --git a/filebrowser/templates/filebrowser/index.html b/filebrowser/templates/filebrowser/index.html index 6da676f..066b8b4 100755 --- a/filebrowser/templates/filebrowser/index.html +++ b/filebrowser/templates/filebrowser/index.html @@ -25,7 +25,7 @@ {% endifequal %} {{ media }} - + {% if not actions_on_top and not actions_on_bottom %}