From 55076beae41c986dd7b684d49008ae43fc24978e Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Fri, 24 Aug 2012 15:59:44 +0530 Subject: [PATCH 001/541] Add testing.py --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 379571ab..0586dace 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ dist/ .webassets-cache nosetests.xml .sass-cache -instance/testing.py instance/production.py instance/development.py baseframe-packed.css From 5b4f063833ab3e364b7fb1db1e69f2f6bde649ec Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Fri, 24 Aug 2012 16:01:31 +0530 Subject: [PATCH 002/541] Add tests! --- imgee/tests/__init__.py | 1 + imgee/tests/fixtures.py | 10 ++++++++++ imgee/tests/test_listing.py | 28 ++++++++++++++++++++++++++++ runtests.py | 1 + 4 files changed, 40 insertions(+) create mode 100644 imgee/tests/__init__.py create mode 100644 imgee/tests/fixtures.py create mode 100644 imgee/tests/test_listing.py diff --git a/imgee/tests/__init__.py b/imgee/tests/__init__.py new file mode 100644 index 00000000..8a7593ae --- /dev/null +++ b/imgee/tests/__init__.py @@ -0,0 +1 @@ +from test_listing import * diff --git a/imgee/tests/fixtures.py b/imgee/tests/fixtures.py new file mode 100644 index 00000000..1dbcde99 --- /dev/null +++ b/imgee/tests/fixtures.py @@ -0,0 +1,10 @@ +from imgee.models import db, User + +def create_user(self): + user = User(username=u'nigelb', userid=u'WzTwFMKEQ5yozIkDj7feCw', + lastuser_token_scope=u'id', lastuser_token_type=u'bearer', + lastuser_token=u'-zhQm-NRSRaVfLQnebR3Mw', + fullname=u'Nigel Babu', id=1) + db.session.add(user) + db.session.commit() + return user diff --git a/imgee/tests/test_listing.py b/imgee/tests/test_listing.py new file mode 100644 index 00000000..aaab42a3 --- /dev/null +++ b/imgee/tests/test_listing.py @@ -0,0 +1,28 @@ +import os +from imgee import init_for, app +from imgee.models import db, User +import unittest +import tempfile + +class UploadTestCase(unittest.TestCase): + + def setUp(self): + init_for('testing') + app.config['TESTING'] = True + self.app = app.test_client() + db.create_all() + + def tearDown(self): + db.drop_all() + + def create_user(self): + user = User(username=u'nigelb', userid=u'WzTwFMKEQ5yozIkDj7feCw', + lastuser_token_scope=u'id', lastuser_token_type=u'bearer', + lastuser_token=u'-zhQm-NRSRaVfLQnebR3Mw', + fullname=u'Nigel Babu', id=1) + db.session.add(user) + db.session.commit() + + def test_listing_without_login(self): + rv = self.app.get('/list') + assert "You should be redirected automatically to target URL" in rv.data diff --git a/runtests.py b/runtests.py index af8b126d..09f59e13 100755 --- a/runtests.py +++ b/runtests.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # -*- coding: iso-8859-15 -*- import nose From 2a61eb284be343481bd385bcc3c8f4c045072ac6 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 30 Aug 2012 21:32:58 +0530 Subject: [PATCH 003/541] Create thumbnail model --- imgee/models/__init__.py | 2 +- imgee/models/thumbnail.py | 9 +++++++++ imgee/models/uploaded_file.py | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 imgee/models/thumbnail.py diff --git a/imgee/models/__init__.py b/imgee/models/__init__.py index 05c6ff47..05e9691e 100644 --- a/imgee/models/__init__.py +++ b/imgee/models/__init__.py @@ -2,9 +2,9 @@ from flask.ext.sqlalchemy import SQLAlchemy from imgee import app -from coaster.sqlalchemy import IdMixin, TimestampMixin, BaseMixin, BaseNameMixin db = SQLAlchemy(app) from imgee.models.user import * from imgee.models.uploaded_file import * +from imgee.models.thumbnail import * diff --git a/imgee/models/thumbnail.py b/imgee/models/thumbnail.py new file mode 100644 index 00000000..b8a372bd --- /dev/null +++ b/imgee/models/thumbnail.py @@ -0,0 +1,9 @@ +from coaster.sqlalchemy import BaseMixin +from imgee.models import db + + +class Thumbnail(BaseMixin, db.Model): + __tablename__ = 'file' + + name = db.Column(db.Unicode(250), nullable=False, unique=True) + uploaded_file_id = db.Column(db.Integer, db.ForeignKey('file.id')) diff --git a/imgee/models/uploaded_file.py b/imgee/models/uploaded_file.py index 07a9b70f..fd99b6aa 100644 --- a/imgee/models/uploaded_file.py +++ b/imgee/models/uploaded_file.py @@ -7,4 +7,5 @@ class UploadedFile(BaseNameMixin, db.Model): __tablename__ = 'file' + thumbnails = db.relationship('Thumbnail', backref='uploadedfile') user_id = db.Column(db.Integer, db.ForeignKey('user.id')) From eaa9de7bc7cc54f755a70dc02e3144b47bb82599 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 30 Aug 2012 22:00:13 +0530 Subject: [PATCH 004/541] Rename the thumbnail table --- imgee/models/thumbnail.py | 2 +- imgee/storage.py | 14 ++++++++++++++ imgee/views/index.py | 11 +++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/imgee/models/thumbnail.py b/imgee/models/thumbnail.py index b8a372bd..1fe02724 100644 --- a/imgee/models/thumbnail.py +++ b/imgee/models/thumbnail.py @@ -3,7 +3,7 @@ class Thumbnail(BaseMixin, db.Model): - __tablename__ = 'file' + __tablename__ = 'thumbnail' name = db.Column(db.Unicode(250), nullable=False, unique=True) uploaded_file_id = db.Column(db.Integer, db.ForeignKey('file.id')) diff --git a/imgee/storage.py b/imgee/storage.py index 80dd2e53..b22bbb84 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -4,6 +4,10 @@ from boto.s3.key import Key from imgee import app + +IMAGES = list('jpg jpe jpeg png gif svg bmp'.split()) + + def upload(name, title): """ Upload a file to S3 @@ -13,3 +17,13 @@ def upload(name, title): k = Key(bucket) k.key = name k.set_contents_from_filename(path.join(app.config['UPLOADED_FILES_DEST'], title)) + + +def is_image(filename): + """ + Check if a given filename is an image or not + """ + extension = filename.rsplit('.', 1)[-1] + if extension in IMAGES: + return True + return False diff --git a/imgee/views/index.py b/imgee/views/index.py index 42226ac5..291273d6 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -5,7 +5,7 @@ from imgee.forms import UploadForm from imgee.models import UploadedFile, db from imgee.views.login import lastuser -from imgee.storage import upload +from imgee.storage import upload, check_file_type @app.route('/upload', methods=('GET', 'POST')) @@ -18,7 +18,7 @@ def upload_files(): db.session.commit() upload(uploaded_file.name, uploaded_file.title) return jsonify({'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], uploaded_file.name)}) - + return jsonify({'error': 'No file was uploaded'}) @app.route('/list') @lastuser.requires_login @@ -26,3 +26,10 @@ def list_files(): files = UploadedFile.query.filter_by(user=g.user).all() file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} return jsonify(file_list) + + +@app.route('/file/') +@lastuser.requires_login +def get_thumbnail(filename): + uploadedfile = UploadedFile.query.filter_by(name=filename).first() + if check_file_type From a453c3f376ee0b0dad3ff3e1b1478b22a14b28a3 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 30 Aug 2012 22:59:07 +0530 Subject: [PATCH 005/541] Add index to thumbnail name --- imgee/models/thumbnail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgee/models/thumbnail.py b/imgee/models/thumbnail.py index 1fe02724..51fcb4de 100644 --- a/imgee/models/thumbnail.py +++ b/imgee/models/thumbnail.py @@ -5,5 +5,6 @@ class Thumbnail(BaseMixin, db.Model): __tablename__ = 'thumbnail' - name = db.Column(db.Unicode(250), nullable=False, unique=True) + name = db.Column(db.Unicode(250), nullable=False, unique=True, index=True) + size = db.Column(db.Unicode(100), nullable=False, index=True) uploaded_file_id = db.Column(db.Integer, db.ForeignKey('file.id')) From c3ec8df340fb1c23d837920bcc3802cb813dfb61 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 30 Aug 2012 22:59:48 +0530 Subject: [PATCH 006/541] Add storage code for thumbnail creation --- imgee/storage.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/imgee/storage.py b/imgee/storage.py index b22bbb84..4ce83b5f 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -1,7 +1,9 @@ from os import path +from uuid import uuid4 from boto import connect_s3 from boto.s3.bucket import Bucket from boto.s3.key import Key +from PIL import Image from imgee import app @@ -27,3 +29,36 @@ def is_image(filename): if extension in IMAGES: return True return False + + +def create_thumbnail(uploadedfile, size): + """ + Create a thumbnail for a given file and given size + """ + thumbnail = Thumbnail(name=uuid4().hex, size=size, uploadedfile=uploadedfile) + conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) + bucket = Bucket(conn, app.config['AWS_BUCKET']) + thumbnail_path = path.join(app.config['UPLOADED_FILES_DEST'], thumbnail.name) + k = Key(bucket) + k.key = uploadedfile.name + k.get_contents_to_filename(thumbnail_path) + try: + img = Image.open(thumbnail_path) + img.load() + img.thumbnail(size, Image.ANTIALIAS) + img.save(thumbnail_pathj) + except IOError: + return None + return thumbnail.name + + +def convert_size(size): + converted = size.split('x') + if len(converted) != 2: + return None + for k, v in enumerate(converted): + if v.isdigit(): + converted[k] = int(v) + else: + return None + return converted From 22def3457dc058dc0c86235cba300c9bcaca834b Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 30 Aug 2012 23:00:01 +0530 Subject: [PATCH 007/541] Add API endpoint for thumbnail generation --- imgee/views/index.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 291273d6..11928724 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -3,9 +3,9 @@ from flask import render_template, request, flash, g, jsonify from imgee import app, uploadedfiles from imgee.forms import UploadForm -from imgee.models import UploadedFile, db +from imgee.models import UploadedFile, Thumbnail, db from imgee.views.login import lastuser -from imgee.storage import upload, check_file_type +from imgee.storage import upload, is_image, create_thumbnail, convert_size @app.route('/upload', methods=('GET', 'POST')) @@ -20,6 +20,7 @@ def upload_files(): return jsonify({'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], uploaded_file.name)}) return jsonify({'error': 'No file was uploaded'}) + @app.route('/list') @lastuser.requires_login def list_files(): @@ -31,5 +32,19 @@ def list_files(): @app.route('/file/') @lastuser.requires_login def get_thumbnail(filename): + size = request.args.get('size') + if not size: + return jsonify({'error': 'Size not specified'}) uploadedfile = UploadedFile.query.filter_by(name=filename).first() - if check_file_type + if not is_image(uploadedfile.name): + return jsonify({'error': 'File is not an image'}) + existing_thumnail = Thumbnail.query.filter_by(size=size, uploadedfile=uploadedfile).first() + if existing_thumbnail: + return jsonify({'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], existing_thumnail.name)}) + converted_size = convert_size(size) + if not converted_size: + return jsonify({'error': 'The size is invalid'}) + new_thumbnail = create_thumbnail(uploadedfile, converted_size) + if new_thumbnail: + return jsonify({'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumnail.name)}) + return jsonify({'error': 'Thumbnail creation error'}) From bfcb86d5ff2448a056eeb67c6ff412deb00cf6ba Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 4 Sep 2012 16:30:37 +0530 Subject: [PATCH 008/541] Add an index route --- imgee/templates/index.html | 4 +--- imgee/views/index.py | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/imgee/templates/index.html b/imgee/templates/index.html index 0abb4981..75ee0b9e 100644 --- a/imgee/templates/index.html +++ b/imgee/templates/index.html @@ -6,8 +6,6 @@ {% block content %} -

- Insert content here. -

+

The HasGeek Image Hosting Website.

