From a0b15b1f0032293c2441e6d77ac89bac37457fe7 Mon Sep 17 00:00:00 2001 From: Oliver Falk Date: Tue, 8 May 2018 15:10:26 +0200 Subject: [PATCH] Make uploading new photos work --- config.py | 3 ++ ivatar/ivataraccount/forms.py | 34 +++++++++++- .../ivataraccount/templates/upload_photo.html | 32 ++++++++++++ ivatar/ivataraccount/urls.py | 6 +-- ivatar/ivataraccount/views.py | 52 ++++++++++++++++++- 5 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 ivatar/ivataraccount/templates/upload_photo.html diff --git a/config.py b/config.py index 472fe4c..bc2c284 100644 --- a/config.py +++ b/config.py @@ -48,5 +48,8 @@ MAX_LENGTH_EMAIL = 254 # http://stackoverflow.com/questions/386294 SERVER_EMAIL = 'accounts@ivatar.io' DEFAULT_FROM_EMAIL = SERVER_EMAIL +MAX_NUM_PHOTOS = 5 +MAX_PHOTO_SIZE = 10485760 # in bytes + if os.path.isfile(os.path.join(BASE_DIR, 'config_local.py')): from config_local import * # noqa # flake8: noqa # NOQA # pragma: no cover diff --git a/ivatar/ivataraccount/forms.py b/ivatar/ivataraccount/forms.py index 696fc8b..324d708 100644 --- a/ivatar/ivataraccount/forms.py +++ b/ivatar/ivataraccount/forms.py @@ -5,10 +5,12 @@ from django.template.loader import render_to_string from django.core.mail import send_mail from ivatar import settings -from . models import UnconfirmedEmail, ConfirmedEmail +from . models import UnconfirmedEmail, ConfirmedEmail, Photo from ivatar.settings import MAX_LENGTH_EMAIL +from ipware import get_client_ip + class AddEmailForm(forms.Form): email = forms.EmailField( label=_('Email'), @@ -58,3 +60,33 @@ class AddEmailForm(forms.Form): send_mail(email_subject, email_body, settings.SERVER_EMAIL, [unconfirmed.email]) return True + + +class UploadPhotoForm(forms.Form): + photo = forms.FileField( + label=_('Photo'), + error_messages={'required': _('You must choose an image to upload.')}) + not_porn = forms.BooleanField( + label=_('suitable for all ages (i.e. no offensive content)'), + required=True, + error_messages={ + 'required': + _('We only host "G-rated" images and so this field must be checked.') + }) + can_distribute = forms.BooleanField( + label=_('can be freely copied'), + required=True, + error_messages={ + 'required': + _('This field must be checked since we need to be able to distribute photos to third parties.') + }) + + def save(self, request, data): + # Link this file to the user's profile + photo = Photo() + photo.user = request.user + photo.ip_address = get_client_ip(request) + photo.data = data.read() + if not photo.save(): + return None + return photo diff --git a/ivatar/ivataraccount/templates/upload_photo.html b/ivatar/ivataraccount/templates/upload_photo.html new file mode 100644 index 0000000..6f98ed9 --- /dev/null +++ b/ivatar/ivataraccount/templates/upload_photo.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans 'Upload a new photo' %} - ivatar{% endblock title %} + +{% block header %} +{% endblock header %} + +{% block content %} + +

{% trans 'Upload a new photo' %}

+ +
{% csrf_token %} +{% if email %}{% endif %} +{% if openid %}{% endif %} + +{{ form.photo.errors }} +

{{ form.photo.label_tag }} +
{% blocktrans with max_file_size|filesizeformat as max_size %}Maximum file size of {{ max_size }}.{% endblocktrans %}

+ + + +

{% trans 'Cancel' %}

