Repository: flask-admin/flask-admin

Issue:
File upload custom actions broken redirect
When adding a custom action (via @ action) in a custom FileAdmin view - after the action finishes the redirection is broken (500 internal server error). Not sure what's causing this yet.



Discussion:
Do you have a stack trace?

Here you go:

```
Traceback (most recent call last):
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\_compat.py", line 33, in reraise
    raise value
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\vToMy\PycharmProjects\flask-admin\flask_admin\base.py", line 68, in inner
    return self._run_view(f, *args, **kwargs)
  File "C:\Users\vToMy\PycharmProjects\flask-admin\flask_admin\base.py", line 367, in _run_view
    return fn(self, *args, **kwargs)
  File "C:\Users\vToMy\PycharmProjects\flask-admin\flask_admin\contrib\fileadmin.py", line 961, in action_view
    return self.handle_action()
  File "C:\Users\vToMy\PycharmProjects\flask-admin\flask_admin\actions.py", line 121, in handle_action
    url = get_redirect_target() or self.get_url('.index_view')
  File "C:\Users\vToMy\PycharmProjects\flask-admin\flask_admin\base.py", line 389, in get_url
    return url_for(endpoint, **kwargs)
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\helpers.py", line 312, in url_for
    return appctx.app.handle_url_build_error(error, endpoint, values)
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\app.py", line 1641, in handle_url_build_error
    reraise(exc_type, exc_value, tb)
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\_compat.py", line 33, in reraise
    raise value
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\flask\helpers.py", line 305, in url_for
    force_external=external)
  File "C:\Users\vToMy\AppData\Local\Programs\Python\Python35-32\lib\site-packages\werkzeug\routing.py", line 1678, in build
    raise BuildError(endpoint, values, method)
werkzeug.routing.BuildError: ('fileuploadmodelview.index_view', {}, None)
```

This is how I created the instance:

```
FileAdmin(upload_dir, '/static/', url='/upload', name='Upload Files', category=category)
```

Figured out the cause - FileAdmin defines the index endpoint as ".index" where as the action tries to redirect to ".index_view". Here is a patch if anyone finds it useful:

```
    def get_url(self, endpoint, **kwargs):
        if endpoint == '.index_view':
            endpoint = '.index'
        return super().get_url(endpoint, **kwargs)
```

I recommend just changing the ".index" to ".index_view" in FileAdmin to follow the convention.


Resolving patch:
diff --git a/flask_admin/contrib/fileadmin/__init__.py b/flask_admin/contrib/fileadmin/__init__.py
--- a/flask_admin/contrib/fileadmin/__init__.py
+++ b/flask_admin/contrib/fileadmin/__init__.py
@@ -1,3 +1,4 @@
+import warnings
 from datetime import datetime
 import os
 import os.path as op
@@ -696,9 +697,15 @@ def _get_breadcrumbs(self, path):
             breadcrumbs.append((n, self._separator.join(accumulator)))
         return breadcrumbs
 
+    @expose('/old_index')
+    @expose('/old_b/<path:path>')
+    def index(self, path=None):
+        warnings.warn('deprecated: use index_view instead.', DeprecationWarning)
+        return redirect(self.get_url('.index_view', path=path))
+
     @expose('/')
     @expose('/b/<path:path>')
-    def index(self, path=None):
+    def index_view(self, path=None):
         """
             Index view method
 
@@ -714,7 +721,7 @@ def index(self, path=None):
         base_path, directory, path = self._normalize_path(path)
         if not self.is_accessible_path(path):
             flash(gettext('Permission denied.'), 'error')
-            return redirect(self._get_dir_url('.index'))
+            return redirect(self._get_dir_url('.index_view'))
 
         # Get directory listing
         items = []
@@ -771,11 +778,11 @@ def upload(self, path=None):
 
         if not self.can_upload:
             flash(gettext('File uploading is disabled.'), 'error')
-            return redirect(self._get_dir_url('.index', path))
+            return redirect(self._get_dir_url('.index_view', path))
 
         if not self.is_accessible_path(path):
             flash(gettext('Permission denied.'), 'error')
-            return redirect(self._get_dir_url('.index'))
+            return redirect(self._get_dir_url('.index_view'))
 
         form = self.upload_form()
         if self.validate_form(form):
@@ -783,7 +790,7 @@ def upload(self, path=None):
                 self._save_form_files(directory, path, form)
                 flash(gettext('Successfully saved file: %(name)s',
                               name=form.upload.data.filename))
-                return redirect(self._get_dir_url('.index', path))
+                return redirect(self._get_dir_url('.index_view', path))
             except Exception as ex:
                 flash(gettext('Failed to save file: %(error)s', error=ex), 'error')
 
@@ -812,7 +819,7 @@ def download(self, path=None):
         # backward compatibility with base_url
         base_url = self.get_base_url()
         if base_url:
-            base_url = urljoin(self.get_url('.index'), base_url)
+            base_url = urljoin(self.get_url('.index_view'), base_url)
             return redirect(urljoin(base_url, path))
 
         return self.storage.send_file(directory)
@@ -829,7 +836,7 @@ def mkdir(self, path=None):
         # Get path and verify if it is valid
         base_path, directory, path = self._normalize_path(path)
 
-        dir_url = self._get_dir_url('.index', path)
+        dir_url = self._get_dir_url('.index_view', path)
 
         if not self.can_mkdir:
             flash(gettext('Directory creation is disabled.'), 'error')
@@ -837,7 +844,7 @@ def mkdir(self, path=None):
 
         if not self.is_accessible_path(path):
             flash(gettext('Permission denied.'), 'error')
-            return redirect(self._get_dir_url('.index'))
+            return redirect(self._get_dir_url('.index_view'))
 
         form = self.name_form()
 
@@ -876,9 +883,9 @@ def delete(self):
 
         path = form.path.data
         if path:
-            return_url = self._get_dir_url('.index', op.dirname(path))
+            return_url = self._get_dir_url('.index_view', op.dirname(path))
         else:
-            return_url = self.get_url('.index')
+            return_url = self.get_url('.index_view')
 
         if self.validate_form(form):
             # Get path and verify if it is valid
@@ -890,7 +897,7 @@ def delete(self):
 
             if not self.is_accessible_path(path):
                 flash(gettext('Permission denied.'), 'error')
-                return redirect(self._get_dir_url('.index'))
+                return redirect(self._get_dir_url('.index_view'))
 
             if self.storage.is_dir(full_path):
                 if not self.can_delete_dirs:
@@ -927,9 +934,9 @@ def rename(self):
         if path:
             base_path, full_path, path = self._normalize_path(path)
 
-            return_url = self._get_dir_url('.index', op.dirname(path))
+            return_url = self._get_dir_url('.index_view', op.dirname(path))
         else:
-            return redirect(self.get_url('.index'))
+            return redirect(self.get_url('.index_view'))
 
         if not self.can_rename:
             flash(gettext('Renaming is disabled.'), 'error')
@@ -937,7 +944,7 @@ def rename(self):
 
         if not self.is_accessible_path(path):
             flash(gettext('Permission denied.'), 'error')
-            return redirect(self._get_dir_url('.index'))
+            return redirect(self._get_dir_url('.index_view'))
 
         if not self.storage.path_exists(full_path):
             flash(gettext('Path does not exist.'), 'error')
@@ -978,7 +985,7 @@ def edit(self):
 
         path = request.args.getlist('path')
         if not path:
-            return redirect(self.get_url('.index'))
+            return redirect(self.get_url('.index_view'))
 
         if len(path) > 1:
             next_url = self.get_url('.edit', path=path[1:])
@@ -989,9 +996,9 @@ def edit(self):
 
         if not self.is_accessible_path(path) or not self.is_file_editable(path):
             flash(gettext('Permission denied.'), 'error')
-            return redirect(self._get_dir_url('.index'))
+            return redirect(self._get_dir_url('.index_view'))
 
-        dir_url = self._get_dir_url('.index', op.dirname(path))
+        dir_url = self._get_dir_url('.index_view', op.dirname(path))
         next_url = next_url or dir_url
 
         form = self.edit_form()


Test patch: