Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Email Notifications): Ajout du formulaire d'édition des préférences #1595

Merged
merged 4 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions lemarche/conversations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.urls import reverse
from django.utils.html import format_html

from lemarche.conversations.models import Conversation, TemplateTransactional, TemplateTransactionalSendLog
from lemarche.conversations.models import Conversation, EmailGroup, TemplateTransactional, TemplateTransactionalSendLog
from lemarche.utils.admin.admin_site import admin_site
from lemarche.utils.fields import pretty_print_readonly_jsonfield, pretty_print_readonly_jsonfield_to_table
from lemarche.www.conversations.tasks import send_first_email_from_conversation
Expand Down Expand Up @@ -153,7 +153,7 @@ class TemplateTransactionalAdmin(admin.ModelAdmin):
readonly_fields = ["code", "template_transactional_send_log_count_with_link", "created_at", "updated_at"]

fieldsets = (
(None, {"fields": ("name", "code", "description")}),
(None, {"fields": ("name", "code", "description", "group")}),
("Paramètres d'envoi", {"fields": ("mailjet_id", "brevo_id", "source", "is_active")}),
("Stats", {"fields": ("template_transactional_send_log_count_with_link",)}),
("Dates", {"fields": ("created_at", "updated_at")}),
Expand Down Expand Up @@ -234,3 +234,10 @@ def extra_data_display(self, instance: TemplateTransactionalSendLog = None):
return "-"

extra_data_display.short_description = TemplateTransactionalSendLog._meta.get_field("extra_data").verbose_name


@admin.register(EmailGroup, site=admin_site)
class EmailGroupAdmin(admin.ModelAdmin):
list_display = ["id", "relevant_user_kind", "display_name", "description", "can_be_unsubscribed"]
search_fields = ["id", "display_name"]
readonly_fields = []
11 changes: 10 additions & 1 deletion lemarche/conversations/factories.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import factory
from factory.django import DjangoModelFactory

from lemarche.conversations.models import Conversation, TemplateTransactional
from lemarche.conversations.models import Conversation, EmailGroup, TemplateTransactional
from lemarche.siaes.factories import SiaeFactory


Expand All @@ -17,9 +17,18 @@ class Meta:
initial_body_message = factory.Faker("name", locale="fr_FR")


class EmailGroupFactory(DjangoModelFactory):
class Meta:
model = EmailGroup

display_name = factory.Faker("name", locale="fr_FR")
description = factory.Faker("name", locale="fr_FR")


class TemplateTransactionalFactory(DjangoModelFactory):
class Meta:
model = TemplateTransactional

name = factory.Faker("name", locale="fr_FR")
code = factory.Faker("name", locale="fr_FR")
group = factory.SubFactory(EmailGroupFactory)
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Generated by Django 4.2.15 on 2024-12-11 17:02

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


def create_email_groups(apps, schema_editor):
# Get the model
EmailGroup = apps.get_model("conversations", "EmailGroup")

# Create email groups
email_groups = [
{
"id": 1,
"display_name": "Structure(s) intéressée(s)",
"description": "En désactivant cette option, vous ne serez plus averti par email lorsque des fournisseurs s'intéressent à votre besoin, ce qui pourrait vous faire perdre des opportunités de collaboration rapide et efficace.",
"relevant_user_kind": "BUYER",
"can_be_unsubscribed": True,
},
{
"id": 2,
"display_name": "Communication marketing",
"description": "En désactivant cette option, vous ne recevrez plus par email nos newsletters, enquêtes, invitations à des webinaires et Open Labs, ce qui pourrait vous priver d'informations utiles et de moments d'échange exclusifs.",
"relevant_user_kind": "BUYER",
"can_be_unsubscribed": True,
},
{
"id": 3,
"display_name": "Opportunités commerciales",
"description": "En désactivant cette option, vous ne recevrez plus par email les demandes de devis et les appels d'offres spécialement adaptés à votre activité, ce qui pourrait vous faire manquer des opportunités importantes pour votre entreprise.",
"relevant_user_kind": "SIAE",
"can_be_unsubscribed": True,
},
{
"id": 4,
"display_name": "Demandes de mise en relation",
"description": "En désactivant cette option, vous ne recevrez plus par email les demandes de mise en relation de clients intéressés par votre structure, ce qui pourrait vous faire perdre des opportunités précieuses de collaboration et de développement.",
"relevant_user_kind": "SIAE",
"can_be_unsubscribed": True,
},
{
"id": 5,
"display_name": "Communication marketing",
"description": "En désactivant cette option, vous ne recevrez plus par email nos newsletters, enquêtes, invitations aux webinaires et Open Labs, ce qui pourrait vous faire passer à côté d’informations clés, de ressources utiles et d’événements exclusifs.",
"relevant_user_kind": "SIAE",
"can_be_unsubscribed": True,
},
]

for group in email_groups:
EmailGroup.objects.create(**group)


def delete_email_groups(apps, schema_editor):
# Get the model
EmailGroup = apps.get_model("conversations", "EmailGroup")
# Delete all email groups
EmailGroup.objects.all().delete()


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("conversations", "0016_templatetransactionalsendlog"),
]

