diff --git a/.coveragerc b/.coveragerc
index 01c3fe6..b023fc9 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,6 +1,10 @@
[run]
plugins =
django_coverage_plugin
+omit =
+ node_modules/*
+ .virtualenv/*
+ import_libravatar.py
[html]
extra_css = coverage_extra_style.css
diff --git a/.gitignore b/.gitignore
index 9c5e063..9e5fc42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,6 @@ htmlcov/
**.pyc
.ropeproject/
db.sqlite3.SAVE
-
+node_modules/
+config_local.py
+locale/*/LC_MESSAGES/django.mo
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index de476e1..3214a43 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,6 +16,9 @@ before_script:
test_and_coverage:
stage: test
script:
+ - echo 'from ivatar.settings import TEMPLATES' > config_local.py
+ - echo 'TEMPLATES[0]["OPTIONS"]["debug"] = True' >> config_local.py
+ - echo "DEBUG = True" >> config_local.py
- python manage.py collectstatic --noinput
- coverage run --source . manage.py test -v3
- coverage report --fail-under=70
diff --git a/config.py b/config.py
index 4866404..f12f91a 100644
--- a/config.py
+++ b/config.py
@@ -4,14 +4,18 @@ Configuration overrides for settings.py
import os
import sys
-from socket import gethostname, gethostbyname
from django.urls import reverse_lazy
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.messages import constants as message_constants
from ivatar.settings import BASE_DIR
-ADMIN_USERS = []
-ALLOWED_HOSTS = [ '*' ]
+from ivatar.settings import MIDDLEWARE
+from ivatar.settings import INSTALLED_APPS
+from ivatar.settings import TEMPLATES
+
+ADMIN_USERS = []
+ALLOWED_HOSTS = ['*']
-from ivatar.settings import INSTALLED_APPS # noqa
INSTALLED_APPS.extend([
'django_extensions',
'django_openid_auth',
@@ -22,10 +26,12 @@ INSTALLED_APPS.extend([
'ivatar.tools',
])
-from ivatar.settings import MIDDLEWARE # noqa
MIDDLEWARE.extend([
'django.middleware.locale.LocaleMiddleware',
])
+MIDDLEWARE.insert(
+ 0, 'ivatar.middleware.MultipleProxyMiddleware',
+)
AUTHENTICATION_BACKENDS = (
# Enable this to allow LDAP authentication.
@@ -35,7 +41,6 @@ AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
-from ivatar.settings import TEMPLATES # noqa
TEMPLATES[0]['DIRS'].extend([
os.path.join(BASE_DIR, 'templates'),
])
@@ -46,16 +51,14 @@ TEMPLATES[0]['OPTIONS']['context_processors'].append(
OPENID_CREATE_USERS = True
OPENID_UPDATE_DETAILS_FROM_SREG = True
-SITE_NAME = 'ivatar'
-IVATAR_VERSION = '0.1'
+SITE_NAME = os.environ.get('SITE_NAME', 'libravatar')
+IVATAR_VERSION = '1.0'
-SECURE_BASE_URL = 'https://avatars.linux-kernel.at/avatar/'
-BASE_URL = 'http://avatars.linux-kernel.at/avatar/'
+SECURE_BASE_URL = os.environ.get('SECURE_BASE_URL', 'https://avatars.linux-kernel.at/avatar/')
+BASE_URL = os.environ.get('BASE_URL', 'http://avatars.linux-kernel.at/avatar/')
LOGIN_REDIRECT_URL = reverse_lazy('profile')
MAX_LENGTH_EMAIL = 254 # http://stackoverflow.com/questions/386294
-SERVER_EMAIL = 'accounts@mg.linux-kernel.at'
-DEFAULT_FROM_EMAIL = SERVER_EMAIL
MAX_NUM_PHOTOS = 5
MAX_NUM_UNCONFIRMED_EMAILS = 5
@@ -76,7 +79,8 @@ BOOTSTRAP4 = {
'javascript_in_head': False,
'css_url': {
'href': '/static/css/bootstrap.min.css',
- 'integrity': 'sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB', # noqa
+ 'integrity':
+ 'sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB',
'crossorigin': 'anonymous',
},
'javascript_url': {
@@ -86,18 +90,26 @@ BOOTSTRAP4 = {
},
'popper_url': {
'url': '/static/js/popper.min.js',
- 'integrity': 'sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49', # noqa
+ 'integrity':
+ 'sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49',
'crossorigin': 'anonymous',
},
}
-if 'test' not in sys.argv and 'collectstatic' not in sys.argv:
- ANYMAIL = { # pragma: no cover
- 'MAILGUN_API_KEY': os.environ['IVATAR_MAILGUN_API_KEY'],
- 'MAILGUN_SENDER_DOMAIN': os.environ['IVATAR_MAILGUN_SENDER_DOMAIN'],
- }
- EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' # pragma: no cover
-DEFAULT_FROM_EMAIL = 'ivatar@mg.linux-kernel.at'
+if 'EMAIL_BACKEND' in os.environ:
+ EMAIL_BACKEND = os.environ['EMAIL_BACKEND'] # pragma: no cover
+else:
+ if 'test' in sys.argv or 'collectstatic' in sys.argv:
+ EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
+ else:
+ ANYMAIL = { # pragma: no cover
+ 'MAILGUN_API_KEY': os.environ['IVATAR_MAILGUN_API_KEY'],
+ 'MAILGUN_SENDER_DOMAIN': os.environ['IVATAR_MAILGUN_SENDER_DOMAIN'],
+ }
+ EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' # pragma: no cover
+
+SERVER_EMAIL = os.environ.get('SERVER_EMAIL', 'ivatar@mg.linux-kernel.at')
+DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'ivatar@mg.linux-kernel.at')
try:
from ivatar.settings import DATABASES
@@ -132,3 +144,43 @@ if os.path.isfile(os.path.join(BASE_DIR, 'config_local.py')):
from config_local import * # noqa # flake8: noqa # NOQA # pragma: no cover
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
+
+USE_X_FORWARDED_HOST = True
+ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = ['avatars.linux-kernel.at', 'localhost',]
+
+DEFAULT_AVATAR_SIZE = 80
+
+LANGUAGES = (
+ ('de', _('Deutsch')),
+ ('en', _('English')),
+ ('ca', _('Català')),
+ ('cs', _('Česky')),
+ ('es', _('Español')),
+ ('eu', _('Basque')),
+ ('fr', _('Français')),
+ ('it', _('Italiano')),
+ ('ja', _('日本語')),
+ ('nl', _('Nederlands')),
+ ('pt', _('Português')),
+ ('ru', _('Русский')),
+ ('sq', _('Shqip')),
+ ('tr', _('Türkçe')),
+ ('uk', _('Українська')),
+)
+
+MESSAGE_TAGS = {
+ message_constants.DEBUG: 'debug',
+ message_constants.INFO: 'info',
+ message_constants.SUCCESS: 'success',
+ message_constants.WARNING: 'warning',
+ message_constants.ERROR: 'danger',
+}
+
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+ 'LOCATION': [
+ '127.0.0.1:11211',
+ ],
+ }
+}
diff --git a/config_local.py b/config_local.py
deleted file mode 100644
index cb6aa6a..0000000
--- a/config_local.py
+++ /dev/null
@@ -1,9 +0,0 @@
-'''
-Local config
-'''
-
-from ivatar.settings import TEMPLATES # noqa
-
-SESSION_COOKIE_SECURE = False
-DEBUG = True
-TEMPLATES[0]['OPTIONS']['debug'] = True
diff --git a/exportaccounts.py b/exportaccounts.py
new file mode 100755
index 0000000..b921b99
--- /dev/null
+++ b/exportaccounts.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2018 Oliver Falk
+#
+# This file is part of Libravatar
+#
+# Libravatar is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Libravatar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Libravatar. If not, see .
+
+import base64
+import gzip
+import json
+import os
+import sys
+from xml.sax import saxutils
+import hashlib
+
+# pylint: disable=relative-import
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libravatar.settings")
+import libravatar.settings as settings
+from libravatar.utils import create_logger, is_hex
+import django
+django.setup()
+from django.contrib.auth.models import User
+
+os.umask(022)
+LOGGER = create_logger('exportaccount')
+
+SCHEMA_ROOT = 'https://www.libravatar.org/schemas/export/0.2'
+SCHEMA_XSD = '%s/export.xsd' % SCHEMA_ROOT
+
+
+def xml_header():
+ return '''
+\n''' % (SCHEMA_ROOT, SCHEMA_XSD, SCHEMA_ROOT)
+
+
+def xml_footer():
+ return '\n'
+
+
+def xml_account(username, password):
+ escaped_username = saxutils.quoteattr(username)
+ escaped_site_url = saxutils.quoteattr(settings.SITE_URL)
+ escaped_password = saxutils.quoteattr(password)
+ return ' \n' % (escaped_username, escaped_password, escaped_site_url)
+
+def xml_email(emails):
+ returnstring = " \n"
+ for email in emails:
+ returnstring += ' ' + email.email.encode('utf-8') + '' + "\n"
+ returnstring += " \n"
+ return returnstring
+
+def xml_openid(openids):
+ returnstring = " \n"
+ for openid in openids:
+ returnstring += ' ' + openid.openid.encode('utf-8') + '' + "\n"
+ returnstring += " \n"
+ return returnstring
+
+
+def xml_photos(photos):
+ s = ' \n'
+ for photo in photos:
+ (photo_filename, photo_format, id) = photo
+ encoded_photo = encode_photo(photo_filename, photo_format)
+ if encoded_photo:
+ s += '''
+%s
+ \n''' % (id, saxutils.quoteattr(photo_format), encoded_photo)
+ s += ' \n'
+ return s
+
+
+def encode_photo(photo_filename, photo_format):
+ filename = settings.USER_FILES_ROOT + photo_filename + '.' + photo_format
+ if not os.path.isfile(filename):
+ LOGGER.warning('Photo not found: %s', filename)
+ return None
+
+ photo_content = None
+ with open(filename) as photo:
+ photo_content = photo.read()
+
+ if not photo_content:
+ LOGGER.warning('Could not read photo: %s', filename)
+ return None
+
+ return base64.b64encode(photo_content)
+
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+
+ if(len(sys.argv) > 1):
+ userobjs = User.objects.filter(username=sys.argv[1])
+ else:
+ userobjs = User.objects.all()
+
+ for user in userobjs:
+ hash_object = hashlib.new('sha256')
+ hash_object.update(user.username + user.password)
+ file_hash = hash_object.hexdigest()
+ photos = []
+ for photo in user.photos.all():
+ photo_details = (photo.filename, photo.format, photo.id)
+ photos.append(photo_details)
+ username = user.username
+
+ dest_filename = settings.EXPORT_FILES_ROOT + file_hash + '.xml.gz'
+
+ destination = gzip.open(dest_filename, 'w')
+ destination.write(xml_header())
+ destination.write(xml_account(username, user.password))
+ destination.write(xml_email(user.confirmed_emails.all()))
+ destination.write(xml_openid(user.confirmed_openids.all()))
+ destination.write(xml_photos(photos))
+ destination.write(xml_footer())
+ destination.close()
+ print(dest_filename)
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/import_libravatar.py b/import_libravatar.py
new file mode 100644
index 0000000..c000633
--- /dev/null
+++ b/import_libravatar.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+'''
+Import the whole libravatar export
+'''
+
+import os
+from os.path import isfile, isdir, join
+import sys
+import base64
+from io import BytesIO
+import django
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ivatar.settings") # pylint: disable=wrong-import-position
+django.setup() # pylint: disable=wrong-import-position
+from django.contrib.auth.models import User
+from PIL import Image
+from django_openid_auth.models import UserOpenID
+from ivatar.settings import JPEG_QUALITY
+from ivatar.ivataraccount.read_libravatar_export import read_gzdata as libravatar_read_gzdata
+from ivatar.ivataraccount.models import ConfirmedEmail
+from ivatar.ivataraccount.models import ConfirmedOpenId
+from ivatar.ivataraccount.models import Photo
+from ivatar.ivataraccount.models import file_format
+
+if len(sys.argv) < 2:
+ print("First argument to '%s' must be the path to the exports" % sys.argv[0])
+ exit(-255)
+
+if not isdir(sys.argv[1]):
+ print("First argument to '%s' must be a directory containing the exports" % sys.argv[0])
+ exit(-255)
+
+PATH = sys.argv[1]
+for file in os.listdir(PATH):
+ if not file.endswith('.xml.gz'):
+ continue
+ if isfile(join(PATH, file)):
+ fh = open(join(PATH, file), 'rb')
+ items = libravatar_read_gzdata(fh.read())
+ print('Adding user "%s"' % items['username'])
+ (user, created) = User.objects.get_or_create(username=items['username'])
+ user.password = items['password']
+ user.save()
+
+ saved_photos = {}
+ for photo in items['photos']:
+ photo_id = photo['id']
+ data = base64.decodebytes(bytes(photo['data'], 'utf-8'))
+ pilobj = Image.open(BytesIO(data))
+ out = BytesIO()
+ pilobj.save(out, pilobj.format, quality=JPEG_QUALITY)
+ out.seek(0)
+ photo = Photo()
+ photo.user = user
+ photo.ip_address = '0.0.0.0'
+ photo.format = file_format(pilobj.format)
+ photo.data = out.read()
+ photo.save()
+ saved_photos[photo_id] = photo
+
+ for email in items['emails']:
+ try:
+ ConfirmedEmail.objects.get_or_create(email=email['email'], user=user,
+ photo=saved_photos.get(email['photo_id']))
+ except django.db.utils.IntegrityError:
+ print('%s not unique?' % email['email'])
+
+ for openid in items['openids']:
+ try:
+ ConfirmedOpenId.objects.get_or_create(openid=openid['openid'], user=user,
+ photo=saved_photos.get(openid['photo_id'])) # pylint: disable=no-member
+ UserOpenID.objects.get_or_create(
+ user_id=user.id,
+ claimed_id=openid['openid'],
+ display_id=openid['openid'],
+ )
+ except django.db.utils.IntegrityError:
+ print('%s not unique?' % openid['openid'])
+
+ fh.close()
diff --git a/ivatar/ivataraccount/forms.py b/ivatar/ivataraccount/forms.py
index 4b12088..21acf57 100644
--- a/ivatar/ivataraccount/forms.py
+++ b/ivatar/ivataraccount/forms.py
@@ -90,16 +90,14 @@ class UploadPhotoForm(forms.Form):
required=True,
error_messages={
'required':
- _('We only host "G-rated" images and so this field must\
- be checked.')
+ _('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.')
+ _('This field must be checked since we need to be able to distribute photos to third parties.')
})
@staticmethod
@@ -191,8 +189,7 @@ class UploadLibravatarExportForm(forms.Form):
required=True,
error_messages={
'required':
- _('We only host "G-rated" images and so this field must\
- be checked.')
+ _('We only host "G-rated" images and so this field must be checked.')
})
can_distribute = forms.BooleanField(
label=_('can be freely copied'),
@@ -202,3 +199,6 @@ class UploadLibravatarExportForm(forms.Form):
_('This field must be checked since we need to be able to\
distribute photos to third parties.')
})
+
+class DeleteAccountForm(forms.Form):
+ password = forms.CharField(label=_('Password'), required=False, widget=forms.PasswordInput())
diff --git a/ivatar/ivataraccount/gravatar.py b/ivatar/ivataraccount/gravatar.py
index a1889be..df1cfe6 100644
--- a/ivatar/ivataraccount/gravatar.py
+++ b/ivatar/ivataraccount/gravatar.py
@@ -5,6 +5,8 @@ from ssl import SSLError
from urllib.request import urlopen, HTTPError, URLError
import hashlib
+from .. settings import AVATAR_MAX_SIZE
+
URL_TIMEOUT = 5 # in seconds
@@ -15,7 +17,7 @@ def get_photo(email):
hash_object = hashlib.new('md5')
hash_object.update(email.lower().encode('utf-8'))
thumbnail_url = 'https://secure.gravatar.com/avatar/' + \
- hash_object.hexdigest() + '?s=80&d=404'
+ hash_object.hexdigest() + '?s=%i&d=404' % AVATAR_MAX_SIZE
image_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest(
) + '?s=512&d=404'
@@ -44,8 +46,8 @@ def get_photo(email):
return {
'thumbnail_url': thumbnail_url,
'image_url': image_url,
- 'width': 80,
- 'height': 80,
+ 'width': AVATAR_MAX_SIZE,
+ 'height': AVATAR_MAX_SIZE,
'service_url': service_url,
'service_name': 'Gravatar'
}
diff --git a/ivatar/ivataraccount/migrations/0008_userpreference.py b/ivatar/ivataraccount/migrations/0008_userpreference.py
index 5f76f52..5838a97 100644
--- a/ivatar/ivataraccount/migrations/0008_userpreference.py
+++ b/ivatar/ivataraccount/migrations/0008_userpreference.py
@@ -1,3 +1,4 @@
+# pylint: disable=invalid-name,missing-docstring
# Generated by Django 2.0.6 on 2018-07-04 12:32
from django.conf import settings
@@ -5,18 +6,18 @@ from django.db import migrations, models
import django.db.models.deletion
-def add_preference_to_user(apps, schema_editor):
+def add_preference_to_user(apps, schema_editor): # pylint: disable=unused-argument
'''
Make sure all users have preferences set up
'''
from django.contrib.auth.models import User
- UserPreference = apps.get_model('ivataraccount', 'UserPreference')
- for u in User.objects.filter(userpreference=None):
- p = UserPreference.objects.create(user_id=u.pk)
- p.save()
+ UserPreference = apps.get_model('ivataraccount', 'UserPreference') # pylint: disable=invalid-name
+ for user in User.objects.filter(userpreference=None):
+ pref = UserPreference.objects.create(user_id=user.pk) # pragma: no cover
+ pref.save() # pragma: no cover
-class Migration(migrations.Migration):
+class Migration(migrations.Migration): # pylint: disable=missing-docstring
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
@@ -27,8 +28,16 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserPreference',
fields=[
- ('theme', models.CharField(choices=[('default', 'Default theme'), ('clime', 'Climes theme')], default='default', max_length=10)),
- ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
+ ('theme', models.CharField(
+ choices=[
+ ('default', 'Default theme'),
+ ('clime', 'Climes theme')],
+ default='default', max_length=10)),
+ ('user', models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ primary_key=True,
+ serialize=False,
+ to=settings.AUTH_USER_MODEL)),
],
),
migrations.RunPython(add_preference_to_user),
diff --git a/ivatar/ivataraccount/migrations/0013_auto_20181203_1421.py b/ivatar/ivataraccount/migrations/0013_auto_20181203_1421.py
new file mode 100644
index 0000000..e857c27
--- /dev/null
+++ b/ivatar/ivataraccount/migrations/0013_auto_20181203_1421.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1.3 on 2018-12-03 14:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ivataraccount', '0012_auto_20181107_1732'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='userpreference',
+ name='theme',
+ field=models.CharField(choices=[('default', 'Default theme'), ('clime', 'climes theme'), ('green', 'green theme'), ('red', 'red theme')], default='default', max_length=10),
+ ),
+ ]
diff --git a/ivatar/ivataraccount/migrations/0014_auto_20190218_1602.py b/ivatar/ivataraccount/migrations/0014_auto_20190218_1602.py
new file mode 100644
index 0000000..a934735
--- /dev/null
+++ b/ivatar/ivataraccount/migrations/0014_auto_20190218_1602.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.1.5 on 2019-02-18 16:02
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ivataraccount', '0013_auto_20181203_1421'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='unconfirmedemail',
+ options={'verbose_name': 'unconfirmed email', 'verbose_name_plural': 'unconfirmed emails'},
+ ),
+ ]
diff --git a/ivatar/ivataraccount/models.py b/ivatar/ivataraccount/models.py
index ca1c823..1200666 100644
--- a/ivatar/ivataraccount/models.py
+++ b/ivatar/ivataraccount/models.py
@@ -31,7 +31,7 @@ from libravatar import libravatar_url
from ivatar.settings import MAX_LENGTH_EMAIL, logger
from ivatar.settings import MAX_PIXELS, AVATAR_MAX_SIZE, JPEG_QUALITY
from ivatar.settings import MAX_LENGTH_URL
-from ivatar.settings import SECURE_BASE_URL, SITE_NAME, SERVER_EMAIL
+from ivatar.settings import SECURE_BASE_URL, SITE_NAME, DEFAULT_FROM_EMAIL
from .gravatar import get_photo as get_gravatar_photo
@@ -70,7 +70,8 @@ class UserPreference(models.Model):
THEMES = (
('default', 'Default theme'),
('clime', 'climes theme'),
- ('falko', 'falkos theme'),
+ ('green', 'green theme'),
+ ('red', 'red theme'),
)
theme = models.CharField(
@@ -135,7 +136,7 @@ class Photo(BaseAccountModel):
image_url = gravatar['image_url']
if service_name == 'Libravatar':
- image_url = libravatar_url(email_address)
+ image_url = libravatar_url(email_address, size=AVATAR_MAX_SIZE)
if not image_url:
return False # pragma: no cover
@@ -220,8 +221,9 @@ class Photo(BaseAccountModel):
if dimensions['a'] > MAX_PIXELS or dimensions['b'] > MAX_PIXELS:
messages.error(
request,
- _('Image dimensions are too big(max: %s x %s' %
- (MAX_PIXELS, MAX_PIXELS)))
+ _('Image dimensions are too big (max: %(max_pixels)s x %(max_pixels)s' % {
+ max_pixels: MAX_PIXELS,
+ }))
return HttpResponseRedirect(reverse_lazy('profile'))
if dimensions['w'] == 0 and dimensions['h'] == 0:
@@ -349,8 +351,8 @@ class UnconfirmedEmail(BaseAccountModel):
'''
Class attributes
'''
- verbose_name = _('unconfirmed_email')
- verbose_name_plural = _('unconfirmed_emails')
+ verbose_name = _('unconfirmed email')
+ verbose_name_plural = _('unconfirmed emails')
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@@ -382,7 +384,7 @@ class UnconfirmedEmail(BaseAccountModel):
# if settings.DEBUG:
# print('DEBUG: %s' % link)
send_mail(
- email_subject, email_body, SERVER_EMAIL,
+ email_subject, email_body, DEFAULT_FROM_EMAIL,
[self.email])
return True
@@ -448,8 +450,8 @@ class ConfirmedOpenId(BaseAccountModel):
lowercase_url = urlunsplit(
(url.scheme.lower(), netloc, url.path, url.query, url.fragment)
)
- if lowercase_url[-1] != '/':
- lowercase_url += '/'
+ #if lowercase_url[-1] != '/':
+ # lowercase_url += '/'
self.openid = lowercase_url
self.digest = hashlib.sha256(lowercase_url.encode('utf-8')).hexdigest()
return super().save(force_insert, force_update, using, update_fields)
diff --git a/ivatar/ivataraccount/read_libravatar_export.py b/ivatar/ivataraccount/read_libravatar_export.py
index db09992..d3afc01 100644
--- a/ivatar/ivataraccount/read_libravatar_export.py
+++ b/ivatar/ivataraccount/read_libravatar_export.py
@@ -20,6 +20,8 @@ def read_gzdata(gzdata=None):
emails = [] # pylint: disable=invalid-name
openids = [] # pylint: disable=invalid-name
photos = [] # pylint: disable=invalid-name
+ username = None # pylint: disable=invalid-name
+ password = None # pylint: disable=invalid-name
if not gzdata:
return False
@@ -32,15 +34,22 @@ def read_gzdata(gzdata=None):
print('Unknown export format: %s' % root.tag)
exit(-1)
+ # Username
+ for item in root.findall('{%s}account' % SCHEMAROOT)[0].items():
+ if item[0] == 'username':
+ username = item[1]
+ if item[0] == 'password':
+ password = item[1]
+
# Emails
for email in root.findall('{%s}emails' % SCHEMAROOT)[0]:
if email.tag == '{%s}email' % SCHEMAROOT:
- emails.append(email.text)
+ emails.append({'email': email.text, 'photo_id': email.attrib['photo_id']})
# OpenIDs
for openid in root.findall('{%s}openids' % SCHEMAROOT)[0]:
if openid.tag == '{%s}openid' % SCHEMAROOT:
- openids.append(openid.text)
+ openids.append({'openid': openid.text, 'photo_id': openid.attrib['photo_id']})
# Photos
for photo in root.findall('{%s}photos' % SCHEMAROOT)[0]:
@@ -48,14 +57,14 @@ def read_gzdata(gzdata=None):
try:
data = base64.decodebytes(bytes(photo.text, 'utf-8'))
except binascii.Error as exc:
- print('Cannot decode photo; Encoding: %s, Format: %s: %s' % (
- photo.attrib['encoding'], photo.attrib['format'], exc))
+ print('Cannot decode photo; Encoding: %s, Format: %s, Id: %s: %s' % (
+ photo.attrib['encoding'], photo.attrib['format'], photo.attrib['id'], exc))
continue
try:
Image.open(BytesIO(data))
except Exception as exc: # pylint: disable=broad-except
- print('Cannot decode photo; Encoding: %s, Format: %s: %s' % (
- photo.attrib['encoding'], photo.attrib['format'], exc))
+ print('Cannot decode photo; Encoding: %s, Format: %s, Id: %s: %s' % (
+ photo.attrib['encoding'], photo.attrib['format'], photo.attrib['id'], exc))
continue
else:
# If it is a working image, we can use it
@@ -63,10 +72,13 @@ def read_gzdata(gzdata=None):
photos.append({
'data': photo.text,
'format': photo.attrib['format'],
+ 'id': photo.attrib['id'],
})
return {
'emails': emails,
'openids': openids,
'photos': photos,
+ 'username': username,
+ 'password': password,
}
diff --git a/ivatar/ivataraccount/templates/_import_photo_form.html b/ivatar/ivataraccount/templates/_import_photo_form.html
index 77070b2..ef950e4 100644
--- a/ivatar/ivataraccount/templates/_import_photo_form.html
+++ b/ivatar/ivataraccount/templates/_import_photo_form.html
@@ -25,7 +25,7 @@
-
+
diff --git a/ivatar/ivataraccount/templates/choose_libravatar_export.html b/ivatar/ivataraccount/templates/choose_libravatar_export.html
index 9767377..3d1b7e1 100644
--- a/ivatar/ivataraccount/templates/choose_libravatar_export.html
+++ b/ivatar/ivataraccount/templates/choose_libravatar_export.html
@@ -7,20 +7,6 @@
{% block content %}
{% trans 'Crop photo' %}
diff --git a/ivatar/ivataraccount/templates/delete.html b/ivatar/ivataraccount/templates/delete.html
new file mode 100644
index 0000000..d0d55b6
--- /dev/null
+++ b/ivatar/ivataraccount/templates/delete.html
@@ -0,0 +1,35 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans 'Delete your Libravatar account' %}{% endblock title %}
+
+{% block content %}
+
+{% trans 'Delete your account' %}
+
+{% trans 'There is no way to undo this operation.' %}
+
+
+
+
+{% endblock content %}
diff --git a/ivatar/ivataraccount/templates/preferences.html b/ivatar/ivataraccount/templates/preferences.html
index e391abf..545abb0 100644
--- a/ivatar/ivataraccount/templates/preferences.html
+++ b/ivatar/ivataraccount/templates/preferences.html
@@ -7,11 +7,48 @@
{% block content %}
{% trans 'Account settings' %}
-{% if has_password %}
-{% trans 'Change your password' %}
-{% else %}
-{% trans 'Set a password' %}
-{% endif %}
+{% trans 'Theme' %}
+
+
+
+
+
+
+
diff --git a/ivatar/ivataraccount/templates/upload_libravatar_export.html b/ivatar/ivataraccount/templates/upload_libravatar_export.html
index c75a1ab..1886a5c 100644
--- a/ivatar/ivataraccount/templates/upload_libravatar_export.html
+++ b/ivatar/ivataraccount/templates/upload_libravatar_export.html
@@ -5,30 +5,6 @@
{% block title %}{% trans 'Upload an export from libravatar' %} - ivatar{% endblock title %}
{% block content %}
-
{% trans 'Upload an export from libravatar' %}