+ +
+ +{% endblock content %} diff --git a/ivatar/ivataraccount/urls.py b/ivatar/ivataraccount/urls.py index 733f3b5..8ef2084 100644 --- a/ivatar/ivataraccount/urls.py +++ b/ivatar/ivataraccount/urls.py @@ -5,7 +5,7 @@ from django.views.generic import TemplateView from . views import CreateView, PasswordSetView, AddEmailView from . views import RemoveUnconfirmedEmailView, ConfirmEmailView from . views import RemoveConfirmedEmailView, AssignPhotoEmailView -from . views import ImportPhotoView, RawImageView +from . views import ImportPhotoView, RawImageView, DeletePhotoView, UploadPhotoView from django.contrib.auth.views import login, logout, password_change, password_change_done from django.urls import reverse_lazy @@ -18,13 +18,13 @@ urlpatterns = [ path('profile/', TemplateView.as_view(template_name='profile.html'), name='profile'), path('add_email/', AddEmailView.as_view(), name='add_email'), path('add_openid/', TemplateView.as_view(template_name='add_openid.html'), name='add_openid'), - path('upload_photo/', TemplateView.as_view(template_name='upload_photo.html'), name='upload_photo'), + path('upload_photo/', UploadPhotoView.as_view(), name='upload_photo'), path('password_set/', PasswordSetView.as_view(), name='password_set'), url('confirm_email/(?P\w+)', ConfirmEmailView.as_view(), name='confirm_email'), url('remove_unconfirmed_email/(?P\d+)', RemoveUnconfirmedEmailView.as_view(), name='remove_unconfirmed_email'), url('remove_confirmed_email/(?P\d+)', RemoveConfirmedEmailView.as_view(), name='remove_confirmed_email'), url('assign_photo_email/(?P\d+)', AssignPhotoEmailView.as_view(), name='assign_photo_email'), url('import_photo/(?P\d+)', ImportPhotoView.as_view(), name='import_photo'), - url('delete_photo/(?P\d+)', TemplateView.as_view(template_name='asdf'), name='delete_photo'), + url('delete_photo/(?P\d+)', DeletePhotoView.as_view(), name='delete_photo'), url('raw_photo/(?P[-\w]+)', RawImageView.as_view(), name='raw_image'), ] diff --git a/ivatar/ivataraccount/views.py b/ivatar/ivataraccount/views.py index 3df5701..437c821 100644 --- a/ivatar/ivataraccount/views.py +++ b/ivatar/ivataraccount/views.py @@ -14,9 +14,11 @@ from django.utils.translation import ugettext_lazy as _ from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse_lazy, reverse -from . forms import AddEmailForm +from . forms import AddEmailForm, UploadPhotoForm from . models import UnconfirmedEmail, ConfirmedEmail, Photo +from ivatar.settings import MAX_NUM_PHOTOS, MAX_PHOTO_SIZE + import io from ipware import get_client_ip @@ -125,7 +127,7 @@ class ConfirmEmailView(SuccessMessageMixin, TemplateView): confirmed.set_photo(confirmed.user.photos.get()) kwargs['photos'] = [ external_photos ] kwargs['email_id'] = confirmed_id - return super(ConfirmEmailView, self).get(*args, **kwargs) + return super().get(*args, **kwargs) class RemoveConfirmedEmailView(SuccessMessageMixin, View): @@ -185,3 +187,49 @@ class RawImageView(DetailView): return HttpResponse( io.BytesIO(photo.data), content_type='image/%s' % photo.format) + +class DeletePhotoView(SuccessMessageMixin, DetailView): + model = Photo + success_message = _('Photo deleted successfully') + success_url = reverse_lazy('profile') + + def get(self, *args, **kwargs): + try: + photo = self.model.objects.get(pk=kwargs['pk'], user=self.request.user) + photo.delete() + except: + messages.error(self.request, _('No such image or no permission to delete it')) + return super().get(*args, **kwargs) + +class UploadPhotoView(SuccessMessageMixin, FormView): + model = Photo + template_name = 'upload_photo.html' + form_class = UploadPhotoForm + success_message = _('uploaded successfully') + success_url = reverse_lazy('profile') + + def post(self, *args, **kwargs): + num_photos = self.request.user.photo_set.count() + if num_photos >= MAX_NUM_PHOTOS: + messages.error(self.request, _('Maximum number of photos (%i) reached' % MAX_NUM_PHOTOS)) + return HttpResponseRedirect(reverse_lazy('profile')) + return super().post(*args, **kwargs) + + def form_valid(self, form, *args, **kwargs): + photo_data = self.request.FILES['photo'] + if photo_data.size > MAX_PHOTO_SIZE: + messsages.error(self.request, _('Image to big')) + return HttpResponseRedirect(reverse_lazy('profile')) + + try: + photo = form.save(self.request, photo_data) + except IOError as e: + print('Error in IO: %s' % e) + messages.error(self.request, _('IO error')) + return HttpResponseRedirect(reverse_lazy('profile')) + + if not photo: + messages.error(self.request, _('Invalid Format')) + return HttpResponseRedirect(reverse_lazy('profile')) + + return super().form_valid(*args, **kwargs)