mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-05 16:58:59 +00:00
Adding avatar photo uploading.
This commit is contained in:
parent
62597c61e8
commit
4dd1d10d5a
14 changed files with 262 additions and 11 deletions
|
@ -351,7 +351,7 @@ class UserSubscription(models.Model):
|
||||||
|
|
||||||
for story_id in set(story_ids):
|
for story_id in set(story_ids):
|
||||||
try:
|
try:
|
||||||
story = MStory.objects.get(story_feed_id=self.feed_id, story_guid=story_id)
|
story = MStory.get_story(story_feed_id=self.feed_id, story_guid=story_id)
|
||||||
except MStory.DoesNotExist:
|
except MStory.DoesNotExist:
|
||||||
# Story has been deleted, probably by feed_fetcher.
|
# Story has been deleted, probably by feed_fetcher.
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1401,7 +1401,7 @@ class MFeedPage(mongo.Document):
|
||||||
|
|
||||||
class MStory(mongo.Document):
|
class MStory(mongo.Document):
|
||||||
'''A feed item'''
|
'''A feed item'''
|
||||||
story_feed_id = mongo.IntField(unique_with='story_guid')
|
story_feed_id = mongo.IntField()
|
||||||
story_date = mongo.DateTimeField()
|
story_date = mongo.DateTimeField()
|
||||||
story_title = mongo.StringField(max_length=1024)
|
story_title = mongo.StringField(max_length=1024)
|
||||||
story_content = mongo.StringField()
|
story_content = mongo.StringField()
|
||||||
|
@ -1414,6 +1414,7 @@ class MStory(mongo.Document):
|
||||||
story_author_name = mongo.StringField()
|
story_author_name = mongo.StringField()
|
||||||
story_permalink = mongo.StringField()
|
story_permalink = mongo.StringField()
|
||||||
story_guid = mongo.StringField()
|
story_guid = mongo.StringField()
|
||||||
|
story_hash = mongo.StringField()
|
||||||
story_tags = mongo.ListField(mongo.StringField(max_length=250))
|
story_tags = mongo.ListField(mongo.StringField(max_length=250))
|
||||||
comment_count = mongo.IntField()
|
comment_count = mongo.IntField()
|
||||||
comment_user_ids = mongo.ListField(mongo.IntField())
|
comment_user_ids = mongo.ListField(mongo.IntField())
|
||||||
|
@ -1432,6 +1433,10 @@ class MStory(mongo.Document):
|
||||||
@property
|
@property
|
||||||
def guid_hash(self):
|
def guid_hash(self):
|
||||||
return hashlib.sha1(self.story_guid).hexdigest()[:6]
|
return hashlib.sha1(self.story_guid).hexdigest()[:6]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def feed_guid_hash(self):
|
||||||
|
return hashlib.sha1("%s:%s" % (self.story_feed_id, self.story_guid)).hexdigest()[:6]
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
story_title_max = MStory._fields['story_title'].max_length
|
story_title_max = MStory._fields['story_title'].max_length
|
||||||
|
@ -1449,6 +1454,7 @@ class MStory(mongo.Document):
|
||||||
self.story_title = self.story_title[:story_title_max]
|
self.story_title = self.story_title[:story_title_max]
|
||||||
if self.story_content_type and len(self.story_content_type) > story_content_type_max:
|
if self.story_content_type and len(self.story_content_type) > story_content_type_max:
|
||||||
self.story_content_type = self.story_content_type[:story_content_type_max]
|
self.story_content_type = self.story_content_type[:story_content_type_max]
|
||||||
|
|
||||||
super(MStory, self).save(*args, **kwargs)
|
super(MStory, self).save(*args, **kwargs)
|
||||||
|
|
||||||
self.sync_redis()
|
self.sync_redis()
|
||||||
|
|
|
@ -32,6 +32,7 @@ from utils import json_functions as json
|
||||||
from utils.feed_functions import relative_timesince
|
from utils.feed_functions import relative_timesince
|
||||||
from utils.story_functions import truncate_chars, strip_tags, linkify, image_size
|
from utils.story_functions import truncate_chars, strip_tags, linkify, image_size
|
||||||
from utils.scrubber import SelectiveScriptScrubber
|
from utils.scrubber import SelectiveScriptScrubber
|
||||||
|
from utils import s3_utils
|
||||||
|
|
||||||
RECOMMENDATIONS_LIMIT = 5
|
RECOMMENDATIONS_LIMIT = 5
|
||||||
IGNORE_IMAGE_SOURCES = [
|
IGNORE_IMAGE_SOURCES = [
|
||||||
|
@ -244,6 +245,8 @@ class MSocialProfile(mongo.Document):
|
||||||
return photo_url + '?type=large'
|
return photo_url + '?type=large'
|
||||||
elif 'twimg' in photo_url:
|
elif 'twimg' in photo_url:
|
||||||
return photo_url.replace('_normal', '')
|
return photo_url.replace('_normal', '')
|
||||||
|
elif '/avatars/' in photo_url:
|
||||||
|
return photo_url.replace('thumbnail_', 'large_')
|
||||||
return photo_url
|
return photo_url
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1991,6 +1994,22 @@ class MSocialServices(mongo.Document):
|
||||||
def profile(cls, user_id):
|
def profile(cls, user_id):
|
||||||
profile = cls.get_user(user_id=user_id)
|
profile = cls.get_user(user_id=user_id)
|
||||||
return profile.to_json()
|
return profile.to_json()
|
||||||
|
|
||||||
|
def save_uploaded_photo(self, photo):
|
||||||
|
photo_body = photo.read()
|
||||||
|
filename = photo.name
|
||||||
|
|
||||||
|
s3 = s3_utils.S3Store()
|
||||||
|
image_name = s3.save_profile_picture(self.user_id, filename, photo_body)
|
||||||
|
if image_name:
|
||||||
|
self.upload_picture_url = "https://s3.amazonaws.com/%s/avatars/%s/thumbnail_%s" % (
|
||||||
|
settings.S3_AVATARS_BUCKET_NAME,
|
||||||
|
self.user_id,
|
||||||
|
image_name,
|
||||||
|
)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
return image_name and self.upload_picture_url
|
||||||
|
|
||||||
def twitter_api(self):
|
def twitter_api(self):
|
||||||
twitter_consumer_key = settings.TWITTER_CONSUMER_KEY
|
twitter_consumer_key = settings.TWITTER_CONSUMER_KEY
|
||||||
|
|
|
@ -10,6 +10,7 @@ urlpatterns = patterns('',
|
||||||
url(r'^profile/?$', views.profile, name='profile'),
|
url(r'^profile/?$', views.profile, name='profile'),
|
||||||
url(r'^load_user_profile/?$', views.load_user_profile, name='load-user-profile'),
|
url(r'^load_user_profile/?$', views.load_user_profile, name='load-user-profile'),
|
||||||
url(r'^save_user_profile/?$', views.save_user_profile, name='save-user-profile'),
|
url(r'^save_user_profile/?$', views.save_user_profile, name='save-user-profile'),
|
||||||
|
url(r'^upload_avatar/?', views.upload_avatar, name='upload-avatar'),
|
||||||
url(r'^save_blurblog_settings/?$', views.save_blurblog_settings, name='save-blurblog-settings'),
|
url(r'^save_blurblog_settings/?$', views.save_blurblog_settings, name='save-blurblog-settings'),
|
||||||
url(r'^interactions/?$', views.load_interactions, name='social-interactions'),
|
url(r'^interactions/?$', views.load_interactions, name='social-interactions'),
|
||||||
url(r'^activities/?$', views.load_activities, name='social-activities'),
|
url(r'^activities/?$', views.load_activities, name='social-activities'),
|
||||||
|
|
|
@ -870,7 +870,24 @@ def save_user_profile(request):
|
||||||
|
|
||||||
return dict(code=1, user_profile=profile.to_json(include_follows=True))
|
return dict(code=1, user_profile=profile.to_json(include_follows=True))
|
||||||
|
|
||||||
|
|
||||||
|
@ajax_login_required
|
||||||
|
@json.json_view
|
||||||
|
def upload_avatar(request):
|
||||||
|
photo = request.FILES['photo']
|
||||||
|
profile = MSocialProfile.get_user(request.user.pk)
|
||||||
|
social_services = MSocialServices.objects.get(user_id=request.user.pk)
|
||||||
|
image_url = social_services.save_uploaded_photo(photo)
|
||||||
|
if image_url:
|
||||||
|
profile = social_services.set_photo('upload')
|
||||||
|
|
||||||
|
return {
|
||||||
|
"code": 1 if image_url else -1,
|
||||||
|
"uploaded": image_url,
|
||||||
|
"services": social_services,
|
||||||
|
"user_profile": profile.to_json(include_follows=True),
|
||||||
|
}
|
||||||
|
|
||||||
@ajax_login_required
|
@ajax_login_required
|
||||||
@json.json_view
|
@json.json_view
|
||||||
def save_blurblog_settings(request):
|
def save_blurblog_settings(request):
|
||||||
|
|
|
@ -7197,7 +7197,7 @@ form.opml_import_form input {
|
||||||
|
|
||||||
.NB-modal-preferences .NB-preferences-scroll {
|
.NB-modal-preferences .NB-preferences-scroll {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 500px;
|
max-height: 600px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<key>application-identifier</key>
|
<key>application-identifier</key>
|
||||||
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
|
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
|
||||||
<key>get-task-allow</key>
|
<key>get-task-allow</key>
|
||||||
<false/>
|
<true/>
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
|
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
NEWSBLUR.ReaderAccount = function(options) {
|
NEWSBLUR.ReaderAccount = function(options) {
|
||||||
var defaults = {
|
var defaults = {
|
||||||
|
'width': 700,
|
||||||
'animate_email': false,
|
'animate_email': false,
|
||||||
'change_password': false,
|
'change_password': false,
|
||||||
'onOpen': _.bind(function() {
|
'onOpen': _.bind(function() {
|
||||||
|
|
|
@ -593,7 +593,7 @@ _.extend(NEWSBLUR.ReaderPreferences.prototype, {
|
||||||
},
|
},
|
||||||
|
|
||||||
resize_modal: function() {
|
resize_modal: function() {
|
||||||
var $scroll = $('.NB-preferences-scroll', this.$modal);
|
var $scroll = $('.NB-tab.NB-active', this.$modal);
|
||||||
var $modal = this.$modal;
|
var $modal = this.$modal;
|
||||||
var $modal_container = $modal.closest('.simplemodal-container');
|
var $modal_container = $modal.closest('.simplemodal-container');
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ _.extend(NEWSBLUR.ReaderProfileEditor.prototype, {
|
||||||
this.fetch_user_profile();
|
this.fetch_user_profile();
|
||||||
|
|
||||||
this.$modal.bind('click', $.rescope(this.handle_click, this));
|
this.$modal.bind('click', $.rescope(this.handle_click, this));
|
||||||
|
this.$modal.bind('change', $.rescope(this.handle_change, this));
|
||||||
this.handle_profile_counts();
|
this.handle_profile_counts();
|
||||||
this.delegate_change();
|
this.delegate_change();
|
||||||
},
|
},
|
||||||
|
@ -217,7 +218,9 @@ _.extend(NEWSBLUR.ReaderProfileEditor.prototype, {
|
||||||
make_profile_photo_chooser: function() {
|
make_profile_photo_chooser: function() {
|
||||||
var $profiles = $('.NB-friends-profilephoto', this.$modal).empty();
|
var $profiles = $('.NB-friends-profilephoto', this.$modal).empty();
|
||||||
|
|
||||||
_.each(['nothing', 'twitter', 'facebook', 'gravatar'], _.bind(function(service) {
|
$profiles.append($.make('div', { className: "NB-photo-upload-error NB-error" }));
|
||||||
|
|
||||||
|
_.each(['nothing', 'upload', 'twitter', 'facebook', 'gravatar'], _.bind(function(service) {
|
||||||
var $profile = $.make('div', { className: 'NB-friends-profile-photo-group NB-friends-photo-'+service }, [
|
var $profile = $.make('div', { className: 'NB-friends-profile-photo-group NB-friends-photo-'+service }, [
|
||||||
$.make('div', { className: 'NB-friends-photo-title' }, [
|
$.make('div', { className: 'NB-friends-photo-title' }, [
|
||||||
$.make('input', { type: 'radio', name: 'profile_photo_service', value: service, id: 'NB-profile-photo-service-'+service }),
|
$.make('input', { type: 'radio', name: 'profile_photo_service', value: service, id: 'NB-profile-photo-service-'+service }),
|
||||||
|
@ -233,8 +236,10 @@ _.extend(NEWSBLUR.ReaderProfileEditor.prototype, {
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
(service == 'upload' && $.make('div', { className: 'NB-photo-link' }, [
|
(service == 'upload' && $.make('div', { className: 'NB-photo-link' }, [
|
||||||
$.make('a', { href: '#', className: 'NB-photo-upload-link NB-splash-link' }, 'Upload picture'),
|
$.make('form', { method: 'post', enctype: 'multipart/form-data', encoding: 'multipart/form-data' }, [
|
||||||
$.make('input', { type: 'file', name: 'photo' })
|
$.make('a', { href: '#', className: 'NB-photo-upload-link NB-splash-link' }, 'upload picture'),
|
||||||
|
$.make('input', { type: 'file', name: 'photo', id: "NB-photo-upload-file", className: 'NB-photo-upload-file' })
|
||||||
|
])
|
||||||
])),
|
])),
|
||||||
(service == 'gravatar' && $.make('div', { className: 'NB-gravatar-link' }, [
|
(service == 'gravatar' && $.make('div', { className: 'NB-gravatar-link' }, [
|
||||||
$.make('a', { href: 'http://www.gravatar.com', className: 'NB-splash-link', target: '_blank' }, 'gravatar.com')
|
$.make('a', { href: 'http://www.gravatar.com', className: 'NB-splash-link', target: '_blank' }, 'gravatar.com')
|
||||||
|
@ -463,6 +468,15 @@ _.extend(NEWSBLUR.ReaderProfileEditor.prototype, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handle_change: function(elem, e) {
|
||||||
|
var self = this;
|
||||||
|
$.targetIs(e, { tagSelector: '.NB-photo-upload-file' }, function($t, $p) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
self.handle_photo_upload();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
handle_cancel: function() {
|
handle_cancel: function() {
|
||||||
var $cancel = $('.NB-modal-cancel', this.$modal);
|
var $cancel = $('.NB-modal-cancel', this.$modal);
|
||||||
|
|
||||||
|
@ -492,6 +506,65 @@ _.extend(NEWSBLUR.ReaderProfileEditor.prototype, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
handle_photo_upload: function() {
|
||||||
|
var self = this;
|
||||||
|
var $loading = $('.NB-modal-loading', this.$modal);
|
||||||
|
var $error = $('.NB-photo-upload-error', this.$modal);
|
||||||
|
var $file = $('.NB-photo-upload-file', this.$modal);
|
||||||
|
|
||||||
|
$error.slideUp(300);
|
||||||
|
$loading.addClass('NB-active');
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
url: NEWSBLUR.URLs['upload-avatar'],
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
success: _.bind(function(data, status) {
|
||||||
|
if (data.code < 0) {
|
||||||
|
this.error_uploading_photo();
|
||||||
|
} else {
|
||||||
|
$loading.removeClass('NB-active');
|
||||||
|
console.log(["success uploading", data, status, this]);
|
||||||
|
NEWSBLUR.assets.user_profile.set(data.user_profile);
|
||||||
|
this.services = data.services;
|
||||||
|
this.make_profile_section();
|
||||||
|
this.make_profile_photo_chooser();
|
||||||
|
}
|
||||||
|
}, this),
|
||||||
|
error: _.bind(this.error_uploading_photo, this),
|
||||||
|
cache: false,
|
||||||
|
contentType: false,
|
||||||
|
processData: false
|
||||||
|
};
|
||||||
|
if (window.FormData) {
|
||||||
|
var formData = new FormData($file.closest('form')[0]);
|
||||||
|
params['data'] = formData;
|
||||||
|
|
||||||
|
$.ajax(params);
|
||||||
|
} else {
|
||||||
|
// IE9 has no FormData
|
||||||
|
params['secureuri'] = false;
|
||||||
|
params['fileElementId'] = 'NB-photo-upload-file';
|
||||||
|
params['dataType'] = 'json';
|
||||||
|
|
||||||
|
$.ajaxFileUpload(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file.replaceWith($file.clone());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
error_uploading_photo: function() {
|
||||||
|
var $loading = $('.NB-modal-loading', this.$modal);
|
||||||
|
var $error = $('.NB-photo-upload-error', this.$modal);
|
||||||
|
|
||||||
|
$loading.removeClass('NB-active');
|
||||||
|
$error.text("There was a problem uploading your photo.");
|
||||||
|
$error.slideDown(300);
|
||||||
|
},
|
||||||
|
|
||||||
delegate_change: function() {
|
delegate_change: function() {
|
||||||
$('.NB-tab-profile', this.$modal).delegate('input[type=radio],input[type=checkbox],select', 'change', _.bind(this.enable_save_profile, this));
|
$('.NB-tab-profile', this.$modal).delegate('input[type=radio],input[type=checkbox],select', 'change', _.bind(this.enable_save_profile, this));
|
||||||
$('.NB-tab-profile', this.$modal).delegate('input[type=text]', 'keydown', _.bind(this.enable_save_profile, this));
|
$('.NB-tab-profile', this.$modal).delegate('input[type=text]', 'keydown', _.bind(this.enable_save_profile, this));
|
||||||
|
|
|
@ -452,6 +452,7 @@ PROXY_S3_PAGES = True
|
||||||
S3_BACKUP_BUCKET = 'newsblur_backups'
|
S3_BACKUP_BUCKET = 'newsblur_backups'
|
||||||
S3_PAGES_BUCKET_NAME = 'pages.newsblur.com'
|
S3_PAGES_BUCKET_NAME = 'pages.newsblur.com'
|
||||||
S3_ICONS_BUCKET_NAME = 'icons.newsblur.com'
|
S3_ICONS_BUCKET_NAME = 'icons.newsblur.com'
|
||||||
|
S3_AVATARS_BUCKET_NAME = 'avatars.newsblur.com'
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
# = Configurations =
|
# = Configurations =
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
};
|
};
|
||||||
NEWSBLUR.URLs = {
|
NEWSBLUR.URLs = {
|
||||||
'google-reader-authorize' : "{% url google-reader-authorize %}",
|
'google-reader-authorize' : "{% url google-reader-authorize %}",
|
||||||
|
'upload-avatar' : "{% url upload-avatar %}",
|
||||||
'opml-upload' : "{% url opml-upload %}",
|
'opml-upload' : "{% url opml-upload %}",
|
||||||
'opml-export' : "{% url opml-export %}",
|
'opml-export' : "{% url opml-export %}",
|
||||||
'domain' : "{% current_domain %}",
|
'domain' : "{% current_domain %}",
|
||||||
|
|
70
utils/image_functions.py
Normal file
70
utils/image_functions.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
"""Operations for images through the PIL."""
|
||||||
|
|
||||||
|
import Image
|
||||||
|
import ImageOps as PILOps
|
||||||
|
from ExifTags import TAGS
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
PROFILE_PICTURE_SIZES = {
|
||||||
|
'fullsize': (256, 256),
|
||||||
|
'thumbnail': (64, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageOps:
|
||||||
|
"""Module that holds all image operations. Since there's no state,
|
||||||
|
everything is a classmethod."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resize_image(cls, image_body, size, fit_to_size=False):
|
||||||
|
"""Takes a raw image (in image_body) and resizes it to fit given
|
||||||
|
dimensions. Returns a file-like object in the form of a StringIO.
|
||||||
|
This must happen in this function because PIL is transforming the
|
||||||
|
original as it works."""
|
||||||
|
|
||||||
|
image_file = StringIO(image_body)
|
||||||
|
try:
|
||||||
|
image = Image.open(image_file)
|
||||||
|
except IOError:
|
||||||
|
# Invalid image file
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get the image format early, as we lose it after perform a `thumbnail` or `fit`.
|
||||||
|
format = image.format
|
||||||
|
|
||||||
|
# Check for rotation
|
||||||
|
image = cls.adjust_image_orientation(image)
|
||||||
|
|
||||||
|
if not fit_to_size:
|
||||||
|
image.thumbnail(PROFILE_PICTURE_SIZES[size], Image.ANTIALIAS)
|
||||||
|
else:
|
||||||
|
image = PILOps.fit(image, PROFILE_PICTURE_SIZES[size],
|
||||||
|
method=Image.ANTIALIAS,
|
||||||
|
centering=(0.5, 0.5))
|
||||||
|
|
||||||
|
output = StringIO()
|
||||||
|
if format.lower() == 'jpg':
|
||||||
|
format = 'jpeg'
|
||||||
|
image.save(output, format=format, quality=95)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def adjust_image_orientation(cls, image):
|
||||||
|
"""Since the iPhone will store an image on its side but with EXIF
|
||||||
|
data stating that it should be rotated, we need to find that
|
||||||
|
EXIF data and correctly rotate the image before storage."""
|
||||||
|
|
||||||
|
if hasattr(image, '_getexif'):
|
||||||
|
exif = image._getexif()
|
||||||
|
if exif:
|
||||||
|
for tag, value in exif.items():
|
||||||
|
decoded = TAGS.get(tag, tag)
|
||||||
|
if decoded == 'Orientation':
|
||||||
|
if value == 6:
|
||||||
|
image = image.rotate(-90)
|
||||||
|
if value == 8:
|
||||||
|
image = image.rotate(90)
|
||||||
|
if value == 3:
|
||||||
|
image = image.rotate(180)
|
||||||
|
break
|
||||||
|
return image
|
|
@ -1,7 +1,10 @@
|
||||||
from boto.s3.connection import S3Connection
|
|
||||||
from boto.s3.key import Key
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
import mimetypes
|
||||||
|
from boto.s3.connection import S3Connection
|
||||||
|
from boto.s3.key import Key
|
||||||
|
from utils.image_functions import ImageOps
|
||||||
|
|
||||||
if '/home/sclay/newsblur' not in ' '.join(sys.path):
|
if '/home/sclay/newsblur' not in ' '.join(sys.path):
|
||||||
sys.path.append("/home/sclay/newsblur")
|
sys.path.append("/home/sclay/newsblur")
|
||||||
|
@ -60,3 +63,62 @@ if __name__ == '__main__':
|
||||||
delete_all_backups()
|
delete_all_backups()
|
||||||
else:
|
else:
|
||||||
print 'Usage: %s <get/set/list/delete> <backup_filename>' % (sys.argv[0])
|
print 'Usage: %s <get/set/list/delete> <backup_filename>' % (sys.argv[0])
|
||||||
|
|
||||||
|
|
||||||
|
class S3Store:
|
||||||
|
|
||||||
|
def __init__(self, bucket_name=settings.S3_AVATARS_BUCKET_NAME):
|
||||||
|
self.s3 = S3Connection(ACCESS_KEY, SECRET)
|
||||||
|
self.bucket = self.create_bucket(bucket_name)
|
||||||
|
|
||||||
|
def create_bucket(self, bucket_name):
|
||||||
|
return self.s3.create_bucket(bucket_name)
|
||||||
|
|
||||||
|
def save_profile_picture(self, user_id, filename, image_body):
|
||||||
|
mimetype, extension = self._extract_mimetype(filename)
|
||||||
|
if not mimetype or not extension:
|
||||||
|
return
|
||||||
|
|
||||||
|
image_name = 'profile_%s.%s' % (int(time.time()), extension)
|
||||||
|
|
||||||
|
image = ImageOps.resize_image(image_body, 'fullsize', fit_to_size=False)
|
||||||
|
if image:
|
||||||
|
key = 'avatars/%s/large_%s' % (user_id, image_name)
|
||||||
|
self._save_object(key, image, mimetype=mimetype)
|
||||||
|
|
||||||
|
image = ImageOps.resize_image(image_body, 'thumbnail', fit_to_size=True)
|
||||||
|
if image:
|
||||||
|
key = 'avatars/%s/thumbnail_%s' % (user_id, image_name)
|
||||||
|
self._save_object(key, image, mimetype=mimetype)
|
||||||
|
|
||||||
|
return image and image_name
|
||||||
|
|
||||||
|
def _extract_mimetype(self, filename):
|
||||||
|
mimetype = mimetypes.guess_type(filename)[0]
|
||||||
|
extension = None
|
||||||
|
|
||||||
|
if mimetype == 'image/jpeg':
|
||||||
|
extension = 'jpg'
|
||||||
|
elif mimetype == 'image/png':
|
||||||
|
extension = 'png'
|
||||||
|
elif mimetype == 'image/gif':
|
||||||
|
extension = 'gif'
|
||||||
|
|
||||||
|
return mimetype, extension
|
||||||
|
|
||||||
|
def _make_key(self):
|
||||||
|
return Key(bucket=self.bucket)
|
||||||
|
|
||||||
|
def _save_object(self, key, file_object, mimetype=None):
|
||||||
|
k = self._make_key()
|
||||||
|
k.key = key
|
||||||
|
file_object.seek(0)
|
||||||
|
|
||||||
|
if mimetype:
|
||||||
|
k.set_contents_from_file(file_object, headers={
|
||||||
|
'Content-Type': mimetype,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
k.set_contents_from_file(file_object)
|
||||||
|
k.set_acl('public-read')
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue