Skip to content

Commit

Permalink
Merge pull request #228 from euphorie/scrum-1621-mailing-lists-langua…
Browse files Browse the repository at this point in the history
…ge-specific

Mailing lists: Language specific lists for countries
  • Loading branch information
reinhardt authored Jan 25, 2024
2 parents 98b6ec9 + 5ef1b76 commit 7c3db72
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 13 deletions.
3 changes: 3 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Changelog
9.0.6 (unreleased)
------------------

- Mailing lists: Language specific lists for countries
(`#1621 <https://github.com/syslabcom/scrum/issues/1621>`_)
[reinhardt]
- Mailing lists: Allow searching for path. Show path in result.
(`#1638 <https://github.com/syslabcom/scrum/issues/1638>`_)
[reinhardt]
Expand Down
2 changes: 2 additions & 0 deletions src/osha/oira/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Make sure sqlalchemy subscribers are initialized
from .subscribers import update_user_languages_subscriber # noqa: F401
82 changes: 69 additions & 13 deletions src/osha/oira/client/browser/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
from os import path
from osha.oira import _
from osha.oira.client.interfaces import IOSHAClientSkinLayer
from osha.oira.client.model import NewsletterSetting
from osha.oira.client.model import NewsletterSubscription
from plone import api
from plone.memoize.view import memoize
from plone.scale.scale import scaleImage
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
Expand Down Expand Up @@ -88,12 +90,51 @@ def _get_entry(self, list_id, title):
"text": f"{title} ({list_id})" if "/" in list_id else title,
}

@property
@memoize
def client_path(self):
return "/".join(self.context.getPhysicalPath())

def _get_mailing_lists_for(self, brain):
if brain.portal_type != "euphorie.clientcountry":
return [
self._get_entry(
path.relpath(brain.getPath(), self.client_path), brain.Title
)
]
languages = set()
tools = api.content.find(
path=brain.getPath(),
portal_type="euphorie.survey",
review_state="published",
)
# No filter for non-obsolete -
# if we get unexpected languages we can try adding that
for tool in tools:
language = tool.Language
if language:
if "-" in language:
language = language.split("-")[0]
if language and language != "None":
languages.add(language)

return [
self._get_entry(
"-".join((brain.getId, language)), f"{brain.Title} ({language})"
)
for language in languages
]

@property
def results(self):
"""List of "mailing list" path/names.
The format fits pat-autosuggest. There is a special label for
the "all" list.
The format fits pat-autosuggest.
The mailing list IDs are relative paths.
Countries have one mailing list per language.
The language is appended with a '-', e.g. "be-fr".
There is a special ID for the "all users" list.
"""
user_id = self.request.get("user_id")

Expand Down Expand Up @@ -155,17 +196,12 @@ def filter_items(brain):

filtered_brains = filter(filter_items, brains)

client_path = "/".join(self.context.getPhysicalPath())
cnt = len(results)
for brain in filtered_brains:
if cnt > 10:
break
cnt += 1
results.append(
self._get_entry(
path.relpath(brain.getPath(), client_path), brain.Title
)
)
results.extend(self._get_mailing_lists_for(brain))

return results

Expand Down Expand Up @@ -240,13 +276,32 @@ def get_token(self):
return token

