Repository: fossasia/open-event-server

Issue:
Store data on Amazon S3
- Store uploads and other files on Amazon S3.
- When a data is uploaded to server, upload it to S3 and store links in the database. 

Related discussion #1015 



Discussion:
Status update - I have finished the coding part and tested it with profile image upload. It is working but I haven't written the docs so I will send the PR tomorrow as I am busy rest of today. 


Resolving patch:
diff --git a/open_event/helpers/storage.py b/open_event/helpers/storage.py
new file mode 100644
--- /dev/null
+++ b/open_event/helpers/storage.py
@@ -0,0 +1,65 @@
+import os
+from boto.s3.connection import S3Connection
+from boto.s3.key import Key
+
+
+BUCKET_NAME = os.environ.get('BUCKET_NAME')
+if BUCKET_NAME:
+    S3_URL = 'http://%s.s3.amazonaws.com/' % (BUCKET_NAME)
+AWS_KEY = os.environ.get('AWS_KEY')
+AWS_SECRET = os.environ.get('AWS_SECRET')
+
+
+def upload(file, key, **kwargs):
+    """
+    Upload handler
+    """
+    if BUCKET_NAME and AWS_KEY and AWS_SECRET:
+        return upload_to_aws(file, key, **kwargs)
+    else:
+        return upload_local(file, key, **kwargs)
+
+
+def upload_local(file, key, **kwargs):
+    """
+    Uploads file locally. Base dir - static/media/
+    """
+    basename, ext = os.path.splitext(file.filename)
+    file_path = 'static/media/' + key + ext
+    dir_path = file_path.rsplit('/', 1)[0]
+    if not os.path.isdir(dir_path):
+        os.makedirs(dir_path)
+    file.save(file_path)
+    return '/serve_' + file_path
+
+
+def upload_to_aws(file, key, acl='public-read'):
+    """
+    Uploads to AWS at key
+    http://{bucket}.s3.amazonaws.com/{key}
+    """
+    conn = S3Connection(AWS_KEY, AWS_SECRET)
+    bucket = conn.get_bucket(BUCKET_NAME)
+    k = Key(bucket)
+    # generate key using key + extension
+    basename, ext = os.path.splitext(file.filename)  # includes dot
+    k.key = key
+    key_name = key.rsplit('/')[-1]
+    # set object settings
+    file_data = file.read()
+    size = len(file_data)
+    sent = k.set_contents_from_string(
+        file_data,
+        headers={
+            'Content-Disposition': 'attachment; filename=%s%s' % (key_name, ext)
+        }
+    )
+    k.set_acl(acl)
+    if sent == size:
+        return S3_URL + k.key
+    return False
+
+
+if __name__ == '__main__':
+    f = open('storage.py', 'r')
+    upload_to_aws(f, 'test')
diff --git a/open_event/views/admin/models_views/profile.py b/open_event/views/admin/models_views/profile.py
--- a/open_event/views/admin/models_views/profile.py
+++ b/open_event/views/admin/models_views/profile.py
@@ -1,13 +1,11 @@
-import os
-
 from flask import request, url_for, redirect
 from flask.ext.admin import BaseView
 from flask_admin import expose
 from flask.ext import login
-from werkzeug.utils import secure_filename
 
 from ....helpers.data import DataManager
 from ....helpers.data_getter import DataGetter
+from open_event.helpers.storage import upload
 
 
 class ProfileView(BaseView):
@@ -29,9 +27,8 @@ def index_view(self):
     def edit_view(self):
         if request.method == 'POST':
             avatar_img = request.files['avatar']
-            filename = secure_filename(avatar_img.filename)
-            avatar_img.save(os.path.join(os.path.realpath('.') + '/static/media/image/', filename))
-            profile = DataManager.update_user(request.form, login.current_user.id, avatar_img.filename)
+            url = upload(avatar_img, 'users/%d/avatar' % login.current_user.id)
+            profile = DataManager.update_user(request.form, login.current_user.id, url)
             return redirect(url_for('.index_view'))
         profile = DataGetter.get_user(login.current_user.id)
         return self.render('/gentelella/admin/profile/edit.html', profile=profile)
diff --git a/open_event/views/api_v1_views.py b/open_event/views/api_v1_views.py
--- a/open_event/views/api_v1_views.py
+++ b/open_event/views/api_v1_views.py
@@ -440,10 +440,22 @@ def send_cal(filename):
     return send_from_directory(os.path.realpath('.') + '/static/', filename)
 
 
+@app.route('/serve_static/<path:filename>')
 @app.route('/documentation')
+def serve_static(filename):
+    """
+    Sends static file
+    Note - This is not the most efficient method but since only development
+    system will be using it, it's OK.
+    Static files in production are stored on AWS so this won't be used
+    """
+    return send_from_directory(os.path.realpath('.') + '/static/', filename)
+
+
 def documentation():
     return auto.html()
 
+
 @app.route('/migrate/', methods=('GET', 'POST'))
 def run_migrations():
     try:


Test patch: