From 4daecd17f0930daecae009a2b6bebef80c4dab3d Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Thu, 13 Mar 2014 09:32:44 -0400
Subject: [PATCH 1/9] Added file extension validation to AJAX uploader, fixed
 #216.

---
 docs/changelog.rst                               |  3 ++-
 .../static/filebrowser/js/fileuploader.js        |  4 ++--
 filebrowser/templates/filebrowser/upload.html    |  1 +
 filebrowser/templatetags/fb_tags.py              | 11 ++++++++++-
 filebrowser/tests/test_templatetags.py           | 16 ++++++++++++++++
 5 files changed, 31 insertions(+), 4 deletions(-)
 create mode 100644 filebrowser/tests/test_templatetags.py

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 740d4f5..3f21f94 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -14,6 +14,7 @@ Changelog
 * Improved tests with convert/normalize (removed special chars from test filename).
 * Fixed file selection after using search box (CKEditor).
 * Removed encoding of file URIs with CKEditor.
+* Added client-side (JavaScript) file extension validation to the AJAX uploader.
 
 3.5.4 (February 21st, 2014)
 ---------------------------
@@ -108,4 +109,4 @@ Changelog
 3.4.0 (15/11/2011)
 ------------------
 
-* Final release of 3.4, see :ref:`releasenotes`
\ No newline at end of file
+* Final release of 3.4, see :ref:`releasenotes`
diff --git a/filebrowser/static/filebrowser/js/fileuploader.js b/filebrowser/static/filebrowser/js/fileuploader.js
index d813cfb..7afbb68 100644
--- a/filebrowser/static/filebrowser/js/fileuploader.js
+++ b/filebrowser/static/filebrowser/js/fileuploader.js
@@ -447,7 +447,7 @@ qq.FileUploaderBasic.prototype = {
         return name;
     },
     _isAllowedExtension: function(fileName){
-        var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
+        var ext = (-1 !== fileName.indexOf('.')) ? fileName.substr(fileName.lastIndexOf('.')).toLowerCase() : '';
         var allowed = this._options.allowedExtensions;
         
         if (!allowed.length){return true;}        
@@ -1247,4 +1247,4 @@ qq.extend(qq.UploadHandlerXhr.prototype, {
             this._xhrs[id] = null;                                   
         }
     }
-});
\ No newline at end of file
+});
diff --git a/filebrowser/templates/filebrowser/upload.html b/filebrowser/templates/filebrowser/upload.html
index f0e8a99..8998813 100644
--- a/filebrowser/templates/filebrowser/upload.html
+++ b/filebrowser/templates/filebrowser/upload.html
@@ -58,6 +58,7 @@
                           'csrf_xname': 'X-CSRFToken',
                           'folder': '{{ query.dir|escapejs }}', },
 
+                allowedExtensions: {% get_file_extensions_for_file_type request.GET.type %},
                 sizeLimit: {{ settings_var.MAX_UPLOAD_SIZE|unlocalize }},
                 minSizeLimit: 0,
                 debug: false,
diff --git a/filebrowser/templatetags/fb_tags.py b/filebrowser/templatetags/fb_tags.py
index 524b812..1f3de7b 100644
--- a/filebrowser/templatetags/fb_tags.py
+++ b/filebrowser/templatetags/fb_tags.py
@@ -5,7 +5,7 @@
 from django.utils.http import urlquote
 
 # FILEBROWSER IMPORTS
-from filebrowser.settings import SELECT_FORMATS
+from filebrowser.settings import EXTENSIONS, SELECT_FORMATS
 
 register = template.Library()
 
@@ -141,3 +141,12 @@ def selectable(parser, token):
     return SelectableNode(filetype, format)
 
 register.tag(selectable)
+
+
+def get_file_extensions_for_file_type(query_format):
+    extensions = []
+    for format in SELECT_FORMATS.get(query_format, []):
+        extensions.extend(EXTENSIONS[format])
+    return extensions
+
+register.simple_tag(get_file_extensions_for_file_type)
diff --git a/filebrowser/tests/test_templatetags.py b/filebrowser/tests/test_templatetags.py
new file mode 100644
index 0000000..533697d
--- /dev/null
+++ b/filebrowser/tests/test_templatetags.py
@@ -0,0 +1,16 @@
+# coding: utf-8
+
+# DJANGO IMPORTS
+from django.test import TestCase
+
+# FILEBROWSER IMPORTS
+from filebrowser.templatetags.fb_tags import get_file_extensions_for_file_type
+
+
+class TemplateTagsTests(TestCase):
+    def test_get_file_extensions_for_file_type(self):
+        self.assertEqual(get_file_extensions_for_file_type(''), [])
+        self.assertEqual(
+            get_file_extensions_for_file_type('image'),
+            ['.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff']
+        )

From 39d2607a50e190ce8aa7857cfa03bcf4a90d644e Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Thu, 13 Mar 2014 10:01:12 -0400
Subject: [PATCH 2/9] Added experimental support for Python 3.3.

---
 docs/changelog.rst                              |  3 ++-
 filebrowser/base.py                             | 16 +++++++---------
 filebrowser/decorators.py                       |  2 +-
 filebrowser/fields.py                           |  4 ++--
 .../management/commands/fb_version_generate.py  |  5 +++--
 .../management/commands/fb_version_remove.py    |  7 ++++---
 filebrowser/settings.py                         |  2 +-
 filebrowser/sites.py                            | 17 +++++++++--------
 filebrowser/templatetags/fb_tags.py             |  4 ++--
 filebrowser/templatetags/fb_versions.py         |  8 ++++----
 filebrowser/tests/test_base.py                  |  6 +++---
 filebrowser/tests/test_commands.py              |  4 ++--
 filebrowser/tests/test_sites.py                 | 12 ++++++------
 filebrowser/utils.py                            |  5 ++++-
 setup.py                                        |  3 +--
 15 files changed, 51 insertions(+), 47 deletions(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 3f21f94..49e2940 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -15,6 +15,7 @@ Changelog
 * Fixed file selection after using search box (CKEditor).
 * Removed encoding of file URIs with CKEditor.
 * Added client-side (JavaScript) file extension validation to the AJAX uploader.
+* Added experimental Python 3.3 support.
 
 3.5.4 (February 21st, 2014)
 ---------------------------
@@ -88,7 +89,7 @@ Changelog
 -----------------
 
 * Fixed security bug: added staff_member_required decorator to the upload-function.
-* Fixed a XSS vulnerability with fb_tags. 
+* Fixed a XSS vulnerability with fb_tags.
 
 3.4.1 (7.3.2012)
 ----------------
diff --git a/filebrowser/base.py b/filebrowser/base.py
index e2ed09b..7b92b89 100644
--- a/filebrowser/base.py
+++ b/filebrowser/base.py
@@ -15,7 +15,7 @@
 # FILEBROWSER IMPORTS
 from filebrowser.settings import EXTENSIONS, VERSIONS, ADMIN_VERSIONS, VERSIONS_BASEDIR, VERSION_QUALITY, PLACEHOLDER, FORCE_PLACEHOLDER, SHOW_PLACEHOLDER, STRICT_PIL, IMAGE_MAXBLOCK
 from filebrowser.utils import path_strip, scale_and_crop
-from django.utils.encoding import smart_str, smart_text
+from django.utils.encoding import python_2_unicode_compatible, smart_str
 
 # PIL import
 if STRICT_PIL:
@@ -86,7 +86,7 @@ def sort_by_attr(self, seq, attr):
         # only to provide stable sorting, but mainly to eliminate comparison of objects
         # (which can be expensive or prohibited) in case of equal attribute values.
         intermed = sorted(zip(map(getattr, seq, (attr,)*len(seq)), range(len(seq)), seq))
-        return map(operator.getitem, intermed, (-1,) * len(intermed))
+        return list(map(operator.getitem, intermed, (-1,) * len(intermed)))
 
     _is_folder_stored = None
     @property
@@ -165,7 +165,7 @@ def files_walk_total(self):
     def files_listing_filtered(self):
         "Returns FileObjects for filtered files in listing"
         if self.filter_func:
-            listing = filter(self.filter_func, self.files_listing_total())
+            listing = list(filter(self.filter_func, self.files_listing_total()))
         else:
             listing = self.files_listing_total()
         self._results_listing_filtered = len(listing)
@@ -174,7 +174,7 @@ def files_listing_filtered(self):
     def files_walk_filtered(self):
         "Returns FileObjects for filtered files in walk"
         if self.filter_func:
-            listing = filter(self.filter_func, self.files_walk_total())
+            listing = list(filter(self.filter_func, self.files_walk_total()))
         else:
             listing = self.files_walk_total()
         self._results_walk_filtered = len(listing)
@@ -205,6 +205,7 @@ def results_walk_filtered(self):
         return len(self.files_walk_filtered())
 
 
+@python_2_unicode_compatible
 class FileObject():
     """
     The FileObject represents a file (or directory) on the server.
@@ -235,9 +236,6 @@ def __init__(self, path, site=None):
     def __str__(self):
         return smart_str(self.path)
 
-    def __unicode__(self):
-        return smart_text(self.path)
-
     @property
     def name(self):
         return self.path
@@ -254,7 +252,7 @@ def __len__(self):
     def _get_file_type(self):
         "Get file type as defined in EXTENSIONS."
         file_type = ''
-        for k, v in EXTENSIONS.iteritems():
+        for k, v in EXTENSIONS.items():
             for extension in v:
                 if self.extension.lower() == extension.lower():
                     file_type = k
@@ -482,7 +480,7 @@ def versions(self):
         "List of versions (not checking if they actually exist)"
         version_list = []
         if self.filetype == "Image" and not self.is_version:
-            for version in VERSIONS:
+            for version in sorted(VERSIONS):
                 version_list.append(os.path.join(self.versions_basedir, self.dirname, self.version_name(version)))
         return version_list
 
diff --git a/filebrowser/decorators.py b/filebrowser/decorators.py
index fa28639..5ed1a2c 100644
--- a/filebrowser/decorators.py
+++ b/filebrowser/decorators.py
@@ -36,7 +36,7 @@ def path_exists(site, function):
     def decorator(request, *args, **kwargs):
         if get_path('', site=site) is None:
             # The storage location does not exist, raise an error to prevent eternal redirecting.
-            raise ImproperlyConfigured, _("Error finding Upload-Folder (site.storage.location + site.directory). Maybe it does not exist?")
+            raise ImproperlyConfigured(_("Error finding Upload-Folder (site.storage.location + site.directory). Maybe it does not exist?"))
         if get_path(request.GET.get('dir', ''), site=site) is None:
             msg = _('The requested Folder does not exist.')
             messages.add_message(request, messages.ERROR, msg)
diff --git a/filebrowser/fields.py b/filebrowser/fields.py
index f920c00..49d86aa 100644
--- a/filebrowser/fields.py
+++ b/filebrowser/fields.py
@@ -10,6 +10,7 @@
 from django import forms
 from django.forms.widgets import Input
 from django.template.loader import render_to_string
+from django.utils.six import with_metaclass
 from django.utils.translation import ugettext_lazy as _
 
 # FILEBROWSER IMPORTS
@@ -83,9 +84,8 @@ def clean(self, value):
         return value
 
 
-class FileBrowseField(CharField):
+class FileBrowseField(with_metaclass(models.SubfieldBase, CharField)):
     description = "FileBrowseField"
-    __metaclass__ = models.SubfieldBase
 
     def __init__(self, *args, **kwargs):
         self.site = kwargs.pop('filebrowser_site', site)
diff --git a/filebrowser/management/commands/fb_version_generate.py b/filebrowser/management/commands/fb_version_generate.py
index 264eacb..da15c73 100644
--- a/filebrowser/management/commands/fb_version_generate.py
+++ b/filebrowser/management/commands/fb_version_generate.py
@@ -7,6 +7,7 @@
 # DJANGO IMPORTS
 from django.core.management.base import BaseCommand, CommandError
 from django.conf import settings
+from django.utils.six.moves import input
 
 # FILEBROWSER IMPORTS
 from filebrowser.settings import EXTENSION_LIST, EXCLUDE, DIRECTORY, VERSIONS, EXTENSIONS
@@ -16,7 +17,7 @@
 filter_re = []
 for exp in EXCLUDE:
     filter_re.append(re.compile(exp))
-for k, v in VERSIONS.iteritems():
+for k, v in VERSIONS.items():
     exp = (r'_%s(%s)') % (k, '|'.join(EXTENSION_LIST))
     filter_re.append(re.compile(exp))
 
@@ -43,7 +44,7 @@ def handle(self, *args, **options):
             for version in VERSIONS:
                 self.stdout.write(' * %s\n' % version)
 
-            version_name = raw_input('(leave blank to generate all versions): ')
+            version_name = input('(leave blank to generate all versions): ')
 
             if version_name == "":
                 selected_version = None
diff --git a/filebrowser/management/commands/fb_version_remove.py b/filebrowser/management/commands/fb_version_remove.py
index 879509f..2c737f9 100644
--- a/filebrowser/management/commands/fb_version_remove.py
+++ b/filebrowser/management/commands/fb_version_remove.py
@@ -7,6 +7,7 @@
 # DJANGO IMPORTS
 from django.core.management.base import BaseCommand, CommandError
 from django.conf import settings
+from django.utils.six.moves import input
 
 # FILEBROWSER IMPORTS
 from filebrowser.settings import EXTENSION_LIST, EXCLUDE, DIRECTORY, VERSIONS, EXTENSIONS
@@ -36,7 +37,7 @@ def handle(self, *args, **options):
         while 1:
             self.stdout.write('\nOlder versions of the FileBrowser used to prefix the filename with the version name.\n')
             self.stdout.write('Current version of the FileBrowser adds the version name as suffix.\n')
-            prefix_or_suffix = raw_input('"p" for prefix or "s" for suffix (leave blank for "%s"): ' % default_prefix_or_suffix)
+            prefix_or_suffix = input('"p" for prefix or "s" for suffix (leave blank for "%s"): ' % default_prefix_or_suffix)
 
             if default_prefix_or_suffix and prefix_or_suffix == '':
                 prefix_or_suffix = default_prefix_or_suffix
@@ -48,7 +49,7 @@ def handle(self, *args, **options):
 
         # get version name
         while 1:
-            version_name = raw_input('\nversion name as defined with VERSIONS: ')
+            version_name = input('\nversion name as defined with VERSIONS: ')
 
             if version_name == "":
                 self.stderr.write('Error: You have to enter a version name.\n')
@@ -84,7 +85,7 @@ def handle(self, *args, **options):
         # ask to make sure
         do_remove = ""
         self.stdout.write('Are Sure you want to delete these files?\n')
-        do_remove = raw_input('"y" for Yes or "n" for No (leave blank for "n"): ')
+        do_remove = input('"y" for Yes or "n" for No (leave blank for "n"): ')
 
         # if "yes" we delete. any different case we finish without removing anything
         if do_remove == "y":
diff --git a/filebrowser/settings.py b/filebrowser/settings.py
index 640047f..1994d2c 100644
--- a/filebrowser/settings.py
+++ b/filebrowser/settings.py
@@ -100,7 +100,7 @@
 # Traverse directories when searching
 SEARCH_TRAVERSE = getattr(settings, "FILEBROWSER_SEARCH_TRAVERSE", False)
 # Default Upload and Version Permissions
-DEFAULT_PERMISSIONS = getattr(settings, "FILEBROWSER_DEFAULT_PERMISSIONS", 0755)
+DEFAULT_PERMISSIONS = getattr(settings, "FILEBROWSER_DEFAULT_PERMISSIONS", 0o755)
 # Overwrite existing files on upload
 OVERWRITE_EXISTING = getattr(settings, "FILEBROWSER_OVERWRITE_EXISTING", True)
 
diff --git a/filebrowser/sites.py b/filebrowser/sites.py
index f5ae844..71a3584 100644
--- a/filebrowser/sites.py
+++ b/filebrowser/sites.py
@@ -67,7 +67,7 @@ def get_site_dict(app_name='filebrowser'):
     # Get names of all deployed filebrowser sites with a give app_name
     deployed = get_resolver(get_urlconf()).app_dict[app_name]
     # Get the deployed subset from the cache
-    return dict((k, v) for k, v in _sites_cache[app_name].iteritems() if k in deployed)
+    return dict((k, v) for k, v in _sites_cache[app_name].items() if k in deployed)
 
 
 def register_site(app_name, site_name, site):
@@ -165,7 +165,7 @@ def handle_file_upload(path, file, site):
     try:
         file_path = os.path.join(path, file.name)
         uploadedfile = site.storage.save(file_path, file)
-    except Exception, inst:
+    except Exception as inst:
         raise inst
     return uploadedfile
 
@@ -269,7 +269,7 @@ def actions(self):
         Get all the enabled actions as a list of (name, func). The list
         is sorted alphabetically by actions names
         """
-        res = self._actions.items()
+        res = list(self._actions.items())
         res.sort(key=lambda name_func: name_func[0])
         return res
 
@@ -283,7 +283,7 @@ def browse(self, request):
         filter_re = []
         for exp in EXCLUDE:
             filter_re.append(re.compile(exp))
-        for k, v in VERSIONS.iteritems():
+        for k, v in VERSIONS.items():
             exp = (r'_%s(%s)$') % (k, '|'.join(EXTENSION_LIST))
             filter_re.append(re.compile(exp, re.IGNORECASE))
 
@@ -372,7 +372,8 @@ def createdir(self, request):
                     messages.add_message(request, messages.SUCCESS, _('The Folder %s was successfully created.') % form.cleaned_data['name'])
                     redirect_url = reverse("filebrowser:fb_browse", current_app=self.name) + query_helper(query, "ot=desc,o=date", "ot,o,filter_type,filter_date,q,p")
                     return HttpResponseRedirect(redirect_url)
-                except OSError, (errno, strerror):
+                except OSError as e:
+                    errno = e.args[0]
                     if errno == 13:
                         form.errors['name'] = forms.util.ErrorList([_('Permission denied.')])
                     else:
@@ -449,7 +450,7 @@ def delete(self, request):
                 fileobject.delete()
                 signals.filebrowser_post_delete.send(sender=request, path=fileobject.path, name=fileobject.filename, site=self)
                 messages.add_message(request, messages.SUCCESS, _('Successfully deleted %s') % fileobject.filename)
-            except OSError, (errno, strerror):
+            except OSError:
                 # TODO: define error-message
                 pass
         redirect_url = reverse("filebrowser:fb_browse", current_app=self.name) + query_helper(query, "", "filename,filetype")
@@ -493,7 +494,7 @@ def detail(self, request):
                     else:
                         redirect_url = reverse("filebrowser:fb_browse", current_app=self.name) + query_helper(query, "", "filename")
                     return HttpResponseRedirect(redirect_url)
-                except OSError, (errno, strerror):
+                except OSError:
                     form.errors['name'] = forms.util.ErrorList([_('Error.')])
         else:
             form = ChangeForm(initial={"name": fileobject.filename}, path=path, fileobject=fileobject, filebrowser_site=self)
@@ -537,7 +538,7 @@ def _upload_file(self, request):
             if len(request.FILES) > 1:
                 return HttpResponseBadRequest('Invalid request! Multiple files included.')
 
-            filedata = request.FILES.values()[0]
+            filedata = list(request.FILES.values())[0]
 
             fb_uploadurl_re = re.compile(r'^.*(%s)' % reverse("filebrowser:fb_upload", current_app=self.name))
             folder = fb_uploadurl_re.sub('', folder)
diff --git a/filebrowser/templatetags/fb_tags.py b/filebrowser/templatetags/fb_tags.py
index 1f3de7b..2259d25 100644
--- a/filebrowser/templatetags/fb_tags.py
+++ b/filebrowser/templatetags/fb_tags.py
@@ -52,7 +52,7 @@ def get_query_string(p, new_params=None, remove=None):
     if remove is None:
         remove = []
     for r in remove:
-        for k in p.keys():
+        for k in list(p):
             #if k.startswith(r):
             if k == r:
                 del p[k]
@@ -136,7 +136,7 @@ def selectable(parser, token):
     try:
         tag, filetype, format = token.split_contents()
     except:
-        raise TemplateSyntaxError, "%s tag requires 2 arguments" % token.contents.split()[0]
+        raise TemplateSyntaxError("%s tag requires 2 arguments" % token.contents.split()[0])
 
     return SelectableNode(filetype, format)
 
diff --git a/filebrowser/templatetags/fb_versions.py b/filebrowser/templatetags/fb_versions.py
index 78c41a0..624d617 100644
--- a/filebrowser/templatetags/fb_versions.py
+++ b/filebrowser/templatetags/fb_versions.py
@@ -44,7 +44,7 @@ def render(self, context):
         try:
             version = fileobject.version_generate(version_suffix)
             return version.url
-        except Exception, e:
+        except Exception as e:
             if settings.TEMPLATE_DEBUG:
                 raise e
         return ""
@@ -93,7 +93,7 @@ def render(self, context):
         try:
             version = fileobject.version_generate(version_suffix)
             context[self.var_name] = version
-        except Exception, e:
+        except Exception as e:
             if settings.TEMPLATE_DEBUG:
                 raise e
             context[self.var_name] = ""
@@ -146,9 +146,9 @@ def version_setting(parser, token):
     try:
         tag, version_suffix = token.split_contents()
     except:
-        raise TemplateSyntaxError, "%s tag requires 1 argument" % token.contents.split()[0]
+        raise TemplateSyntaxError("%s tag requires 1 argument" % token.contents.split()[0])
     if (version_suffix[0] == version_suffix[-1] and version_suffix[0] in ('"', "'")) and version_suffix.lower()[1:-1] not in VERSIONS:
-        raise TemplateSyntaxError, "%s tag received bad version_suffix %s" % (tag, version_suffix)
+        raise TemplateSyntaxError("%s tag received bad version_suffix %s" % (tag, version_suffix))
     return VersionSettingNode(version_suffix)
 
 
diff --git a/filebrowser/tests/test_base.py b/filebrowser/tests/test_base.py
index 97af575..5c739cc 100644
--- a/filebrowser/tests/test_base.py
+++ b/filebrowser/tests/test_base.py
@@ -301,7 +301,7 @@ def test_version_attributes_1(self):
         }
         filebrowser.base.ADMIN_VERSIONS = ['large']
         # expected test results
-        version_list = ['fb_test_directory/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg', 'fb_test_directory/fb_tmp_dir/fb_tmp_dir_sub/testimage_admin_thumbnail.jpg']
+        version_list = ['fb_test_directory/fb_tmp_dir/fb_tmp_dir_sub/testimage_admin_thumbnail.jpg', 'fb_test_directory/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg',]
         admin_version_list = ['fb_test_directory/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg']
 
         self.assertEqual(self.f_image.is_version, False)
@@ -346,7 +346,7 @@ def test_version_attributes_2(self):
         }
         filebrowser.base.ADMIN_VERSIONS = ['large']
         # expected test results
-        version_list = ['fb_test_directory/_versions/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg', 'fb_test_directory/_versions/fb_tmp_dir/fb_tmp_dir_sub/testimage_admin_thumbnail.jpg']
+        version_list = ['fb_test_directory/_versions/fb_tmp_dir/fb_tmp_dir_sub/testimage_admin_thumbnail.jpg', 'fb_test_directory/_versions/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg']
         admin_version_list = ['fb_test_directory/_versions/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg']
 
         self.assertEqual(self.f_image.is_version, False)
@@ -392,7 +392,7 @@ def test_version_attributes_3(self):
         }
         filebrowser.base.ADMIN_VERSIONS = ['large']
         # expected test results
-        version_list = ['_versionstestdirectory/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg', '_versionstestdirectory/fb_tmp_dir/fb_tmp_dir_sub/testimage_admin_thumbnail.jpg']
+        version_list = ['_versionstestdirectory/fb_tmp_dir/fb_tmp_dir_sub/testimage_admin_thumbnail.jpg', '_versionstestdirectory/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg']
         admin_version_list = ['_versionstestdirectory/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg']
 
         self.assertEqual(self.f_image.is_version, False)
diff --git a/filebrowser/tests/test_commands.py b/filebrowser/tests/test_commands.py
index 51d7778..5ecaf12 100644
--- a/filebrowser/tests/test_commands.py
+++ b/filebrowser/tests/test_commands.py
@@ -6,7 +6,6 @@
 import posixpath
 import shutil
 import sys
-import StringIO
 
 # DJANGO IMPORTS
 from django.conf import settings
@@ -15,6 +14,7 @@
 from django.utils.encoding import filepath_to_uri
 from django.template import Context, Template, TemplateSyntaxError
 from django.core.management import call_command
+from django.utils.six import StringIO
 
 # FILEBROWSER IMPORTS
 import filebrowser
@@ -100,7 +100,7 @@ def test_fb_version_generate(self):
         # no versions
         self.assertEqual(os.path.exists(os.path.join(settings.MEDIA_ROOT, "fb_test_directory/_versions/fb_tmp_dir/fb_tmp_dir_sub/testimage_large.jpg")), False)
 
