├── requirements.txt ├── awesome_avatar ├── models.py ├── __init__.py ├── static │ ├── imgareaselect │ │ ├── css │ │ │ ├── border-h.gif │ │ │ ├── border-v.gif │ │ │ ├── border-anim-h.gif │ │ │ ├── border-anim-v.gif │ │ │ ├── imgareaselect-deprecated.css │ │ │ ├── imgareaselect-default.css │ │ │ └── imgareaselect-animated.css │ │ ├── MIT-LICENSE.txt │ │ ├── scripts │ │ │ ├── jquery.imgareaselect.pack.js │ │ │ ├── jquery.imgareaselect.min.js │ │ │ ├── jquery.imgareaselect.js │ │ │ └── jquery.min.js │ │ └── GPL-LICENSE.txt │ └── awesome_avatar │ │ ├── awesome-avatar.css │ │ └── awesome-avatar.js ├── settings.py ├── forms.py ├── widgets.py ├── templates │ └── awesome_avatar │ │ └── widget.html └── fields.py ├── .gitignore ├── setup.py └── README.rst /requirements.txt: -------------------------------------------------------------------------------- 1 | Django -------------------------------------------------------------------------------- /awesome_avatar/models.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /awesome_avatar/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'dimka' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | *.pyc 3 | 4 | .idea/ 5 | build/ 6 | dist/ 7 | MANIFEST 8 | -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/css/border-h.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voronind/django-awesome-avatar/HEAD/awesome_avatar/static/imgareaselect/css/border-h.gif -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/css/border-v.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voronind/django-awesome-avatar/HEAD/awesome_avatar/static/imgareaselect/css/border-v.gif -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/css/border-anim-h.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voronind/django-awesome-avatar/HEAD/awesome_avatar/static/imgareaselect/css/border-anim-h.gif -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/css/border-anim-v.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voronind/django-awesome-avatar/HEAD/awesome_avatar/static/imgareaselect/css/border-anim-v.gif -------------------------------------------------------------------------------- /awesome_avatar/static/awesome_avatar/awesome-avatar.css: -------------------------------------------------------------------------------- 1 | 2 | .awesome-avatar-select-area { 3 | border: 1px solid #ddd; 4 | box-sizing: content-box; 5 | display: table-cell; 6 | vertical-align: middle; 7 | text-align: center; 8 | } 9 | 10 | .awesome-avatar-input { 11 | width: 100%; 12 | margin-top: 6px; 13 | } 14 | 15 | .awesome-avatar-preview { 16 | margin-right: 20px; 17 | border: 1px solid #ddd; 18 | box-sizing: content-box; 19 | overflow: hidden; 20 | } -------------------------------------------------------------------------------- /awesome_avatar/settings.py: -------------------------------------------------------------------------------- 1 | 2 | from django.conf import settings 3 | 4 | 5 | class config(object): 6 | width = 100 7 | height = 100 8 | 9 | upload_to = 'avatars' 10 | save_format = 'png' 11 | save_quality = 90 12 | 13 | select_area_width = 400 14 | select_area_height = 250 15 | 16 | 17 | settings_config = getattr(settings, 'AWESOME_AVATAR', {}) 18 | 19 | for key, value in settings_config.items(): 20 | if key in config.__dict__: 21 | setattr(config, key, value) 22 | else: 23 | raise KeyError('Incorect option name of AWESOME_AVATAR in settings.py ({})'.format(key)) -------------------------------------------------------------------------------- /awesome_avatar/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from awesome_avatar.settings import config 4 | from awesome_avatar.widgets import AvatarWidget 5 | 6 | 7 | class AvatarField(forms.ImageField): 8 | widget = AvatarWidget 9 | 10 | def __init__(self, **defaults): 11 | self.width = defaults.pop('width', config.width) 12 | self.height = defaults.pop('height', config.height) 13 | super(AvatarField, self).__init__(**defaults) 14 | 15 | def to_python(self, data): 16 | super(AvatarField, self).to_python(data['file']) 17 | return data 18 | 19 | def widget_attrs(self, widget): 20 | return {'width': self.width, 'height': self.height} -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/css/imgareaselect-deprecated.css: -------------------------------------------------------------------------------- 1 | /* 2 | * imgAreaSelect style to be used with deprecated options 3 | */ 4 | 5 | .imgareaselect-border1, .imgareaselect-border2, 6 | .imgareaselect-border3, .imgareaselect-border4 { 7 | filter: alpha(opacity=50); 8 | opacity: 0.5; 9 | } 10 | 11 | .imgareaselect-border1 { 12 | border: solid 1px #000; 13 | } 14 | 15 | .imgareaselect-border2 { 16 | border: dashed 1px #fff; 17 | } 18 | 19 | .imgareaselect-handle { 20 | background-color: #fff; 21 | border: solid 1px #000; 22 | filter: alpha(opacity=50); 23 | opacity: 0.5; 24 | } 25 | 26 | .imgareaselect-outer { 27 | background-color: #000; 28 | filter: alpha(opacity=40); 29 | opacity: 0.4; 30 | } 31 | 32 | .imgareaselect-selection { 33 | background-color: #fff; 34 | filter: alpha(opacity=0); 35 | opacity: 0; 36 | } 37 | -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/css/imgareaselect-default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * imgAreaSelect default style 3 | */ 4 | 5 | .imgareaselect-border1 { 6 | background: url(border-v.gif) repeat-y left top; 7 | } 8 | 9 | .imgareaselect-border2 { 10 | background: url(border-h.gif) repeat-x left top; 11 | } 12 | 13 | .imgareaselect-border3 { 14 | background: url(border-v.gif) repeat-y right top; 15 | } 16 | 17 | .imgareaselect-border4 { 18 | background: url(border-h.gif) repeat-x left bottom; 19 | } 20 | 21 | .imgareaselect-border1, .imgareaselect-border2, 22 | .imgareaselect-border3, .imgareaselect-border4 { 23 | filter: alpha(opacity=50); 24 | opacity: 0.5; 25 | } 26 | 27 | .imgareaselect-handle { 28 | background-color: #fff; 29 | border: solid 1px #000; 30 | filter: alpha(opacity=50); 31 | opacity: 0.5; 32 | } 33 | 34 | .imgareaselect-outer { 35 | background-color: #000; 36 | filter: alpha(opacity=50); 37 | opacity: 0.5; 38 | } 39 | 40 | .imgareaselect-selection { 41 | } -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/css/imgareaselect-animated.css: -------------------------------------------------------------------------------- 1 | /* 2 | * imgAreaSelect animated border style 3 | */ 4 | 5 | .imgareaselect-border1 { 6 | background: url(border-anim-v.gif) repeat-y left top; 7 | } 8 | 9 | .imgareaselect-border2 { 10 | background: url(border-anim-h.gif) repeat-x left top; 11 | } 12 | 13 | .imgareaselect-border3 { 14 | background: url(border-anim-v.gif) repeat-y right top; 15 | } 16 | 17 | .imgareaselect-border4 { 18 | background: url(border-anim-h.gif) repeat-x left bottom; 19 | } 20 | 21 | .imgareaselect-border1, .imgareaselect-border2, 22 | .imgareaselect-border3, .imgareaselect-border4 { 23 | filter: alpha(opacity=50); 24 | opacity: 0.5; 25 | } 26 | 27 | .imgareaselect-handle { 28 | background-color: #fff; 29 | border: solid 1px #000; 30 | filter: alpha(opacity=50); 31 | opacity: 0.5; 32 | } 33 | 34 | .imgareaselect-outer { 35 | background-color: #000; 36 | filter: alpha(opacity=50); 37 | opacity: 0.5; 38 | } 39 | 40 | .imgareaselect-selection { 41 | } -------------------------------------------------------------------------------- /awesome_avatar/static/imgareaselect/MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2013 Michal Wojciechowski, http://odyniec.net/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | 4 | name = 'django-awesome-avatar' 5 | package = 'awesome_avatar' 6 | version = '1.1.3' 7 | 8 | url = 'https://github.com/dimka665/django-awesome-avatar' 9 | author = 'Dmitry Voronin' 10 | author_email = 'dimka665@gmail.com' 11 | license_ = 'BSD' 12 | description = 'Django Avatar field' 13 | long_description = open('README.rst').read() 14 | 15 | 16 | def get_packages(package): 17 | """ 18 | Return root package and all sub-packages. 19 | """ 20 | return [dirpath for dirpath, dirnames, filenames in os.walk(package) 21 | if os.path.exists(os.path.join(dirpath, '__init__.py'))] 22 | 23 | 24 | def get_package_data(package): 25 | """ 26 | Return all files under the root package, that are not in a 27 | package themselves. 28 | """ 29 | walk = [(dirpath.replace(package + os.sep, '', 1), filenames) 30 | for dirpath, dirnames, filenames in os.walk(package) 31 | if not os.path.exists(os.path.join(dirpath, '__init__.py'))] 32 | 33 | filepaths = [] 34 | for base, filenames in walk: 35 | filepaths.extend([os.path.join(base, filename) 36 | for filename in filenames]) 37 | return {package: filepaths} 38 | 39 | 40 | setup( 41 | name=name, 42 | version=version, 43 | url=url, 44 | license=license_, 45 | description=description, 46 | long_description=long_description, 47 | author=author, 48 | author_email=author_email, 49 | packages=get_packages(package), 50 | package_data=get_package_data(package), 51 | ) 52 | 53 | 54 | -------------------------------------------------------------------------------- /awesome_avatar/widgets.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.forms import FileInput 3 | from django.template.loader import render_to_string 4 | 5 | from awesome_avatar.settings import config 6 | 7 | 8 | class AvatarWidget(FileInput): 9 | 10 | def value_from_datadict(self, data, files, name): 11 | value = {} 12 | value['file'] = super(AvatarWidget, self).value_from_datadict(data, files, name) 13 | 14 | x1 = data.get(name + '-x1', 0) 15 | y1 = data.get(name + '-y1', 0) 16 | x2 = data.get(name + '-x2', x1) 17 | y2 = data.get(name + '-y2', y1) 18 | ratio = data.get(name + '-ratio', 1) 19 | ratio = float(1 if not ratio else ratio) 20 | 21 | box_raw = [x1, y1, x2, y2] 22 | box = [] 23 | 24 | for coord in box_raw: 25 | try: 26 | coord = int(coord) 27 | except ValueError: 28 | coord = 0 29 | 30 | if ratio > 1: 31 | coord = int(coord * ratio) 32 | box.append(coord) 33 | 34 | value['box'] = box 35 | return value 36 | 37 | def render(self, name, value, attrs=None): 38 | 39 | config.height = self.attrs['height'] 40 | config.width = self.attrs['width'] 41 | 42 | context = {} 43 | context['name'] = name 44 | context['config'] = config 45 | 46 | context['avatar_url'] = value.url if value else '/static/awesome_avatar/default.png' 47 | context['id'] = attrs.get('id', 'id_' + name) 48 | # todo fix HACK 49 | context['STATIC_URL'] = settings.STATIC_URL 50 | return render_to_string('awesome_avatar/widget.html', context) 51 | -------------------------------------------------------------------------------- /awesome_avatar/templates/awesome_avatar/widget.html: -------------------------------------------------------------------------------- 1 |
42 | -------------------------------------------------------------------------------- /awesome_avatar/fields.py: -------------------------------------------------------------------------------- 1 | import os 2 | from awesome_avatar.settings import config 3 | from django.core.files.uploadedfile import InMemoryUploadedFile 4 | from django.db import models 5 | from awesome_avatar import forms 6 | 7 | try: 8 | from cStringIO import StringIO 9 | except ImportError: 10 | from StringIO import StringIO 11 | 12 | try: 13 | from PIL import Image 14 | except ImportError: 15 | import Image 16 | 17 | try: 18 | from south.modelsinspector import add_introspection_rules 19 | add_introspection_rules([], ['^awesome_avatar\.fields\.AvatarField']) 20 | except ImportError: 21 | pass 22 | 23 | 24 | class AvatarField(models.ImageField): 25 | def __init__(self, *args, **kwargs): 26 | 27 | self.width = kwargs.pop('width', config.width) 28 | self.height = kwargs.pop('height', config.height) 29 | 30 | kwargs['upload_to'] = kwargs.get('upload_to', config.upload_to) 31 | 32 | super(AvatarField, self).__init__(*args, **kwargs) 33 | 34 | def formfield(self, **kwargs): 35 | defaults = {'form_class': forms.AvatarField} 36 | defaults['width'] = self.width 37 | defaults['height'] = self.height 38 | defaults.update(kwargs) 39 | return super(AvatarField, self).formfield(**defaults) 40 | 41 | def save_form_data(self, instance, data): 42 | # if data and self.width and self.height: 43 | file_ = data['file'] 44 | if file_: 45 | 46 | image = Image.open(StringIO(file_.read())) 47 | image = image.crop(data['box']) 48 | image = image.resize((self.width, self.height), Image.ANTIALIAS) 49 | 50 | content = StringIO() 51 | image.save(content, config.save_format, quality=config.save_quality) 52 | 53 | file_name = u'{}.{}'.format(os.path.splitext(file_.name)[0], config.save_format) 54 | 55 | # new_data = SimpleUploadedFile(file.name, content.getvalue(), content_type='image/' + config.save_format) 56 | new_data = InMemoryUploadedFile(content, None, file_name, 'image/' + config.save_format, len(content.getvalue()), None) 57 | super(AvatarField, self).save_form_data(instance, new_data) 58 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | django-awesome-avatar 3 | ===================== 4 | 5 | Django-awesome-avatar is a reusable application providing Avatar model field. 6 | It allows crop selected area before saving image. 7 | 8 | Purpose 9 | ======= 10 | 11 | | Unlike django-avatar_ and django-upload-avatar_ 12 | | django-awesome-avatar_ uses: 13 | 14 | - field in profile model instead creating model for saving images 15 | - HTML5 File API instead hidden iframe AJAX for image preview 16 | - easy customizable presence (any view and widget templates) 17 | 18 | Install 19 | ======= 20 | 21 | To integrate ``django-awesome-avatar`` with your site, there are few things 22 | that are required: 23 | 24 | #. Installing:: 25 | 26 | pip install django-awesome-avatar 27 | 28 | #. List this application in the ``INSTALLED_APPS`` portion of your settings file. 29 | Your settings file will look something like:: 30 | 31 | INSTALLED_APPS = ( 32 | ... 33 | 'awesome_avatar', 34 | ) 35 | 36 | Usage examples 37 | ============== 38 | 39 | with ModelForm 40 | -------------- 41 | 42 | Add the ``AvatarField`` to your user or profile model:: 43 | 44 | from awesome_avatar.fields import AvatarField 45 | 46 | class Profile(Model): 47 | user = OneToOneField(User, related_name='profile') 48 | ... 49 | avatar = AvatarField(upload_to='avatars', width=100, height=100) 50 | 51 | Use model form usually way:: 52 | 53 | class AvatarChangeForm(ModelForm): 54 | class Meta: 55 | model = Profile 56 | fields = ['avatar'] 57 | 58 | def change_avatar(request): 59 | if request.method == 'POST': 60 | form = AvatarChangeForm(request.POST, request.FILES, 61 | instance=request.user.profile) 62 | if form.is_valid(): 63 | form.save() 64 | return HttpResponseRedirect('/profile/') 65 | else: 66 | form = AvatarChangeForm(instance=request.user.profile) 67 | 68 | return render(request, 'template.html', {'form': form}) 69 | 70 | with Form 71 | --------- 72 | 73 | Define some model for saving images:: 74 | 75 | class Images(Model): 76 | image = ImageField(upload_to='images') 77 | 78 | Use form field for cropping image:: 79 | 80 | from awesome_avatar import forms as avatar_forms 81 | 82 | class UploadAndCropImageForm(Form): 83 | image = avatar_forms.AvatarField() 84 | 85 | def upload_and_crop_image(request): 86 | if request.method == 'POST': 87 | form = UploadAndCropImageForm(request.POST) 88 | 89 | if form.is_valid(): 90 | Images(image=form.image).save() 91 | return HttpResponseRedirect('/any/') 92 | else: 93 | form = UploadAndCropImageForm() 94 | 95 | return render(request, 'template.html', {'form': form}) 96 | 97 | 98 | Global Settings 99 | =============== 100 | 101 | Django's ``settings.py``:: 102 | 103 | AWESOME_AVATAR = { 104 | 'width': 100, 105 | 'height': 100, 106 | 107 | 'select_area_width': 400, 108 | 'select_area_height': 300, 109 | 110 | 'save_quality': 90, 111 | 'save_format': 'png', 112 | ... 113 | } 114 | 115 | .. _django-avatar: https://github.com/jezdez/django-avatar 116 | .. _django-upload-avatar: https://github.com/yueyoum/django-upload-avatar 117 | .. _django-awesome-avatar: https://github.com/dimka665/django-awesome-avatar 118 | -------------------------------------------------------------------------------- /awesome_avatar/static/awesome_avatar/awesome-avatar.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | bind_on_change_input_file = function(selector, config) { 3 | 4 | $(selector).change(function() { 5 | if($(this).val() == '') { 6 | return; 7 | } 8 | 9 | // if selection, remove 10 | var $last_img = $(selector + '-select-area img'); 11 | if ($last_img.length) { 12 | var img_obj = $last_img.imgAreaSelect({instance: true}); 13 | img_obj.remove(); 14 | } 15 | 16 | $(selector + '-select-area').empty(); 17 | // $(selector + '-select-area img').hide(); 18 | // $(selector + '-preview').empty(); 19 | $(selector + '-preview img').hide(); 20 | 21 | var p = new RegExp(/\.(jpg|jpeg|png|gif)$/); 22 | var fileanme = $(this).val().toLowerCase().replace(/^\s+|\s+$/g, ''); 23 | if(!p.test(fileanme)){ 24 | alert('{% trans "Неверный формат. Выберите изображение" %}'); 25 | return ; 26 | } 27 | 28 | var file = this.files[0]; 29 | if (!file.type.match(/image.*/)) { 30 | return; 31 | } 32 | var reader = new FileReader(); 33 | reader.onload = bind_preview.bind(null, selector, config); 34 | reader.readAsDataURL(file); 35 | }); 36 | }; 37 | 38 | bind_preview = function(selector, config, e) { 39 | var image_data = e.target.result; 40 | // $(selector + '-preview').empty(); 41 | // $(selector + '-preview').append('| t |