def get_addresses_for_groups(self, group_paths):
subscribers = (
subscribers = []
other = []
for group_id in group_paths:
if "/" in group_id or "-" not in group_id:
other.append(group_id)
continue
# Special handling for language specific mailing lists, e.g. "be-fr"
country, lang = group_id.split("-")
country_subscribers = (
Session.query(Account.loginname)
.filter(Account.id == NewsletterSubscription.account_id)
.filter(NewsletterSubscription.zodb_path == (country))
.filter(Account.id == NewsletterSetting.account_id)
.filter(NewsletterSetting.value == f"language:{lang}")
.group_by(Account.loginname)
)
subscribers.extend([s.loginname for s in country_subscribers])

other_subscribers = (
Session.query(Account.loginname)
.filter(Account.id == NewsletterSubscription.account_id)
.filter(NewsletterSubscription.zodb_path.in_(group_paths))
.filter(NewsletterSubscription.zodb_path.in_(other))
.group_by(Account.loginname)
)
return [s.loginname for s in subscribers]
subscribers.extend([s.loginname for s in other_subscribers])
return subscribers

@property
def results(self):
Expand All @@ -259,8 +314,9 @@ def __call__(self):
"""Json list of email addresses subscribed to given group path
(parameter `group`).
Group paths are relative to the client, i.e. only ids ("fr") for
countries.
Group paths are relative to the client, e.g. "be/agriculture/agriculture".
For countries the ID is combined with a language, e.g. "be-fr".
See also `MailingListsJson`
"""
token = self.request.get("token", "")
if token != self.get_token():
Expand Down
44 changes: 44 additions & 0 deletions src/osha/oira/client/subscribers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from euphorie.client.model import get_current_account
from euphorie.client.model import SurveySession
from osha.oira.client.model import NewsletterSetting
from plone import api
from sqlalchemy import event
from z3c.saconfig import Session

import logging


logger = logging.getLogger(__name__)


def update_user_languages(account_id, tool):
if not tool:
return
if not tool.language:
logger.info("No language for tool %s", "/".join(tool.getPhysicalPath()))
return
language = tool.language.split("-")[0]
if (
Session.query(NewsletterSetting.value)
.filter(NewsletterSetting.account_id == account_id)
.filter(NewsletterSetting.value == f"language:{language}")
).count() == 0:
Session.add(
NewsletterSetting(
account_id=account_id,
value=f"language:{language}",
)
)


@event.listens_for(SurveySession, "init")
def update_user_languages_subscriber(target, args, kwargs):
if "zodb_path" not in kwargs:
return
# kwargs["account_id"] can refer to the account of the session being cloned
account = get_current_account()
if not account:
return
client = api.portal.get().client
tool = client.restrictedTraverse(str(kwargs["zodb_path"]), None)
update_user_languages(account.id, tool)
101 changes: 101 additions & 0 deletions src/osha/oira/client/tests/test_mailing_lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from euphorie.client.interfaces import IClientSkinLayer
from euphorie.client.model import Account
from euphorie.client.model import SurveySession
from euphorie.client.tests.utils import addSurvey
from euphorie.content.tests.utils import BASIC_SURVEY
from euphorie.testing import EuphorieIntegrationTestCase
from osha.oira.client.browser.client import GroupToAddresses
from osha.oira.client.browser.client import MailingListsJson
from osha.oira.client.model import NewsletterSubscription
from plone import api
from z3c.saconfig import Session
from zope.interface import alsoProvides


class TestMailingLists(EuphorieIntegrationTestCase):
def setUp(self):
super().setUp()
survey = """<sector xmlns="http://xml.simplon.biz/euphorie/survey/1.0">
<title>Test</title>
<survey>
<title>Second Survey</title>
<language>fr</language>
</survey>
</sector>"""
with api.env.adopt_user("admin"):
addSurvey(self.portal, BASIC_SURVEY)
addSurvey(self.portal, survey)
self.portal.client.nl.ict["software-development"].reindexObject()
self.portal.client.nl.test["second-survey"].reindexObject()

def test_mailing_lists(self):
request = self.request.clone()
request.form = {"user_id": "admin"}
view = MailingListsJson(context=self.portal.client, request=request)
results = view.results
self.assertIn({"id": "general|QWxsIHVzZXJz", "text": "All users"}, results)
self.assertIn(
{
"id": "nl-nl|VGhlIE5ldGhlcmxhbmRzIChubCk=",
"text": "The Netherlands (nl)",
},
results,
)
self.assertIn(
{
"id": "nl-fr|VGhlIE5ldGhlcmxhbmRzIChmcik=",
"text": "The Netherlands (fr)",
},
results,
)
self.assertIn(
{
"id": "nl/ict/software-development|U29mdHdhcmUgZGV2ZWxvcG1lbnQ=",
"text": "Software development (nl/ict/software-development)",
},
results,
)

def test_group_to_addresses(self):
user = Account(loginname="[email protected]", password="secret")
Session.add(user)
alsoProvides(self.request, IClientSkinLayer)
with api.env.adopt_user("[email protected]"):
survey_session = SurveySession(
id=1,
title="Software",
zodb_path="nl/ict/software-development",
account=user,
)
Session.add(survey_session)
Session.add(
NewsletterSubscription(
account_id=user.id,
zodb_path="nl",
)
)
Session.add(
NewsletterSubscription(
account_id=user.id,
zodb_path="nl/ict/software-development",
)
)
Session.flush()

request = self.request.clone()
request.form = {"groups": "nl-nl"}
view = GroupToAddresses(context=self.portal.client, request=request)
results = view.results
self.assertIn("[email protected]", results)

request = self.request.clone()
request.form = {"groups": "nl-fr"}
view = GroupToAddresses(context=self.portal.client, request=request)
results = view.results
self.assertNotIn("[email protected]", results)

request = self.request.clone()
request.form = {"groups": "nl/ict/software-development"}
view = GroupToAddresses(context=self.portal.client, request=request)
results = view.results
self.assertIn("[email protected]", results)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from collections import defaultdict
from euphorie.client.model import Account
from euphorie.client.model import SurveySession
from ftw.upgrade import UpgradeStep
from osha.oira.client.subscribers import update_user_languages
from plone import api
from plone.memoize.view import memoize
from z3c.saconfig import Session

import logging


logger = logging.getLogger(__name__)


class SetNewsletterLanguagesPerUser(UpgradeStep):
"""Set newsletter languages per user."""

@property
@memoize
def client(self):
return api.portal.get().client

@memoize
def get_tool(self, zodb_path):
tool = self.client.restrictedTraverse(zodb_path, None)
if not tool:
return None
if not tool.language:
logger.info("No language for tool %s", zodb_path)
return None
return tool

def __call__(self):
sessions = (
Session.query(SurveySession, Account)
.filter(SurveySession.account_id == Account.id)
.filter(Account.account_type != "guest")
.filter(SurveySession.archived.is_(None))
)
done = defaultdict(list)
for session, account in sessions:
if session.zodb_path in done[account.id]:
continue
tool = self.get_tool(session.zodb_path)
if not tool:
continue
update_user_languages(account.id, tool)
done[account.id].append(session.zodb_path)

0 comments on commit 7c3db72

Please sign in to comment.