-        sys.stdin = StringIO.StringIO("large")
+        sys.stdin = StringIO("large")
         call_command('fb_version_generate', 'fb_test_directory')
 
         # versions
diff --git a/filebrowser/tests/test_sites.py b/filebrowser/tests/test_sites.py
index a2172de..b7fb44e 100644
--- a/filebrowser/tests/test_sites.py
+++ b/filebrowser/tests/test_sites.py
@@ -13,7 +13,6 @@
 import os
 import sys
 import shutil
-from urllib import urlencode
 from types import MethodType
 
 # DJANGO IMPORTS
@@ -22,6 +21,7 @@
 from django.core.urlresolvers import get_resolver, get_urlconf, resolve, reverse
 from django.contrib.admin.templatetags.admin_static import static
 from django.test.utils import override_settings
+from django.utils.six.moves.urllib.parse import urlencode
 
 # FILEBROWSER IMPORTS
 import filebrowser.settings
@@ -388,7 +388,7 @@ def setUp(self):
 def tearDown(self):
     # Delete a left-over tmp directories, if there's any
     if hasattr(self, 'tmpdir') and self.tmpdir:
-        print "Removing left-over tmp dir:", self.tmpdir.path
+        print("Removing left-over tmp dir:", self.tmpdir.path)
         self.site.storage.rmtree(self.tmpdir.path)
 
 
@@ -417,13 +417,13 @@ def runTest(self):
 
 ## Create a test class for each deployed filebrowser site
 for site in all_sites:
-    print 'Creating Test for the FileBrowser site:', site
+    print('Creating Test for the FileBrowser site:', site)
     # Create a subclass of TestCase
     testcase_class = type('TestSite_' + site, (TestCase,), {'site_name': site, 'c': Client(), 'tmpdirs': None})
     # Add setUp, tearDown, and runTest methods
-    setattr(testcase_class, 'setUp', MethodType(setUp, None, testcase_class))
-    setattr(testcase_class, 'tearDown', MethodType(tearDown, None, testcase_class))
-    setattr(testcase_class, 'runTest', MethodType(runTest, None, testcase_class))
+    setattr(testcase_class, 'setUp', setUp)
+    setattr(testcase_class, 'tearDown', tearDown)
+    setattr(testcase_class, 'runTest', runTest)
     # Add the test case class to this module
     setattr(this_module, 'TestSite_' + site, testcase_class)
 
diff --git a/filebrowser/utils.py b/filebrowser/utils.py
index f1f9008..dd805e9 100644
--- a/filebrowser/utils.py
+++ b/filebrowser/utils.py
@@ -5,6 +5,9 @@
 import os
 import unicodedata
 
+# DJANGO IMPORTS
+from django.utils import six
+
 # FILEBROWSER IMPORTS
 from filebrowser.settings import STRICT_PIL, NORMALIZE_FILENAME, CONVERT_FILENAME
 
@@ -27,7 +30,7 @@ def convert_filename(value):
         chunks = value.split(os.extsep)
         normalized = []
         for v in chunks:
-            v = unicodedata.normalize('NFKD', unicode(v)).encode('ascii', 'ignore')
+            v = unicodedata.normalize('NFKD', six.text_type(v)).encode('ascii', 'ignore').decode('ascii')
             v = re.sub(r'[^\w\s-]', '', v).strip()
             normalized.append(v)
 
diff --git a/setup.py b/setup.py
index 3719c3f..ad92c85 100644
--- a/setup.py
+++ b/setup.py
@@ -28,5 +28,4 @@ def read(fname):
     install_requires = [
         'django-grappelli>=2.4,<2.5.99',
     ],
-    use_2to3=True,
-)
\ No newline at end of file
+)

From 8527e881e7e5ffedbcc1e3113593d8d604e70269 Mon Sep 17 00:00:00 2001
From: Patrick Kranzlmueller <sehmaschine@gmail.com>
Date: Sat, 29 Mar 2014 11:34:52 +0100
Subject: [PATCH 3/9] better client-side ajax validation, #218

---
 docs/admin.rst                                |  2 +-
 filebrowser/templates/filebrowser/upload.html |  2 +-
 filebrowser/templatetags/fb_tags.py           | 13 +++++++++----
 filebrowser/tests/test_templatetags.py        | 12 ++++++++----
 4 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/docs/admin.rst b/docs/admin.rst
index 4e4dcde..fa4f804 100644
--- a/docs/admin.rst
+++ b/docs/admin.rst
@@ -182,7 +182,7 @@ All views use the ``staff_member_requird`` and ``path_exists`` decorator in orde
 * Upload, ``fb_upload``
     Multiple upload.
 
-    * Optional query string args: ``dir``
+    * Optional query string args: ``dir``, ``type``
     * Signals: `filebrowser_pre_upload`, `filebrowser_post_upload`
 
 * Edit, ``fb_edit``
diff --git a/filebrowser/templates/filebrowser/upload.html b/filebrowser/templates/filebrowser/upload.html
index 8998813..03c0390 100644
--- a/filebrowser/templates/filebrowser/upload.html
+++ b/filebrowser/templates/filebrowser/upload.html
@@ -58,7 +58,7 @@
                           'csrf_xname': 'X-CSRFToken',
                           'folder': '{{ query.dir|escapejs }}', },
 
