mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-18 14:08:04 +00:00
Feature 'Import libravatar export (mails + photos)'
This commit is contained in:
@@ -30,5 +30,5 @@ def basepage(request):
|
|||||||
unconfirmed = request.user.unconfirmedemail_set.count()
|
unconfirmed = request.user.unconfirmedemail_set.count()
|
||||||
if unconfirmed >= MAX_NUM_UNCONFIRMED_EMAILS:
|
if unconfirmed >= MAX_NUM_UNCONFIRMED_EMAILS:
|
||||||
context['max_emails'] = True
|
context['max_emails'] = True
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|||||||
@@ -2,21 +2,26 @@
|
|||||||
Classes for our ivatar.ivataraccount.forms
|
Classes for our ivatar.ivataraccount.forms
|
||||||
'''
|
'''
|
||||||
from urllib.parse import urlsplit, urlunsplit
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
from ipware import get_client_ip
|
from ipware import get_client_ip
|
||||||
|
|
||||||
from ivatar import settings
|
from ivatar import settings
|
||||||
from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
|
from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
|
||||||
from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL
|
from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL
|
||||||
|
from ivatar.settings import JPEG_QUALITY
|
||||||
from . models import UnconfirmedEmail, ConfirmedEmail, Photo
|
from . models import UnconfirmedEmail, ConfirmedEmail, Photo
|
||||||
from . models import UnconfirmedOpenId, ConfirmedOpenId
|
from . models import UnconfirmedOpenId, ConfirmedOpenId
|
||||||
from . models import UserPreference
|
from . models import UserPreference
|
||||||
|
from . models import pil_format, file_format
|
||||||
|
from . read_libravatar_export import read_gzdata as libravatar_read_gzdata
|
||||||
|
|
||||||
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
|
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
|
||||||
|
|
||||||
@@ -76,22 +81,7 @@ class AddEmailForm(forms.Form):
|
|||||||
unconfirmed.email = self.cleaned_data['email']
|
unconfirmed.email = self.cleaned_data['email']
|
||||||
unconfirmed.user = user
|
unconfirmed.user = user
|
||||||
unconfirmed.save()
|
unconfirmed.save()
|
||||||
|
unconfirmed.send_confirmation_mail(url=request.build_absolute_uri('/')[:-1])
|
||||||
link = request.build_absolute_uri('/')[:-1] + \
|
|
||||||
reverse(
|
|
||||||
'confirm_email',
|
|
||||||
kwargs={'verification_key': unconfirmed.verification_key})
|
|
||||||
email_subject = _('Confirm your email address on %s') % \
|
|
||||||
settings.SITE_NAME
|
|
||||||
email_body = render_to_string('email_confirmation.txt', {
|
|
||||||
'verification_link': link,
|
|
||||||
'site_name': settings.SITE_NAME,
|
|
||||||
})
|
|
||||||
# if settings.DEBUG:
|
|
||||||
# print('DEBUG: %s' % link)
|
|
||||||
send_mail(
|
|
||||||
email_subject, email_body, settings.SERVER_EMAIL,
|
|
||||||
[unconfirmed.email])
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -182,6 +172,7 @@ class AddOpenIDForm(forms.Form):
|
|||||||
|
|
||||||
return unconfirmed.pk
|
return unconfirmed.pk
|
||||||
|
|
||||||
|
|
||||||
class UpdatePreferenceForm(forms.ModelForm):
|
class UpdatePreferenceForm(forms.ModelForm):
|
||||||
'''
|
'''
|
||||||
Form for updating user preferences
|
Form for updating user preferences
|
||||||
@@ -193,3 +184,68 @@ class UpdatePreferenceForm(forms.ModelForm):
|
|||||||
'''
|
'''
|
||||||
model = UserPreference
|
model = UserPreference
|
||||||
fields = ['theme']
|
fields = ['theme']
|
||||||
|
|
||||||
|
|
||||||
|
class UploadLibravatarExportForm(forms.Form):
|
||||||
|
'''
|
||||||
|
Form handling libravatar user export upload
|
||||||
|
'''
|
||||||
|
export_file = forms.FileField(
|
||||||
|
label=_('Export file'),
|
||||||
|
error_messages={'required': _('You must choose an export file 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.')
|
||||||
|
})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save(request, data):
|
||||||
|
'''
|
||||||
|
Save the models and assign it to the current user
|
||||||
|
'''
|
||||||
|
items = libravatar_read_gzdata(data.read())
|
||||||
|
for email in items['emails']:
|
||||||
|
if not ConfirmedEmail.objects.filter(email=email) and \
|
||||||
|
not UnconfirmedEmail.objects.filter(email=email): # pylint: disable=no-member
|
||||||
|
try:
|
||||||
|
unconfirmed = UnconfirmedEmail.objects.create( # pylint: disable=no-member
|
||||||
|
user=request.user,
|
||||||
|
email=email
|
||||||
|
)
|
||||||
|
unconfirmed.save()
|
||||||
|
unconfirmed.send_confirmation_mail(url=request.build_absolute_uri('/')[:-1])
|
||||||
|
except Exception as e: # pylint: disable=broad-except,invalid-name
|
||||||
|
# Debugging only
|
||||||
|
print('Exception while trying to import mail addresses: %s' % e)
|
||||||
|
if 'openids' in items:
|
||||||
|
messages.warning(
|
||||||
|
request,
|
||||||
|
_('You have OpenIDs in your export file, but we cannot\
|
||||||
|
import those at the moment'))
|
||||||
|
|
||||||
|
for pilobj in items['photos']:
|
||||||
|
# Is there a reasonable way to check if that photo already exists!?
|
||||||
|
try:
|
||||||
|
data = BytesIO()
|
||||||
|
pilobj.save(data, pilobj.format, quality=JPEG_QUALITY)
|
||||||
|
data.seek(0)
|
||||||
|
photo = Photo()
|
||||||
|
photo.user = request.user
|
||||||
|
photo.ip_address = get_client_ip(request)[0]
|
||||||
|
photo.format = file_format(pilobj.format)
|
||||||
|
photo.data = data.read()
|
||||||
|
photo.save()
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
pass
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ from django.contrib import messages
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy, reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from openid.association import Association as OIDAssociation
|
from openid.association import Association as OIDAssociation
|
||||||
from openid.store import nonce as oidnonce
|
from openid.store import nonce as oidnonce
|
||||||
from openid.store.interface import OpenIDStore
|
from openid.store.interface import OpenIDStore
|
||||||
@@ -29,6 +31,7 @@ from libravatar import libravatar_url
|
|||||||
from ivatar.settings import MAX_LENGTH_EMAIL, logger
|
from ivatar.settings import MAX_LENGTH_EMAIL, logger
|
||||||
from ivatar.settings import MAX_PIXELS, AVATAR_MAX_SIZE, JPEG_QUALITY
|
from ivatar.settings import MAX_PIXELS, AVATAR_MAX_SIZE, JPEG_QUALITY
|
||||||
from ivatar.settings import MAX_LENGTH_URL
|
from ivatar.settings import MAX_LENGTH_URL
|
||||||
|
from ivatar.settings import SECURE_BASE_URL, SITE_NAME, SERVER_EMAIL
|
||||||
from .gravatar import get_photo as get_gravatar_photo
|
from .gravatar import get_photo as get_gravatar_photo
|
||||||
|
|
||||||
|
|
||||||
@@ -257,7 +260,7 @@ class Photo(BaseAccountModel):
|
|||||||
return HttpResponseRedirect(reverse_lazy('profile'))
|
return HttpResponseRedirect(reverse_lazy('profile'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s (%i) from %s' % (self.format, self.pk, self.user)
|
return '%s (%i) from %s' % (self.format, self.pk or 0, self.user)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@@ -361,6 +364,27 @@ class UnconfirmedEmail(BaseAccountModel):
|
|||||||
using,
|
using,
|
||||||
update_fields)
|
update_fields)
|
||||||
|
|
||||||
|
def send_confirmation_mail(self, url=SECURE_BASE_URL):
|
||||||
|
'''
|
||||||
|
Send confirmation mail to that mail address
|
||||||
|
'''
|
||||||
|
link = url + \
|
||||||
|
reverse(
|
||||||
|
'confirm_email',
|
||||||
|
kwargs={'verification_key': self.verification_key})
|
||||||
|
email_subject = _('Confirm your email address on %s') % \
|
||||||
|
SITE_NAME
|
||||||
|
email_body = render_to_string('email_confirmation.txt', {
|
||||||
|
'verification_link': link,
|
||||||
|
'site_name': SITE_NAME,
|
||||||
|
})
|
||||||
|
# if settings.DEBUG:
|
||||||
|
# print('DEBUG: %s' % link)
|
||||||
|
send_mail(
|
||||||
|
email_subject, email_body, SERVER_EMAIL,
|
||||||
|
[self.email])
|
||||||
|
return True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s (%i) from %s' % (self.email, self.pk, self.user)
|
return '%s (%i) from %s' % (self.email, self.pk, self.user)
|
||||||
|
|
||||||
|
|||||||
64
ivatar/ivataraccount/read_libravatar_export.py
Normal file
64
ivatar/ivataraccount/read_libravatar_export.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
'''
|
||||||
|
Reading libravatar export
|
||||||
|
'''
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
import gzip
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
import base64
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
SCHEMAROOT = 'https://www.libravatar.org/schemas/export/0.2'
|
||||||
|
|
||||||
|
def read_gzdata(gzdata=None):
|
||||||
|
'''
|
||||||
|
Read gzipped data file
|
||||||
|
'''
|
||||||
|
emails = [] # pylint: disable=invalid-name
|
||||||
|
openids = [] # pylint: disable=invalid-name
|
||||||
|
photos = [] # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
if not gzdata:
|
||||||
|
return False
|
||||||
|
|
||||||
|
fh = gzip.open(BytesIO(gzdata), 'rb') # pylint: disable=invalid-name
|
||||||
|
content = fh.read()
|
||||||
|
fh.close()
|
||||||
|
root = xml.etree.ElementTree.fromstring(content)
|
||||||
|
if not root.tag == '{%s}user' % SCHEMAROOT:
|
||||||
|
print('Unknown export format: %s' % root.tag)
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
# Emails
|
||||||
|
for email in root.findall('{%s}emails' % SCHEMAROOT)[0]:
|
||||||
|
if email.tag == '{%s}email' % SCHEMAROOT:
|
||||||
|
emails.append(email.text)
|
||||||
|
|
||||||
|
# OpenIDs
|
||||||
|
for openid in root.findall('{%s}openids' % SCHEMAROOT)[0]:
|
||||||
|
if openid.tag == '{%s}openid' % SCHEMAROOT:
|
||||||
|
openids.append(openid.text)
|
||||||
|
|
||||||
|
# Photos
|
||||||
|
for photo in root.findall('{%s}photos' % SCHEMAROOT)[0]:
|
||||||
|
if photo.tag == '{%s}photo' % SCHEMAROOT:
|
||||||
|
try:
|
||||||
|
data = base64.decodebytes(bytes(photo.text, 'utf-8'))
|
||||||
|
except Exception as e: # pylint: disable=broad-except,invalid-name
|
||||||
|
print('Cannot decode photo; Encoding: %s, Format: %s: %s' % (
|
||||||
|
photo.attrib['encoding'], photo.attrib['format'], e))
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
img = Image.open(BytesIO(data))
|
||||||
|
except Exception as e: # pylint: disable=broad-except,invalid-name
|
||||||
|
print('Cannot decode photo; Encoding: %s, Format: %s: %s' % (
|
||||||
|
photo.attrib['encoding'], photo.attrib['format'], e))
|
||||||
|
continue
|
||||||
|
|
||||||
|
photos.append(img)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'emails': emails,
|
||||||
|
'openids': openids,
|
||||||
|
'photos': photos,
|
||||||
|
}
|
||||||
20
ivatar/ivataraccount/templates/upload_libravatar_export.html
Normal file
20
ivatar/ivataraccount/templates/upload_libravatar_export.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
|
||||||
|
{% block title %}{% trans 'Upload an export from libravatar' %} - ivatar{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% trans 'Upload an export from libravatar' %}</h1>
|
||||||
|
|
||||||
|
<div style="max-width:600px;">
|
||||||
|
<form enctype="multipart/form-data" method="post">{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-primary">{% trans 'Upload' %}</button>
|
||||||
|
<button type="cancel" class="btn btn-danger">{% trans 'Cancel' %}</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
@@ -16,7 +16,7 @@ from . views import ImportPhotoView, RawImageView, DeletePhotoView
|
|||||||
from . views import UploadPhotoView, AssignPhotoOpenIDView
|
from . views import UploadPhotoView, AssignPhotoOpenIDView
|
||||||
from . views import AddOpenIDView, RedirectOpenIDView, ConfirmOpenIDView
|
from . views import AddOpenIDView, RedirectOpenIDView, ConfirmOpenIDView
|
||||||
from . views import CropPhotoView
|
from . views import CropPhotoView
|
||||||
from . views import UserPreferenceView
|
from . views import UserPreferenceView, UploadLibravatarExportView
|
||||||
|
|
||||||
# Define URL patterns, self documenting
|
# Define URL patterns, self documenting
|
||||||
# To see the fancy, colorful evaluation of these use:
|
# To see the fancy, colorful evaluation of these use:
|
||||||
@@ -84,4 +84,5 @@ urlpatterns = [ # pylint: disable=invalid-name
|
|||||||
url(r'raw_image/(?P<pk>\d+)', RawImageView.as_view(), name='raw_image'),
|
url(r'raw_image/(?P<pk>\d+)', RawImageView.as_view(), name='raw_image'),
|
||||||
url(r'crop_photo/(?P<pk>\d+)', CropPhotoView.as_view(), name='crop_photo'),
|
url(r'crop_photo/(?P<pk>\d+)', CropPhotoView.as_view(), name='crop_photo'),
|
||||||
url(r'pref/$', UserPreferenceView.as_view(), name='user_preference'),
|
url(r'pref/$', UserPreferenceView.as_view(), name='user_preference'),
|
||||||
|
url(r'upload_export/$', UploadLibravatarExportView.as_view(), name='upload_export'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import io
|
|||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@@ -31,7 +32,7 @@ from .gravatar import get_photo as get_gravatar_photo
|
|||||||
from ivatar.settings import MAX_NUM_PHOTOS, MAX_PHOTO_SIZE
|
from ivatar.settings import MAX_NUM_PHOTOS, MAX_PHOTO_SIZE
|
||||||
|
|
||||||
from .forms import AddEmailForm, UploadPhotoForm, AddOpenIDForm
|
from .forms import AddEmailForm, UploadPhotoForm, AddOpenIDForm
|
||||||
from .forms import UpdatePreferenceForm
|
from .forms import UpdatePreferenceForm, UploadLibravatarExportForm
|
||||||
from .models import UnconfirmedEmail, ConfirmedEmail, Photo
|
from .models import UnconfirmedEmail, ConfirmedEmail, Photo
|
||||||
from .models import UnconfirmedOpenId, ConfirmedOpenId, DjangoOpenIDStore
|
from .models import UnconfirmedOpenId, ConfirmedOpenId, DjangoOpenIDStore
|
||||||
from .models import UserPreference
|
from .models import UserPreference
|
||||||
@@ -690,6 +691,7 @@ class CropPhotoView(TemplateView):
|
|||||||
|
|
||||||
return photo.perform_crop(request, dimensions, email, openid)
|
return photo.perform_crop(request, dimensions, email, openid)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(login_required, name='dispatch') # pylint: disable=too-many-ancestors
|
@method_decorator(login_required, name='dispatch') # pylint: disable=too-many-ancestors
|
||||||
class UserPreferenceView(FormView, UpdateView):
|
class UserPreferenceView(FormView, UpdateView):
|
||||||
'''
|
'''
|
||||||
@@ -702,3 +704,21 @@ class UserPreferenceView(FormView, UpdateView):
|
|||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
return self.request.user.userpreference
|
return self.request.user.userpreference
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
class UploadLibravatarExportView(SuccessMessageMixin, FormView):
|
||||||
|
'''
|
||||||
|
View class responsible for libravatar user data export upload
|
||||||
|
'''
|
||||||
|
template_name = 'upload_libravatar_export.html'
|
||||||
|
form_class = UploadLibravatarExportForm
|
||||||
|
success_message = _('Successfully uploaded')
|
||||||
|
success_url = reverse_lazy('profile')
|
||||||
|
model = User
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
data = self.request.FILES['export_file']
|
||||||
|
|
||||||
|
form.save(self.request, data)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|||||||
Reference in New Issue
Block a user