Skip to content

Commit

Permalink
Merge pull request #1518 from nextcloud/feat/odoo-mailing-list
Browse files Browse the repository at this point in the history
implemented Odoo integration(newsletter mail list)
  • Loading branch information
oleksandr-nc authored Dec 13, 2024
2 parents d5a50d8 + 31b3308 commit 8d8a00c
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 17 deletions.
8 changes: 8 additions & 0 deletions nextcloudappstore/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,11 @@
NEXTCLOUD_INTEGRATIONS_APPROVAL_EMAILS = ["[email protected]"]

DEFAULT_AUTO_FIELD = "django.db.models.AutoField"


# Do not fill in these values here, use the "development.py" or "production.py" files.
ODOO_URL = ""
ODOO_DB = ""
ODOO_USERNAME = ""
ODOO_PASSWORD = ""
ODOO_MAILING_LIST_ID = 0
31 changes: 30 additions & 1 deletion nextcloudappstore/user/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,33 @@
from django.forms import CharField, EmailField, PasswordInput
from django.utils.translation import gettext_lazy as _

from .odoo import subscribe_user_to_news


class SignupFormRecaptcha(forms.Form):
"""integrate a recaptcha field."""

captcha = CaptchaField()
first_name = CharField(max_length=30, label=_("First name"))
last_name = CharField(max_length=30, label=_("Last name"))
subscribe_to_news = forms.BooleanField(
label=_("I would like to receive app developer news and updates from Nextcloud by email (optional)"),
required=False,
initial=False,
)

def signup(self, request, user):
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
user.save()

# Set the subscription preference on the user's profile
user.profile.subscribe_to_news = self.cleaned_data["subscribe_to_news"]
user.profile.save()

if self.cleaned_data["subscribe_to_news"]:
subscribe_user_to_news(user)


class DeleteAccountForm(forms.Form):
email = EmailField(required=True, label=_("Your email address"))
Expand Down Expand Up @@ -65,10 +79,25 @@ class AccountForm(forms.ModelForm):
"password!"
),
)
subscribe_to_news = forms.BooleanField(
label=_("I would like to receive app developer news and updates from Nextcloud by email (optional)"),
required=False,
)

class Meta:
model = get_user_model()
fields = ("first_name", "last_name", "email")
fields = (
"first_name",
"last_name",
"email",
)

def __init__(self, *args, **kwargs):
self.user = kwargs.get("instance", None)
super().__init__(*args, **kwargs)
# Set initial value of subscribe_to_news based on user's profile
if self.user and hasattr(self.user, "profile"):
self.fields["subscribe_to_news"].initial = self.user.profile.subscribe_to_news

def clean_email(self):
value = self.cleaned_data["email"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand

from nextcloudappstore.user.models import UserProfile


class Command(BaseCommand):
help = "Create missing UserProfile instances for existing users"

def handle(self, *args, **kwargs):
users_without_profiles = User.objects.filter(profile__isnull=True)
for user in users_without_profiles:
UserProfile.objects.create(user=user, subscribe_to_news=False)
self.stdout.write(f"Created profile for user: {user.username}")

self.stdout.write("Finished creating missing profiles.")
35 changes: 35 additions & 0 deletions nextcloudappstore/user/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.16 on 2024-12-06 11:59

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


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="UserProfile",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"subscribe_to_news",
models.BooleanField(
default=True, help_text="User has opted in to receive Nextcloud news and updates."
),
),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE, related_name="profile", to=settings.AUTH_USER_MODEL
),
),
],
),
]
46 changes: 44 additions & 2 deletions nextcloudappstore/user/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
from django.db import models # noqa
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver

# Create your models here.
from .odoo import subscribe_user_to_news, unsubscribe_user_from_news


class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
subscribe_to_news = models.BooleanField(
default=True, help_text="User has opted in to receive Nextcloud news and updates."
)

def __str__(self):
return f"Profile of {self.user.username}"


# Signal to create a profile for each new user
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()


# Detect changes to subscribe_to_news and trigger actions
@receiver(pre_save, sender=UserProfile)
def handle_subscription_change(sender, instance, **kwargs):
if instance.pk: # Ensure this is an update, not a creation
# Fetch the old value of subscribe_to_news
old_value = UserProfile.objects.filter(pk=instance.pk).values_list("subscribe_to_news", flat=True).first()
new_value = instance.subscribe_to_news

if old_value != new_value:
if new_value:
# Logic to subscribe the user
subscribe_user_to_news(instance.user)
else:
# Logic to unsubscribe the user
unsubscribe_user_from_news(instance.user)
162 changes: 162 additions & 0 deletions nextcloudappstore/user/odoo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from defusedxml import xmlrpc
from django.conf import settings