-                allowedExtensions: {% get_file_extensions_for_file_type request.GET.type %},
+                allowedExtensions: {% get_file_extensions request.GET %},
                 sizeLimit: {{ settings_var.MAX_UPLOAD_SIZE|unlocalize }},
                 minSizeLimit: 0,
                 debug: false,
diff --git a/filebrowser/templatetags/fb_tags.py b/filebrowser/templatetags/fb_tags.py
index 2259d25..47a8757 100644
--- a/filebrowser/templatetags/fb_tags.py
+++ b/filebrowser/templatetags/fb_tags.py
@@ -143,10 +143,15 @@ def selectable(parser, token):
 register.tag(selectable)
 
 
-def get_file_extensions_for_file_type(query_format):
+def get_file_extensions(qs):
     extensions = []
-    for format in SELECT_FORMATS.get(query_format, []):
-        extensions.extend(EXTENSIONS[format])
+    if "type" in qs and qs.get("type") in SELECT_FORMATS:
+        for format in SELECT_FORMATS.get(qs.get("type"), []):
+            extensions.extend(EXTENSIONS[format])
+    else:
+        for k, v in EXTENSIONS.items():
+            for item in v:
+                if item: extensions.append(item)
     return extensions
 
-register.simple_tag(get_file_extensions_for_file_type)
+register.simple_tag(get_file_extensions)
diff --git a/filebrowser/tests/test_templatetags.py b/filebrowser/tests/test_templatetags.py
index 533697d..0db4a84 100644
--- a/filebrowser/tests/test_templatetags.py
+++ b/filebrowser/tests/test_templatetags.py
@@ -2,15 +2,19 @@
 
 # DJANGO IMPORTS
 from django.test import TestCase
+from django.http import QueryDict
 
 # FILEBROWSER IMPORTS
-from filebrowser.templatetags.fb_tags import get_file_extensions_for_file_type
+from filebrowser.templatetags.fb_tags import get_file_extensions
 
 
 class TemplateTagsTests(TestCase):
-    def test_get_file_extensions_for_file_type(self):
-        self.assertEqual(get_file_extensions_for_file_type(''), [])
+    def test_get_file_extensions(self):
+        self.assertEqual(get_file_extensions(''),
+            ['.pdf', '.doc', '.rtf', '.txt', '.xls', '.csv', '.docx', '.mov', \
+            '.wmv', '.mpeg', '.mpg', '.avi', '.rm', '.jpg', '.jpeg', '.gif', '.png', \
+            '.tif', '.tiff', '.mp3', '.mp4', '.wav', '.aiff', '.midi', '.m4p'])
         self.assertEqual(
-            get_file_extensions_for_file_type('image'),
+            get_file_extensions(QueryDict('type=image')),
             ['.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff']
         )

From c5b4162038d792f0de8833f7c0e0b20d69954f71 Mon Sep 17 00:00:00 2001
From: Patrick Kranzlmueller <sehmaschine@gmail.com>
Date: Sat, 29 Mar 2014 14:31:10 +0100
Subject: [PATCH 4/9] finally fixed convert/normalize tests, #213 #209

---
 docs/changelog.rst                            |  10 +--
 ...{TEST IMAGE ***.jpg => TEST IMAGE 000.jpg} | Bin
 filebrowser/tests/test_sites.py               |  78 ++++++++++--------
 3 files changed, 48 insertions(+), 40 deletions(-)
 rename filebrowser/static/filebrowser/img/{TEST IMAGE ***.jpg => TEST IMAGE 000.jpg} (100%)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 49e2940..b424904 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -11,11 +11,11 @@ Changelog
 3.5.5 (not yet released)
 ------------------------
 
-* Improved tests with convert/normalize (removed special chars from test filename).
-* Fixed file selection after using search box (CKEditor).
-* Removed encoding of file URIs with CKEditor.
-* Added client-side (JavaScript) file extension validation to the AJAX uploader.
-* Added experimental Python 3.3 support.
+* New: Added client-side (JavaScript) file extension validation to the AJAX uploader.
+* New: Added experimental Python 3.3 support.
+* Improved: Tests with convert/normalize (removed special chars from test filename).
+* Fixed: File selection after using search box (CKEditor).
+* Fixed: Removed encoding of file URIs with CKEditor.
 
 3.5.4 (February 21st, 2014)
 ---------------------------
