Verified Commit 7fc54a3d authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Add Groups Settings API support

parent 778717d3
...@@ -32,6 +32,14 @@ mailinglists.apps module ...@@ -32,6 +32,14 @@ mailinglists.apps module
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
mailinglists.gsuite module
--------------------------
.. automodule:: mailinglists.gsuite
:members:
:undoc-members:
:show-inheritance:
mailinglists.models module mailinglists.models module
-------------------------- --------------------------
...@@ -48,3 +56,11 @@ mailinglists.services module ...@@ -48,3 +56,11 @@ mailinglists.services module
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
mailinglists.signals module
---------------------------
.. automodule:: mailinglists.signals
:members:
:undoc-members:
:show-inheritance:
...@@ -43,6 +43,14 @@ utils.exception\_filter module ...@@ -43,6 +43,14 @@ utils.exception\_filter module
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
utils.google\_api module
------------------------
.. automodule:: utils.google_api
:members:
:undoc-members:
:show-inheritance:
utils.snippets module utils.snippets module
--------------------- ---------------------
......
"""GSuite syncing helpers defined by the mailinglists package""" """GSuite syncing helpers defined by the mailinglists package"""
import threading
from time import sleep from time import sleep
from django.conf import settings
from django.utils.datastructures import ImmutableList from django.utils.datastructures import ImmutableList
from googleapiclient.errors import HttpError from googleapiclient.errors import HttpError
from mailinglists.models import MailingList from mailinglists.models import MailingList
from mailinglists.services import get_automatic_lists from mailinglists.services import get_automatic_lists
from utils.google_api import get_directory_api from utils.google_api import get_directory_api, get_groups_settings_api
from django.conf import settings
class GroupData: class GroupData:
def __init__(self, name, description, moderated=False, archived=False, def __init__(self, name, description='', moderated=False, archived=False,
prefix='', aliases=ImmutableList([]), prefix='', aliases=ImmutableList([]),
addresses=ImmutableList([])): addresses=ImmutableList([])):
super().__init__() super().__init__()
...@@ -31,6 +31,7 @@ def create_group(group): ...@@ -31,6 +31,7 @@ def create_group(group):
Create a new group based on the provided data Create a new group based on the provided data
:param group: group data :param group: group data
""" """
groups_settings_api = get_groups_settings_api()
directory_api = get_directory_api() directory_api = get_directory_api()
directory_api.groups().insert( directory_api.groups().insert(
body={ body={
...@@ -43,7 +44,30 @@ def create_group(group): ...@@ -43,7 +44,30 @@ def create_group(group):
# Docs say we need to wait a minute, but since we always update lists # Docs say we need to wait a minute, but since we always update lists
# an error in the list members update is not a problem # an error in the list members update is not a problem
sleep(0.5) sleep(0.5)
update_group_members(group) groups_settings_api.groups().update(
groupUniqueId=f'{group.name}@{settings.GSUITE_DOMAIN}',
body={
"allowExternalMembers": "true",
"allowWebPosting": "false",
"isArchived": str(group.archived).lower(),
"membersCanPostAsTheGroup": "false",
"messageModerationLevel": "MODERATE_ALL_MESSAGES"
if group.moderated else "MODERATE_NONE",
"replyTo": "REPLY_TO_LIST",
"whoCanAssistContent": "NONE",
"whoCanContactOwner": "ALL_MANAGERS_CAN_CONTACT",
"whoCanDiscoverGroup": "ALL_MEMBERS_CAN_DISCOVER",
"whoCanJoin": "INVITED_CAN_JOIN",
"whoCanLeaveGroup": "NONE_CAN_LEAVE",
"whoCanModerateContent": "OWNERS_AND_MANAGERS",
"whoCanModerateMembers": "NONE",
"whoCanPostMessage": "ANYONE_CAN_POST",
"whoCanViewGroup": "ALL_MANAGERS_CAN_VIEW",
"whoCanViewMembership": "ALL_MANAGERS_CAN_VIEW"
}
).execute()
_update_group_members(group)
_update_group_aliases(group)
def update_group(old_name, group): def update_group(old_name, group):
...@@ -52,6 +76,7 @@ def update_group(old_name, group): ...@@ -52,6 +76,7 @@ def update_group(old_name, group):
:param old_name: old group name :param old_name: old group name
:param group: new group data :param group: new group data
""" """
groups_settings_api = get_groups_settings_api()
directory_api = get_directory_api() directory_api = get_directory_api()
directory_api.groups().update( directory_api.groups().update(
groupKey=f'{old_name}@{settings.GSUITE_DOMAIN}', groupKey=f'{old_name}@{settings.GSUITE_DOMAIN}',
...@@ -61,11 +86,19 @@ def update_group(old_name, group): ...@@ -61,11 +86,19 @@ def update_group(old_name, group):
'description': group.description, 'description': group.description,
} }
) )
update_group_aliases(group) groups_settings_api.groups().patch(
update_group_members(group) groupUniqueId=f'{group.name}@{settings.GSUITE_DOMAIN}',
body={
"isArchived": str(group.archived).lower(),
"messageModerationLevel": "MODERATE_ALL_MESSAGES"
if group.moderated else "MODERATE_NONE",
}
).execute()
_update_group_aliases(group)
_update_group_members(group)
def update_group_aliases(group: GroupData): def _update_group_aliases(group: GroupData):
""" """
Update the aliases of a group based on existing values Update the aliases of a group based on existing values
:param group: group data :param group: group data
...@@ -103,18 +136,18 @@ def update_group_aliases(group: GroupData): ...@@ -103,18 +136,18 @@ def update_group_aliases(group: GroupData):
pass # Ignore error, API returned failing value pass # Ignore error, API returned failing value
def delete_group(group: GroupData): def delete_group(name: str):
""" """
Removes the specified list from Removes the specified list
:param group: group data :param name: Group name
""" """
directory_api = get_directory_api() directory_api = get_directory_api()
directory_api.groups().delete( directory_api.groups().delete(
groupKey=f'{group.name}@{settings.GSUITE_DOMAIN}', groupKey=f'{name}@{settings.GSUITE_DOMAIN}',
).execute() ).execute()
def update_group_members(group: GroupData): def _update_group_members(group: GroupData):
""" """
Update the group members of the specified group based Update the group members of the specified group based
on the existing members on the existing members
...@@ -133,13 +166,17 @@ def update_group_members(group: GroupData): ...@@ -133,13 +166,17 @@ def update_group_members(group: GroupData):
).execute() ).execute()
members_list += members_response.get('members', []) members_list += members_response.get('members', [])
existing_members = [m['email'] for m in members_list] existing_members = [m['email'] for m in members_list
if m['role'] == 'MEMBER']
existing_managers = [m['email'] for m in members_list
if m['role'] == 'MANAGER']
except HttpError: except HttpError:
return # the list does not exist or something else is wrong return # the list does not exist or something else is wrong
new_members = list(group.addresses) new_members = list(group.addresses)
remove_list = [x for x in existing_members if x not in new_members] remove_list = [x for x in existing_members if x not in new_members]
insert_list = [x for x in new_members if x not in existing_members] insert_list = [x for x in new_members if x not in existing_members
and x not in existing_managers]
for remove_member in remove_list: for remove_member in remove_list:
try: try:
...@@ -224,10 +261,25 @@ def sync_mailinglists(lists=None): ...@@ -224,10 +261,25 @@ def sync_mailinglists(lists=None):
remove_list = [x for x in existing_groups if x not in new_groups] remove_list = [x for x in existing_groups if x not in new_groups]
insert_list = [x for x in new_groups if x not in existing_groups] insert_list = [x for x in new_groups if x not in existing_groups]
threads = []
for l in lists: for l in lists:
if l.name in remove_list: if l.name in insert_list:
delete_group(l) thread = threading.Thread(target=create_group,
elif l.name in insert_list: args=(l,))
create_group(l) threads.append(thread)
thread.start()
else: else:
update_group(l.name, l) thread = threading.Thread(target=update_group,
args=(l.name, l))
threads.append(thread)
thread.start()
for l in remove_list:
thread = threading.Thread(target=delete_group,
args=(l,))
threads.append(thread)
thread.start()
for th in threads:
th.join()
...@@ -40,42 +40,41 @@ def get_automatic_lists(): ...@@ -40,42 +40,41 @@ def get_automatic_lists():
if 0 < timezone.now().month < 9: if 0 < timezone.now().month < 9:
lectureyear += 1 lectureyear += 1
active_mentorships = Mentorship.objects.filter( active_mentorships = Mentorship.objects.filter(
year=lectureyear) year=lectureyear).prefetch_related('member')
mentors = [x.member for x in active_mentorships] mentors = [x.member for x in active_mentorships]
lists = [] lists = []
lists += _create_automatic_list( lists += _create_automatic_list(
['members', 'leden'], '[THALIA]', ['members', 'leden'], '[THALIA]',
Member.all_with_membership('member'), True, True, True) Member.all_with_membership('member'), '', True, True, True)
lists += _create_automatic_list( lists += _create_automatic_list(
['benefactors', 'begunstigers'], ['benefactors', 'begunstigers'],
'[THALIA]', '[THALIA]',
Member.all_with_membership(Membership.BENEFACTOR), Member.all_with_membership(Membership.BENEFACTOR), '',
multilingual=True) multilingual=True)
lists += _create_automatic_list( lists += _create_automatic_list(
['honorary', 'ereleden'], '[THALIA]', Member.all_with_membership( ['honorary', 'ereleden'], '[THALIA]', Member.all_with_membership(
'honorary'), multilingual=True) 'honorary'), '', multilingual=True)
lists += _create_automatic_list( lists += _create_automatic_list(
['mentors'], '[THALIA] [MENTORS]', mentors, moderated=False) ['mentors'], '[THALIA] [MENTORS]', mentors, '', moderated=False)
lists += _create_automatic_list( lists += _create_automatic_list(
['activemembers'], '[THALIA] [COMMITTEES]', ['activemembers'], '[THALIA] [COMMITTEES]',
active_members) active_members, '')
lists += _create_automatic_list( lists += _create_automatic_list(
['committeechairs', 'commissievoorzitters'], '[THALIA] [CHAIRS]', ['committeechairs', 'commissievoorzitters'], '[THALIA] [CHAIRS]',
committee_chair_emails, moderated=False) committee_chair_emails, '', moderated=False)
lists += _create_automatic_list( lists += _create_automatic_list(
['societychairs', 'gezelschapvoorzitters'], '[THALIA] [SOCIETY]', ['societychairs', 'gezelschapvoorzitters'], '[THALIA] [SOCIETY]',
society_chair_emails, moderated=False) society_chair_emails, '', moderated=False)
lists += _create_automatic_list( lists += _create_automatic_list(
['optin'], '[THALIA] [OPTIN]', Member.current_members.filter( ['optin'], '[THALIA] [OPTIN]', Member.current_members.filter(
profile__receive_optin=True), profile__receive_optin=True), '', multilingual=True)
multilingual=True)
all_previous_board_members = [] all_previous_board_members = []
for board in Board.objects.filter( for board in Board.objects.filter(
since__year__lte=lectureyear).order_by('since__year'): since__year__lte=lectureyear).order_by('since__year'):
board_members = [board.member for board in board_members = [board.member for board in
MemberGroupMembership.objects.filter MemberGroupMembership.objects.filter
(group=board).prefetch_related('member')] (group=board).prefetch_related('member')]
......
from django.db.models.signals import post_save, post_delete from django.db.models.signals import pre_save, \
pre_delete
from django.dispatch import receiver from django.dispatch import receiver
from googleapiclient.errors import HttpError
from mailinglists.gsuite import mailinglist_to_group, sync_mailinglists from mailinglists.gsuite import (mailinglist_to_group, sync_mailinglists,
update_group, create_group)
from mailinglists.models import MailingList
@receiver(post_save, sender='mailinglists.MailingList') @receiver(pre_save, sender='mailinglists.MailingList')
def post_mailinglist_save(instance, **kwargs): def pre_mailinglist_save(instance, **kwargs):
sync_mailinglists([mailinglist_to_group(instance)]) group = mailinglist_to_group(instance)
old_list = MailingList.objects.filter(pk=instance.pk).first()
try:
if old_list is None:
create_group(group)
else:
update_group(old_list.name, group)
except HttpError:
# Cannot do direct create or update, do full sync for list
sync_mailinglists([group])
@receiver(post_delete, sender='mailinglists.MailingList') @receiver(pre_delete, sender='mailinglists.MailingList')
def post_mailinglist_delete(instance, **kwargs): def pre_mailinglist_delete(instance, **kwargs):
sync_mailinglists([mailinglist_to_group(instance)]) sync_mailinglists([mailinglist_to_group(instance)])
...@@ -43,15 +43,6 @@ class MailingListTest(TestCase): ...@@ -43,15 +43,6 @@ class MailingListTest(TestCase):
mailinglist.name = "activemembers1" mailinglist.name = "activemembers1"
mailinglist.clean() mailinglist.clean()
def test_autoresponse_has_text(self):
self.mailinglist.autoresponse_enabled = True
with self.assertRaises(ValidationError):
self.mailinglist.clean()
self.mailinglist.autoresponse_text = "Hello World"
self.mailinglist.clean()
class ListAliasTest(TestCase): class ListAliasTest(TestCase):
"""Tests list aliases""" """Tests list aliases"""
......
...@@ -43,8 +43,9 @@ if FIREBASE_CREDENTIALS != {}: ...@@ -43,8 +43,9 @@ if FIREBASE_CREDENTIALS != {}:
logger.error('Firebase application failed to initialise') logger.error('Firebase application failed to initialise')
GSUITE_ADMIN_CREDENTIALS = ( if GSUITE_ADMIN_CREDENTIALS != {}:
service_account.Credentials.from_service_account_info( GSUITE_ADMIN_CREDENTIALS = (
GSUITE_ADMIN_CREDENTIALS, scopes=GSUITE_ADMIN_SCOPES service_account.Credentials.from_service_account_info(
).with_subject(GSUITE_ADMIN_USER) GSUITE_ADMIN_CREDENTIALS, scopes=GSUITE_ADMIN_SCOPES
) ).with_subject(GSUITE_ADMIN_USER)
)
...@@ -25,11 +25,9 @@ def get_directory_api(): ...@@ -25,11 +25,9 @@ def get_directory_api():
) )
def get_group_settings_api(): def get_groups_settings_api():
return build( return build(
'groups', 'v1', 'groupssettings', 'v1',
credentials=settings.GSUITE_ADMIN_CREDENTIALS, credentials=settings.GSUITE_ADMIN_CREDENTIALS,
cache=memory_cache cache=memory_cache
) )
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment