bug 558508, add/remove userpics.
This commit is contained in:
Родитель
366efe5ef5
Коммит
a68f534fb2
|
@ -8,9 +8,10 @@ from django.forms.util import ErrorList
|
|||
import captcha.fields
|
||||
import commonware.log
|
||||
import happyforms
|
||||
from tower import ugettext as _
|
||||
from tower import ugettext as _, ugettext_lazy as _lazy
|
||||
|
||||
from .models import UserProfile, BlacklistedUsername
|
||||
import tasks
|
||||
|
||||
log = commonware.log.getLogger('z.users')
|
||||
|
||||
|
@ -125,6 +126,8 @@ class UserEditForm(UserRegisterForm):
|
|||
password2 = forms.CharField(max_length=255, required=False,
|
||||
widget=forms.PasswordInput(render_value=False))
|
||||
|
||||
photo = forms.FileField(label=_lazy('Profile Photo'), required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop('request', None)
|
||||
super(UserEditForm, self).__init__(*args, **kwargs)
|
||||
|
@ -155,18 +158,49 @@ class UserEditForm(UserRegisterForm):
|
|||
super(UserEditForm, self).clean()
|
||||
return data
|
||||
|
||||
def clean_photo(self):
|
||||
photo = self.cleaned_data['photo']
|
||||
|
||||
if not photo:
|
||||
return
|
||||
|
||||
if photo.content_type not in ('image/png', 'image/jpeg'):
|
||||
raise forms.ValidationError(
|
||||
_('Images must be either PNG or JPG.'))
|
||||
|
||||
if photo.size > settings.MAX_PHOTO_UPLOAD_SIZE:
|
||||
raise forms.ValidationError(
|
||||
_('Please use images smaller than %dMB.' %
|
||||
(settings.MAX_PHOTO_UPLOAD_SIZE / 1024 / 1024 - 1)))
|
||||
|
||||
return photo
|
||||
|
||||
def save(self):
|
||||
super(UserEditForm, self).save()
|
||||
u = super(UserEditForm, self).save(commit=False)
|
||||
data = self.cleaned_data
|
||||
amouser = self.request.user.get_profile()
|
||||
photo = data['photo']
|
||||
if photo:
|
||||
u.picture_type = 'image/png'
|
||||
tmp_destination = u.picture_path + '__unconverted'
|
||||
|
||||
if not os.path.exists(u.picture_dir):
|
||||
os.mkdir(u.picture_dir)
|
||||
|
||||
fh = open(tmp_destination, 'w')
|
||||
for chunk in photo.chunks():
|
||||
fh.write(chunk)
|
||||
|
||||
fh.close()
|
||||
tasks.resize_photo.delay(tmp_destination, u.picture_path)
|
||||
|
||||
if data['password']:
|
||||
amouser.set_password(data['password'])
|
||||
log.info(u'User (%s) changed their password' % amouser)
|
||||
u.set_password(data['password'])
|
||||
log.info(u'User (%s) changed their password' % u)
|
||||
|
||||
log.debug(u'User (%s) updated their profile' % amouser)
|
||||
log.debug(u'User (%s) updated their profile' % u)
|
||||
|
||||
amouser.save()
|
||||
u.save()
|
||||
return u
|
||||
|
||||
|
||||
class BlacklistedUsernameAddForm(forms.Form):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from datetime import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
|
@ -104,12 +105,22 @@ class UserProfile(amo.models.ModelBase):
|
|||
return self.addons.valid().filter(addonuser__listed=True).distinct()
|
||||
|
||||
@property
|
||||
def picture_url(self):
|
||||
def picture_dir(self):
|
||||
split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
|
||||
return os.path.join(settings.USERPICS_PATH, split_id.group(2) or 0,
|
||||
split_id.group(1) or 0)
|
||||
|
||||
@property
|
||||
def picture_path(self):
|
||||
return os.path.join(self.picture_dir, str(self.id) + '.png')
|
||||
|
||||
@property
|
||||
def picture_url(self):
|
||||
if not self.picture_type:
|
||||
return settings.MEDIA_URL + '/img/zamboni/anon_user.png'
|
||||
else:
|
||||
return settings.USER_PIC_URL % (
|
||||
split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))
|
||||
return settings.USERPICS_URL % (
|
||||
split_id.group(2) or 0, split_id.group(1) or 0, self.id,
|
||||
int(time.mktime(self.modified.timetuple())))
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import os
|
||||
import random
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User as DjangoUser
|
||||
from django.db import IntegrityError
|
||||
|
||||
import commonware.log
|
||||
from celery.decorators import task
|
||||
from easy_thumbnails import processors
|
||||
from PIL import Image
|
||||
|
||||
from . import cron
|
||||
from amo.utils import slugify
|
||||
|
@ -32,19 +36,19 @@ def add_usernames(data, **kw):
|
|||
try:
|
||||
UserProfile.objects.filter(id=user[0]).update(username=name_slug,
|
||||
display_name=name)
|
||||
except IntegrityError, e:
|
||||
except IntegrityError:
|
||||
try:
|
||||
name_slug = "%s%s" % (name_slug, user[0])
|
||||
if not len(name_slug) > 10:
|
||||
# This can happen if they have a blank name_slug and then
|
||||
# there is already a username in the system that corresponds
|
||||
# to their user id. It's a total edge case.
|
||||
name_slug = "%s%s" % (random.randint(1000,100000),
|
||||
# there is already a username in the system that
|
||||
# corresponds to their user id. It's a total edge case.
|
||||
name_slug = "%s%s" % (random.randint(1000, 100000),
|
||||
name_slug)
|
||||
|
||||
UserProfile.objects.filter(id=user[0]).update(username=name_slug,
|
||||
display_name=name)
|
||||
except IntegrityError, e:
|
||||
UserProfile.objects.filter(id=user[0]).update(
|
||||
username=name_slug, display_name=name)
|
||||
except IntegrityError:
|
||||
task_log.error(u"""F-F-Fail! I tried setting a user's (id:%s)
|
||||
username to to %s and it was already taken. This should never
|
||||
happen.""" % (user[0], name_slug))
|
||||
|
@ -60,3 +64,32 @@ def _delete_users(data, **kw):
|
|||
|
||||
UserProfile.objects.filter(pk__in=data).delete()
|
||||
DjangoUser.objects.filter(pk__in=data).delete()
|
||||
|
||||
|
||||
@task
|
||||
def delete_photo(dst):
|
||||
task_log.info('[%s@%s] Deleting photo.' % (dst, delete_photo.rate_limit))
|
||||
|
||||
if not dst.startswith(settings.USERPICS_PATH):
|
||||
task_log.error("Someone tried deleting something they shouldn't: %s"
|
||||
% dst)
|
||||
return
|
||||
|
||||
try:
|
||||
os.remove(dst)
|
||||
except Exception, e:
|
||||
task_log.error("Error deleting userpic: %s" % e)
|
||||
|
||||
|
||||
@task
|
||||
def resize_photo(src, dst):
|
||||
"""Resizes userpics to 200x200"""
|
||||
task_log.info('[%s@%s] Resizing photo.' % (dst, resize_photo.rate_limit))
|
||||
|
||||
try:
|
||||
im = Image.open(src)
|
||||
im = processors.scale_and_crop(im, (200, 200))
|
||||
im.save(dst)
|
||||
os.remove(src)
|
||||
except Exception, e:
|
||||
task_log.error("Error saving userpic: %s" % e)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ page_title(_('Delete User Photo')) }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="primary" role="main">
|
||||
<div class="primary">
|
||||
<h2>{{ _('Delete User Photo') }}</h2>
|
||||
<form method="post" action="{{ url('users.delete_photo') }}"
|
||||
class="featured-inner object-lead user-input">
|
||||
{{ csrf() }}
|
||||
<div class="fm-control">
|
||||
<button type="submit">{{ _('Delete my user picture now') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>{# .primary #}
|
||||
|
||||
{% endblock content %}
|
||||
|
|
@ -17,7 +17,8 @@
|
|||
{% endfor %}
|
||||
{% endif %}
|
||||
<h1>{{ _('Account Settings') }}</h1>
|
||||
<form method="post" action="" class="user-input">
|
||||
<form method="post" action="" class="user-input"
|
||||
enctype="multipart/form-data">
|
||||
{{ csrf() }}
|
||||
<div id="user-edit" class="tab-wrapper">
|
||||
<ul class="tab-nav main">
|
||||
|
@ -73,8 +74,9 @@
|
|||
<legend>{{ _('Notifications') }}</legend>
|
||||
<p>
|
||||
{% trans %}
|
||||
From time to time, Mozilla may send you email about upcoming releases
|
||||
and add-on events. Please select the topics you are interested in below:
|
||||
From time to time, Mozilla may send you email about upcoming
|
||||
releases and add-on events. Please select the topics you are
|
||||
interested in below:
|
||||
{% endtrans %}
|
||||
</p>
|
||||
<ul>
|
||||
|
@ -95,8 +97,8 @@
|
|||
</ul>
|
||||
<p class="note">
|
||||
{% trans %}
|
||||
Mozilla reserves the right to contact you individually about specific
|
||||
concerns with your hosted add-ons.
|
||||
Mozilla reserves the right to contact you individually about
|
||||
specific concerns with your hosted add-ons.
|
||||
{% endtrans %}
|
||||
</p>
|
||||
</fieldset>
|
||||
|
@ -126,12 +128,15 @@
|
|||
{{ form.homepage.errors|safe }}
|
||||
</li>
|
||||
<li class="profile-photo">
|
||||
{# TODO XXX: Change these fields out for whatever we need to do notifications #}
|
||||
{# TODO XXX: Add L10n #}
|
||||
<label for="id_photo">Profile Photo</label>
|
||||
<input type="file" id="id_photo" name="photo">
|
||||
<img src="../media/img/amo2009/site-images/avatar-developer-200x200.jpg" alt="" width="200" height="200" class="avatar photo-large photo" />
|
||||
<a href="#">Delete current photo</a>
|
||||
<label for="id_photo">{{ _('Profile Photo') }}</label>
|
||||
<input type="file" id="id_photo" name="photo">
|
||||
{{ form.photo.errors|safe }}
|
||||
<img src="{{ amouser.picture_url }}" alt=""
|
||||
class="avatar photo-large photo" />
|
||||
|
||||
{% if amouser.picture_type %}
|
||||
<a href="{{ url('users.delete_photo') }}">{{ _('Delete current photo') }}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
|
|
@ -21,6 +21,7 @@ detail_patterns = patterns('',
|
|||
users_patterns = patterns('',
|
||||
url('^ajax$', views.ajax, name='users.ajax'),
|
||||
url('^delete$', views.delete, name='users.delete'),
|
||||
url('^delete_photo$', views.delete_photo, name='users.delete_photo'),
|
||||
url('^edit$', views.edit, name='users.edit'),
|
||||
url('^login', views.login, name='users.login'),
|
||||
url('^logout', views.logout, name='users.logout'),
|
||||
|
|
|
@ -21,6 +21,7 @@ from .models import UserProfile
|
|||
from .signals import logged_out
|
||||
from .users import forms
|
||||
from .utils import EmailResetCode
|
||||
import tasks
|
||||
|
||||
log = commonware.log.getLogger('z.users')
|
||||
|
||||
|
@ -76,7 +77,7 @@ def confirm_resend(request, user_id):
|
|||
|
||||
@login_required
|
||||
def delete(request):
|
||||
amouser = request.user.get_profile()
|
||||
amouser = request.amo_user
|
||||
if request.method == 'POST':
|
||||
form = forms.UserDeleteForm(request.POST, request=request)
|
||||
if form.is_valid():
|
||||
|
@ -92,6 +93,21 @@ def delete(request):
|
|||
{'form': form, 'amouser': amouser})
|
||||
|
||||
|
||||
@login_required
|
||||
def delete_photo(request):
|
||||
u = request.amo_user
|
||||
|
||||
if request.method == 'POST':
|
||||
u.picture_type = ''
|
||||
u.save()
|
||||
tasks.delete_photo.delay(u.picture_path)
|
||||
messages.success(request, _('Photo Deleted'))
|
||||
return http.HttpResponseRedirect(reverse('users.edit') +
|
||||
'#user-profile')
|
||||
|
||||
return jingo.render(request, 'users/delete_photo.html', dict(user=u))
|
||||
|
||||
|
||||
@login_required
|
||||
def edit(request):
|
||||
amouser = request.user.get_profile()
|
||||
|
@ -99,7 +115,7 @@ def edit(request):
|
|||
# ModelForm alters the instance you pass in. We need to keep a copy
|
||||
# around in case we need to use it below (to email the user)
|
||||
original_email = amouser.email
|
||||
form = forms.UserEditForm(request.POST, request=request,
|
||||
form = forms.UserEditForm(request.POST, request.FILES, request=request,
|
||||
instance=amouser)
|
||||
if form.is_valid():
|
||||
messages.success(request, _('Profile Updated'))
|
||||
|
|
|
@ -403,7 +403,7 @@ PREVIEW_THUMBNAIL_URL = (STATIC_URL +
|
|||
'/img/uploads/previews/thumbs/%s/%d.png?modified=%d')
|
||||
PREVIEW_FULL_URL = (STATIC_URL +
|
||||
'/img/uploads/previews/full/%s/%d.png?modified=%d')
|
||||
USER_PIC_URL = STATIC_URL + '/img/uploads/userpics/%s/%s/%s.png?modified=%d'
|
||||
USERPICS_URL = STATIC_URL + '/img/uploads/userpics/%s/%s/%s.png?modified=%d'
|
||||
# paths for uploaded extensions
|
||||
FILES_URL = STATIC_URL + "/%s/%s/downloads/file/%d/%s?src=%s"
|
||||
COLLECTION_ICON_URL = ('%s/%s/%s/images/collection_icon/%%s/%%s' %
|
||||
|
@ -536,6 +536,7 @@ def read_only_mode(env):
|
|||
|
||||
# Uploaded file limits
|
||||
MAX_ICON_UPLOAD_SIZE = 4 * 1024 * 1024
|
||||
MAX_PHOTO_UPLOAD_SIZE = MAX_ICON_UPLOAD_SIZE
|
||||
|
||||
## Feature switches
|
||||
# Use this to keep collections compatible with remora before we're ready to
|
||||
|
|
Загрузка…
Ссылка в новой задаче