diff --git a/filebrowser/static/filebrowser/img/TEST IMAGE ***.jpg b/filebrowser/static/filebrowser/img/TEST IMAGE 000.jpg
similarity index 100%
rename from filebrowser/static/filebrowser/img/TEST IMAGE ***.jpg
rename to filebrowser/static/filebrowser/img/TEST IMAGE 000.jpg
diff --git a/filebrowser/tests/test_sites.py b/filebrowser/tests/test_sites.py
index b7fb44e..6d9a91a 100644
--- a/filebrowser/tests/test_sites.py
+++ b/filebrowser/tests/test_sites.py
@@ -185,8 +185,8 @@ def test_convert_normalize(test):
     """
 
     url = reverse('%s:fb_do_upload' % test.site_name)
-    url = '?'.join([url, urlencode({'folder': test.tmpdir.path_relative_directory, 'qqfile': 'TEST IMAGE ***.jpg'})])
-    f = open(os.path.join(FILEBROWSER_PATH, u'static/filebrowser/img/TEST IMAGE ***.jpg'), "rb")
+    url = '?'.join([url, urlencode({'folder': test.tmpdir.path_relative_directory, 'qqfile': 'TEST IMAGE 000.jpg'})])
+    f = open(os.path.join(FILEBROWSER_PATH, u'static/filebrowser/img/TEST IMAGE 000.jpg'), "rb")
 
     # Save settings
     oe = filebrowser.sites.OVERWRITE_EXISTING
@@ -198,84 +198,92 @@ def test_convert_normalize(test):
     filebrowser.sites.NORMALIZE_FILENAME = False
     filebrowser.utils.CONVERT_FILENAME = False
     filebrowser.utils.NORMALIZE_FILENAME = False
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'TEST IMAGE ***.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'TEST IMAGE 000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # OVERWRITE true
     filebrowser.sites.OVERWRITE_EXISTING = True
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'TEST IMAGE ***.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'TEST IMAGE 000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    path = os.path.join(test.tmpdir.path, 'TEST IMAGE ***_1.jpg')
+    path = os.path.join(test.tmpdir.path, 'TEST IMAGE 000_1.jpg')
     test.assertFalse(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # OVERWRITE false
     filebrowser.sites.OVERWRITE_EXISTING = False
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'TEST IMAGE ***.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'TEST IMAGE 000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    path = os.path.join(test.tmpdir.path, 'TEST IMAGE ***_1.jpg')
+    path = os.path.join(test.tmpdir.path, 'TEST IMAGE 000_1.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'TEST IMAGE ***_1.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'TEST IMAGE 000_1.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # Set CONVERT_FILENAME, NORMALIZE_FILENAME
     filebrowser.sites.CONVERT_FILENAME = True
     filebrowser.sites.NORMALIZE_FILENAME = False
     filebrowser.utils.CONVERT_FILENAME = True
     filebrowser.utils.NORMALIZE_FILENAME = False
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'test_image_***.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'test_image_000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'TEST IMAGE ***_1.jpg', u'test_image_***.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'TEST IMAGE 000_1.jpg', u'test_image_000.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # OVERWRITE true
     filebrowser.sites.OVERWRITE_EXISTING = True
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'test_image_***.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'test_image_000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    path = os.path.join(test.tmpdir.path, 'test_image_***_1.jpg')
+    path = os.path.join(test.tmpdir.path, 'test_image_000_1.jpg')
     test.assertFalse(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'TEST IMAGE ***_1.jpg', u'test_image_***.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'TEST IMAGE 000_1.jpg', u'test_image_000.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # OVERWRITE false
     filebrowser.sites.OVERWRITE_EXISTING = False
-    response = test.c.post(url, data={'qqfile': 'TTEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'test_image_***.jpg')
+    response = test.c.post(url, data={'qqfile': 'TTEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'test_image_000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    path = os.path.join(test.tmpdir.path, 'test_image_***_1.jpg')
+    path = os.path.join(test.tmpdir.path, 'test_image_000_1.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'TEST IMAGE ***_1.jpg', u'test_image_***.jpg', u'test_image_***_1.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'TEST IMAGE 000_1.jpg', u'test_image_000.jpg', u'test_image_000_1.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # Set CONVERT_FILENAME, NORMALIZE_FILENAME
     filebrowser.sites.CONVERT_FILENAME = True
     filebrowser.sites.NORMALIZE_FILENAME = True
     filebrowser.utils.CONVERT_FILENAME = True
     filebrowser.utils.NORMALIZE_FILENAME = True
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'test_image.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'test_image_000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'TEST IMAGE ***_1.jpg', u'test_image.jpg', u'test_image_***.jpg', u'test_image_***_1.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'TEST IMAGE 000_1.jpg', u'test_image_000.jpg', u'test_image_000_1.jpg', u'test_image_000_2.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # OVERWRITE true
     filebrowser.sites.OVERWRITE_EXISTING = True
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'test_image.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'test_image_000.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    path = os.path.join(test.tmpdir.path, 'test_image_1.jpg')
+    path = os.path.join(test.tmpdir.path, 'test_image_000_1.jpg')
+    test.assertTrue(test.site.storage.exists(path))
+    path = os.path.join(test.tmpdir.path, 'test_image_000_2.jpg')
+    test.assertTrue(test.site.storage.exists(path))
+    path = os.path.join(test.tmpdir.path, 'test_image_000_3.jpg')
     test.assertFalse(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'TEST IMAGE ***_1.jpg', u'test_image.jpg', u'test_image_***.jpg', u'test_image_***_1.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'TEST IMAGE 000_1.jpg', u'test_image_000.jpg', u'test_image_000_1.jpg', u'test_image_000_2.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # OVERWRITE false
     filebrowser.sites.OVERWRITE_EXISTING = False
-    response = test.c.post(url, data={'qqfile': 'TEST IMAGE ***.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
-    path = os.path.join(test.tmpdir.path, 'test_image.jpg')
+    response = test.c.post(url, data={'qqfile': 'TEST IMAGE 000.jpg', 'file': f}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    path = os.path.join(test.tmpdir.path, 'test_image_000.jpg')
+    test.assertTrue(test.site.storage.exists(path))
+    path = os.path.join(test.tmpdir.path, 'test_image_000_1.jpg')
+    test.assertTrue(test.site.storage.exists(path))
+    path = os.path.join(test.tmpdir.path, 'test_image_000_2.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    path = os.path.join(test.tmpdir.path, 'test_image_1.jpg')
+    path = os.path.join(test.tmpdir.path, 'test_image_000_3.jpg')
     test.assertTrue(test.site.storage.exists(path))
-    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE ***.jpg', u'TEST IMAGE ***_1.jpg', u'test_image.jpg', u'test_image_***.jpg', u'test_image_***_1.jpg', u'test_image_1.jpg', u'testimage.jpg', u'testimage_1.jpg'])
+    test.assertEqual(sorted(test.site.storage.listdir(test.tmpdir)[1]), [u'TEST IMAGE 000.jpg', u'TEST IMAGE 000_1.jpg', u'test_image_000.jpg', u'test_image_000_1.jpg', u'test_image_000_2.jpg', u'test_image_000_3.jpg', u'testimage.jpg', u'testimage_1.jpg'])
 
     # Reset settings
     filebrowser.sites.CONVERT_FILENAME = cf

From 24ccf66bc84919b0afd6270590a7ef20528f860d Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Thu, 3 Apr 2014 12:36:24 -0400
Subject: [PATCH 5/9] Fixed test_get_file_extensions() on Python 3.

(removed dependency on dictionary iteration order)
---
 filebrowser/tests/test_templatetags.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/filebrowser/tests/test_templatetags.py b/filebrowser/tests/test_templatetags.py
index 0db4a84..2dfb70c 100644
--- a/filebrowser/tests/test_templatetags.py
+++ b/filebrowser/tests/test_templatetags.py
@@ -10,10 +10,10 @@
 
 class TemplateTagsTests(TestCase):
     def test_get_file_extensions(self):
-        self.assertEqual(get_file_extensions(''),
-            ['.pdf', '.doc', '.rtf', '.txt', '.xls', '.csv', '.docx', '.mov', \
-            '.wmv', '.mpeg', '.mpg', '.avi', '.rm', '.jpg', '.jpeg', '.gif', '.png', \
-            '.tif', '.tiff', '.mp3', '.mp4', '.wav', '.aiff', '.midi', '.m4p'])
+        self.assertEqual(sorted(get_file_extensions('')),
+            sorted(['.pdf', '.doc', '.rtf', '.txt', '.xls', '.csv', '.docx', '.mov',
+            '.wmv', '.mpeg', '.mpg', '.avi', '.rm', '.jpg', '.jpeg', '.gif', '.png',
+            '.tif', '.tiff', '.mp3', '.mp4', '.wav', '.aiff', '.midi', '.m4p']))
         self.assertEqual(
             get_file_extensions(QueryDict('type=image')),
             ['.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff']

From 8faf0a99d86b3870bd2f279d678b0c4cea8defab Mon Sep 17 00:00:00 2001
From: Jason Liu <jxnl@users.noreply.github.com>
Date: Sun, 13 Apr 2014 00:43:12 -0700
Subject: [PATCH 6/9] Corrected Typos

Fixed a spelling error and removed a double `import`
---
 docs/admin.rst | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/admin.rst b/docs/admin.rst
index fa4f804..cb02258 100644
--- a/docs/admin.rst
+++ b/docs/admin.rst
@@ -22,7 +22,7 @@ FileBrowser Site
 
 .. py:class:: FileBrowserSite(name=None, app_name='filebrowser', storage=default_storage)
     
-    Respresens the FileBrowser admin application (similar to Django's admin site).
+    Respresents the FileBrowser admin application (similar to Django's admin site).
 
     :param name: A name for the site, defaults to None.
     :param app_name: Defaults to 'filebrowser'.
@@ -41,7 +41,7 @@ Now you are able to browse the location defined with the storage engine associat
 .. code-block:: python
 
     from django.core.files.storage import DefaultStorage
-    from filebrowser.sites import import FileBrowserSite
+    from filebrowser.sites import FileBrowserSite
     
     # Default FileBrowser site
     site = FileBrowserSite(name='filebrowser', storage=DefaultStorage())
@@ -285,4 +285,4 @@ Here's a small example for using the above Signals::
         print "Filesize:", kwargs['file'].filesize
         print "Orientation:", kwargs['file'].orientation
         print "Extension:", kwargs['file'].extension
-    signals.filebrowser_post_upload.connect(post_upload_callback)
\ No newline at end of file
+    signals.filebrowser_post_upload.connect(post_upload_callback)

From 943024195798edcaf696230a67c1e4731bfe78f8 Mon Sep 17 00:00:00 2001
From: Patrick Kranzlmueller <sehmaschine@gmail.com>
Date: Sun, 13 Apr 2014 13:10:26 +0200
Subject: [PATCH 7/9] compatibility with tests and older django versions (1.4.)

---
 filebrowser/tests/test_sites.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/filebrowser/tests/test_sites.py b/filebrowser/tests/test_sites.py
index 6d9a91a..9611c1b 100644
--- a/filebrowser/tests/test_sites.py
+++ b/filebrowser/tests/test_sites.py
@@ -21,7 +21,10 @@
 from django.core.urlresolvers import get_resolver, get_urlconf, resolve, reverse
 from django.contrib.admin.templatetags.admin_static import static
 from django.test.utils import override_settings
-from django.utils.six.moves.urllib.parse import urlencode
+try:
+    from django.utils.six.moves.urllib.parse import urlencode
+except:
+    from django.utils.http import urlencode
 
 # FILEBROWSER IMPORTS
 import filebrowser.settings

From 04920154abb1ab15176ee47a98c67094344f5fba Mon Sep 17 00:00:00 2001
From: Patrick Kranzlmueller <sehmaschine@gmail.com>
Date: Sun, 13 Apr 2014 13:14:52 +0200
Subject: [PATCH 8/9] final docs update for 3.5.5

---
 docs/changelog.rst | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index b424904..a5d6a46 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -8,7 +8,10 @@
 Changelog
 =========
 
-3.5.5 (not yet released)
+3.5.6 (not yet released)
+------------------------
+
+3.5.5 (April 13th, 2014)
 ------------------------
 
 * New: Added client-side (JavaScript) file extension validation to the AJAX uploader.

From 5147880804ec161c37516739d7dc0af433abd4a8 Mon Sep 17 00:00:00 2001
From: Patrick Kranzlmueller <sehmaschine@gmail.com>
Date: Sun, 13 Apr 2014 13:18:57 +0200
Subject: [PATCH 9/9] final version update (3.5.5)

---
 README.rst     | 4 ++--
 docs/index.rst | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/README.rst b/README.rst
index e5b1a42..3656afa 100644
--- a/README.rst
+++ b/README.rst
@@ -31,7 +31,7 @@ https://www.transifex.com/projects/p/django-filebrowser/
 Releases
 --------
 
-* FileBrowser 3.5.5 (Development Version, not yet released, see Branch Stable/3.5.x)
-* FileBrowser 3.5.4 (February 21st, 2014): Compatible with Django 1.4/1.5/1.6
+* FileBrowser 3.5.6 (Development Version, not yet released, see Branch Stable/3.5.x)
+* FileBrowser 3.5.5 (April 13th, 2014): Compatible with Django 1.4/1.5/1.6
 
 Older versions are availabe at GitHub, but are not supported anymore.
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index 57c9d14..81e1b91 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -90,7 +90,7 @@ Use the `FileBrowser Google Group <http://groups.google.com/group/django-filebro
 Versions and Compatibility
 --------------------------
 
-* FileBrowser 3.5.5 (Development Version, not yet released, see Branch Stable/3.5.x)
-* FileBrowser 3.5.4 (February 21st, 2014): Compatible with Django 1.4/1.5/1.6
+* FileBrowser 3.5.6 (Development Version, not yet released, see Branch Stable/3.5.x)
+* FileBrowser 3.5.5 (April 13th, 2014): Compatible with Django 1.4/1.5/1.6
 
 Older versions are availabe at GitHub, but are not supported anymore.
\ No newline at end of file