xmlrpc.monkey_patch()

import xmlrpc.client # noqa: E402 # nosec


def authenticate():
common = xmlrpc.client.ServerProxy(f"{settings.ODOO_URL}/xmlrpc/2/common")
uid = common.authenticate(settings.ODOO_DB, settings.ODOO_USERNAME, settings.ODOO_PASSWORD, {})
if not uid:
raise Exception("Authentication failed with Odoo")
return uid


def is_odoo_config_valid():
"""Validate Odoo configuration settings."""
required_fields = [
settings.ODOO_URL,
settings.ODOO_DB,
settings.ODOO_USERNAME,
settings.ODOO_PASSWORD,
settings.ODOO_MAILING_LIST_ID,
]
return all(required_fields)


def subscribe_user_to_news(user):
if not is_odoo_config_valid():
print("Odoo configuration is invalid. Skipping subscription.")
return

uid = authenticate()
models = xmlrpc.client.ServerProxy(f"{settings.ODOO_URL}/xmlrpc/2/object")

# Check if the contact already exists in Odoo
contact_ids = models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact",
"search",
[[("email", "=", user["email"])]],
)

if not contact_ids:
# Create a new contact if it doesn't exist
contact_data = {
"name": user.get("name", user["email"]), # Use name if provided, fallback to email
"email": user["email"],
}
contact_id = models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact",
"create",
[contact_data],
)
print(f"New user created with ID {contact_id}")
else:
contact_id = contact_ids[0]
print(f"User exists with ID {contact_id}")

# Check if the user is subscribed to the specific mailing list
subscription_ids = models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact.subscription",
"search",
[
[
("contact_id", "=", contact_id),
("list_id", "=", settings.ODOO_MAILING_LIST_ID),
]
],
)

if not subscription_ids:
# Subscribe the user to the mailing list
subscription_data = {
"contact_id": contact_id,
"list_id": settings.ODOO_MAILING_LIST_ID,
"opt_out": False,
}
subscription_id = models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact.subscription",
"create",
[subscription_data],
)
print(f"User subscribed to mailing list with subscription ID {subscription_id}")
else:
# Update the existing subscription to ensure opt_out is False
models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact.subscription",
"write",
[subscription_ids, {"opt_out": False}],
)
print(f"User's subscription updated to opt-in for mailing list {settings.ODOO_MAILING_LIST_ID}")


def unsubscribe_user_from_news(user):
if not is_odoo_config_valid():
print("Odoo configuration is invalid. Skipping subscription.")
return
uid = authenticate()
models = xmlrpc.client.ServerProxy(f"{settings.ODOO_URL}/xmlrpc/2/object")

# Find the contact by email
contact_ids = models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact",
"search",
[[("email", "=", user["email"])]],
)

if not contact_ids:
print(f"No contact found for email {user['email']} to unsubscribe")
return

contact_id = contact_ids[0]

# Find the subscription for the specific mailing list
subscription_ids = models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact.subscription",
"search",
[
[
("contact_id", "=", contact_id),
("list_id", "=", settings.ODOO_MAILING_LIST_ID),
]
],
)

if not subscription_ids:
print(f"No subscription found for contact {contact_id} to mailing list {settings.ODOO_MAILING_LIST_ID}")
return

# Update the subscription to set opt_out to True
models.execute_kw(
settings.ODOO_DB,
uid,
settings.ODOO_PASSWORD,
"mailing.contact.subscription",
"write",
[subscription_ids, {"opt_out": True}],
)

print(f"User {user['email']} has been unsubscribed from mailing list {settings.ODOO_MAILING_LIST_ID}")
7 changes: 6 additions & 1 deletion nextcloudappstore/user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def post(self, request, *args, **kwargs):


class AccountView(LoginRequiredMixin, UpdateView):
"""Display and allow changing of the user's name."""
"""Display and allow changing of the user's name and subscription."""

template_name = "user/account.html"
template_name_suffix = ""
Expand All @@ -113,6 +113,11 @@ def form_valid(self, form):
if email.email != form.cleaned_data["email"]:
email.email = form.cleaned_data["email"]
email.save(update_fields=["email"])

# Update subscription preference
self.request.user.profile.subscribe_to_news = form.cleaned_data["subscribe_to_news"]
self.request.user.profile.save()

messages.success(self.request, "Account details saved.")
self.request.session["account_update_failed_count"] = 0
return super().form_valid(form)
Expand Down
Loading

0 comments on commit 8d8a00c

Please sign in to comment.