Repository: ckan/ckan

Issue:
Allow uploaders to only override asset / resource uploading
**CKAN version**
2.8.7, 2.9.2

**Describe the bug**
Currently, CKAN allows handling both image / asset uploading and resource uploading via the `IUploader` plugin interface. That interface allows plugins to return `None` from `get_uploader()` / `get_resource_uploader()` so that to signify that this uploader in not meant to handle that type of upload. 

However, when trying to use two different plugins each implementing a different type of uploader, one may encounter a problem where depending on the order of loading, there could be conflicts or CKAN might fall back to it's built-in uploader for no good reason. 

This is because of this logic in https://github.com/ckan/ckan/blob/5a4af6fc3bee81331fbc19c632bf6089dd1aa18a/ckan/lib/uploader.py#L52-L57 - it will iterate over *all* uploaders and call `get_uploader()`. If the *last one* returns None, it will fall back to CKAN's built in. It should pick either the first or last uploader to return a non-`None` value instead. 

Same happens for `get_resource_uploader()` in https://github.com/ckan/ckan/blob/5a4af6fc3bee81331fbc19c632bf6089dd1aa18a/ckan/lib/uploader.py#L65-L70.

This is an easy fix, but I'm not sure if the preferred logic is to pick the *first* or *last* plugin to implement an uploader for that file type. 

**Steps to reproduce**
Install two plugins, each supporting a different kind of uploader, and see them conflict, and see CKAN fall back to it's internal uploader for one of the file types.

**Expected behavior**
CKAN picks the first / last uploader plugin that supports this file type. 

**Additional details**
I have encountered this when working on ckanext-asset-storage, but I suppose it can affect other uploaders / custom built ones as well.


Discussion:
You are right that this is bad behavior. The convention in other plugin hooks across CKAN is that first plugin wins, so that should be the case here as well. If anyone wants to pick this up this should be an easy fix.
@amercader , I am interested to working on this issue. What is your suggestion to do in this issue?
@Gauravp-NEC as the convention in CKAN is "first plugin wins", we need to break the loop whenever the first plugin returns a valid `upload` value:

```python
 for plugin in plugins.PluginImplementations(plugins.IUploader): 
     upload = plugin.get_uploader(upload_to, old_filename)
     if upload:
        break
```

and  

```python
 for plugin in plugins.PluginImplementations(plugins.IUploader): 
     upload = plugin.get_resource_uploader(data_dict) 
     if upload:
        break
```

Resolving patch:
diff --git a/ckan/lib/uploader.py b/ckan/lib/uploader.py
--- a/ckan/lib/uploader.py
+++ b/ckan/lib/uploader.py
@@ -52,6 +52,8 @@ def get_uploader(upload_to, old_filename=None):
     upload = None
     for plugin in plugins.PluginImplementations(plugins.IUploader):
         upload = plugin.get_uploader(upload_to, old_filename)
+        if upload:
+            break
 
     # default uploader
     if upload is None:
@@ -65,6 +67,8 @@ def get_resource_uploader(data_dict):
     upload = None
     for plugin in plugins.PluginImplementations(plugins.IUploader):
         upload = plugin.get_resource_uploader(data_dict)
+        if upload:
+            break
 
     # default uploader
     if upload is None:


Test patch: