diff --git a/accounts/migrations/0002_auto_20180226_1532.py b/accounts/migrations/0002_auto_20180226_1532.py new file mode 100644 index 0000000..fe568f1 --- /dev/null +++ b/accounts/migrations/0002_auto_20180226_1532.py @@ -0,0 +1,31 @@ +# Generated by Django 2.0 on 2018-02-26 15:32 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='EmailConfirmation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.CharField(max_length=256)), + ('key', models.CharField(max_length=256)), + ('expires', models.DateTimeField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='account', + name='is_email_confirmed', + field=models.BooleanField(default=False), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 08cd51d..a1a6e82 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -2,6 +2,8 @@ from django.db import models from django.contrib.sites.models import Site from django.contrib.auth.models import User, Group, AnonymousUser from django.utils.translation import ugettext_lazy as _ +from django.utils.crypto import get_random_string +from django.conf import settings import pytz import datetime @@ -12,12 +14,35 @@ class Account(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) acctname = models.CharField(_("Account Name"), max_length=150, blank=True) + is_email_confirmed = models.BooleanField(default=False) badges = models.ManyToManyField('Badge', through='BadgeGrant') class Meta: ordering = ('user__username',) + def new_confirmation_request(self): + valid_for = getattr(settings, 'EMAIL_CONFIRMAION_EXPIRATION_DAYS', 5) + confirmation_key=get_random_string(length=32) + return EmailConfirmation.objects.create( + user=self.user, + email=self.user.email, + key=confirmation_key, + expires=datetime.datetime.now()+datetime.timedelta(days=valid_for) + ) + + def confirm_email(self, confirmation_key): + try: + confirmation_request = EmailConfirmation.objects.get(user=self.user, email=self.user.email, key=confirmation_key, expires__gt=datetime.datetime.now()) + if confirmation_request is not None: + self.is_email_confirmed = True + self.save() + confirmation_request.delete() + return True + except Exception as e: + print(e) + return False + def __str__(self): try: if self.acctname: @@ -50,6 +75,12 @@ def _getAnonAccount(self): User.account = property(_getUserAccount) AnonymousUser.account = property(_getAnonAccount) +class EmailConfirmation(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + email = models.CharField(max_length=256) + key = models.CharField(max_length=256) + expires = models.DateTimeField() + class Badge(models.Model): name = models.CharField(_('Badge Name'), max_length=64, blank=False, null=False) img_url = models.URLField(_('Badge Image'), blank=False, null=False) diff --git a/events/forms.py b/events/forms.py index 8a61d2c..e4e867b 100644 --- a/events/forms.py +++ b/events/forms.py @@ -215,7 +215,18 @@ class UserForm(forms.ModelForm): class UserProfileForm(forms.ModelForm): class Meta: model = UserProfile - fields = ['realname', 'avatar'] + fields = ['realname', 'avatar', 'send_notifications'] + labels = { + 'send_notifications': _('Send me notification emails'), + } + +class SendNotificationsForm(forms.ModelForm): + class Meta: + model = UserProfile + fields = ['send_notifications'] + labels = { + 'send_notifications': _('Send me notification emails'), + } class SearchForm(forms.Form): city = forms.IntegerField(required=False, widget=Lookup(source='/api/cities/', label='name')) diff --git a/events/migrations/0010_userprofile_send_notifications.py b/events/migrations/0010_userprofile_send_notifications.py new file mode 100644 index 0000000..6728403 --- /dev/null +++ b/events/migrations/0010_userprofile_send_notifications.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0 on 2018-02-26 15:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0009_auto_20180224_0556'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='send_notifications', + field=models.BooleanField(default=True, verbose_name='Send notification emails'), + ), + ] diff --git a/events/models/profiles.py b/events/models/profiles.py index 9bb2f2f..932917b 100644 --- a/events/models/profiles.py +++ b/events/models/profiles.py @@ -13,7 +13,7 @@ class UserProfile(models.Model): " Store profile information about a user " user = models.OneToOneField(User, on_delete=models.CASCADE) - realname = models.CharField(_("Real Name"), max_length=150, blank=True) + realname = models.CharField(verbose_name=_("Real Name"), max_length=150, blank=True) tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=[(tz, tz) for tz in pytz.all_timezones], blank=False, null=False, help_text=_('The most commonly used timezone for this User.')) avatar = models.URLField(verbose_name=_("Photo Image"), max_length=150, blank=True, null=True) @@ -21,6 +21,8 @@ class UserProfile(models.Model): twitter = models.CharField(verbose_name=_('Twitter Name'), max_length=32, blank=True, null=True) facebook = models.URLField(verbose_name=_('Facebook URL'), max_length=32, blank=True, null=True) + send_notifications = models.BooleanField(verbose_name=_('Send notification emails'), default=True) + class Meta: ordering = ('user__username',) diff --git a/get_together/templates/get_together/emails/base.html b/get_together/templates/get_together/emails/base.html new file mode 100644 index 0000000..8d9100e --- /dev/null +++ b/get_together/templates/get_together/emails/base.html @@ -0,0 +1,29 @@ +{% load static %} + + +
+ + + +Please confirm this email address with GetTogether.Community by clicking on the link below:
+ + + +This is an automated email sent by https://gettogether.community
+Learn more at https://github.com/GetTogetherComm/GetTogether/
+{% endblock %} diff --git a/get_together/templates/get_together/emails/confirm_email.txt b/get_together/templates/get_together/emails/confirm_email.txt new file mode 100644 index 0000000..f8891a3 --- /dev/null +++ b/get_together/templates/get_together/emails/confirm_email.txt @@ -0,0 +1,9 @@ +== Request Email Confirmation == + +Please confirm this email address with GetTogether.Community by clicking on the link below: + +Confirm email: {{confirmation_url}} + +-- +This is an automated email sent by https://gettogether.community +Learn more at https://github.com/GetTogetherComm/GetTogether/ diff --git a/get_together/templates/get_together/users/bad_email_confirmation.html b/get_together/templates/get_together/users/bad_email_confirmation.html new file mode 100644 index 0000000..6ee3c41 --- /dev/null +++ b/get_together/templates/get_together/users/bad_email_confirmation.html @@ -0,0 +1,16 @@ +{% extends "get_together/base.html" %} + +{% block content %} +The confirmation link is invalid. Either it does not below to {{request.user.email}} or the confirmation key has expired.
+ + +GetTogether can send notifications to {{request.user.email}} about your upcoming events or new events for teams you are a member of.
+Please confirm whether or not you would like to receive these notifcation emails.
+ ++
+ +You will not be able to receive email notifications from GetTogether until you confirm that this address belongs to you.
+ +An email has been sent to {{request.user.email}}. Click the link it contains to confirm that you have access to this email address.
+ +This confirmation request will expire on {{confirmation.expires}}
+