{% endblock %} diff --git a/imgee/views/index.py b/imgee/views/index.py index 11928724..5a82a2d8 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -8,6 +8,9 @@ from imgee.storage import upload, is_image, create_thumbnail, convert_size +@app.route('/') +def index(): + return render_template('index.html') @app.route('/upload', methods=('GET', 'POST')) @lastuser.requires_login def upload_files(): From de5c216c6c5f074395f433a44de0007ef1791a8e Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Wed, 5 Sep 2012 14:27:32 +0530 Subject: [PATCH 009/541] Switch from requires_login to resource_handler --- imgee/views/index.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 5a82a2d8..a2a2716f 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -11,8 +11,10 @@ @app.route('/') def index(): return render_template('index.html') + + @app.route('/upload', methods=('GET', 'POST')) -@lastuser.requires_login +@lastuser.resource_handler def upload_files(): if request.files['uploaded_file']: filename = uploadedfiles.save(request.files['uploaded_file']) @@ -25,7 +27,7 @@ def upload_files(): @app.route('/list') -@lastuser.requires_login +@lastuser.resource_handler def list_files(): files = UploadedFile.query.filter_by(user=g.user).all() file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} @@ -33,7 +35,7 @@ def list_files(): @app.route('/file/') -@lastuser.requires_login +@lastuser.resource_handler def get_thumbnail(filename): size = request.args.get('size') if not size: From 0e947d563aa5ba8bfd7e2c8591682b1e640dda40 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 6 Sep 2012 18:48:41 +0530 Subject: [PATCH 010/541] Renamed UploadedFile, changed upload view --- imgee/models/{uploaded_file.py => file_storage.py} | 6 +++--- imgee/models/thumbnail.py | 2 +- imgee/models/user.py | 2 +- imgee/views/index.py | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) rename imgee/models/{uploaded_file.py => file_storage.py} (52%) diff --git a/imgee/models/uploaded_file.py b/imgee/models/file_storage.py similarity index 52% rename from imgee/models/uploaded_file.py rename to imgee/models/file_storage.py index fd99b6aa..84e92f9c 100644 --- a/imgee/models/uploaded_file.py +++ b/imgee/models/file_storage.py @@ -4,8 +4,8 @@ from imgee.models import db -class UploadedFile(BaseNameMixin, db.Model): - __tablename__ = 'file' +class FileStorage(BaseNameMixin, db.Model): + __tablename__ = 'file_storage' - thumbnails = db.relationship('Thumbnail', backref='uploadedfile') + thumbnails = db.relationship('Thumbnail', backref='file_storage') user_id = db.Column(db.Integer, db.ForeignKey('user.id')) diff --git a/imgee/models/thumbnail.py b/imgee/models/thumbnail.py index 51fcb4de..19149dbb 100644 --- a/imgee/models/thumbnail.py +++ b/imgee/models/thumbnail.py @@ -7,4 +7,4 @@ class Thumbnail(BaseMixin, db.Model): name = db.Column(db.Unicode(250), nullable=False, unique=True, index=True) size = db.Column(db.Unicode(100), nullable=False, index=True) - uploaded_file_id = db.Column(db.Integer, db.ForeignKey('file.id')) + file_storage_id = db.Column(db.Integer, db.ForeignKey('file_storage.id')) diff --git a/imgee/models/user.py b/imgee/models/user.py index 09b49741..a7f6b12d 100644 --- a/imgee/models/user.py +++ b/imgee/models/user.py @@ -7,4 +7,4 @@ class User(UserBase, db.Model): __tablename__ = 'user' - files = db.relationship('UploadedFile', backref='user') + files = db.relationship('FileStorage', backref='user') diff --git a/imgee/views/index.py b/imgee/views/index.py index a2a2716f..63322baa 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from uuid import uuid4 -from flask import render_template, request, flash, g, jsonify +from flask import render_template, request, flash, g, jsonify, make_response from imgee import app, uploadedfiles from imgee.forms import UploadForm -from imgee.models import UploadedFile, Thumbnail, db +from imgee.models import FileStorage, Thumbnail, db from imgee.views.login import lastuser from imgee.storage import upload, is_image, create_thumbnail, convert_size @@ -18,18 +18,18 @@ def index(): def upload_files(): if request.files['uploaded_file']: filename = uploadedfiles.save(request.files['uploaded_file']) - uploaded_file = UploadedFile(name=uuid4().hex, title=filename, user=g.user) + uploaded_file = FileStorage(name=uuid4().hex, title=filename, user=g.user) db.session.add(uploaded_file) db.session.commit() upload(uploaded_file.name, uploaded_file.title) - return jsonify({'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], uploaded_file.name)}) + return jsonify({'idl': uploaded_file.name}) return jsonify({'error': 'No file was uploaded'}) @app.route('/list') @lastuser.resource_handler def list_files(): - files = UploadedFile.query.filter_by(user=g.user).all() + files = FileStorage.query.filter_by(user=g.user).all() file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} return jsonify(file_list) @@ -40,7 +40,7 @@ def get_thumbnail(filename): size = request.args.get('size') if not size: return jsonify({'error': 'Size not specified'}) - uploadedfile = UploadedFile.query.filter_by(name=filename).first() + uploadedfile = FileStorage.query.filter_by(name=filename).first() if not is_image(uploadedfile.name): return jsonify({'error': 'File is not an image'}) existing_thumnail = Thumbnail.query.filter_by(size=size, uploadedfile=uploadedfile).first() From 888cb413800eaaa6c74e34ba99d5afb0614cfee5 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 6 Sep 2012 19:44:46 +0530 Subject: [PATCH 011/541] Add Profile model --- imgee/models/__init__.py | 1 + imgee/models/file_storage.py | 5 +++-- imgee/models/profile.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 imgee/models/profile.py diff --git a/imgee/models/__init__.py b/imgee/models/__init__.py index 05e9691e..242c55a6 100644 --- a/imgee/models/__init__.py +++ b/imgee/models/__init__.py @@ -8,3 +8,4 @@ from imgee.models.user import * from imgee.models.uploaded_file import * from imgee.models.thumbnail import * +from imgee.models.profile import * diff --git a/imgee/models/file_storage.py b/imgee/models/file_storage.py index 84e92f9c..ff21f226 100644 --- a/imgee/models/file_storage.py +++ b/imgee/models/file_storage.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- from coaster.sqlalchemy import BaseNameMixin -from imgee.models import db +from imgee.models import db, Profile class FileStorage(BaseNameMixin, db.Model): __tablename__ = 'file_storage' + profile_id = db.Column(db.Integer, db.ForeignKey('profile.id'), nullable=False) + profile = db.relationship('Profile', backref='file_storage') thumbnails = db.relationship('Thumbnail', backref='file_storage') - user_id = db.Column(db.Integer, db.ForeignKey('user.id')) diff --git a/imgee/models/profile.py b/imgee/models/profile.py new file mode 100644 index 00000000..2b40c532 --- /dev/null +++ b/imgee/models/profile.py @@ -0,0 +1,29 @@ +from coaster.sqlalchemy import BaseNameMixin +from imgee.models import db + + +class PROFILE_TYPE: + UNDEFINED = 0 + PERSON = 1 + ORGANIZATION = 2 + EVENTSERIES = 3 + + +profile_types = { + 0: u"Undefined", + 1: u"Person", + 2: u"Organization", + 3: u"Event Series", + } + + +class Profile(BaseNameMixin, db.Model): + __tablename__ = 'profile' + + userid = db.Column(db.Unicode(22), nullable=False, unique=True) + description = db.Column(db.UnicodeText, default=u'', nullable=False) + type = db.Column(db.Integer, default=PROFILE_TYPE.UNDEFINED, nullable=False) + + def type_label(self): + return profile_types.get(self.type, profile_types[0]) + From 94930a30de7582791505528089a5c6a486b40764 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 6 Sep 2012 19:46:21 +0530 Subject: [PATCH 012/541] Remove unnecessary import --- imgee/models/file_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgee/models/file_storage.py b/imgee/models/file_storage.py index ff21f226..96ff0967 100644 --- a/imgee/models/file_storage.py +++ b/imgee/models/file_storage.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from coaster.sqlalchemy import BaseNameMixin -from imgee.models import db, Profile +from imgee.models import db class FileStorage(BaseNameMixin, db.Model): From 470ad0f69a0885e1c325c506ace70071b0a15cbb Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Thu, 6 Sep 2012 20:18:26 +0530 Subject: [PATCH 013/541] Change model name again, add the cached_property --- imgee/models/__init__.py | 2 +- imgee/models/file_storage.py | 12 ------------ imgee/models/stored_file.py | 14 ++++++++++++++ imgee/models/thumbnail.py | 2 +- imgee/models/user.py | 10 +++++++++- imgee/views/index.py | 8 ++++---- 6 files changed, 29 insertions(+), 19 deletions(-) delete mode 100644 imgee/models/file_storage.py create mode 100644 imgee/models/stored_file.py diff --git a/imgee/models/__init__.py b/imgee/models/__init__.py index 242c55a6..1733f752 100644 --- a/imgee/models/__init__.py +++ b/imgee/models/__init__.py @@ -6,6 +6,6 @@ db = SQLAlchemy(app) from imgee.models.user import * -from imgee.models.uploaded_file import * +from imgee.models.stored_file import * from imgee.models.thumbnail import * from imgee.models.profile import * diff --git a/imgee/models/file_storage.py b/imgee/models/file_storage.py deleted file mode 100644 index 96ff0967..00000000 --- a/imgee/models/file_storage.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- - -from coaster.sqlalchemy import BaseNameMixin -from imgee.models import db - - -class FileStorage(BaseNameMixin, db.Model): - __tablename__ = 'file_storage' - - profile_id = db.Column(db.Integer, db.ForeignKey('profile.id'), nullable=False) - profile = db.relationship('Profile', backref='file_storage') - thumbnails = db.relationship('Thumbnail', backref='file_storage') diff --git a/imgee/models/stored_file.py b/imgee/models/stored_file.py new file mode 100644 index 00000000..181e1730 --- /dev/null +++ b/imgee/models/stored_file.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +from coaster.sqlalchemy import BaseNameMixin +from imgee.models import db + + +class StoredFile(BaseNameMixin, db.Model): + __tablename__ = 'stored_file' + + profile_id = db.Column(None, db.ForeignKey('profile.id'), nullable=False) + profile = db.relationship('Profile', backref='stored_file') + thumbnails = db.relationship('Thumbnail', backref='stored_file', + cascade='all, delete-orphan') + diff --git a/imgee/models/thumbnail.py b/imgee/models/thumbnail.py index 19149dbb..0a58f204 100644 --- a/imgee/models/thumbnail.py +++ b/imgee/models/thumbnail.py @@ -7,4 +7,4 @@ class Thumbnail(BaseMixin, db.Model): name = db.Column(db.Unicode(250), nullable=False, unique=True, index=True) size = db.Column(db.Unicode(100), nullable=False, index=True) - file_storage_id = db.Column(db.Integer, db.ForeignKey('file_storage.id')) + stored_file_id = db.Column(db.Integer, db.ForeignKey('stored_file.id')) diff --git a/imgee/models/user.py b/imgee/models/user.py index a7f6b12d..7350f603 100644 --- a/imgee/models/user.py +++ b/imgee/models/user.py @@ -1,10 +1,18 @@ # -*- coding: utf-8 -*- from flask.ext.lastuser.sqlalchemy import UserBase +from werkzeug.utils import cached_property from imgee.models import db class User(UserBase, db.Model): __tablename__ = 'user' - files = db.relationship('FileStorage', backref='user') + @cached_property + def profile(self): + return Profile.query.filter_by(userid=self.userid).first() + + @cached_property + def profiles(self): + return [self.profile] + Profile.query.filter( + Profile.userid.in_(self.organizations_owned_ids())).order_by('title').all() diff --git a/imgee/views/index.py b/imgee/views/index.py index 63322baa..7b45f623 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -3,7 +3,7 @@ from flask import render_template, request, flash, g, jsonify, make_response from imgee import app, uploadedfiles from imgee.forms import UploadForm -from imgee.models import FileStorage, Thumbnail, db +from imgee.models import StoredFile, Thumbnail, db from imgee.views.login import lastuser from imgee.storage import upload, is_image, create_thumbnail, convert_size @@ -18,7 +18,7 @@ def index(): def upload_files(): if request.files['uploaded_file']: filename = uploadedfiles.save(request.files['uploaded_file']) - uploaded_file = FileStorage(name=uuid4().hex, title=filename, user=g.user) + uploaded_file = StoredFile(name=uuid4().hex, title=filename, user=g.user) db.session.add(uploaded_file) db.session.commit() upload(uploaded_file.name, uploaded_file.title) @@ -29,7 +29,7 @@ def upload_files(): @app.route('/list') @lastuser.resource_handler def list_files(): - files = FileStorage.query.filter_by(user=g.user).all() + files = StoredFile.query.filter_by(user=g.user).all() file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} return jsonify(file_list) @@ -40,7 +40,7 @@ def get_thumbnail(filename): size = request.args.get('size') if not size: return jsonify({'error': 'Size not specified'}) - uploadedfile = FileStorage.query.filter_by(name=filename).first() + uploadedfile = StoredFile.query.filter_by(name=filename).first() if not is_image(uploadedfile.name): return jsonify({'error': 'File is not an image'}) existing_thumnail = Thumbnail.query.filter_by(size=size, uploadedfile=uploadedfile).first() From d054ea1c3a559ca40745ecbf7e99b1d4c99165e5 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 11 Sep 2012 16:48:46 +0530 Subject: [PATCH 014/541] Added Profile support --- imgee/storage.py | 6 +++--- imgee/views/index.py | 42 ++++++++++++++++++++++++++---------------- imgee/views/login.py | 44 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index 4ce83b5f..e9979927 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -31,16 +31,16 @@ def is_image(filename): return False -def create_thumbnail(uploadedfile, size): +def create_thumbnail(stored_file, size): """ Create a thumbnail for a given file and given size """ - thumbnail = Thumbnail(name=uuid4().hex, size=size, uploadedfile=uploadedfile) + thumbnail = Thumbnail(name=uuid4().hex, size=size, stored_file=stored_file) conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) bucket = Bucket(conn, app.config['AWS_BUCKET']) thumbnail_path = path.join(app.config['UPLOADED_FILES_DEST'], thumbnail.name) k = Key(bucket) - k.key = uploadedfile.name + k.key = stored_file.name k.get_contents_to_filename(thumbnail_path) try: img = Image.open(thumbnail_path) diff --git a/imgee/views/index.py b/imgee/views/index.py index 7b45f623..d11945c6 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -3,8 +3,8 @@ from flask import render_template, request, flash, g, jsonify, make_response from imgee import app, uploadedfiles from imgee.forms import UploadForm -from imgee.models import StoredFile, Thumbnail, db -from imgee.views.login import lastuser +from imgee.models import StoredFile, Thumbnail, db, Profile +from imgee.views.login import lastuser, make_profiles from imgee.storage import upload, is_image, create_thumbnail, convert_size @@ -13,30 +13,40 @@ def index(): return render_template('index.html') -@app.route('/upload', methods=('GET', 'POST')) -@lastuser.resource_handler -def upload_files(): - if request.files['uploaded_file']: +@app.route('/upload', methods=('POST')) +@lastuser.resource_handler('imgee/upload') +def upload_files(callerinfo): + profileid = request.args.get('profileid', g.user.userid) + make_profiles() + if profileid not in g.user.user_organizations_owned_ids(): + return jsonify({'error': 'You do not have permission to access this resource'})j + if 'uploaded_file' in request.files: filename = uploadedfiles.save(request.files['uploaded_file']) - uploaded_file = StoredFile(name=uuid4().hex, title=filename, user=g.user) - db.session.add(uploaded_file) + profile = Profile.query(userid=profileid).first() + stored_file = StoredFile(name=uuid4().hex, title=filename, profile=profile) + db.session.add(stored_file) db.session.commit() - upload(uploaded_file.name, uploaded_file.title) - return jsonify({'idl': uploaded_file.name}) + upload(stored_file.name, stored_file.title) + return jsonify({'id': uploaded_file.name}) return jsonify({'error': 'No file was uploaded'}) @app.route('/list') -@lastuser.resource_handler -def list_files(): - files = StoredFile.query.filter_by(user=g.user).all() - file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} - return jsonify(file_list) +@lastuser.resource_handler('imgee/list') +def list_files(callerinfo): + profileid = request.args.get('profileid', g.user.userid) + make_profiles() + if profileid in g.user.user_organizations_owned_ids(): + files = StoredFile.query.filter(Profile.userid == profileid).all() + file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} + return jsonify(file_list) + return jsonify({'error': 'You do not have permission to access this resource'}) @app.route('/file/') -@lastuser.resource_handler +@lastuser.resource_handler('imgee/thumbnail') def get_thumbnail(filename): + make_profiles() size = request.args.get('size') if not size: return jsonify({'error': 'Size not specified'}) diff --git a/imgee/views/login.py b/imgee/views/login.py index 061bbc73..03f3f58e 100644 --- a/imgee/views/login.py +++ b/imgee/views/login.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- -from flask import Response, redirect, flash +from flask import Response, redirect, flash, g +from flask.ext.lastuser import LastUser from flask.ext.lastuser.sqlalchemy import UserManager from coaster.views import get_next_url from imgee import app, lastuser -from imgee.models import db, User +from imgee.models import db, User, Profile, PROFILE_TYPE lastuser.init_usermanager(UserManager(db, User)) @@ -13,21 +14,52 @@ @app.route('/login') @lastuser.login_handler def login(): - return {'scope': 'id'} + return {'scope': 'id email organizations'} @app.route('/logout') @lastuser.logout_handler def logout(): - flash(u"You are now logged out", category='success') + flash(u"You are now logged out", category='info') return get_next_url() + +def make_profiles(): + # Make profiles for the user's organizations + username = g.user.username or g.user.userid + profile = Profile.query.filter_by(userid=g.user.userid).first() + if profile is None: + profile = Profile(userid=g.user.userid, + name=g.user.username or g.user.userid, + title=g.user.fullname, + type=PROFILE_TYPE.PERSON) + db.session.add(profile) + else: + if profile.name != username: + profile.name = username + if profile.title != g.user.fullname: + profile.title = g.user.fullname + for org in g.user.organizations_owned(): + profile = Profile.query.filter_by(userid=org['userid']).first() + if profile is None: + profile = Profile(userid=org['userid'], + name=org['name'], + title=org['title'], + type=PROFILE_TYPE.ORGANIZATION) + db.session.add(profile) + else: + if profile.name != org['name']: + profile.name = org['name'] + if profile.title != org['title']: + profile.title = org['title'] + db.session.commit() + + @app.route('/login/redirect') @lastuser.auth_handler def lastuserauth(): - # Save the user object - db.session.commit() + make_profiles() return redirect(get_next_url()) From 392d1e21928f4dea33ff0242122e02efe00cffda Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 11 Sep 2012 17:38:35 +0530 Subject: [PATCH 015/541] Upload endpoint working --- imgee/views/index.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index d11945c6..7f0a9b4b 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -13,21 +13,21 @@ def index(): return render_template('index.html') -@app.route('/upload', methods=('POST')) +@app.route('/upload', methods=['POST']) @lastuser.resource_handler('imgee/upload') def upload_files(callerinfo): profileid = request.args.get('profileid', g.user.userid) make_profiles() if profileid not in g.user.user_organizations_owned_ids(): - return jsonify({'error': 'You do not have permission to access this resource'})j - if 'uploaded_file' in request.files: - filename = uploadedfiles.save(request.files['uploaded_file']) - profile = Profile.query(userid=profileid).first() + return jsonify({'error': 'You do not have permission to access this resource'}) + if request.files.get('stored_file', None): + filename = uploadedfiles.save(request.files['stored_file']) + profile = Profile.query.filter_by(userid=profileid).first() stored_file = StoredFile(name=uuid4().hex, title=filename, profile=profile) db.session.add(stored_file) db.session.commit() upload(stored_file.name, stored_file.title) - return jsonify({'id': uploaded_file.name}) + return jsonify({'id': stored_file.name}) return jsonify({'error': 'No file was uploaded'}) From 083af0f0e59b7beb53f33340afb13d8d14130622 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 11 Sep 2012 19:28:32 +0530 Subject: [PATCH 016/541] Use basename --- imgee/views/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 7f0a9b4b..20942490 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import os.path from uuid import uuid4 from flask import render_template, request, flash, g, jsonify, make_response from imgee import app, uploadedfiles @@ -21,7 +22,7 @@ def upload_files(callerinfo): if profileid not in g.user.user_organizations_owned_ids(): return jsonify({'error': 'You do not have permission to access this resource'}) if request.files.get('stored_file', None): - filename = uploadedfiles.save(request.files['stored_file']) + filename = uploadedfiles.save(request.files['stored_file'], name=os.path.basename(request.files['stored_file'].filename)) profile = Profile.query.filter_by(userid=profileid).first() stored_file = StoredFile(name=uuid4().hex, title=filename, profile=profile) db.session.add(stored_file) From c56c3c1d3d695026d07c999b44c20d9062849542 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 11 Sep 2012 19:49:12 +0530 Subject: [PATCH 017/541] Save the title correctly --- imgee/views/index.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 20942490..d004d0e2 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -22,12 +22,12 @@ def upload_files(callerinfo): if profileid not in g.user.user_organizations_owned_ids(): return jsonify({'error': 'You do not have permission to access this resource'}) if request.files.get('stored_file', None): - filename = uploadedfiles.save(request.files['stored_file'], name=os.path.basename(request.files['stored_file'].filename)) + filename = uploadedfiles.save(request.files['stored_file']) profile = Profile.query.filter_by(userid=profileid).first() - stored_file = StoredFile(name=uuid4().hex, title=filename, profile=profile) + stored_file = StoredFile(name=uuid4().hex, title=os.path.basename(request.files['stored_file'].filename), profile=profile) db.session.add(stored_file) db.session.commit() - upload(stored_file.name, stored_file.title) + upload(stored_file.name, filename) return jsonify({'id': stored_file.name}) return jsonify({'error': 'No file was uploaded'}) From 20672996045d15f9a1c3032326c6112dcd034dd6 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 11 Sep 2012 20:52:21 +0530 Subject: [PATCH 018/541] Updated thumbnail view. It's not a resource endpoint --- imgee/views/index.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index d004d0e2..f561054a 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import os.path from uuid import uuid4 -from flask import render_template, request, flash, g, jsonify, make_response +from flask import render_template, request, flash, g, jsonify, make_response, redirect from imgee import app, uploadedfiles from imgee.forms import UploadForm from imgee.models import StoredFile, Thumbnail, db, Profile @@ -45,22 +45,17 @@ def list_files(callerinfo): @app.route('/file/') -@lastuser.resource_handler('imgee/thumbnail') def get_thumbnail(filename): make_profiles() size = request.args.get('size') - if not size: - return jsonify({'error': 'Size not specified'}) - uploadedfile = StoredFile.query.filter_by(name=filename).first() - if not is_image(uploadedfile.name): - return jsonify({'error': 'File is not an image'}) + stored_file = StoredFile.query.filter_by(name=filename).first() + converted_size = convert_size(size) + if not size or is_image(uploadedfile.name) or not converted_size: + return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) existing_thumnail = Thumbnail.query.filter_by(size=size, uploadedfile=uploadedfile).first() if existing_thumbnail: - return jsonify({'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], existing_thumnail.name)}) - converted_size = convert_size(size) - if not converted_size: - return jsonify({'error': 'The size is invalid'}) + return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], existing_thumnail.name)) new_thumbnail = create_thumbnail(uploadedfile, converted_size) if new_thumbnail: - return jsonify({'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumnail.name)}) - return jsonify({'error': 'Thumbnail creation error'}) + return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumnail.name)) + return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) From c740e0a92060d316a859dabac13c4f1510a0ac76 Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 11 Sep 2012 21:05:03 +0530 Subject: [PATCH 019/541] Thumbnail generation code a little more bugfree --- imgee/storage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgee/storage.py b/imgee/storage.py index e9979927..a2684d9f 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -5,6 +5,7 @@ from boto.s3.key import Key from PIL import Image from imgee import app +from imgee.models import db IMAGES = list('jpg jpe jpeg png gif svg bmp'.split()) @@ -36,6 +37,8 @@ def create_thumbnail(stored_file, size): Create a thumbnail for a given file and given size """ thumbnail = Thumbnail(name=uuid4().hex, size=size, stored_file=stored_file) + db.session.add(thumbnail) + db.session.commit() conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) bucket = Bucket(conn, app.config['AWS_BUCKET']) thumbnail_path = path.join(app.config['UPLOADED_FILES_DEST'], thumbnail.name) @@ -46,7 +49,7 @@ def create_thumbnail(stored_file, size): img = Image.open(thumbnail_path) img.load() img.thumbnail(size, Image.ANTIALIAS) - img.save(thumbnail_pathj) + img.save(thumbnail_path) except IOError: return None return thumbnail.name From fdd1eeac041031e72973d3d01f16280db0cb634c Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Tue, 11 Sep 2012 21:05:28 +0530 Subject: [PATCH 020/541] Delete route --- imgee/views/index.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/imgee/views/index.py b/imgee/views/index.py index f561054a..244bb882 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -59,3 +59,21 @@ def get_thumbnail(filename): if new_thumbnail: return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumnail.name)) return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) + + +@app.route('/delete') +@lastuser.resource_handler('imgee/delete') +def list_files(callerinfo): + profileid = request.args.get('profileid', g.user.userid) + fileid = request.args.get('fileid', g.user.userid) + if not fileid: + return jsonify({'error': 'No filename given'}) + make_profiles() + if profileid in g.user.user_organizations_owned_ids(): + stored_file = StoredFile.query.filter_by(name=fileid).first() + if stored_file: + db.session.delete(stored_file) + db.session.commit() + return jsonify({'success': 'File deleted'}) + return jsonify({'error': 'No file found'}) + return jsonify({'error': 'You do not have permission to access this resource'}) From 21d9daa6f74b1f4def0aed2ce99397ce1a82566d Mon Sep 17 00:00:00 2001 From: Nigel Babu Date: Wed, 12 Sep 2012 14:24:06 +0530 Subject: [PATCH 021/541] Delete image and thumbnails --- imgee/storage.py | 11 +++++++++++ imgee/views/index.py | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/imgee/storage.py b/imgee/storage.py index a2684d9f..b5f9aa39 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -65,3 +65,14 @@ def convert_size(size): else: return None return converted + + +def delete_image(stored_file): + """ + Delete all the thumbnails and images associated with a file + """ + keys = [thumbnail.name for thumbnail in stored_file.thumbnails] + keys.append(stored_file.name) + conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) + bucket = Bucket(conn, app.config['AWS_BUCKET']) + bucket.delete_keys(keys) diff --git a/imgee/views/index.py b/imgee/views/index.py index 244bb882..ffe998d1 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -6,7 +6,7 @@ from imgee.forms import UploadForm from imgee.models import StoredFile, Thumbnail, db, Profile from imgee.views.login import lastuser, make_profiles -from imgee.storage import upload, is_image, create_thumbnail, convert_size +from imgee.storage import upload, is_image, create_thumbnail, convert_size, delete_image @app.route('/') @@ -72,6 +72,7 @@ def list_files(callerinfo): if profileid in g.user.user_organizations_owned_ids(): stored_file = StoredFile.query.filter_by(name=fileid).first() if stored_file: + delete_image(stored_file) db.session.delete(stored_file) db.session.commit() return jsonify({'success': 'File deleted'}) From ff6a9aa42cdcef88ec4e719e41edbada01b3abab Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Wed, 12 Sep 2012 16:46:50 +0530 Subject: [PATCH 022/541] Corrections courtesy SublimeLinter. --- imgee/__init__.py | 1 + imgee/models/profile.py | 1 - imgee/models/stored_file.py | 1 - imgee/models/user.py | 1 + imgee/storage.py | 2 +- imgee/views/index.py | 15 +++++++-------- imgee/views/login.py | 2 -- 7 files changed, 10 insertions(+), 13 deletions(-) diff --git a/imgee/__init__.py b/imgee/__init__.py index f369a4c6..c540cfa5 100644 --- a/imgee/__init__.py +++ b/imgee/__init__.py @@ -29,6 +29,7 @@ import imgee.models import imgee.views + # Configure the app def init_for(env): coaster.app.init_app(app, env) diff --git a/imgee/models/profile.py b/imgee/models/profile.py index 2b40c532..5f2aedde 100644 --- a/imgee/models/profile.py +++ b/imgee/models/profile.py @@ -26,4 +26,3 @@ class Profile(BaseNameMixin, db.Model): def type_label(self): return profile_types.get(self.type, profile_types[0]) - diff --git a/imgee/models/stored_file.py b/imgee/models/stored_file.py index 181e1730..6e2bd169 100644 --- a/imgee/models/stored_file.py +++ b/imgee/models/stored_file.py @@ -11,4 +11,3 @@ class StoredFile(BaseNameMixin, db.Model): profile = db.relationship('Profile', backref='stored_file') thumbnails = db.relationship('Thumbnail', backref='stored_file', cascade='all, delete-orphan') - diff --git a/imgee/models/user.py b/imgee/models/user.py index 7350f603..78750535 100644 --- a/imgee/models/user.py +++ b/imgee/models/user.py @@ -3,6 +3,7 @@ from flask.ext.lastuser.sqlalchemy import UserBase from werkzeug.utils import cached_property from imgee.models import db +from imgee.models.profile import Profile class User(UserBase, db.Model): diff --git a/imgee/storage.py b/imgee/storage.py index b5f9aa39..71754f72 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -5,7 +5,7 @@ from boto.s3.key import Key from PIL import Image from imgee import app -from imgee.models import db +from imgee.models import db, Thumbnail IMAGES = list('jpg jpe jpeg png gif svg bmp'.split()) diff --git a/imgee/views/index.py b/imgee/views/index.py index ffe998d1..e654c21a 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- import os.path from uuid import uuid4 -from flask import render_template, request, flash, g, jsonify, make_response, redirect +from flask import render_template, request, g, jsonify, redirect from imgee import app, uploadedfiles -from imgee.forms import UploadForm from imgee.models import StoredFile, Thumbnail, db, Profile from imgee.views.login import lastuser, make_profiles from imgee.storage import upload, is_image, create_thumbnail, convert_size, delete_image @@ -50,20 +49,20 @@ def get_thumbnail(filename): size = request.args.get('size') stored_file = StoredFile.query.filter_by(name=filename).first() converted_size = convert_size(size) - if not size or is_image(uploadedfile.name) or not converted_size: + if not size or is_image(stored_file.name) or not converted_size: return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) - existing_thumnail = Thumbnail.query.filter_by(size=size, uploadedfile=uploadedfile).first() + existing_thumbnail = Thumbnail.query.filter_by(size=size, stored_file=stored_file).first() if existing_thumbnail: - return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], existing_thumnail.name)) - new_thumbnail = create_thumbnail(uploadedfile, converted_size) + return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], existing_thumbnail.name)) + new_thumbnail = create_thumbnail(stored_file, converted_size) if new_thumbnail: - return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumnail.name)) + return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumbnail.name)) return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) @app.route('/delete') @lastuser.resource_handler('imgee/delete') -def list_files(callerinfo): +def delete_files(callerinfo): profileid = request.args.get('profileid', g.user.userid) fileid = request.args.get('fileid', g.user.userid) if not fileid: diff --git a/imgee/views/login.py b/imgee/views/login.py index 03f3f58e..710053dd 100644 --- a/imgee/views/login.py +++ b/imgee/views/login.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from flask import Response, redirect, flash, g -from flask.ext.lastuser import LastUser from flask.ext.lastuser.sqlalchemy import UserManager from coaster.views import get_next_url @@ -24,7 +23,6 @@ def logout(): return get_next_url() - def make_profiles(): # Make profiles for the user's organizations username = g.user.username or g.user.userid From faebaf8ea4f24692c2b8a193e2635b09477f2a0b Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 25 Mar 2013 18:36:43 +0530 Subject: [PATCH 023/541] add missing requirements --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index b30cec40..ec2387c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,6 @@ Flask-SQLAlchemy https://github.com/hasgeek/flask-lastuser/zipball/master Flask-Admin boto +-e git+http://github.com/FelixLoether/flask-uploads#egg=Flask-Uploads +-e git://github.com/kvesteri/flask-storage.git#egg=Flask-Storage +PIL From 5d6b5f70430d75d2f7ba4d428145f6cd612cbc54 Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 25 Mar 2013 18:40:31 +0530 Subject: [PATCH 024/541] make upload work with latest flask-uploads --- imgee/__init__.py | 7 ++++--- imgee/templates/form.html | 2 +- imgee/views/index.py | 26 ++++++++++++++------------ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/imgee/__init__.py b/imgee/__init__.py index c540cfa5..64e0f7b9 100644 --- a/imgee/__init__.py +++ b/imgee/__init__.py @@ -5,7 +5,9 @@ from flask import Flask from flask.ext.assets import Environment, Bundle from flask.ext.lastuser import LastUser -from flask.ext.uploads import UploadSet, configure_uploads, ALL +from flask.ext.sqlalchemy import SQLAlchemy +from flask.ext.uploads import init +from flask.ext.storage import get_default_storage_class from baseframe import baseframe, baseframe_js, baseframe_css import coaster.app @@ -22,7 +24,6 @@ js = Bundle(baseframe_js) css = Bundle(baseframe_css, 'css/app.css') -uploadedfiles = UploadSet(extensions=ALL) # Third, after config, import the models and views @@ -36,4 +37,4 @@ def init_for(env): assets.register('js_all', js) assets.register('css_all', css) lastuser.init_app(app) - configure_uploads(app, (uploadedfiles)) + init(SQLAlchemy(app), get_default_storage_class(app)) diff --git a/imgee/templates/form.html b/imgee/templates/form.html index aa3da206..92c356d1 100644 --- a/imgee/templates/form.html +++ b/imgee/templates/form.html @@ -7,6 +7,6 @@ {% block content %} -{{ renderform(form, multipart=True) }} +{{ renderform(form, "upload", multipart=True) }} {% endblock %} diff --git a/imgee/views/index.py b/imgee/views/index.py index e654c21a..53fd5af9 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -2,33 +2,35 @@ import os.path from uuid import uuid4 from flask import render_template, request, g, jsonify, redirect -from imgee import app, uploadedfiles +from imgee import app, forms +from flask.ext.uploads import save from imgee.models import StoredFile, Thumbnail, db, Profile from imgee.views.login import lastuser, make_profiles from imgee.storage import upload, is_image, create_thumbnail, convert_size, delete_image - @app.route('/') def index(): return render_template('index.html') -@app.route('/upload', methods=['POST']) +@app.route('/upload', methods=['GET', 'POST']) @lastuser.resource_handler('imgee/upload') -def upload_files(callerinfo): - profileid = request.args.get('profileid', g.user.userid) - make_profiles() - if profileid not in g.user.user_organizations_owned_ids(): - return jsonify({'error': 'You do not have permission to access this resource'}) - if request.files.get('stored_file', None): - filename = uploadedfiles.save(request.files['stored_file']) +def upload_files(): + if request.method == 'GET': + upload_form = forms.UploadForm() + return render_template('form.html', form=upload_form) + else: + profileid = request.args.get('profileid', g.user.userid) + make_profiles() + if profileid not in g.user.user_organizations_owned_ids(): + return jsonify({'error': 'You do not have permission to access this resource'}) + filename = save(request.files['uploaded_file']) profile = Profile.query.filter_by(userid=profileid).first() stored_file = StoredFile(name=uuid4().hex, title=os.path.basename(request.files['stored_file'].filename), profile=profile) db.session.add(stored_file) db.session.commit() upload(stored_file.name, filename) return jsonify({'id': stored_file.name}) - return jsonify({'error': 'No file was uploaded'}) @app.route('/list') @@ -60,7 +62,7 @@ def get_thumbnail(filename): return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) -@app.route('/delete') +@app.route('/delete', methods=['POST']) @lastuser.resource_handler('imgee/delete') def delete_files(callerinfo): profileid = request.args.get('profileid', g.user.userid) From 8c02b1af87938869ed6e8db9e3f0f06c1c202217 Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 25 Mar 2013 18:48:04 +0530 Subject: [PATCH 025/541] pass aws credentials to flask-storage --- instance/settings.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/instance/settings.py b/instance/settings.py index 3055e362..23753411 100644 --- a/instance/settings.py +++ b/instance/settings.py @@ -37,5 +37,12 @@ AWS_ACCESS_KEY = 'Set aws access key here' AWS_SECRET_KEY = 'Set aws secret key here' AWS_BUCKET = 'set your bucketname here' + +# bear with the non-standard aws key names used in flask-storage + +AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY +AWS_SECRET_ACCESS_KEY=AWS_SECRET_KEY +AWS_STORAGE_BUCKET_NAME=AWS_BUCKET + #: Domain name for files MEDIA_DOMAIN = 'set domain name here' From 7a8a02e2a0f96fe6388d8bfeb041a97d065cd461 Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 26 Mar 2013 15:11:29 +0530 Subject: [PATCH 026/541] fix db in uploads init --- imgee/__init__.py | 7 ++----- runserver.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/imgee/__init__.py b/imgee/__init__.py index 64e0f7b9..3e54c535 100644 --- a/imgee/__init__.py +++ b/imgee/__init__.py @@ -5,7 +5,6 @@ from flask import Flask from flask.ext.assets import Environment, Bundle from flask.ext.lastuser import LastUser -from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.uploads import init from flask.ext.storage import get_default_storage_class from baseframe import baseframe, baseframe_js, baseframe_css @@ -22,19 +21,17 @@ assets = Environment(app) js = Bundle(baseframe_js) -css = Bundle(baseframe_css, - 'css/app.css') +css = Bundle(baseframe_css, 'css/app.css') # Third, after config, import the models and views import imgee.models import imgee.views - # Configure the app def init_for(env): coaster.app.init_app(app, env) assets.register('js_all', js) assets.register('css_all', css) lastuser.init_app(app) - init(SQLAlchemy(app), get_default_storage_class(app)) + init(imgee.models.db, get_default_storage_class(app)) diff --git a/runserver.py b/runserver.py index 5087fab2..08b48d9d 100755 --- a/runserver.py +++ b/runserver.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from imgee import app, init_for from imgee.models import db -init_for('dev') db.create_all() +init_for('dev') app.run('0.0.0.0', debug=True, port=4500) From 9c13e6552f9469e4faf95fa983afa09584fd1e77 Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 26 Mar 2013 17:40:54 +0530 Subject: [PATCH 027/541] clean up --- imgee/storage.py | 12 +++++------- imgee/views/index.py | 7 ++++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index 71754f72..3be2ec7a 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -8,18 +8,18 @@ from imgee.models import db, Thumbnail -IMAGES = list('jpg jpe jpeg png gif svg bmp'.split()) +IMAGES = 'jpg jpe jpeg png gif svg bmp'.split() -def upload(name, title): +def upload(local_name, remote_name=None): """ Upload a file to S3 """ conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) bucket = Bucket(conn, app.config['AWS_BUCKET']) k = Key(bucket) - k.key = name - k.set_contents_from_filename(path.join(app.config['UPLOADED_FILES_DEST'], title)) + k.key = remote_name or local_name + k.set_contents_from_filename(path.join(app.config['UPLOADED_FILES_DEST'], local_name)) def is_image(filename): @@ -27,9 +27,7 @@ def is_image(filename): Check if a given filename is an image or not """ extension = filename.rsplit('.', 1)[-1] - if extension in IMAGES: - return True - return False + return extension in IMAGES def create_thumbnail(stored_file, size): diff --git a/imgee/views/index.py b/imgee/views/index.py index 53fd5af9..06feb39a 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -24,12 +24,13 @@ def upload_files(): make_profiles() if profileid not in g.user.user_organizations_owned_ids(): return jsonify({'error': 'You do not have permission to access this resource'}) - filename = save(request.files['uploaded_file']) + save(request.files['uploaded_file']) + localname = os.path.basename(request.files['stored_file'].filename) profile = Profile.query.filter_by(userid=profileid).first() - stored_file = StoredFile(name=uuid4().hex, title=os.path.basename(request.files['stored_file'].filename), profile=profile) + stored_file = StoredFile(name=uuid4().hex, title=localname, profile=profile) db.session.add(stored_file) db.session.commit() - upload(stored_file.name, filename) + upload(localname, stored_file.name) return jsonify({'id': stored_file.name}) From 3b6a695b1810889c0386a91295a8cd3eeb75398b Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 26 Mar 2013 17:43:15 +0530 Subject: [PATCH 028/541] create tables at the right place --- runserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runserver.py b/runserver.py index 08b48d9d..5087fab2 100755 --- a/runserver.py +++ b/runserver.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from imgee import app, init_for from imgee.models import db -db.create_all() init_for('dev') +db.create_all() app.run('0.0.0.0', debug=True, port=4500) From 6b82e30ad48ff93b28221e5709ecd00ad33926fd Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 26 Mar 2013 17:46:20 +0530 Subject: [PATCH 029/541] add login_required --- imgee/views/index.py | 5 ++++- imgee/views/login.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 06feb39a..ebac90f1 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -5,7 +5,7 @@ from imgee import app, forms from flask.ext.uploads import save from imgee.models import StoredFile, Thumbnail, db, Profile -from imgee.views.login import lastuser, make_profiles +from imgee.views.login import lastuser, make_profiles, login_required from imgee.storage import upload, is_image, create_thumbnail, convert_size, delete_image @app.route('/') @@ -15,6 +15,7 @@ def index(): @app.route('/upload', methods=['GET', 'POST']) @lastuser.resource_handler('imgee/upload') +@login_required def upload_files(): if request.method == 'GET': upload_form = forms.UploadForm() @@ -36,6 +37,7 @@ def upload_files(): @app.route('/list') @lastuser.resource_handler('imgee/list') +@login_required def list_files(callerinfo): profileid = request.args.get('profileid', g.user.userid) make_profiles() @@ -65,6 +67,7 @@ def get_thumbnail(filename): @app.route('/delete', methods=['POST']) @lastuser.resource_handler('imgee/delete') +@login_required def delete_files(callerinfo): profileid = request.args.get('profileid', g.user.userid) fileid = request.args.get('fileid', g.user.userid) diff --git a/imgee/views/login.py b/imgee/views/login.py index 710053dd..681673b9 100644 --- a/imgee/views/login.py +++ b/imgee/views/login.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- -from flask import Response, redirect, flash, g +from functools import wraps + +from flask import Response, redirect, flash, g, url_for, request from flask.ext.lastuser.sqlalchemy import UserManager from coaster.views import get_next_url @@ -9,6 +11,13 @@ lastuser.init_usermanager(UserManager(db, User)) +def login_required(f): + @wraps(f) + def decorated_func(*args, **kwargs): + if g.user is None: + return redirect(url_for('login', next=request.url)) + return f(*args, **kwargs) + return decorated_func @app.route('/login') @lastuser.login_handler From 751be22c5736c85f2f7b018e9b7a437e34e50bc6 Mon Sep 17 00:00:00 2001 From: Devi Date: Wed, 27 Mar 2013 16:02:35 +0530 Subject: [PATCH 030/541] validate form --- imgee/forms.py | 4 ++-- imgee/views/index.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/imgee/forms.py b/imgee/forms.py index e30fd7ae..bb75ec2d 100644 --- a/imgee/forms.py +++ b/imgee/forms.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from flask.ext.wtf import Form, FileField +from flask.ext.wtf import Form, FileField, Required class UploadForm(Form): - uploaded_file = FileField("Uploaded File") + uploaded_file = FileField("Uploaded File", validators=[Required()]) diff --git a/imgee/views/index.py b/imgee/views/index.py index ebac90f1..9b0640d4 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -17,23 +17,23 @@ def index(): @lastuser.resource_handler('imgee/upload') @login_required def upload_files(): - if request.method == 'GET': - upload_form = forms.UploadForm() - return render_template('form.html', form=upload_form) - else: - profileid = request.args.get('profileid', g.user.userid) - make_profiles() - if profileid not in g.user.user_organizations_owned_ids(): - return jsonify({'error': 'You do not have permission to access this resource'}) + profileid = request.args.get('profileid', g.user.userid) + make_profiles() + if profileid not in g.user.user_organizations_owned_ids(): + return jsonify({'error': 'You do not have permission to access this resource'}) + + upload_form = forms.UploadForm() + if upload_form.validate_on_submit(): save(request.files['uploaded_file']) - localname = os.path.basename(request.files['stored_file'].filename) + localname = os.path.basename(request.files['uploaded_file'].filename) profile = Profile.query.filter_by(userid=profileid).first() stored_file = StoredFile(name=uuid4().hex, title=localname, profile=profile) db.session.add(stored_file) db.session.commit() upload(localname, stored_file.name) return jsonify({'id': stored_file.name}) - + # form invalid or request.method == 'GET' + return render_template('form.html', form=upload_form) @app.route('/list') @lastuser.resource_handler('imgee/list') From 5ef3317493b91c4f12d10f6ef5216f440b228cc9 Mon Sep 17 00:00:00 2001 From: Devi Date: Wed, 27 Mar 2013 16:06:47 +0530 Subject: [PATCH 031/541] save the image directly in s3 --- imgee/views/index.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 9b0640d4..a6a6036d 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -24,13 +24,12 @@ def upload_files(): upload_form = forms.UploadForm() if upload_form.validate_on_submit(): - save(request.files['uploaded_file']) localname = os.path.basename(request.files['uploaded_file'].filename) profile = Profile.query.filter_by(userid=profileid).first() stored_file = StoredFile(name=uuid4().hex, title=localname, profile=profile) db.session.add(stored_file) db.session.commit() - upload(localname, stored_file.name) + save(request.files['uploaded_file'], stored_file.name) return jsonify({'id': stored_file.name}) # form invalid or request.method == 'GET' return render_template('form.html', form=upload_form) From 01ecbdd7ed4f718d8416f14ae28027613b7f1520 Mon Sep 17 00:00:00 2001 From: Devi Date: Wed, 27 Mar 2013 16:19:24 +0530 Subject: [PATCH 032/541] secure filename and other small fixes --- imgee/views/index.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index a6a6036d..101959b8 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -import os.path +from werkzeug import secure_filename from uuid import uuid4 from flask import render_template, request, g, jsonify, redirect -from imgee import app, forms from flask.ext.uploads import save + +from imgee import app, forms from imgee.models import StoredFile, Thumbnail, db, Profile from imgee.views.login import lastuser, make_profiles, login_required from imgee.storage import upload, is_image, create_thumbnail, convert_size, delete_image @@ -13,20 +14,20 @@ def index(): return render_template('index.html') -@app.route('/upload', methods=['GET', 'POST']) +@app.route('/upload', methods=('GET', 'POST')) @lastuser.resource_handler('imgee/upload') @login_required def upload_files(): - profileid = request.args.get('profileid', g.user.userid) + profileid = g.user.userid make_profiles() if profileid not in g.user.user_organizations_owned_ids(): return jsonify({'error': 'You do not have permission to access this resource'}) upload_form = forms.UploadForm() if upload_form.validate_on_submit(): - localname = os.path.basename(request.files['uploaded_file'].filename) + filename = secure_filename(request.files['uploaded_file'].filename) profile = Profile.query.filter_by(userid=profileid).first() - stored_file = StoredFile(name=uuid4().hex, title=localname, profile=profile) + stored_file = StoredFile(name=uuid4().hex, title=filename, profile=profile) db.session.add(stored_file) db.session.commit() save(request.files['uploaded_file'], stored_file.name) From 91154d43cc49d7b21b12239e8bf550e736c76fc2 Mon Sep 17 00:00:00 2001 From: Devi Date: Wed, 27 Mar 2013 16:28:42 +0530 Subject: [PATCH 033/541] upload the file with the same extension as src --- imgee/views/index.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 101959b8..cf371a58 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import os.path from werkzeug import secure_filename from uuid import uuid4 from flask import render_template, request, g, jsonify, redirect @@ -26,8 +27,10 @@ def upload_files(): upload_form = forms.UploadForm() if upload_form.validate_on_submit(): filename = secure_filename(request.files['uploaded_file'].filename) + extn = os.path.splitext(filename)[1] + uniq_name = uuid4().hex + extn profile = Profile.query.filter_by(userid=profileid).first() - stored_file = StoredFile(name=uuid4().hex, title=filename, profile=profile) + stored_file = StoredFile(name=uniq_name, title=filename, profile=profile) db.session.add(stored_file) db.session.commit() save(request.files['uploaded_file'], stored_file.name) From c0fbe45fc41f3d88dd930f0af50f25cca6dfaeec Mon Sep 17 00:00:00 2001 From: Devi Date: Wed, 27 Mar 2013 17:07:54 +0530 Subject: [PATCH 034/541] rename route /upload as /new --- imgee/views/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index cf371a58..ca386061 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -15,7 +15,7 @@ def index(): return render_template('index.html') -@app.route('/upload', methods=('GET', 'POST')) +@app.route('/new', methods=('GET', 'POST')) @lastuser.resource_handler('imgee/upload') @login_required def upload_files(): @@ -41,7 +41,7 @@ def upload_files(): @app.route('/list') @lastuser.resource_handler('imgee/list') @login_required -def list_files(callerinfo): +def list_files(): profileid = request.args.get('profileid', g.user.userid) make_profiles() if profileid in g.user.user_organizations_owned_ids(): From c8be139adee090d92fd1ec06b1cc2b1a87ee8e77 Mon Sep 17 00:00:00 2001 From: Devi Date: Wed, 27 Mar 2013 18:38:33 +0530 Subject: [PATCH 035/541] clean up --- imgee/storage.py | 42 ++++++++++++++++-------------------------- imgee/views/index.py | 20 ++++++++------------ 2 files changed, 24 insertions(+), 38 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index 3be2ec7a..9f6e7ae3 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -1,5 +1,6 @@ -from os import path +import os.path from uuid import uuid4 +import re from boto import connect_s3 from boto.s3.bucket import Bucket from boto.s3.key import Key @@ -8,19 +9,13 @@ from imgee.models import db, Thumbnail -IMAGES = 'jpg jpe jpeg png gif svg bmp'.split() +IMAGES = 'jpg jpe jpeg png gif svg bmp'.split() -def upload(local_name, remote_name=None): - """ - Upload a file to S3 - """ +def get_s3_bucket(): conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) bucket = Bucket(conn, app.config['AWS_BUCKET']) - k = Key(bucket) - k.key = remote_name or local_name - k.set_contents_from_filename(path.join(app.config['UPLOADED_FILES_DEST'], local_name)) - + return bucket def is_image(filename): """ @@ -37,9 +32,8 @@ def create_thumbnail(stored_file, size): thumbnail = Thumbnail(name=uuid4().hex, size=size, stored_file=stored_file) db.session.add(thumbnail) db.session.commit() - conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) - bucket = Bucket(conn, app.config['AWS_BUCKET']) - thumbnail_path = path.join(app.config['UPLOADED_FILES_DEST'], thumbnail.name) + thumbnail_path = os.path.join(app.config['UPLOADED_FILES_DEST'], thumbnail.name) + bucket = get_s3_bucket() k = Key(bucket) k.key = stored_file.name k.get_contents_to_filename(thumbnail_path) @@ -53,17 +47,14 @@ def create_thumbnail(stored_file, size): return thumbnail.name -def convert_size(size): - converted = size.split('x') - if len(converted) != 2: - return None - for k, v in enumerate(converted): - if v.isdigit(): - converted[k] = int(v) - else: - return None - return converted - +def split_size(size): + """ return (a, b) if size is 'axb' + """ + r = r'^(\d+)x(\d+)$' + matched = re.match(r, size) + if matched: + a, b = matched.group(1, 2) + return int(a), int(b) def delete_image(stored_file): """ @@ -71,6 +62,5 @@ def delete_image(stored_file): """ keys = [thumbnail.name for thumbnail in stored_file.thumbnails] keys.append(stored_file.name) - conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) - bucket = Bucket(conn, app.config['AWS_BUCKET']) + bucket = get_s3_bucket() bucket.delete_keys(keys) diff --git a/imgee/views/index.py b/imgee/views/index.py index ca386061..89a267fd 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -8,7 +8,7 @@ from imgee import app, forms from imgee.models import StoredFile, Thumbnail, db, Profile from imgee.views.login import lastuser, make_profiles, login_required -from imgee.storage import upload, is_image, create_thumbnail, convert_size, delete_image +from imgee.storage import is_image, create_thumbnail, split_size, delete_image @app.route('/') def index(): @@ -54,19 +54,15 @@ def list_files(): @app.route('/file/') def get_thumbnail(filename): make_profiles() - size = request.args.get('size') + size = request.args.get('size', '') + converted_size = split_size(size) stored_file = StoredFile.query.filter_by(name=filename).first() - converted_size = convert_size(size) - if not size or is_image(stored_file.name) or not converted_size: + if (not size) and is_image(stored_file.name): return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) - existing_thumbnail = Thumbnail.query.filter_by(size=size, stored_file=stored_file).first() - if existing_thumbnail: - return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], existing_thumbnail.name)) - new_thumbnail = create_thumbnail(stored_file, converted_size) - if new_thumbnail: - return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumbnail.name)) - return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) - + thumbnail = Thumbnail.query.filter_by(size=size, stored_file=stored_file).first() + if not thumbnail: + thumbnail = create_thumbnail(stored_file, converted_size) + return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumbnail.name)) @app.route('/delete', methods=['POST']) @lastuser.resource_handler('imgee/delete') From c2a3cf44cd1b0cecbec83d0632b46fe1a6d34ada Mon Sep 17 00:00:00 2001 From: Devi Date: Fri, 29 Mar 2013 08:30:44 +0530 Subject: [PATCH 036/541] fix resize and upload --- imgee/storage.py | 86 +++++++++++++++++++++++++++++++------------- imgee/views/index.py | 27 ++++++-------- 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index 9f6e7ae3..82b87903 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -5,12 +5,14 @@ from boto.s3.bucket import Bucket from boto.s3.key import Key from PIL import Image +import mimetypes +from StringIO import StringIO +from flask.ext.uploads import save as uploads_save + from imgee import app from imgee.models import db, Thumbnail - - -IMAGES = 'jpg jpe jpeg png gif svg bmp'.split() +IMAGES = 'jpg jpe jpeg png gif bmp'.split() def get_s3_bucket(): conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) @@ -24,37 +26,71 @@ def is_image(filename): extension = filename.rsplit('.', 1)[-1] return extension in IMAGES +def save(fp, img_name, remote=True): + local_path = os.path.join(app.config['UPLOADED_FILES_DEST'], img_name) + with open(local_path, 'w') as img: + img.write(fp.read()) -def create_thumbnail(stored_file, size): - """ - Create a thumbnail for a given file and given size - """ - thumbnail = Thumbnail(name=uuid4().hex, size=size, stored_file=stored_file) - db.session.add(thumbnail) + if remote: + fp.seek(0) + uploads_save(fp, img_name) + +def path_for(img_name): + return os.path.join(app.config['UPLOADED_FILES_DEST'], img_name) + +def get_image_name(img, size): + img_name = img.name + size_t = split_size(size) + if size_t: + scaled = Thumbnail.query.filter_by(size=size, stored_file=img).first() + if scaled: + img_name = scaled.name + else: + img_name = resize_and_save(img, size, format) + return img_name + +def get_image_locally(img_name): + local_path = path_for(img_name) + if not os.path.exists(local_path): + bucket = get_s3_bucket() + k = Key(bucket) + k.key = img_name + k.get_contents_to_filename(local_path) + return local_path + +def resize_and_save(img, size, format): + format = os.path.splitext(img.title)[1].lstrip('.') + src_path = get_image_locally(img.name) + scaled_img_name = uuid4().hex + scaled_path = path_for(scaled_img_name) + resize_img(src_path, scaled_path, size, format) + scaled = Thumbnail(name=scaled_img_name, size=size, stored_file=img) + db.session.add(scaled) db.session.commit() - thumbnail_path = os.path.join(app.config['UPLOADED_FILES_DEST'], thumbnail.name) - bucket = get_s3_bucket() - k = Key(bucket) - k.key = stored_file.name - k.get_contents_to_filename(thumbnail_path) - try: - img = Image.open(thumbnail_path) - img.load() - img.thumbnail(size, Image.ANTIALIAS) - img.save(thumbnail_path) - except IOError: - return None - return thumbnail.name + return scaled_img_name +def resize_img(src, dest, size, format): + """ + `size` is a tuple (width, height) or a string 'x'. + resize the image at `path` to the specified `size` and return the resized img. + """ + if isinstance(size, (str, unicode)): + size = split_size(size) + if (not size) or (not os.path.exists(src)): + return + img = Image.open(src) + img.load() + resized = img.resize(size, Image.ANTIALIAS) + resized.save(dest, format=format, quality=100) def split_size(size): - """ return (a, b) if size is 'axb' + """ return (w, h) if size is 'wxh' """ r = r'^(\d+)x(\d+)$' matched = re.match(r, size) if matched: - a, b = matched.group(1, 2) - return int(a), int(b) + w, h = matched.group(1, 2) + return int(w), int(h) def delete_image(stored_file): """ diff --git a/imgee/views/index.py b/imgee/views/index.py index 89a267fd..06abd658 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -2,19 +2,17 @@ import os.path from werkzeug import secure_filename from uuid import uuid4 -from flask import render_template, request, g, jsonify, redirect -from flask.ext.uploads import save +from flask import render_template, request, g, jsonify, redirect, abort, send_from_directory from imgee import app, forms from imgee.models import StoredFile, Thumbnail, db, Profile from imgee.views.login import lastuser, make_profiles, login_required -from imgee.storage import is_image, create_thumbnail, split_size, delete_image +from imgee.storage import split_size, delete_image, resize_and_save, save, get_image_name @app.route('/') def index(): return render_template('index.html') - @app.route('/new', methods=('GET', 'POST')) @lastuser.resource_handler('imgee/upload') @login_required @@ -27,8 +25,7 @@ def upload_files(): upload_form = forms.UploadForm() if upload_form.validate_on_submit(): filename = secure_filename(request.files['uploaded_file'].filename) - extn = os.path.splitext(filename)[1] - uniq_name = uuid4().hex + extn + uniq_name = uuid4().hex profile = Profile.query.filter_by(userid=profileid).first() stored_file = StoredFile(name=uniq_name, title=filename, profile=profile) db.session.add(stored_file) @@ -50,19 +47,15 @@ def list_files(): return jsonify(file_list) return jsonify({'error': 'You do not have permission to access this resource'}) - -@app.route('/file/') -def get_thumbnail(filename): +@app.route('/') +def get_image(img_name): make_profiles() + img = StoredFile.query.filter_by(name=img_name).first() + if not img: abort(404) size = request.args.get('size', '') - converted_size = split_size(size) - stored_file = StoredFile.query.filter_by(name=filename).first() - if (not size) and is_image(stored_file.name): - return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], stored_file.name)) - thumbnail = Thumbnail.query.filter_by(size=size, stored_file=stored_file).first() - if not thumbnail: - thumbnail = create_thumbnail(stored_file, converted_size) - return redirect('%s/%s' % (app.config['MEDIA_DOMAIN'], new_thumbnail.name)) + img_name = get_image_name(img, size) + image_dir = os.path.abspath(app.config['UPLOADED_FILES_DEST']) + return send_from_directory(image_dir, img_name, mimetype='image/jpeg') @app.route('/delete', methods=['POST']) @lastuser.resource_handler('imgee/delete') From 979063dda5c371982c94bb105ecf3e7056c13d3c Mon Sep 17 00:00:00 2001 From: Devi Date: Fri, 29 Mar 2013 08:39:51 +0530 Subject: [PATCH 037/541] add cache header --- imgee/views/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 06abd658..5c172100 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -55,7 +55,8 @@ def get_image(img_name): size = request.args.get('size', '') img_name = get_image_name(img, size) image_dir = os.path.abspath(app.config['UPLOADED_FILES_DEST']) - return send_from_directory(image_dir, img_name, mimetype='image/jpeg') + year = 60*60*24*365 + return send_from_directory(image_dir, img_name, mimetype='image/jpeg', cache_timeout=year) @app.route('/delete', methods=['POST']) @lastuser.resource_handler('imgee/delete') From 24cc094bf5f41dbaf997d9803067bad4dcffaa3b Mon Sep 17 00:00:00 2001 From: Devi Date: Fri, 29 Mar 2013 08:45:28 +0530 Subject: [PATCH 038/541] unclutter imports --- imgee/storage.py | 8 +++++--- imgee/views/index.py | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index 82b87903..4be848c5 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -1,12 +1,14 @@ import os.path from uuid import uuid4 import re -from boto import connect_s3 -from boto.s3.bucket import Bucket -from boto.s3.key import Key from PIL import Image import mimetypes from StringIO import StringIO + +from boto import connect_s3 +from boto.s3.bucket import Bucket +from boto.s3.key import Key + from flask.ext.uploads import save as uploads_save from imgee import app diff --git a/imgee/views/index.py b/imgee/views/index.py index 5c172100..7b934be0 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -2,12 +2,12 @@ import os.path from werkzeug import secure_filename from uuid import uuid4 -from flask import render_template, request, g, jsonify, redirect, abort, send_from_directory +from flask import render_template, request, g, jsonify, abort, send_from_directory from imgee import app, forms -from imgee.models import StoredFile, Thumbnail, db, Profile +from imgee.models import StoredFile, db, Profile from imgee.views.login import lastuser, make_profiles, login_required -from imgee.storage import split_size, delete_image, resize_and_save, save, get_image_name +from imgee.storage import delete_image, save, get_image_name @app.route('/') def index(): From 143210750310d37bddbd6bcc914c113a080a1440 Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 1 Apr 2013 15:11:25 +0530 Subject: [PATCH 039/541] move format check to form validation --- imgee/forms.py | 13 +++++++++++-- imgee/storage.py | 9 --------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/imgee/forms.py b/imgee/forms.py index bb75ec2d..2cd670bc 100644 --- a/imgee/forms.py +++ b/imgee/forms.py @@ -1,7 +1,16 @@ # -*- coding: utf-8 -*- -from flask.ext.wtf import Form, FileField, Required +import os.path +from flask.ext.wtf import Form, FileField, Required, ValidationError +allowed_extns = 'jpg jpe jpeg png gif bmp'.split() + +def valid_image(form, field): + filename = field.data.filename + extn = os.path.splitext(filename)[1].lstrip('.') + if not extn.lower() in allowed_extns: + raise ValidationError("Sorry, we don't support '%s' images. \ + Please upload images in one of these formats: %s" % (extn, repr(allowed_extns)[1:-1])) class UploadForm(Form): - uploaded_file = FileField("Uploaded File", validators=[Required()]) + uploaded_file = FileField("Uploaded File", validators=[Required(), valid_image]) diff --git a/imgee/storage.py b/imgee/storage.py index 4be848c5..0e92bdc1 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -14,20 +14,11 @@ from imgee import app from imgee.models import db, Thumbnail -IMAGES = 'jpg jpe jpeg png gif bmp'.split() - def get_s3_bucket(): conn = connect_s3(app.config['AWS_ACCESS_KEY'], app.config['AWS_SECRET_KEY']) bucket = Bucket(conn, app.config['AWS_BUCKET']) return bucket -def is_image(filename): - """ - Check if a given filename is an image or not - """ - extension = filename.rsplit('.', 1)[-1] - return extension in IMAGES - def save(fp, img_name, remote=True): local_path = os.path.join(app.config['UPLOADED_FILES_DEST'], img_name) with open(local_path, 'w') as img: From b78e6075f6cb2a84934b6e38a049e689499ca09f Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 1 Apr 2013 15:31:20 +0530 Subject: [PATCH 040/541] preserve aspect ratio --- imgee/storage.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/imgee/storage.py b/imgee/storage.py index 0e92bdc1..e8e30721 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -62,6 +62,14 @@ def resize_and_save(img, size, format): db.session.commit() return scaled_img_name +def get_size((orig_w, orig_h), (w, h)): + # return size which preserves the aspect ratio of the original image. + if w != 0: + size = (w, orig_h*w/orig_w) + else: + size = (orig_w*h/orig_h, h) + return size + def resize_img(src, dest, size, format): """ `size` is a tuple (width, height) or a string 'x'. @@ -69,10 +77,12 @@ def resize_img(src, dest, size, format): """ if isinstance(size, (str, unicode)): size = split_size(size) + if (not size) or (not os.path.exists(src)): return img = Image.open(src) img.load() + size = get_size(img.size, size) resized = img.resize(size, Image.ANTIALIAS) resized.save(dest, format=format, quality=100) From 1ad3d48d78092a0223991636f6e4d4306bf5e1c4 Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 1 Apr 2013 16:27:35 +0530 Subject: [PATCH 041/541] take single dimension as request for square --- imgee/storage.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index e8e30721..f453fdf7 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -64,8 +64,13 @@ def resize_and_save(img, size, format): def get_size((orig_w, orig_h), (w, h)): # return size which preserves the aspect ratio of the original image. + # w or h being None means square size if w != 0: size = (w, orig_h*w/orig_w) + elif w == None: + size = (h, h) + elif h == None: + size = (w, w) else: size = (orig_w*h/orig_h, h) return size @@ -89,11 +94,12 @@ def resize_img(src, dest, size, format): def split_size(size): """ return (w, h) if size is 'wxh' """ - r = r'^(\d+)x(\d+)$' + r= r'^(\d+)(x(\d+))?$' matched = re.match(r, size) if matched: w, h = matched.group(1, 2) - return int(w), int(h) + h = int(h.lstrip('x')) if h != None else None + return int(w), h def delete_image(stored_file): """ From 27421c4ea04b37ef96b439c351200c104d9bed00 Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 1 Apr 2013 22:01:23 +0530 Subject: [PATCH 042/541] save resized images on s3 --- imgee/storage.py | 12 ++++++++---- imgee/views/index.py | 9 ++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index f453fdf7..db2907ba 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -55,8 +55,9 @@ def resize_and_save(img, size, format): format = os.path.splitext(img.title)[1].lstrip('.') src_path = get_image_locally(img.name) scaled_img_name = uuid4().hex - scaled_path = path_for(scaled_img_name) - resize_img(src_path, scaled_path, size, format) + scaled = resize_img(src_path, size, format) + uploads_save(scaled, scaled_img_name) + scaled = Thumbnail(name=scaled_img_name, size=size, stored_file=img) db.session.add(scaled) db.session.commit() @@ -75,7 +76,7 @@ def get_size((orig_w, orig_h), (w, h)): size = (orig_w*h/orig_h, h) return size -def resize_img(src, dest, size, format): +def resize_img(src, size, format): """ `size` is a tuple (width, height) or a string 'x'. resize the image at `path` to the specified `size` and return the resized img. @@ -89,7 +90,10 @@ def resize_img(src, dest, size, format): img.load() size = get_size(img.size, size) resized = img.resize(size, Image.ANTIALIAS) - resized.save(dest, format=format, quality=100) + imgio = StringIO() + resized.save(imgio, format=format, quality=100) + imgio.seek(0) + return imgio def split_size(size): """ return (w, h) if size is 'wxh' diff --git a/imgee/views/index.py b/imgee/views/index.py index 7b934be0..a0fea9cc 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -2,7 +2,8 @@ import os.path from werkzeug import secure_filename from uuid import uuid4 -from flask import render_template, request, g, jsonify, abort, send_from_directory +from flask import render_template, request, g, jsonify, abort, send_from_directory, redirect +from urlparse import urljoin from imgee import app, forms from imgee.models import StoredFile, db, Profile @@ -47,16 +48,14 @@ def list_files(): return jsonify(file_list) return jsonify({'error': 'You do not have permission to access this resource'}) -@app.route('/') +@app.route('/file/') def get_image(img_name): make_profiles() img = StoredFile.query.filter_by(name=img_name).first() if not img: abort(404) size = request.args.get('size', '') img_name = get_image_name(img, size) - image_dir = os.path.abspath(app.config['UPLOADED_FILES_DEST']) - year = 60*60*24*365 - return send_from_directory(image_dir, img_name, mimetype='image/jpeg', cache_timeout=year) + return redirect(urljoin(app.config.get('MEDIA_DOMAIN'), img_name), code=301) @app.route('/delete', methods=['POST']) @lastuser.resource_handler('imgee/delete') From a67a4803b5a832b18e9dd221090ee9212069a78a Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 2 Apr 2013 12:48:39 +0530 Subject: [PATCH 043/541] get rid of not-so-flexible flask-uploads/storage --- imgee/__init__.py | 3 --- imgee/storage.py | 28 ++++++++++++++++++++-------- imgee/views/index.py | 5 +++-- requirements.txt | 2 -- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/imgee/__init__.py b/imgee/__init__.py index 3e54c535..2c25a5d6 100644 --- a/imgee/__init__.py +++ b/imgee/__init__.py @@ -5,8 +5,6 @@ from flask import Flask from flask.ext.assets import Environment, Bundle from flask.ext.lastuser import LastUser -from flask.ext.uploads import init -from flask.ext.storage import get_default_storage_class from baseframe import baseframe, baseframe_js, baseframe_css import coaster.app @@ -34,4 +32,3 @@ def init_for(env): assets.register('js_all', js) assets.register('css_all', css) lastuser.init_app(app) - init(imgee.models.db, get_default_storage_class(app)) diff --git a/imgee/storage.py b/imgee/storage.py index db2907ba..e9353426 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -9,8 +9,6 @@ from boto.s3.bucket import Bucket from boto.s3.key import Key -from flask.ext.uploads import save as uploads_save - from imgee import app from imgee.models import db, Thumbnail @@ -19,14 +17,27 @@ def get_s3_bucket(): bucket = Bucket(conn, app.config['AWS_BUCKET']) return bucket -def save(fp, img_name, remote=True): +def save(fp, img_name, remote=True, content_type=None): local_path = os.path.join(app.config['UPLOADED_FILES_DEST'], img_name) with open(local_path, 'w') as img: img.write(fp.read()) if remote: fp.seek(0) - uploads_save(fp, img_name) + save_on_s3(fp, img_name, content_type=content_type) + +def get_file_type(filename): + return mimetypes.guess_type(filename)[0] + +def save_on_s3(fp, filename, content_type=''): + b = get_s3_bucket() + k = b.new_key(filename) + content_type = content_type or get_file_type(filename) + headers = { + 'Cache-Control': 'max-age=31536000', #60*60*24*365 + 'Content-Type': content_type, + } + k.set_contents_from_file(fp, policy='public-read', headers=headers) def path_for(img_name): return os.path.join(app.config['UPLOADED_FILES_DEST'], img_name) @@ -39,7 +50,7 @@ def get_image_name(img, size): if scaled: img_name = scaled.name else: - img_name = resize_and_save(img, size, format) + img_name = resize_and_save(img, size) return img_name def get_image_locally(img_name): @@ -51,12 +62,13 @@ def get_image_locally(img_name): k.get_contents_to_filename(local_path) return local_path -def resize_and_save(img, size, format): - format = os.path.splitext(img.title)[1].lstrip('.') +def resize_and_save(img, size): src_path = get_image_locally(img.name) scaled_img_name = uuid4().hex + content_type = get_file_type(img.title) # eg: image/jpeg + format = content_type.split('/')[1] if content_type else None scaled = resize_img(src_path, size, format) - uploads_save(scaled, scaled_img_name) + save_on_s3(scaled, scaled_img_name, content_type) scaled = Thumbnail(name=scaled_img_name, size=size, stored_file=img) db.session.add(scaled) diff --git a/imgee/views/index.py b/imgee/views/index.py index a0fea9cc..1f0a8d14 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -8,7 +8,7 @@ from imgee import app, forms from imgee.models import StoredFile, db, Profile from imgee.views.login import lastuser, make_profiles, login_required -from imgee.storage import delete_image, save, get_image_name +from imgee.storage import delete_image, save, get_image_name, get_file_type @app.route('/') def index(): @@ -31,7 +31,8 @@ def upload_files(): stored_file = StoredFile(name=uniq_name, title=filename, profile=profile) db.session.add(stored_file) db.session.commit() - save(request.files['uploaded_file'], stored_file.name) + content_type = get_file_type(filename) + save(request.files['uploaded_file'], stored_file.name, content_type=content_type) return jsonify({'id': stored_file.name}) # form invalid or request.method == 'GET' return render_template('form.html', form=upload_form) diff --git a/requirements.txt b/requirements.txt index ec2387c0..1445f546 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,4 @@ Flask-SQLAlchemy https://github.com/hasgeek/flask-lastuser/zipball/master Flask-Admin boto --e git+http://github.com/FelixLoether/flask-uploads#egg=Flask-Uploads --e git://github.com/kvesteri/flask-storage.git#egg=Flask-Storage PIL From eee2f71baade310e770139b8911b522452a5b705 Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 2 Apr 2013 16:42:03 +0530 Subject: [PATCH 044/541] thumbnails --- imgee/storage.py | 45 +++++++++++++++++++++++++++++--------------- imgee/views/index.py | 12 ++++++++++-- instance/settings.py | 1 + 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/imgee/storage.py b/imgee/storage.py index e9353426..d82fb7df 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -42,15 +42,18 @@ def save_on_s3(fp, filename, content_type=''): def path_for(img_name): return os.path.join(app.config['UPLOADED_FILES_DEST'], img_name) -def get_image_name(img, size): +def get_resized_image(img, size, thumbnail=False): img_name = img.name - size_t = split_size(size) + if isinstance(size, (str, unicode)): + size_t = split_size(size) + elif isinstance(size, tuple): + size_t, size = size, "%sx%s" % size if size_t: scaled = Thumbnail.query.filter_by(size=size, stored_file=img).first() if scaled: img_name = scaled.name else: - img_name = resize_and_save(img, size) + img_name = resize_and_save(img, size_t, thumbnail=thumbnail) return img_name def get_image_locally(img_name): @@ -62,46 +65,58 @@ def get_image_locally(img_name): k.get_contents_to_filename(local_path) return local_path -def resize_and_save(img, size): +def resize_and_save(img, size, thumbnail=False): src_path = get_image_locally(img.name) scaled_img_name = uuid4().hex content_type = get_file_type(img.title) # eg: image/jpeg format = content_type.split('/')[1] if content_type else None - scaled = resize_img(src_path, size, format) + scaled = resize_img(src_path, size, format, thumbnail=thumbnail) save_on_s3(scaled, scaled_img_name, content_type) - scaled = Thumbnail(name=scaled_img_name, size=size, stored_file=img) + size_s = "%sx%s" % size + scaled = Thumbnail(name=scaled_img_name, size=size_s, stored_file=img) db.session.add(scaled) db.session.commit() return scaled_img_name def get_size((orig_w, orig_h), (w, h)): - # return size which preserves the aspect ratio of the original image. # w or h being None means square size - if w != 0: + # w or h being 0 means preserve aspect ratio with that height or width + # else return w, h + if h == 0: size = (w, orig_h*w/orig_w) + elif w == 0: + size = (orig_w*h/orig_h, h) elif w == None: size = (h, h) elif h == None: size = (w, w) else: - size = (orig_w*h/orig_h, h) + size = (w, h) return size -def resize_img(src, size, format): +def resize_img(src, size, format, thumbnail): """ - `size` is a tuple (width, height) or a string 'x'. - resize the image at `path` to the specified `size` and return the resized img. + `size` is a tuple (width, height) + resize the image at `src` to the specified `size` and return the resized img. """ - if isinstance(size, (str, unicode)): - size = split_size(size) - if (not size) or (not os.path.exists(src)): return img = Image.open(src) img.load() + if thumbnail: + # fit the image to the box along the smaller side and preserve aspect ratio. + (ow, oh), (w, h) = img.size, size + size = (0, h) if ow>=oh else (w, 0) + size = get_size(img.size, size) resized = img.resize(size, Image.ANTIALIAS) + + if thumbnail: + # and crop the rest + right, lower = app.config.get('THUMBNAIL_SIZE') + resized = resized.crop((0, 0, right, lower)) + imgio = StringIO() resized.save(imgio, format=format, quality=100) imgio.seek(0) diff --git a/imgee/views/index.py b/imgee/views/index.py index 1f0a8d14..952ab110 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -8,7 +8,7 @@ from imgee import app, forms from imgee.models import StoredFile, db, Profile from imgee.views.login import lastuser, make_profiles, login_required -from imgee.storage import delete_image, save, get_image_name, get_file_type +from imgee.storage import delete_image, save, get_resized_image, get_file_type @app.route('/') def index(): @@ -55,9 +55,17 @@ def get_image(img_name): img = StoredFile.query.filter_by(name=img_name).first() if not img: abort(404) size = request.args.get('size', '') - img_name = get_image_name(img, size) + img_name = get_resized_image(img, size) return redirect(urljoin(app.config.get('MEDIA_DOMAIN'), img_name), code=301) +@app.route('/thumbnail/') +def get_thumbnail(img_name): + img = StoredFile.query.filter_by(name=img_name).first() + if not img: abort(404) + tn_size = app.config.get('THUMBNAIL_SIZE') + thumbnail = get_resized_image(img, tn_size, thumbnail=True) + return redirect(urljoin(app.config.get('MEDIA_DOMAIN'), thumbnail), code=301) + @app.route('/delete', methods=['POST']) @lastuser.resource_handler('imgee/delete') @login_required diff --git a/instance/settings.py b/instance/settings.py index 23753411..d28d7e8a 100644 --- a/instance/settings.py +++ b/instance/settings.py @@ -46,3 +46,4 @@ #: Domain name for files MEDIA_DOMAIN = 'set domain name here' +THUMBNAIL_SIZE=(75, 75) # (w, h) in px \ No newline at end of file From c975c7948cabd2074a49e1152986d253eed17691 Mon Sep 17 00:00:00 2001 From: Devi Date: Mon, 8 Apr 2013 16:21:25 +0530 Subject: [PATCH 045/541] fix deletion of image --- imgee/forms.py | 3 +++ imgee/storage.py | 2 +- imgee/views/index.py | 28 ++++++++++++++-------------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/imgee/forms.py b/imgee/forms.py index 2cd670bc..1612c378 100644 --- a/imgee/forms.py +++ b/imgee/forms.py @@ -14,3 +14,6 @@ def valid_image(form, field): class UploadForm(Form): uploaded_file = FileField("Uploaded File", validators=[Required(), valid_image]) + +class DeleteForm(Form): + pass diff --git a/imgee/storage.py b/imgee/storage.py index d82fb7df..dafade37 100644 --- a/imgee/storage.py +++ b/imgee/storage.py @@ -132,7 +132,7 @@ def split_size(size): h = int(h.lstrip('x')) if h != None else None return int(w), h -def delete_image(stored_file): +def delete_on_s3(stored_file): """ Delete all the thumbnails and images associated with a file """ diff --git a/imgee/views/index.py b/imgee/views/index.py index 952ab110..c04df430 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -8,7 +8,7 @@ from imgee import app, forms from imgee.models import StoredFile, db, Profile from imgee.views.login import lastuser, make_profiles, login_required -from imgee.storage import delete_image, save, get_resized_image, get_file_type +from imgee.storage import delete_on_s3, save, get_resized_image, get_file_type @app.route('/') def index(): @@ -66,21 +66,21 @@ def get_thumbnail(img_name): thumbnail = get_resized_image(img, tn_size, thumbnail=True) return redirect(urljoin(app.config.get('MEDIA_DOMAIN'), thumbnail), code=301) -@app.route('/delete', methods=['POST']) +@app.route('/delete/', methods=('GET','POST')) @lastuser.resource_handler('imgee/delete') @login_required -def delete_files(callerinfo): - profileid = request.args.get('profileid', g.user.userid) - fileid = request.args.get('fileid', g.user.userid) - if not fileid: - return jsonify({'error': 'No filename given'}) +def delete_files(img_name): + profileid = g.user.userid make_profiles() if profileid in g.user.user_organizations_owned_ids(): - stored_file = StoredFile.query.filter_by(name=fileid).first() - if stored_file: - delete_image(stored_file) - db.session.delete(stored_file) - db.session.commit() - return jsonify({'success': 'File deleted'}) - return jsonify({'error': 'No file found'}) + form = forms.DeleteForm() + if form.validate_on_submit(): + stored_file = StoredFile.query.filter_by(name=img_name).first() + if stored_file: + delete_on_s3(stored_file) + db.session.delete(stored_file) + db.session.commit() + return jsonify({'success': 'File deleted'}) + return jsonify({'error': 'No file found'}) + return render_template('delete.html', form=form, filename=img_name) return jsonify({'error': 'You do not have permission to access this resource'}) From 617bbac1a12e5ca67e898a0854587bae4b638c64 Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 9 Apr 2013 14:49:43 +0530 Subject: [PATCH 046/541] seperate out authorization --- imgee/views/index.py | 40 +++++++++++++++------------------------- imgee/views/login.py | 10 ++++++++++ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index c04df430..f4c55b7f 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -16,13 +16,10 @@ def index(): @app.route('/new', methods=('GET', 'POST')) @lastuser.resource_handler('imgee/upload') +@authorize @login_required def upload_files(): profileid = g.user.userid - make_profiles() - if profileid not in g.user.user_organizations_owned_ids(): - return jsonify({'error': 'You do not have permission to access this resource'}) - upload_form = forms.UploadForm() if upload_form.validate_on_submit(): filename = secure_filename(request.files['uploaded_file'].filename) @@ -42,16 +39,12 @@ def upload_files(): @login_required def list_files(): profileid = request.args.get('profileid', g.user.userid) - make_profiles() - if profileid in g.user.user_organizations_owned_ids(): - files = StoredFile.query.filter(Profile.userid == profileid).all() - file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} - return jsonify(file_list) - return jsonify({'error': 'You do not have permission to access this resource'}) + files = StoredFile.query.filter(Profile.userid == profileid).all() + file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} + return jsonify(file_list) @app.route('/file/') def get_image(img_name): - make_profiles() img = StoredFile.query.filter_by(name=img_name).first() if not img: abort(404) size = request.args.get('size', '') @@ -68,19 +61,16 @@ def get_thumbnail(img_name): @app.route('/delete/', methods=('GET','POST')) @lastuser.resource_handler('imgee/delete') +@authorize @login_required def delete_files(img_name): - profileid = g.user.userid - make_profiles() - if profileid in g.user.user_organizations_owned_ids(): - form = forms.DeleteForm() - if form.validate_on_submit(): - stored_file = StoredFile.query.filter_by(name=img_name).first() - if stored_file: - delete_on_s3(stored_file) - db.session.delete(stored_file) - db.session.commit() - return jsonify({'success': 'File deleted'}) - return jsonify({'error': 'No file found'}) - return render_template('delete.html', form=form, filename=img_name) - return jsonify({'error': 'You do not have permission to access this resource'}) + form = forms.DeleteForm() + if form.validate_on_submit(): + stored_file = StoredFile.query.filter_by(name=img_name).first() + if stored_file: + delete_on_s3(stored_file) + db.session.delete(stored_file) + db.session.commit() + return jsonify({'success': 'File deleted'}) + return jsonify({'error': 'No file found'}) + return render_template('delete.html', form=form, filename=img_name) diff --git a/imgee/views/login.py b/imgee/views/login.py index 681673b9..b2689429 100644 --- a/imgee/views/login.py +++ b/imgee/views/login.py @@ -19,6 +19,16 @@ def decorated_func(*args, **kwargs): return f(*args, **kwargs) return decorated_func +def authorize(f): + @wraps(f) + def decorated_func(*args, **kwargs): + profileid = g.user.userid + make_profiles() + if profileid in g.user.user_organizations_owned_ids(): + return f(*args, **kwargs) + return jsonify({'error': 'You do not have permission to access this resource'}) + return decorated_func + @app.route('/login') @lastuser.login_handler def login(): From b4c64366d54a1426b8e205d609bd0e35ad6c3d75 Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 9 Apr 2013 16:31:07 +0530 Subject: [PATCH 047/541] show thumbnails on profile page --- imgee/static/css/app.css | 32 ++++++++++++++++++++++++++++++++ imgee/templates/profile.html | 19 +++++++++++++++++++ imgee/views/index.py | 22 +++++++++++++--------- 3 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 imgee/templates/profile.html diff --git a/imgee/static/css/app.css b/imgee/static/css/app.css index e69de29b..1080d253 100644 --- a/imgee/static/css/app.css +++ b/imgee/static/css/app.css @@ -0,0 +1,32 @@ +div.gallery div.image + { + margin:2px; + border:1px solid #b7b7b7; + height:auto; + width:auto; + float:left; + text-align:center; + } +div.gallery div.image img + { + display:inline; + margin:3px; + border:1px solid #ffffff; + } +div.gallery div.image a:hover img + { + border:1px solid #0000ff; + } +div.gallery div.image div.title + { + text-align:center; + font-weight:normal; + width:120px; + margin:2px; + } +div.gallery div.image span.delete +{ + font-weight:bold; + float: right; + padding-right: 2px; +} diff --git a/imgee/templates/profile.html b/imgee/templates/profile.html new file mode 100644 index 00000000..29b1e4fa --- /dev/null +++ b/imgee/templates/profile.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} +{% block titletags -%} + {% block title %}{{ config['SITE_TITLE'] }}{% endblock %} + +{%- endblock %} + +{% block content %} + + +{% endblock %} + diff --git a/imgee/views/index.py b/imgee/views/index.py index f4c55b7f..3dc4a27a 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -7,7 +7,7 @@ from imgee import app, forms from imgee.models import StoredFile, db, Profile -from imgee.views.login import lastuser, make_profiles, login_required +from imgee.views.login import lastuser, authorize, login_required from imgee.storage import delete_on_s3, save, get_resized_image, get_file_type @app.route('/') @@ -34,14 +34,18 @@ def upload_files(): # form invalid or request.method == 'GET' return render_template('form.html', form=upload_form) -@app.route('/list') +@app.route('/') @lastuser.resource_handler('imgee/list') @login_required -def list_files(): - profileid = request.args.get('profileid', g.user.userid) - files = StoredFile.query.filter(Profile.userid == profileid).all() - file_list = {'files': [{'name': x.title, 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name)} for x in files]} - return jsonify(file_list) +@authorize +def profile(profile_name): + files = StoredFile.query.filter(Profile.name == profile_name).all() + data = {'files': [{'title': x.title, + 'name': x.name, + 'url': '%s/%s' % (app.config['MEDIA_DOMAIN'], x.name), + 'thumbnail': 'thumbnail/%s' % x.name} + for x in files]} + return render_template('profile.html', files=data['files']) @app.route('/file/') def get_image(img_name): @@ -61,9 +65,9 @@ def get_thumbnail(img_name): @app.route('/delete/', methods=('GET','POST')) @lastuser.resource_handler('imgee/delete') -@authorize @login_required -def delete_files(img_name): +@authorize +def delete_file(img_name): form = forms.DeleteForm() if form.validate_on_submit(): stored_file = StoredFile.query.filter_by(name=img_name).first() From d18c866b32e12c7acc04c5ff65936d6fda011545 Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 9 Apr 2013 16:32:47 +0530 Subject: [PATCH 048/541] delete template --- imgee/templates/delete.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 imgee/templates/delete.html diff --git a/imgee/templates/delete.html b/imgee/templates/delete.html new file mode 100644 index 00000000..e293c7fb --- /dev/null +++ b/imgee/templates/delete.html @@ -0,0 +1,14 @@ +{% extends "layout.html" %} +{% from 'baseframe/forms.html' import renderform %} +{% block titletags -%} + {% block title %}{{ config['SITE_TITLE'] }}{% endblock %} + +{%- endblock %} + +{% block content %} + +Are you sure that you want to delete this image? + +{{ renderform(form, "delete", "Yes, delete it") }} +{% endblock %} + From ecf5dcca328beaa5388a3bd67da8c5a89b2ac4af Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 9 Apr 2013 16:34:21 +0530 Subject: [PATCH 049/541] no resource handlers for now --- imgee/views/index.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/imgee/views/index.py b/imgee/views/index.py index 3dc4a27a..ba05de08 100644 --- a/imgee/views/index.py +++ b/imgee/views/index.py @@ -15,7 +15,6 @@ def index(): return render_template('index.html') @app.route('/new', methods=('GET', 'POST')) -@lastuser.resource_handler('imgee/upload') @authorize @login_required def upload_files(): @@ -35,7 +34,6 @@ def upload_files(): return render_template('form.html', form=upload_form) @app.route('/') -@lastuser.resource_handler('imgee/list') @login_required @authorize def profile(profile_name): @@ -64,7 +62,6 @@ def get_thumbnail(img_name): return redirect(urljoin(app.config.get('MEDIA_DOMAIN'), thumbnail), code=301) @app.route('/delete/', methods=('GET','POST')) -@lastuser.resource_handler('imgee/delete') @login_required @authorize def delete_file(img_name): From a6194435245b86555186f1e5853cdc2b4477d49c Mon Sep 17 00:00:00 2001 From: Devi Date: Tue, 9 Apr 2013 17:25:27 +0530 Subject: [PATCH 050/541] link all the pages --- imgee/static/css/app.css | 1 - imgee/templates/profile.html | 3 +++ imgee/views/index.py | 28 ++++++++++++++++++---------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/imgee/static/css/app.css b/imgee/static/css/app.css index 1080d253..1d729913 100644 --- a/imgee/static/css/app.css +++ b/imgee/static/css/app.css @@ -21,7 +21,6 @@ div.gallery div.image div.title { text-align:center; font-weight:normal; - width:120px; margin:2px; } div.gallery div.image span.delete diff --git a/imgee/templates/profile.html b/imgee/templates/profile.html index 29b1e4fa..4260a6af 100644 --- a/imgee/templates/profile.html +++ b/imgee/templates/profile.html @@ -6,6 +6,9 @@ {% block content %} + + Upload images +