operations = [
migrations.CreateModel(
name="EmailGroup",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("display_name", models.CharField(blank=True, max_length=255, verbose_name="Nom")),
("description", models.TextField(blank=True, verbose_name="Description")),
(
"relevant_user_kind",
models.CharField(
choices=[
("SIAE", "Structure"),
("BUYER", "Acheteur"),
("PARTNER", "Partenaire"),
("INDIVIDUAL", "Particulier"),
],
default="BUYER",
max_length=20,
verbose_name="Type d'utilisateur",
),
),
(
"can_be_unsubscribed",
models.BooleanField(default=False, verbose_name="L'utilisateur peut s'y désinscrire"),
),
],
),
migrations.CreateModel(
name="DisabledEmail",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("disabled_at", models.DateTimeField(auto_now_add=True)),
(
"group",
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="conversations.emailgroup"),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="disabled_emails",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.AddField(
model_name="templatetransactional",
name="group",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.CASCADE, to="conversations.emailgroup"
),
),
migrations.AddConstraint(
model_name="disabledemail",
constraint=models.UniqueConstraint(models.F("user"), models.F("group"), name="unique_group_per_user"),
),
migrations.RunPython(create_email_groups, delete_email_groups),
]
31 changes: 31 additions & 0 deletions lemarche/conversations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from shortuuid import uuid

from lemarche.conversations import constants as conversation_constants
from lemarche.users import constants as user_constants
from lemarche.utils.apis import api_brevo, api_mailjet
from lemarche.utils.data import add_validation_error

Expand Down Expand Up @@ -200,6 +201,24 @@ def set_validated(self):
self.save()


class EmailGroup(models.Model):
display_name = models.CharField(verbose_name="Nom", max_length=255, blank=True)
description = models.TextField(verbose_name="Description", blank=True)
relevant_user_kind = models.CharField(
verbose_name="Type d'utilisateur",
max_length=20,
choices=user_constants.KIND_CHOICES,
default=user_constants.KIND_BUYER,
)
can_be_unsubscribed = models.BooleanField(verbose_name="L'utilisateur peut s'y désinscrire", default=False)

def __str__(self):
return f"{self.display_name} ({self.relevant_user_kind if self.relevant_user_kind else 'Tous'})"

def disabled_for_user(self, user):
return DisabledEmail.objects.filter(user=user, group=self).exists()


class TemplateTransactionalQuerySet(models.QuerySet):
def with_stats(self):
return self.annotate(
Expand All @@ -213,6 +232,7 @@ class TemplateTransactional(models.Model):
verbose_name="Nom technique", max_length=255, unique=True, db_index=True, blank=True, null=True
)
description = models.TextField(verbose_name="Description", blank=True)
group = models.ForeignKey("EmailGroup", on_delete=models.CASCADE, null=True)

# email_subject = models.CharField(
# verbose_name="E-mail : objet",
Expand Down Expand Up @@ -363,3 +383,14 @@ class TemplateTransactionalSendLog(models.Model):
class Meta:
verbose_name = "Template transactionnel: logs d'envois"
verbose_name_plural = "Templates transactionnels: logs d'envois"


class DisabledEmail(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="disabled_emails")
group = models.ForeignKey("EmailGroup", on_delete=models.CASCADE)
disabled_at = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
models.UniqueConstraint("user", "group", name="unique_group_per_user"),
]
2 changes: 2 additions & 0 deletions lemarche/conversations/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ def test_get_template_id(self):


class TemplateTransactionalModelSaveTest(TransactionTestCase):
reset_sequences = True

@classmethod
def setUpTestData(cls):
pass
Expand Down
62 changes: 62 additions & 0 deletions lemarche/templates/dashboard/disabled_email_edit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{% extends "layouts/base.html" %}
{% load static widget_tweaks dsfr_tags process_dict theme_inclusion %}
{% block page_title %}
Notifications{{ block.super }}
{% endblock page_title %}
{% block breadcrumb %}
{% process_dict root_dir=HOME_PAGE_PATH current="Notifications" as breadcrumb_data %}
{% dsfr_breadcrumb breadcrumb_data %}
{% endblock breadcrumb %}
{% block content %}
<div class="fr-grid-row fr-grid-row-gutters fr-grid-row--center">
<div class="fr-col-12 fr-col-lg-10">
<div class="fr-container fr-px-md-0 fr-py-2v fr-py-md-4v">
<div class="fr-grid-row fr-grid-row-gutters fr-grid-row--center">
<div class="fr-col-12 fr-col-lg-8">
<h1>Notifications</h1>
<div>
<form method="post">
{% csrf_token %}
<fieldset class="fr-fieldset">
<div class="fr-fieldset__element">
{% if form.non_field_errors %}
<section class="fr-my-4v fr-input-group fr-input-group--error">
{{ form.non_field_errors }}
</section>
{% endif %}
<ul class="fr-toggle__list">
{% for group_item in form.group_items %}
{% get_form_field form group_item.field_name as field %}
<li>
<div class="fr-toggle fr-toggle--label-left fr-toggle--border-bottom fr-mt-8v">
{% with aria_describedby="aria-describedby:"|add:field.auto_id|add:"-hint-text" %}
{{ field|dsfr_input_class_attr|attr:"type:checkbox"|attr:aria_describedby|attr:"class:fr-toggle__input" }}
{% endwith %}
<label class="fr-toggle__label"
for="{{ field.id_for_label }}"
data-fr-checked-label="Activé"
data-fr-unchecked-label="Désactivé">
{{ group_item.group.display_name }}
</label>
<p class="fr-hint-text" id="{{ field.id_for_label }}-hint-text">{{ group_item.group.description }}</p>
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="fr-fieldset__element">
<ul class="fr-btns-group--right fr-btns-group fr-btns-group--inline">
<li>
<button class="fr-mt-2v fr-btn fr-btn" type="submit">Sauvegarder</button>
</li>
</ul>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
6 changes: 6 additions & 0 deletions lemarche/utils/templatetags/theme_inclusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Exist also in the base code of C1 :
https://github.com/betagouv/itou/blob/master/itou/utils/templatetags/theme_inclusion.py
"""

from django import template
from django.templatetags.static import static
from django.utils.safestring import mark_safe
Expand Down Expand Up @@ -99,3 +100,8 @@ def import_static_JS_theme_inclusion():
else:
scripts_import += '<script src="{}"></script>'.format(static_theme(js_dep["src"]))
return mark_safe(scripts_import)


@register.simple_tag
def get_form_field(form, field_name):
return form[field_name]
34 changes: 34 additions & 0 deletions lemarche/www/dashboard/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import forms

from lemarche.conversations.models import DisabledEmail, EmailGroup
from lemarche.sectors.models import Sector
from lemarche.users.models import User
from lemarche.utils.fields import GroupedModelMultipleChoiceField
Expand Down Expand Up @@ -32,3 +33,36 @@ def __init__(self, *args, **kwargs):

# Disabled fields
self.fields["email"].disabled = True


class DisabledEmailEditForm(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
self.group_items = []
super().__init__(*args, **kwargs)

disabled_groups = [disable_email.group for disable_email in self.user.disabled_emails.all()]
for email_group in EmailGroup.objects.filter(can_be_unsubscribed=True, relevant_user_kind=self.user.kind):
field_name = f"email_group_{email_group.pk}"
self.fields[field_name] = forms.BooleanField(
required=False,
label=email_group.display_name,
initial=email_group not in disabled_groups,
widget=forms.CheckboxInput(),
)
self.group_items.append({"group": email_group, "field_name": field_name})

def save(self):
disabled_emails = []

# add unchecked fields to disabled_emails
for field_name, value in self.cleaned_data.items():
if field_name.startswith("email_group_"):
if not value:
group = EmailGroup.objects.get(pk=int(field_name.replace("email_group_", "")))
disabled_email, _ = DisabledEmail.objects.get_or_create(user=self.user, group=group)
disabled_emails.append(disabled_email)
self.user.disabled_emails.set(disabled_emails)

# remove old disabled_emails
DisabledEmail.objects.exclude(pk__in=[de.pk for de in disabled_emails]).delete()
Loading
Loading