From 97e15cf6e7ece3c4499f632548868b946f9fc794 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Tue, 28 Nov 2023 18:43:56 +0000 Subject: [PATCH] lists: Implement some basic code to talk to Mailman Signed-off-by: Michael Tremer --- Makefile.am | 7 ++ src/backend/accounts.py | 5 ++ src/backend/base.py | 5 ++ src/backend/lists.py | 131 +++++++++++++++++++++++++++++++++ src/templates/lists/index.html | 42 +++++++++++ src/web/__init__.py | 4 + src/web/lists.py | 16 ++++ 7 files changed, 210 insertions(+) create mode 100644 src/backend/lists.py create mode 100644 src/templates/lists/index.html create mode 100644 src/web/lists.py diff --git a/Makefile.am b/Makefile.am index 1ed1a498..92e494ad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -62,6 +62,7 @@ backend_PYTHON = \ src/backend/httpclient.py \ src/backend/hwdata.py \ src/backend/iuse.py \ + src/backend/lists.py \ src/backend/messages.py \ src/backend/mirrors.py \ src/backend/misc.py \ @@ -92,6 +93,7 @@ web_PYTHON = \ src/web/fireinfo.py \ src/web/handlers.py \ src/web/iuse.py \ + src/web/lists.py \ src/web/location.py \ src/web/nopaste.py \ src/web/ui_modules.py \ @@ -256,6 +258,11 @@ templates_location_DATA = \ templates_locationdir = $(templatesdir)/location +templates_lists_DATA = \ + src/templates/lists/index.html + +templates_listsdir = $(templatesdir)/lists + templates_messages_DATA = \ src/templates/messages/base.html \ src/templates/messages/base-promo.html \ diff --git a/src/backend/accounts.py b/src/backend/accounts.py index a9942085..1634e45f 100644 --- a/src/backend/accounts.py +++ b/src/backend/accounts.py @@ -1309,6 +1309,11 @@ class Account(LDAPObject): # Disable the user await user.disable(text) + # Mailman + + async def get_lists(self): + return await self.backend.lists.get_subscribed_lists(self) + class Groups(Object): hidden_groups = ( diff --git a/src/backend/base.py b/src/backend/base.py index f5d267f4..00dcefcb 100644 --- a/src/backend/base.py +++ b/src/backend/base.py @@ -17,6 +17,7 @@ from . import database from . import fireinfo from . import httpclient from . import iuse +from . import lists from . import messages from . import mirrors from . import netboot @@ -176,6 +177,10 @@ class Backend(object): def groups(self): return accounts.Groups(self) + @lazy_property + def lists(self): + return lists.Lists(self) + @lazy_property def messages(self): return messages.Messages(self) diff --git a/src/backend/lists.py b/src/backend/lists.py new file mode 100644 index 00000000..f5d1692a --- /dev/null +++ b/src/backend/lists.py @@ -0,0 +1,131 @@ +#!/usr/bin/python3 + +import json +import urllib.parse + +from . import accounts +from . import misc + +class Lists(misc.Object): + @property + def url(self): + """ + Returns the base URL of a Mailman instance + """ + return self.settings.get("mailman-url") + + @property + def username(self): + return self.settings.get("mailman-username") + + @property + def password(self): + return self.settings.get("mailman-password") + + async def _request(self, method, url, data=None): + headers, body = {}, None + + # URL + url = urllib.parse.urljoin(self.url, url) + + # For GET requests, append query arguments + if method == "GET": + if data: + url = "%s?%s" % (url, urllib.parse.urlencode(data)) + + # For POST/PUT encode all arguments as JSON + elif method in ("POST", "PUT", "PATCH"): + headers |= { + "Content-Type" : "application/json", + } + + body = json.dumps(data) + + # Send the request and wait for a response + res = await self.backend.http_client.fetch(url, method=method, + headers=headers, body=body, + + # Authentication + auth_username=self.username, auth_password=self.password, + ) + + # Decode JSON response + body = json.loads(res.body) + + # XXX handle errors + + return body + + async def _get_lists(self, *args, **kwargs): + lists = [] + + # Fetch the response + response = await self._request(*args, **kwargs) + + # Fetch entries + for entry in response.get("entries", []): + list = List(self.backend, **entry) + lists.append(list) + + return lists + + async def get_lists(self): + """ + Fetches all available lists + """ + data = { + "advertised" : True, + } + + return await self._get_lists("GET", "/3.1/lists", data=data) + + async def get_subscribed_lists(self, account): + data = { + "subscriber" : account.email, + "role" : "member", + } + + return await self._get_lists("GET", "/3.1/members/find", data=data) + + +class List(misc.Object): + def init(self, list_id, **kwargs): + self.list_id = list_id + + # Store all other data + self.data = kwargs + + def __repr__(self): + return "" % self.list_id + + def __str__(self): + return self.display_name + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.list_id == other.list_id + + return NotImplemented + + def __lt__(self, other): + if isinstance(other, self.__class__): + return self.list_id < other.list_id + + return NotImplemented + + def __len__(self): + return self.data.get("member_count") + + @property + def display_name(self): + return self.data.get("display_name") + + @property + def description(self): + return self.data.get("description") + + async def subscribe(self, account): + pass # XXX TODO + + async def unsubscribe(self, account): + pass # XXX TODO diff --git a/src/templates/lists/index.html b/src/templates/lists/index.html new file mode 100644 index 00000000..5be27f8e --- /dev/null +++ b/src/templates/lists/index.html @@ -0,0 +1,42 @@ +{% extends "../base.html" %} + +{% block title %}{{ _("Lists") }}{% end block %} + +{% block container %} +
+
+
+ + +

{{ _("Mailing Lists") }}

+
+
+
+ +
+ +
+{% end block %} diff --git a/src/web/__init__.py b/src/web/__init__.py index 94dc5069..04e71e9f 100644 --- a/src/web/__init__.py +++ b/src/web/__init__.py @@ -22,6 +22,7 @@ from . import donate from . import downloads from . import fireinfo from . import iuse +from . import lists from . import location from . import nopaste from . import ui_modules @@ -164,6 +165,9 @@ class Application(tornado.web.Application): (r"/donate/thank-you", donate.ThankYouHandler), (r"/donate/error", donate.ErrorHandler), + # Lists + (r"/lists", lists.IndexHandler), + # Password Reset (r"/password\-reset", auth.PasswordResetInitiationHandler), (r"/password\-reset/([a-z_][a-z0-9_-]{0,31})/(\w+)", auth.PasswordResetHandler), diff --git a/src/web/lists.py b/src/web/lists.py new file mode 100644 index 00000000..14256035 --- /dev/null +++ b/src/web/lists.py @@ -0,0 +1,16 @@ +#!/usr/bin/python3 + +import tornado.web + +from . import base + +class IndexHandler(base.BaseHandler): + @tornado.web.authenticated + async def get(self): + # Fetch all available lists + lists = await self.backend.lists.get_lists() + + # Fetch all subscribed lists + subscribed_lists = await self.current_user.get_lists() + + self.render("lists/index.html", lists=lists, subscribed_lists=subscribed_lists) -- 2.39.2