Notification emails #80

Merged
Oleg-Komarov merged 31 commits from notifications into main 2024-04-18 16:11:20 +02:00
8 changed files with 87 additions and 2 deletions
Showing only changes of commit 4de74cfa0d - Show all commits

View File

@ -26,7 +26,15 @@ class UserAdmin(auth_admin.UserAdmin):
(None, {'fields': ('username', 'password')}), (None, {'fields': ('username', 'password')}),
( (
_('Personal info'), _('Personal info'),
{'fields': ('full_name', 'image', 'email', 'badges')}, {
'fields': (
'full_name',
'image',
'email',
'badges',
'is_subscribed_to_notification_emails',
)
},
), ),
( (
_('Permissions'), _('Permissions'),

5
users/forms.py Normal file
View File

@ -0,0 +1,5 @@
from django import forms
class SubscribeNotificationEmailsForm(forms.Form):
subscribe = forms.BooleanField(widget=forms.HiddenInput(), required=False)

View File

@ -25,7 +25,11 @@ class Command(BaseCommand):
to_send = [] to_send = []
for n in batch: for n in batch:
n.processed_by_mailer_at = timezone.now() n.processed_by_mailer_at = timezone.now()
# TODO check some form of recipient.is_subscribed(n): if not recipient.is_subscribed_to_notification_emails:
continue
# check that email is confirmed to avoid spamming unsuspecting email owners
if recipient.confirmed_email_at is None:
continue
n.sent = True n.sent = True
to_send.append(n) to_send.append(n)
# first mark as processed, then send: avoid spamming in case of a crash-loop # first mark as processed, then send: avoid spamming in case of a crash-loop

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-04-15 12:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_moderators_group'),
]
operations = [
migrations.AddField(
model_name='user',
name='is_subscribed_to_notification_emails',
field=models.BooleanField(default=True),
),
]

View File

@ -36,6 +36,7 @@ class User(TrackChangesMixin, AbstractUser):
'confirmed_email_at', 'confirmed_email_at',
'full_name', 'full_name',
'email', 'email',
'is_subscribed_to_notification_emails',
} }
class Meta: class Meta:
@ -49,6 +50,8 @@ class User(TrackChangesMixin, AbstractUser):
date_deletion_requested = models.DateTimeField(null=True, blank=True) date_deletion_requested = models.DateTimeField(null=True, blank=True)
confirmed_email_at = models.DateTimeField(null=True, blank=True) confirmed_email_at = models.DateTimeField(null=True, blank=True)
is_subscribed_to_notification_emails = models.BooleanField(null=False, default=True)
def __str__(self) -> str: def __str__(self) -> str:
return f'{self.full_name or self.username}' return f'{self.full_name or self.username}'

View File

@ -117,4 +117,25 @@
</div> </div>
</div> </div>
</div> </div>
<h1 class="mb-3 mt-5">Notifications</h1>
<div class="box settings">
<div class="row">
<div class="col-md-6 mb-4">
<form action="{% url 'users:subscribe-notification-emails' %}" method="post">
{% csrf_token %}
{{ subscribe_notification_emails_form }}
{% if user.is_subscribed_to_notification_emails %}
You are subscribed to notification emails.
<button class="btn" type="submit">Unsubscribe</button>
{% if not user.confirmed_email_at %}
<p class="helptext text-warning">Your need to confirm your email to receive notification emails.</p>
{% endif %}
{% else %}
You are not subscribed to notification emails.
<button class="btn" type="submit">Subscribe</button>
{% endif %}
</form>
</div>
</div>
</div>
{% endblock settings %} {% endblock settings %}

View File

@ -11,6 +11,11 @@ urlpatterns = [
include( include(
[ [
path('profile/', settings.ProfileView.as_view(), name='my-profile'), path('profile/', settings.ProfileView.as_view(), name='my-profile'),
path(
'profile/subscribe-notification-emails/',
settings.SubscribeNotificationEmailsView.as_view(),
name='subscribe-notification-emails',
),
path('delete/', settings.DeleteView.as_view(), name='my-profile-delete'), path('delete/', settings.DeleteView.as_view(), name='my-profile-delete'),
] ]
), ),

View File

@ -1,7 +1,11 @@
"""User profile pages.""" """User profile pages."""
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from users.forms import SubscribeNotificationEmailsForm
User = get_user_model() User = get_user_model()
@ -11,8 +15,25 @@ class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'users/settings/profile.html' template_name = 'users/settings/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['subscribe_notification_emails_form'] = SubscribeNotificationEmailsForm(
{'subscribe': not self.request.user.is_subscribed_to_notification_emails},
)
return context
class DeleteView(LoginRequiredMixin, TemplateView): class DeleteView(LoginRequiredMixin, TemplateView):
"""Template view where account deletion can be requested.""" """Template view where account deletion can be requested."""
template_name = 'users/settings/delete.html' template_name = 'users/settings/delete.html'
class SubscribeNotificationEmailsView(LoginRequiredMixin, FormView):
form_class = SubscribeNotificationEmailsForm
success_url = reverse_lazy('users:my-profile')
def form_valid(self, form):
self.request.user.is_subscribed_to_notification_emails = form.cleaned_data['subscribe']
self.request.user.save(update_fields={'is_subscribed_to_notification_emails'})
return super().form_valid(form)