misc: allow email domains suggestions (#40166)
This commit is contained in:
parent
162a984897
commit
dc7bce7c05
|
@ -238,6 +238,11 @@ default_settings = dict(
|
|||
A2_AUTH_PASSWORD_ENABLE=Setting(
|
||||
default=True,
|
||||
definition='Activate login/password authentication', names=('AUTH_PASSWORD',)),
|
||||
A2_SUGGESTED_EMAIL_DOMAINS=Setting(
|
||||
default=['gmail.com', 'msn.com', 'hotmail.com', 'hotmail.fr',
|
||||
'wanadoo.fr', 'yahoo.fr', 'yahoo.com', 'laposte.fr',
|
||||
'free.fr', 'orange.fr', 'numericable.fr'],
|
||||
definition='List of suggested email domains'),
|
||||
A2_LOGIN_FAILURE_COUNT_BEFORE_WARNING=Setting(
|
||||
default=0,
|
||||
definition='Failure count before logging a warning to '
|
||||
|
|
|
@ -25,7 +25,8 @@ from django.core.files import File
|
|||
from authentic2 import app_settings
|
||||
from authentic2.passwords import password_help_text, validate_password
|
||||
from authentic2.forms.widgets import (PasswordInput, NewPasswordInput,
|
||||
CheckPasswordInput, ProfileImageInput)
|
||||
CheckPasswordInput, ProfileImageInput,
|
||||
EmailInput)
|
||||
from authentic2.validators import email_validator
|
||||
|
||||
import PIL.Image
|
||||
|
@ -116,3 +117,4 @@ class ProfileImageField(FileField):
|
|||
|
||||
class ValidatedEmailField(EmailField):
|
||||
default_validators = [email_validator]
|
||||
widget = EmailInput
|
||||
|
|
|
@ -29,9 +29,10 @@ import uuid
|
|||
import datetime
|
||||
|
||||
import django
|
||||
from django import forms
|
||||
from django.forms.widgets import DateTimeInput, DateInput, TimeInput, ClearableFileInput
|
||||
from django.forms.widgets import PasswordInput as BasePasswordInput
|
||||
from django.forms.widgets import TextInput
|
||||
from django.forms.widgets import TextInput, EmailInput as BaseEmailInput
|
||||
from django.utils.formats import get_language, get_format
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -337,3 +338,24 @@ class DatalistTextInput(TextInput):
|
|||
|
||||
class PhoneNumberInput(TextInput):
|
||||
input_type = 'tel'
|
||||
|
||||
|
||||
class EmailInput(BaseEmailInput):
|
||||
|
||||
template_name = 'authentic2/widgets/email.html'
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
if app_settings.A2_SUGGESTED_EMAIL_DOMAINS:
|
||||
return forms.Media(
|
||||
js = (
|
||||
xstatic('jquery', 'jquery.min.js'),
|
||||
'authentic2/js/email_domains_suggestions.js',
|
||||
)
|
||||
)
|
||||
def get_context(self, *args, **kwargs):
|
||||
context = super(EmailInput, self).get_context(*args, **kwargs)
|
||||
if app_settings.A2_SUGGESTED_EMAIL_DOMAINS:
|
||||
context['widget']['attrs']['data-suggested-domains'] = ':'.join(app_settings.A2_SUGGESTED_EMAIL_DOMAINS)
|
||||
context['domains_suggested'] = True
|
||||
return context
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
String.prototype.similarity = function(string) {
|
||||
// adapted from https://github.com/jordanthomas/jaro-winkler (licensed as MIT)
|
||||
var s1 = this, s2 = string;
|
||||
var m = 0;
|
||||
var i;
|
||||
var j;
|
||||
// Exit early if either are empty.
|
||||
if (s1.length === 0 || s2.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert to upper
|
||||
s1 = s1.toUpperCase();
|
||||
s2 = s2.toUpperCase();
|
||||
// Exit early if they're an exact match.
|
||||
if (s1 === s2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var range = (Math.floor(Math.max(s1.length, s2.length) / 2)) - 1;
|
||||
var s1Matches = new Array(s1.length);
|
||||
var s2Matches = new Array(s2.length);
|
||||
for (i = 0; i < s1.length; i++) {
|
||||
var low = (i >= range) ? i - range : 0;
|
||||
var high = (i + range <= (s2.length - 1)) ? (i + range) : (s2.length - 1);
|
||||
for (j = low; j <= high; j++) {
|
||||
if (s1Matches[i] !== true && s2Matches[j] !== true && s1[i] === s2[j]) {
|
||||
++m;
|
||||
s1Matches[i] = s2Matches[j] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit early if no matches were found.
|
||||
if (m === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Count the transpositions.
|
||||
var k = 0;
|
||||
var numTrans = 0;
|
||||
for (i = 0; i < s1.length; i++) {
|
||||
if (s1Matches[i] === true) {
|
||||
for (j = k; j < s2.length; j++) {
|
||||
if (s2Matches[j] === true) {
|
||||
k = j + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s1[i] !== s2[j]) {
|
||||
++numTrans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var weight = (m / s1.length + m / s2.length + (m - (numTrans / 2)) / m) / 3;
|
||||
var l = 0;
|
||||
var p = 0.1;
|
||||
if (weight > 0.7) {
|
||||
while (s1[l] === s2[l] && l < 4) {
|
||||
++l;
|
||||
}
|
||||
weight = weight + l * p * (1 - weight);
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('input[type=email]').on('change', function() {
|
||||
var $email_input = $(this);
|
||||
var suggested_domains = $(this).data('suggested-domains').split(':');
|
||||
var val = $email_input.val();
|
||||
var val_domain = val.split('@')[1];
|
||||
var highest_ratio = 0;
|
||||
var suggestion = null;
|
||||
for (var i=0; i < suggested_domains.length; i++) {
|
||||
var domain = suggested_domains[i];
|
||||
var ratio = val_domain.similarity(domain);
|
||||
if (ratio > highest_ratio) {
|
||||
highest_ratio = ratio;
|
||||
suggestion = domain;
|
||||
}
|
||||
}
|
||||
if (highest_ratio > 0.80 && highest_ratio < 1) {
|
||||
$domain_hint_div = $('div.field-live-hint')
|
||||
$domain_hint_div.find('button.action').on('click', function() {
|
||||
$email_input.val($email_input.val().replace(/@.*/, '@' + $(this).data('suggestion')));
|
||||
$email_input.trigger('change');
|
||||
$domain_hint_div.hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
$domain_hint_div.find('button.close').on('click', function() {
|
||||
$domain_hint_div.hide();
|
||||
return false;
|
||||
});
|
||||
$domain_hint_div.find('> span').text($domain_hint_div.find('> span').data('hint-prefix') + ' @' + suggestion + ' ?');
|
||||
$domain_hint_div.find('button.action').data('suggestion', suggestion);
|
||||
$domain_hint_div.show();
|
||||
} else if ($domain_hint_div) {
|
||||
$domain_hint_div.hide();
|
||||
}
|
||||
});
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
{% load i18n %}
|
||||
{% include "django/forms/widgets/email.html" %}
|
||||
{% if domains_suggested %}
|
||||
<div class="field-live-hint" style="display: none">
|
||||
<span class="message" data-hint-prefix="{% trans "Did you want to write" %}"></span>
|
||||
<button class="action">{% trans "Apply fix" %}</button>
|
||||
<button class="close">
|
||||
<span class="sr-only">x</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
|
@ -666,3 +666,16 @@ def test_authentication_method(app, db, rf, hooks):
|
|||
assert hooks.calls['event'][-2]['kwargs']['authentication_method'] == 'another'
|
||||
assert hooks.calls['event'][-1]['kwargs']['name'] == 'login'
|
||||
assert hooks.calls['event'][-1]['kwargs']['how'] == 'another'
|
||||
|
||||
|
||||
def test_registration_with_email_suggestions(app, db, settings):
|
||||
url = utils.make_url('registration_register')
|
||||
response = app.get(url)
|
||||
assert "email_domains_suggestions.js" in response.text
|
||||
assert "field-live-hint" in response.text
|
||||
|
||||
|
||||
settings.A2_SUGGESTED_EMAIL_DOMAINS = []
|
||||
response = app.get(url)
|
||||
assert "email_domains_suggestions.js" not in response.text
|
||||
assert "field-live-hint" not in response.text
|
||||
|
|
Loading…
Reference in New Issue