diff --git a/.coveragerc b/.coveragerc index b023fc9..315f343 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,6 +5,11 @@ omit = node_modules/* .virtualenv/* import_libravatar.py + requirements.txt + static/admin/* + static/humans.txt + static/img/robots.txt + [html] extra_css = coverage_extra_style.css diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 854773c..9217ca8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,6 @@ -image: docker.io/ofalk/fedora31-python3 +image: + name: quay.io/rhn_support_ofalk/fedora34-python3 + entrypoint: [ '/bin/sh', '-c' ] before_script: - virtualenv -p python3 /tmp/.virtualenv diff --git a/INSTALL.md b/INSTALL.md index da3e500..ee596c4 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -23,6 +23,7 @@ cd ivatar virtualenv -p python3 .virtualenv source .virtualenv/bin/activate pip install -r requirements.txt +pip install pillow ~~~~ ## (SQL) Migrations diff --git a/ivatar/ivataraccount/migrations/0016_auto_20210413_0904.py b/ivatar/ivataraccount/migrations/0016_auto_20210413_0904.py new file mode 100644 index 0000000..d1cc066 --- /dev/null +++ b/ivatar/ivataraccount/migrations/0016_auto_20210413_0904.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.7 on 2021-04-13 09:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ivataraccount', '0015_auto_20200225_0934'), + ] + + operations = [ + migrations.AddField( + model_name='unconfirmedemail', + name='last_send_date', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='unconfirmedemail', + name='last_status', + field=models.TextField(blank=True, max_length=2047, null=True), + ), + ] diff --git a/ivatar/ivataraccount/migrations/0017_auto_20210528_1314.py b/ivatar/ivataraccount/migrations/0017_auto_20210528_1314.py new file mode 100644 index 0000000..411c08f --- /dev/null +++ b/ivatar/ivataraccount/migrations/0017_auto_20210528_1314.py @@ -0,0 +1,48 @@ +# Generated by Django 3.2.3 on 2021-05-28 13:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ivataraccount', '0016_auto_20210413_0904'), + ] + + operations = [ + migrations.AlterField( + model_name='confirmedemail', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='confirmedopenid', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='openidassociation', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='openidnonce', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='photo', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='unconfirmedemail', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='unconfirmedopenid', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/ivatar/ivataraccount/models.py b/ivatar/ivataraccount/models.py index bc51d64..1d7b97a 100644 --- a/ivatar/ivataraccount/models.py +++ b/ivatar/ivataraccount/models.py @@ -347,6 +347,8 @@ class UnconfirmedEmail(BaseAccountModel): ''' email = models.EmailField(max_length=MAX_LENGTH_EMAIL) verification_key = models.CharField(max_length=64) + last_send_date = models.DateTimeField(null=True, blank=True) + last_status = models.TextField(max_length=2047, null=True, blank=True) class Meta: # pylint: disable=too-few-public-methods ''' @@ -357,11 +359,12 @@ class UnconfirmedEmail(BaseAccountModel): def save(self, force_insert=False, force_update=False, using=None, update_fields=None): - hash_object = hashlib.new('sha256') - hash_object.update( - urandom(1024) + self.user.username.encode('utf-8') # pylint: disable=no-member - ) # pylint: disable=no-member - self.verification_key = hash_object.hexdigest() + if not self.verification_key: + hash_object = hashlib.new('sha256') + hash_object.update( + urandom(1024) + self.user.username.encode('utf-8') # pylint: disable=no-member + ) # pylint: disable=no-member + self.verification_key = hash_object.hexdigest() super(UnconfirmedEmail, self).save( force_insert, force_update, @@ -382,11 +385,17 @@ class UnconfirmedEmail(BaseAccountModel): 'verification_link': link, 'site_name': SITE_NAME, }) + self.last_send_date = timezone.now() + self.last_status = 'OK' # if settings.DEBUG: # print('DEBUG: %s' % link) - send_mail( - email_subject, email_body, DEFAULT_FROM_EMAIL, - [self.email]) + try: + send_mail( + email_subject, email_body, DEFAULT_FROM_EMAIL, + [self.email]) + except Exception as e: + self.last_status = "%s" % e + self.save() return True def __str__(self): diff --git a/ivatar/ivataraccount/views.py b/ivatar/ivataraccount/views.py index ab78a25..94c1b30 100644 --- a/ivatar/ivataraccount/views.py +++ b/ivatar/ivataraccount/views.py @@ -998,7 +998,7 @@ class PasswordResetView(PasswordResetViewOriginal): # ResetPasswordView class will silently ignore the password # reset request if user: - if not user.password or user.password == '!': + if not user.password or user.password.startswith('!'): random_pass = User.objects.make_random_password() user.set_password(random_pass) user.save() diff --git a/ivatar/settings.py b/ivatar/settings.py index 7546ee1..82cf3f0 100644 --- a/ivatar/settings.py +++ b/ivatar/settings.py @@ -118,4 +118,6 @@ PROJECT_ROOT = os.path.abspath( STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + from config import * # pylint: disable=wildcard-import,wrong-import-position,unused-wildcard-import diff --git a/ivatar/urls.py b/ivatar/urls.py index df3b450..96b4d22 100644 --- a/ivatar/urls.py +++ b/ivatar/urls.py @@ -7,7 +7,7 @@ from django.conf.urls import url from django.conf.urls.static import static from django.views.generic import TemplateView, RedirectView from ivatar import settings -from . views import AvatarImageView, GravatarProxyView +from . views import AvatarImageView, GravatarProxyView, StatsView urlpatterns = [ # pylint: disable=invalid-name path('admin/', admin.site.urls), @@ -36,6 +36,7 @@ urlpatterns = [ # pylint: disable=invalid-name url('privacy/', TemplateView.as_view(template_name='privacy.html'), name='privacy'), url('contact/', TemplateView.as_view(template_name='contact.html'), name='contact'), path('talk_to_us/', RedirectView.as_view(url='/contact'), name='talk_to_us'), + url('stats/', StatsView.as_view(), name='stats'), ] MAINTENANCE = False diff --git a/ivatar/views.py b/ivatar/views.py index fb79958..49a8b33 100644 --- a/ivatar/views.py +++ b/ivatar/views.py @@ -8,12 +8,14 @@ from urllib.request import urlopen from urllib.error import HTTPError, URLError from ssl import SSLError from django.views.generic.base import TemplateView, View -from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound +from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponseNotFound, JsonResponse from django.core.exceptions import ObjectDoesNotExist from django.core.cache import cache, caches from django.utils.translation import ugettext_lazy as _ from django.urls import reverse_lazy from django.db.models import Q +from django.contrib.auth.models import User from PIL import Image @@ -23,7 +25,8 @@ from pydenticon5 import Pydenticon5 import pagan from robohash import Robohash -from ivatar.settings import AVATAR_MAX_SIZE, JPEG_QUALITY, DEFAULT_AVATAR_SIZE, CACHE_RESPONSE +from ivatar.settings import AVATAR_MAX_SIZE, JPEG_QUALITY, DEFAULT_AVATAR_SIZE +from ivatar.settings import CACHE_RESPONSE from ivatar.settings import CACHE_IMAGES_MAX_AGE from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId from . ivataraccount.models import pil_format, file_format @@ -60,7 +63,8 @@ class CachingHttpResponse(HttpResponse): ''' Handle caching of response ''' - def __init__(self, uri, content=b'', content_type=None, status=200, reason=None, charset=None): # pylint: disable=too-many-arguments + def __init__(self, uri, content=b'', content_type=None, status=200, # pylint: disable=too-many-arguments + reason=None, charset=None): if CACHE_RESPONSE: caches['filesystem'].set(uri, { 'content': content, @@ -394,3 +398,17 @@ class GravatarProxyView(View): # We shouldn't reach this point... But make sure we do something return redir_default(default) + + +class StatsView(TemplateView, JsonResponse): + ''' + Return stats + ''' + def get(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,no-self-use,unused-argument,too-many-return-statements + retval = { + 'users': User.objects.all().count(), + 'mails': ConfirmedEmail.objects.all().count(), + 'openids': ConfirmedOpenId.objects.all().count(), # pylint: disable=no-member + } + + return JsonResponse(retval) diff --git a/requirements.txt b/requirements.txt index d8ae36a..760b3df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ autopep8 bcrypt defusedxml -Django<3.2 +Django django-auth-ldap django-bootstrap4 django-coverage-plugin diff --git a/templates/contact.html b/templates/contact.html index 6d02405..65997d0 100644 --- a/templates/contact.html +++ b/templates/contact.html @@ -14,9 +14,7 @@ There are a few ways to get in touch with the ivatar/libravatar developers:

IRC

-If you have an IRC client already, you can join #libravatar on chat.freenode.net. -
-Otherwise, you can use this simple web interface. +You can join the Libravatar community chat at #libravatar:matrix.org. It is also bridged to #libravatar on irc.libera.chat for those prefering IRC.
Please keep in mind that you may live in a different timezone than most of the developers. So if you do not get a response, it's not because we're ignoring you, it's probably because we're sleeping :)