1
0
Fork 0

app/mailman-web: create

There's a lot of ugly hacks here, but this has been the state of prod
for months now, so we should reflect that.
Also, this bumps a bunch of workspace deps.

Change-Id: I744e0d3aff27036cfed73416cf442c7d62444a8b
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1473
Reviewed-by: q3k <q3k@hackerspace.pl>
master
implr 2023-02-04 23:47:44 +01:00 committed by implr
parent 8036d7f4da
commit f5b1a215f4
17 changed files with 1413 additions and 40 deletions

View File

@ -129,9 +129,9 @@ go_repositories()
http_archive(
name = "io_bazel_rules_docker",
sha256 = "4349f2b0b45c860dd2ffe18802e9f79183806af93ce5921fb12cbd6c07ab69a8",
strip_prefix = "rules_docker-0.21.0",
urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.21.0/rules_docker-v0.21.0.tar.gz"],
sha256 = "27d53c1d646fc9537a70427ad7b034734d08a9c38924cc6357cc973fed300820",
strip_prefix = "rules_docker-0.24.0",
urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.24.0/rules_docker-v0.24.0.tar.gz"],
)
load("@io_bazel_rules_docker//toolchains/docker:toolchain.bzl", docker_toolchain_configure = "toolchain_configure")
@ -139,7 +139,6 @@ load("@io_bazel_rules_docker//toolchains/docker:toolchain.bzl", docker_toolchain
# This forces the use of Docker $HOME/.docker configuration.
docker_toolchain_configure(
name = "docker_config",
client_config = "",
docker_path = "/usr/bin/docker",
)
@ -150,10 +149,26 @@ load(
container_repositories()
load(
"@io_bazel_rules_docker//python3:image.bzl",
_py_image_repos = "repositories",
)
_py_image_repos()
# Docker base images
load("@io_bazel_rules_docker//container:container.bzl", "container_pull")
container_pull(
name = "python-debian",
digest = "sha256:cfa3b79333c4e56fc675b6800445b6dcbb3e6cd4d52f2a9ade944ab73dadc6a1",
registry = "index.docker.io",
repository = "python",
tag = "3.10-bullseye", # use the same version as in python_register_toolchains
)
container_pull(
name = "prodimage-bionic",
digest = "sha256:1cd1f84169b8e1414a5d511b42909f2d540831c67b6799ae9af8cd6a80d75b5f",

150
app/mailman-web/BUILD Normal file
View File

@ -0,0 +1,150 @@
load("@pydeps//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_binary")
load("@io_bazel_rules_docker//python:image.bzl", "py_layer")
load("@io_bazel_rules_docker//python3:image.bzl", "py3_image")
load("@io_bazel_rules_docker//container:container.bzl", "container_layer", "container_image")
load("@io_bazel_rules_docker//docker/util:run.bzl", "container_run_and_extract", "container_run_and_commit_layer")
load("@io_bazel_rules_docker//docker/package_managers:download_pkgs.bzl", "download_pkgs")
load("@io_bazel_rules_docker//docker/package_managers:install_pkgs.bzl", "install_pkgs")
# - - base docker stuff - -
download_pkgs(
name = "apt_py_is_py3",
image_tar = "@python-debian//image",
packages = [
# rules_docker python wants /usr/bin/python
"python-is-python3",
],
)
install_pkgs(
name = "base_image",
output_image_name = "base_image",
image_tar = "@python-debian//image",
installables_tar = ":apt_py_is_py3.tar",
installation_cleanup_commands = "rm -rf /var/lib/apt/lists/* /usr/share/doc && apt remove -y libbluetooth3 mariadb-common tk && apt autoremove -y",
)
BASE_IMAGE = ":base_image"
# overkill rube goldberg setup to build static files begins
# - - - -
container_run_and_extract(
name = "static_pack",
commands = [
"tar cpJvf /out.tar.xz -C /opt/mailman/web/static ./",
],
extract_file = "/out.tar.xz",
image = ":static_build_image.tar",
)
container_image(
name = "static_build_image",
layers = [":static_build_layer"],
base = BASE_IMAGE,
)
# this will also contain .pyc files, but the python binary will be the same
# on prod, so it's fine
container_run_and_commit_layer(
name = "static_build_layer",
commands = [
"./app/mailman-web/manage collectstatic",
"./app/mailman-web/manage compress",
# gettext is cursed, TODO make this work
#"./app/mailman-web/manage compilemessages",
],
image = ":build_container.tar",
docker_run_flags = ["--entrypoint="],
)
py3_image(
name = "build_container",
srcs = [":manage"],
main = "manage.py",
base = ":build_tools_container",
layers = [":deps_layer"],
# this doesn't work for some reason - this is always rebuilt, unless
# you pass --nostamp globally
stamp = 0,
)
download_pkgs(
name = "build_tools",
image_tar = "@python-debian//image",
packages = [
"sassc",
"gettext",
],
)
install_pkgs(
name = "build_tools_container",
output_image_name = "build_tools_container",
image_tar = BASE_IMAGE + '.tar',
installables_tar = ":build_tools.tar",
installation_cleanup_commands = "rm -rf /var/lib/apt/lists/* /usr/share/doc",
)
# - - - -
# overkill rube goldberg setup to build static files ends
# - - python stuff - -
# this is purely a build optimization - put the pip deps into a separate layer
py_layer(
name = "deps_layer",
deps = [
requirement("Django"),
requirement("postorius"),
requirement("hyperkitty"),
requirement("gunicorn"),
requirement("psycopg2-binary"),
],
)
py_library(
name = "django_base",
srcs = ["settings.py", "urls.py"]
+ glob(["upstream_settings/*.py"]),
deps = [
requirement("Django"),
requirement("postorius"),
requirement("hyperkitty"),
requirement("gunicorn"),
requirement("psycopg2-binary"),
],
)
py_binary(
name = "manage",
srcs = ["manage.py"],
deps = [":django_base"],
)
py_binary(
name = "serve",
srcs = ["serve.py"],
deps = [":django_base"],
)
# prod docker image
py3_image(
name = "mailman-web",
srcs = ["container_main.py"],
deps = [
":django_base",
":manage",
":serve",
],
layers = [
":deps_layer",
],
main = "container_main.py",
#base = ":base_container"
base = ":static_build_image",
)

3
app/mailman-web/LICENSE Normal file
View File

@ -0,0 +1,3 @@
Mailman and its components (postorius, hyperkitty) are licensed under GPLv3 and we link/import that code here directly.
Also, a good portion of this wsgi launcher is copied from https://gitlab.com/mailman/mailman-web, GPLv3 as well.
Therefore, this entire directory likely falls under GPLv3.

View File

@ -0,0 +1,7 @@
Web parts of mailman3 - postorius and hyperkitty.
Postgres only, TODO attempt cockroachization.
This currently serves static files via an extremely cursed hack:
lists.hackerspace.pl points to boston-packets, which serves /static/* from
a local directory there, made by extracting :static_pack there; and proxy_passes
every other path to the k8s Service defined here.

View File

@ -0,0 +1,13 @@
from sys import argv, exit
# simple wrapper so we don't need two container entrypoints
assert len(argv) > 1, "specify a command"
if argv[1] == "serve":
import serve
serve.main()
elif argv[1] == "manage":
import manage
manage.main(argv[1:])
else:
print("unknown command", argv[1])
exit(1)

View File

@ -0,0 +1,215 @@
local kube = import "../../../kube/kube.libsonnet";
{
local app = self,
local cfg = app.cfg,
cfg:: {
namespace: error "cfg.namespace must be set",
webDomain: error "cfg.webDomain must be set",
images: {
web: "registry.k0.hswaw.net/implr/mailman-web:0.6",
# https://github.com/octeep/wireproxy
wireproxy: "registry.k0.hswaw.net/implr/wireproxy:1.0.5"
},
passwords: {
postgres: error "cfg.secrets.postgres must be set",
mailmanRest: error "cfg.secrets.mailmanRest must be set",
mailmanArchiver: error "cfg.secrets.mailmanArchiver must be set",
},
smtp: {
user: "postorius",
# from mail server
password: error "cfg.smtp.password must be set",
},
secrets: {
djangoSecretKey: error "cfg.secrets.djangoSecretKey must be set",
},
wg: {
peerPubkey: error "cfg.wg.peerPubkey must be set",
privkey: error "cfg.wg.privkey must be set",
endpoint: error "cfg.wg.endpoint must be set",
},
},
env:: {
WEB_DOMAIN: cfg.webDomain,
BIND_ADDR: "0.0.0.0:8080",
//DB_HOST: app.postgres.svc.host,
DB_HOST: "boston-packets.hackerspace.pl",
DB_USER: "mailman",
DB_NAME: "mailman-web",
DB_PASS: kube.SecretKeyRef(app.config, "postgres-pass"),
DB_PORT: "5432",
SMTP_HOST: "mail.hackerspace.pl",
SMTP_PORT: "587",
SMTP_USER: "postorius",
SMTP_PASSWORD: kube.SecretKeyRef(app.config, "smtp-password"),
SECRET_KEY: kube.SecretKeyRef(app.config, "django-secret-key"),
MAILMAN_REST_API_PASS: kube.SecretKeyRef(app.config, 'mailman-api-password'),
MAILMAN_ARCHIVER_KEY: kube.SecretKeyRef(app.config, 'mailman-archiver-key'),
},
namespace: kube.Namespace(cfg.namespace),
local ns = self.namespace,
web: ns.Contain(kube.Deployment("web")) {
spec+: {
minReadySeconds: 10,
replicas: 1,
template+: {
spec+: {
initContainers_: {
migrate: kube.Container("migrate") {
image: cfg.images.web,
env_: app.env,
args: [
"manage", "migrate",
],
},
},
volumes_: {
config: kube.SecretVolume(app.wireproxyConfig),
},
containers_: {
default: kube.Container("default") {
image: cfg.images.web,
env_: app.env,
args: ["serve"],
ports_: {
web: { containerPort: 8080 },
},
# readinessProbe: {
# httpGet: {
# path: "/",
# port: "web",
# },
# failureThreshold: 10,
# periodSeconds: 5,
# },
resources: {
requests: {
cpu: "250m",
memory: "1024M",
},
limits: {
cpu: "1",
memory: "1024M",
},
},
},
wireproxy: kube.Container("wireproxy") {
image: cfg.images.wireproxy,
resources: {
requests: {
cpu: "100m",
memory: "64M",
},
limits: {
cpu: "200m",
memory: "128M",
},
},
volumeMounts_: {
config: { mountPath: "/etc/wireproxy/config", subPath: "config" }
},
},
},
},
},
},
},
local manifestIniMultisection(sname, values) = std.join('\n',
[std.manifestIni({
sections: {
[sname]: i,
}}) for i in values]),
wireproxyConfig: ns.Contain(kube.Secret("wireproxy-config")) {
data: {
config: std.base64(std.manifestIni({
sections: {
Interface: {
Address: cfg.wg.address,
PrivateKey: cfg.wg.privkey,
},
Peer: {
PublicKey: cfg.wg.peerPubkey,
Endpoint: cfg.wg.endpoint,
},
},
}) + manifestIniMultisection("TCPClientTunnel", [
# {
# # postgres
# ListenPort: 5432,
# Target: "localhost:5432",
# },
{
# mailman core api
BindAddress: "127.0.0.1:8001",
Target: "172.17.1.1:8001",
},
])),
},
},
svcWeb: ns.Contain(kube.Service("web")) {
target_pod: app.web.spec.template,
spec+: {
# hax
type: "LoadBalancer",
externalTrafficPolicy: "Local",
},
},
#ingress: ns.Contain(kube.Ingress("mailman")) {
# metadata+: {
# annotations+: {
# "kubernetes.io/tls-acme": "true",
# "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
# "nginx.ingress.kubernetes.io/proxy-body-size": "0",
# },
# },
# spec+: {
# tls: [
# {
# hosts: [cfg.webDomain],
# secretName: "mailman-ingress-tls",
# },
# ],
# rules: [
# {
# host: cfg.webDomain,
# http: {
# paths: [
# { path: "/", backend: app.svcWeb.name_port },
# //{ path: "/static/", backend: app.svcStatic.name_port },
# ],
# },
# },
# ],
# },
#},
config: ns.Contain(kube.Secret("config")) {
data_: {
"postgres-pass": cfg.passwords.postgres,
"django-secret-key": cfg.secrets.djangoSecretKey,
"smtp-password": cfg.smtp.password,
"mailman-api-password": cfg.mailmanCore.mailmanApiPass,
"mailman-archiver-key": cfg.mailmanCore.mailmanArchiverKey,
},
},
}

View File

@ -0,0 +1,20 @@
local mailman = import "mailman.libsonnet";
local secrets = import "secrets/plain/prod.libsonnet";
mailman {
cfg+: secrets {
namespace: "mailman-hackerspace-prod",
webDomain: "lists2.hackerspace.pl",
wg+: {
address: "172.17.1.2/32",
peerPubkey: "sKobxe3U6Gz72MWXEETTr8fSFIPSuX/WOGGFwd3oXy8=",
endpoint: "boston-packets.hackerspace.pl:51820"
},
//objectStorage+: {
//bucket: "mailman-prod",
//},
},
}

13
app/mailman-web/manage.py Normal file
View File

@ -0,0 +1,13 @@
import os
import sys
def main(argv):
os.environ['DJANGO_SETTINGS_MODULE'] = "settings"
os.environ['DJANGO_IS_MANAGEMENT_COMMAND'] = '1'
from django.core.management import execute_from_command_line
execute_from_command_line(argv)
if __name__ == "__main__":
main(sys.argv)

38
app/mailman-web/serve.py Normal file
View File

@ -0,0 +1,38 @@
import os
import gunicorn.app.base
from django.core.wsgi import get_wsgi_application
class StandaloneApplication(gunicorn.app.base.BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self):
config = {key: value for key, value in self.options.items()
if key in self.cfg.settings and value is not None}
for key, value in config.items():
self.cfg.set(key.lower(), value)
def load(self):
return self.application
def main():
options = {
'bind': os.environ.get('BIND_ADDR', '127.0.0.1:8080'),
'workers': int(os.environ.get("GUNICORN_WORKERS", "4")),
'capture_output': True,
'disable_redirect_access_to_syslog': True,
'accesslog': '-',
'errorlog': '-',
}
os.environ['DJANGO_SETTINGS_MODULE'] = "settings"
application = get_wsgi_application()
StandaloneApplication(application, options).run()
if __name__ == '__main__':
main()

102
app/mailman-web/settings.py Normal file
View File

@ -0,0 +1,102 @@
import sys
import os
from upstream_settings.base import *
from upstream_settings.mailman import *
# we're in a container, stdout only
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'stream': sys.stdout,
'formatter': 'verbose'
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
},
}
SECRET_KEY = os.environ.get("SECRET_KEY", "hackme")
# assert len(SECRET_KEY) > 16
ROOT_URLCONF = "urls"
ALLOWED_HOSTS = [
"localhost", # Archiving API from Mailman, keep it.
os.environ.get('WEB_DOMAIN', "lists.hackerspace.pl"),
]
ALLOWED_HOSTS = ["*"] # TODO deleteme
MAILMAN_REST_API_URL = 'http://localhost:8001'
MAILMAN_REST_API_USER = 'restadmin'
MAILMAN_REST_API_PASS = os.environ.get('MAILMAN_REST_API_PASS')
MAILMAN_ARCHIVER_KEY = os.environ.get('MAILMAN_ARCHIVER_KEY')
MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1', '185.236.240.38', "2a0d:eb00:2137:2::10")
DATABASES = {
'default': {
# Use 'sqlite3', 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'ENGINE': 'django.db.backends.postgresql_psycopg2',
# DB name or path to database file if using sqlite3.
'NAME': os.environ.get('DB_NAME', 'mailman-web'),
# The following settings are not used with sqlite3:
'USER': os.environ.get('DB_USER', 'mailman'),
'PASSWORD': os.environ.get('DB_PASS'),
# HOST: empty for localhost through domain sockets or '127.0.0.1' for
# localhost through TCP.
'HOST': os.environ.get('DB_HOST', '127.0.0.1'),
# PORT: set to empty string for default.
'PORT': os.environ.get('DB_PORT', ''),
# OPTIONS: for mysql engine only, do not use with other engines.
# 'OPTIONS': {'charset': 'utf8mb4'} # Enable utf8 4-byte encodings.
}
}
# TODO check this
USE_X_FORWARDED_HOST = True # behind an Ingress
# And if your proxy does your SSL encoding for you, set SECURE_PROXY_SSL_HEADER
# https://docs.djangoproject.com/en/1.8/ref/settings/#secure-proxy-ssl-header
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_SCHEME', 'https')
DEFAULT_FROM_EMAIL = 'postorius@hackerspace.pl'
SERVER_EMAIL = 'bofh@hackerspace.pl'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('SMTP_HOST', '127.0.0.1')
EMAIL_PORT = int(os.environ.get('SMTP_PORT', '465'))
EMAIL_HOST_USER = os.environ.get('SMTP_USER', 'postorius')
EMAIL_HOST_PASSWORD = os.environ.get('SMTP_PASSWORD')
EMAIL_TIMEOUT=3
EMAIL_USE_TLS=True
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.environ.get('FULLTEXT_INDEX_PATH', "fulltext_index"),
# You can also use the Xapian engine, it's faster and more accurate,
# but requires another library.
# http://django-haystack.readthedocs.io/en/v2.4.1/installing_search_engines.html#xapian
# Example configuration for Xapian:
# 'ENGINE': 'xapian_backend.XapianEngine'
},
}
# Only display mailing-lists from the same virtual host as the webserver
FILTER_VHOST = False
POSTORIUS_TEMPLATE_BASE_URL = 'https://lists.hackerspace.pl'

View File

@ -0,0 +1 @@
Unmodified copy of default settings from mailman-web.

View File

@ -0,0 +1,300 @@
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from django.contrib.messages import constants as messages
from pathlib import Path
#: The base directory for logs and database.
BASE_DIR = Path('/opt/mailman/web')
#: Default list of admins who receive the emails from error logging.
ADMINS = (
('Mailman Suite Admin', 'root@localhost'),
)
#: Hosts/domain names that are valid for this site; required if DEBUG is False.
#: See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = [
"localhost", # Archiving API from Mailman, keep it.
# "lists.your-domain.org",
# Add here all production URLs you may have.
]
#: Enable Development Mode.
DEBUG = False
#: URL Configuration for Django
ROOT_URLCONF = 'mailman_web.urls'
#: Default list of django applications.
#: Each social account provider is an application and by default no social auth
#: providers are enabled. To enable a social auth provider, you can add them
#: to list of INSTALLED_APPS. For example::
#:
#: DJANGO_SOCIAL_AUTH_PROVIDERS = [
#: 'allauth.socialaccount.providers.openid',
#: 'django_mailman3.lib.auth.fedora',
#: 'allauth.socialaccount.providers.github',
#: 'allauth.socialaccount.providers.gitlab',
#: 'allauth.socialaccount.providers.google',
#: 'allauth.socialaccount.providers.facebook',
#: 'allauth.socialaccount.providers.twitter',
#: 'allauth.socialaccount.providers.stackexchange',
#: ]
#: INSTALLED_APPS += DJANGO_SOCIAL_AUTH_PROVIDERS
#:
#: A full list of providers can be found at
#: https://django-allauth.readthedocs.io/en/latest/providers.html
#: Please also note that extra configuration is required after
#: a provider is enabled. Django-allauth's documentation mentioned
#: above provides more details about how to configure one.
INSTALLED_APPS = [
'hyperkitty',
'postorius',
'django_mailman3',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'django_gravatar',
'compressor',
'haystack',
'django_extensions',
'django_q',
'allauth',
'allauth.account',
'allauth.socialaccount',
]
#: Default Django Middlewares.
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django_mailman3.middleware.TimezoneMiddleware',
'postorius.middleware.PostoriusMiddleware',
)
#: Default Template finders.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.template.context_processors.csrf',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django_mailman3.context_processors.common',
'hyperkitty.context_processors.common',
'postorius.context_processors.postorius',
],
},
},
]
#: Wsgi application import path. This will be used by the WSGI server which
#: will be used to deploy this application.
WSGI_APPLICATION = 'mailman_web.wsgi.application'
#: Default Database to be used.
#: Example for PostgreSQL (**recommanded for production**)::
#:
#: 'default': {
#: 'ENGINE': 'django.db.backends.postgresql_psycopg2',
#: 'NAME': 'database_name',
#: 'USER': 'database_user',
#: 'PASSWORD': 'database_password',
#: 'HOST': 'localhost',
#: }
#:
#: For MySQL/MariaDB also add the following to the the configuration::
#:
#: 'OPTIONS': {'charset': 'utf8mb4'} # Enable utf8 4-byte encodings.
#:
#: Check out
#: `Django documentation
#: <https://docs.djangoproject.com/en/3.0/ref/settings/#databases>`_ for
#: more details.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'mailman-web.db'),
'HOST': '',
'PORT': '',
}
}
# Maintain type of autogenerated keys going forward
# https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
#: Default password validators.
AUTH_PASSWORD_VALIDATORS = [
{
'NAME':
'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', # noqa: E501
},
{
'NAME':
'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME':
'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME':
'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
#: Default Language code.
LANGUAGE_CODE = 'en-us'
#: Default timezone.
TIME_ZONE = 'UTC'
#: Enable internationalization.
USE_I18N = True
#: Enable localization.
USE_L10N = True
#: Use the timezone information.
USE_TZ = True
#: Default path where static files will be placed.
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
#: URL prefix for static files.
#: Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = '/static/'
#: Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
# BASE_DIR + '/static/',
)
#: List of finder classes that know how to find static files in
#: various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
'compressor.finders.CompressorFinder',
)
#: Default Django URL to redirect to for Login.
LOGIN_URL = 'account_login'
#: Default Django URL to redirect to after a successful login.
LOGIN_REDIRECT_URL = 'list_index'
#: Default Django URL to Logout the user.
LOGOUT_URL = 'account_logout'
#: If you enable email reporting for error messages, this is where those emails
#: will appear to be coming from. Make sure you set a valid domain name,
#: otherwise the emails may get rejected.
#: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SERVER_EMAIL
SERVER_EMAIL = 'root@localhost.local'
#: The default implementation to send out emails. This can be customized to
#: something else for testing purposes.
#: https://docs.djangoproject.com/en/dev/topics/email/#email-backends
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
MESSAGE_TAGS = {
messages.ERROR: 'danger'
}
#: Default Logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
},
'file': {
'level': 'INFO',
'class': 'logging.handlers.WatchedFileHandler',
'filename': os.path.join(BASE_DIR, 'logs', 'mailmanweb.log'),
'formatter': 'verbose',
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
},
'loggers': {
'django.request': {
'handlers': ['mail_admins', 'file'],
'level': 'ERROR',
'propagate': True,
},
'django': {
'handlers': ['file'],
'level': 'ERROR',
'propagate': True,
},
'hyperkitty': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
'postorius': {
'handlers': ['console', 'file'],
'level': 'INFO',
},
'q': {
'level': 'WARNING',
'propagate': False,
'handlers': ['console', 'file'],
},
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s' # noqa: E501
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
}
#: Current Django Site being served. This is used to customize the web host
#: being used to serve the current website. For more details about Django
#: site, see: https://docs.djangoproject.com/en/dev/ref/contrib/sites/
SITE_ID = 1

View File

@ -0,0 +1,124 @@
#: Mailman Core default API Path
MAILMAN_REST_API_URL = 'http://localhost:8001'
#: Mailman Core API user
MAILMAN_REST_API_USER = 'restadmin'
#: Mailman Core API user's password.
MAILMAN_REST_API_PASS = 'restpass'
#: Mailman Core Shared archiving key. This value is set in the :
#: mailman-hyperkitty's configuration file.
MAILMAN_ARCHIVER_KEY = 'SecretArchiverAPIKey'
#: Host for Mailman Core, from where Hyperkitty will accept connections
#: for archiving.
MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1')
#: Base URL where Django/Mailman-web would be listening for requests. Used by
#: Mailman Core for fetching templates.
POSTORIUS_TEMPLATE_BASE_URL = 'http://localhost:8000'
#: Use gravatar in HyperKitty and Postorius.
#: If disabled django_gravatar can be removed from INSTALLED_APPS:
#: INSTALLED_APPS.remove('django_gravatar')
HYPERKITTY_ENABLE_GRAVATAR = True
#: Filter visible Mailing Lists based on the current host being used to serve.
FILTER_VHOST = False
#: Sender in Emails sent out by Postorius.
DEFAULT_FROM_EMAIL = 'postorius@localhost'
#: Django Allauth
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_UNIQUE_EMAIL = True
#: Protocol for URLs generated for authentication, like email
#: confirmation.
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
#: Extra configuration for Social auth. For these configuration to be used.
#: each of the social account providers must be first added in INSTALLED_APPS.
#: See :py:data:`mailman_web.settings.base.INSTALLED_APPS` for more
#: configuration.
SOCIALACCOUNT_PROVIDERS = {
'openid': {
'SERVERS': [
dict(id='yahoo',
name='Yahoo',
openid_url='http://me.yahoo.com'),
],
},
'google': {
'SCOPE': ['profile', 'email'],
'AUTH_PARAMS': {'access_type': 'online'},
},
'facebook': {
'METHOD': 'oauth2',
'SCOPE': ['email'],
'FIELDS': [
'email',
'name',
'first_name',
'last_name',
'locale',
'timezone',
],
'VERSION': 'v2.4',
},
}
#: django-compressor
#: https://pypi.python.org/pypi/django_compressor
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'sassc -t compressed {infile} {outfile}'),
('text/x-sass', 'sassc -t compressed {infile} {outfile}'),
)
# Social auth
#
#: Authentication backends for Django to be used.
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
#
# Full-text search engine
#
#: Django-Haystack connection parameters.
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': "fulltext_index",
# You can also use the Xapian engine, it's faster and more accurate,
# but requires another library.
# http://django-haystack.readthedocs.io/en/v2.4.1/installing_search_engines.html#xapian
# Example configuration for Xapian:
# 'ENGINE': 'xapian_backend.XapianEngine'
},
}
# Asynchronous tasks
#
#: Django Q connection parameters.
Q_CLUSTER = {
'retry': 360,
'timeout': 300,
'save_limit': 100,
'orm': 'default',
}
#: On a production setup, setting COMPRESS_OFFLINE to True will bring a
#: significant performance improvement, as CSS files will not need to be
#: recompiled on each requests. It means running an additional "compress"
#: management command after each code upgrade.
#: http://django-compressor.readthedocs.io/en/latest/usage/#offline-compression
COMPRESS_OFFLINE = True
# Needed for debug mode
# INTERNAL_IPS = ('127.0.0.1',)

35
app/mailman-web/urls.py Normal file
View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
#
# This file is part of Postorius.
#
# Postorius is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Postorius is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Postorius. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import include
from django.contrib import admin
from django.urls import path, reverse_lazy
from django.views.generic import RedirectView
urlpatterns = [
path(
'',
RedirectView.as_view(url=reverse_lazy('list_index'), permanent=True),
),
path('mailman3/', include('postorius.urls')),
path('archives/', include('hyperkitty.urls')),
path('', include('django_mailman3.urls')),
path('accounts/', include('allauth.urls')),
path('admin/', admin.site.urls),
]

View File

@ -1,12 +1,12 @@
# grpcio and protobuf are installed directly via WORKSPACE
# do NOT add them there
# depending on a py_grpc_library output will pull in the required deps
arrow==0.14.5
arrow==1.1.1
blinker==1.4
Click==7.0
cockroachdb==0.3.3
cryptography==2.9.2
Django==2.2.28
cryptography==3.4.8
Django==3.2.16
fabric==2.4.0
Flask==1.1.1
Flask-Login==0.4.1
@ -15,12 +15,16 @@ Flask-WTF==0.14.2
future==0.17.1
gevent==22.10.2
gunicorn==20.1.0
hyperkitty==1.3.7 # sync with postorius
itsdangerous==1.1.0
Jinja2==2.10.1
MarkupSafe==1.1.1
oauthlib==3.1.1
paramiko==2.7.2
postorius==1.3.7 # sync with hyperkitty
psycopg2==2.9.4
# disgusten, but needed for weird container linking problems
psycopg2-binary==2.9.4
pyelftools==0.26
PyNaCl==1.3.0
python-dateutil==2.8.0

View File

@ -4,10 +4,20 @@
#
# bazel run //third_party/py:requirements.update
#
arrow==0.14.5 \
--hash=sha256:0186026cfd94ca4fb773f30cc5398289a3027480d335e0e5c0d2772643763137 \
--hash=sha256:a12de0124d812d15061ed36c7eb4a421fa1b95026a502a0b2062e9ea00fc4446
# via -r third_party/py/requirements.in
arrow==1.1.1 \
--hash=sha256:77a60a4db5766d900a2085ce9074c5c7b8e2c99afeaa98ad627637ff6f292510 \
--hash=sha256:dee7602f6c60e3ec510095b5e301441bc56288cb8f51def14dcb3079f623823a
# via
# -r third_party/py/requirements.in
# django-q
asgiref==3.6.0 \
--hash=sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac \
--hash=sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506
# via django
atpublic==3.1.1 \
--hash=sha256:3098ee12d0107cc5009d61f4e80e5edcfac4cda2bdaa04644af75827cb121b18 \
--hash=sha256:37f714748e77b8a7b34d59b7b485fd452a0d5906be52cb1bd28d29a2bd84f295
# via flufl-lock
bcrypt==3.2.2 \
--hash=sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521 \
--hash=sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb \
@ -21,6 +31,14 @@ bcrypt==3.2.2 \
--hash=sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40 \
--hash=sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa
# via paramiko
bleach==6.0.0 \
--hash=sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414 \
--hash=sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4
# via readme-renderer
blessed==1.19.1 \
--hash=sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b \
--hash=sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc
# via django-q
blinker==1.4 \
--hash=sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6
# via -r third_party/py/requirements.in
@ -81,6 +99,7 @@ cffi==1.15.0 \
--hash=sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796
# via
# bcrypt
# cmarkgfm
# cryptography
# pynacl
chardet==3.0.4 \
@ -93,37 +112,159 @@ click==7.0 \
# via
# -r third_party/py/requirements.in
# flask
cmarkgfm==2022.10.27 \
--hash=sha256:0023de4b19bb557b143bed274f76cb36551f7f1d1cdffd29b6cde646b85d9ffb \
--hash=sha256:0176d51fb57162c642b1d2c70048950a5ae119af81e77565a0383b992b1f86d6 \
--hash=sha256:071f5f0dac9475bab6a065878f248a69be52a7736b6c661e06ca7199f25fe097 \
--hash=sha256:0756ea0f6b55eff2617ea0518d6730e37d6077c10baaabbe8b46210ff5a250ef \
--hash=sha256:07a06d424ccef98528cba1158946f92117e07579f1dc9942ed4fd70f81693b9f \
--hash=sha256:1013ce61db1dd3febcaca1ee42cad9eb823852bb76cbae61c1488734ce51f2b7 \
--hash=sha256:123ad8d50fbedacd036760ba46e36170bad9dd2c1e83655d8622b7803169bb49 \
--hash=sha256:1790164f84e6b037d0b39df11f757e021a9f9c313681297a051d50bc7b5249fc \
--hash=sha256:20e897160be161161a565df94ce502714a1aa63af3ad682e6d1f1c7e6656fdbb \
--hash=sha256:210c0f0dbc1aadab30bc75c48b14b645414733a668df52b43058028e43a046e8 \
--hash=sha256:21557c06a411b1d754eed7f6fc9a8ff41f8a4a004b32c8bd2cec2ab3f3cb4d3c \
--hash=sha256:216a540e85258839cffa7274731a87d91b3e17c9079b3b02467c312e784b5281 \
--hash=sha256:27149c63b1190ee6e7dd4b32d0a2c313bc1856bcdde7a42a0a5b6ae42d97ed94 \
--hash=sha256:325c03644da5ab81a7071aae6fbafa3beb22413f7fd7440baf6d510cfcf7be21 \
--hash=sha256:3f510fafa9d904336eecc3aa41536fd287c2d32baa21b14d48950ced802ca531 \
--hash=sha256:4325b75a3b5b802d5edcc2378aa6405a1e5df0aeeec583d1b05d73b0562fa7d0 \
--hash=sha256:47e267ce890b579585a32f77d347d61de2390b517cfc52bb4ca67c5c4b4c055a \
--hash=sha256:483e48613f5c7b3350cdabfd0f69aaa086513542d0de533f39e5669bf4df5de4 \
--hash=sha256:5342c6d12e343cc66b4b8dcd09fc0c1977cb32fd1d57c15bd756876606591ee9 \
--hash=sha256:5a39333e1fdcd0116c24adc33423999913865bd3cc83fc44b2218aac7fbe5637 \
--hash=sha256:5bad39b832f734f588aea00868e53ba1aaf058d569e40e5c9016702edebf88e8 \
--hash=sha256:5fc7178a6afd69a5dfc197558791cecedead9fc77e95ec63c201e8219ce33000 \
--hash=sha256:6672784820981d315b695bb7ce08d40886502368e133b453d675ff6f2fffae49 \
--hash=sha256:670b414274edf3ecc0a950a80580e1de553c599a30658827a5d7f7bccbde5843 \
--hash=sha256:69a769feb1b2d16982fe952afd44e124a4d306a44cdfd6857e74b8eb5d47d765 \
--hash=sha256:76beb5b50b32d7bafec2154608a037601a2186d15df95cec6ab4cc937afca365 \
--hash=sha256:799cf03a82a7849d975a3b955798d5e439a08fb678b657c5078115dc61314674 \
--hash=sha256:7a91279ab8e2869c19120595e41ebd81a6f5034c1e6b1cfc5e81cd80d40bf3eb \
--hash=sha256:80cf50b52bc0a47c032706de27b9526b6035c73b57ce06662021144cba4b6c5e \
--hash=sha256:8744be702511464d04c34000005009607471f1afe65d6037777747d6b4607e5f \
--hash=sha256:8830dfb61251f2b677dea7ffc531c3f6037f7e9a66a14ad24bdaf3cefe2dc8c4 \
--hash=sha256:89dcd4fea4ae44f1a0697cf805b6931a126b2b3ea23ed1ccdad7e020425224a9 \
--hash=sha256:8e9f038a4f0e54c135e468994f1ea97141b086d1f1bd8f498c12f3d559017e8e \
--hash=sha256:90ae1b4b2c6b92f8f5b1e5416a2f5b1bba7a5f9aea29b0de79767ed80655527a \
--hash=sha256:93d9ac7716ea901ca0bfd18ae3b68f1f6bf51de0830c3f233ef734fcd52a0799 \
--hash=sha256:98c0527153daf16589ef095aa72f06a4bdb9213433ff47811fbc4172c91d865b \
--hash=sha256:a6a3970cf1c8ba4465d5046dd6a6d7f6024e67d6eec812a4701a21c5161a2fbd \
--hash=sha256:b0b13eac6194d59f9d3ab44af7076221510e788572f34e25104ad47b33d960e1 \
--hash=sha256:b8daf62cddc81b31a8f3c9093936c4cb75b25a8024c09f276cb027f1647e3326 \
--hash=sha256:bd6315e1036d31884bff25719636e3499a7f4593b0f7b47dc742678328f2f26f \
--hash=sha256:c04921575e412a6459d645a45ca987061b17d89310c92aedf108f97f2b8b7b91 \
--hash=sha256:c3a6e597bdf595f81dc214e821b579b8d665116c55ed5288b599ae941e446098 \
--hash=sha256:c66077349e7f7d954aa37d770310de5a8214ac9dca9756440f99e008a0e693de \
--hash=sha256:c804446b941dc08dcc3d2def3913cfc4bae954b80babfaa2a502e8ebdea29185 \
--hash=sha256:c82af8cdb76a71459662e447f9b1545ae6146cb9287df978705a298f87a76a90 \
--hash=sha256:ca0e03a590c6f62738d208f8689da08eae9d3bcc2f4dd97e38df45d8dbc333ab \
--hash=sha256:cc70b89309404dd84a524d439aa2b2e54872e0f623f9523bd77e66526251954f \
--hash=sha256:ccfc25b5abfe1398426f099d840b5fa7dec118b44f06833e2ba8b67c6ffc12d9 \
--hash=sha256:cfe84b8912b355b8036c093ecdd6abbe6df075176879a49867dd72b9e53449f3 \
--hash=sha256:d3fd62dd65c3a64ced175a1447ea41b01a7ac1c0df1c8358323267c9326b7745 \
--hash=sha256:db3449fdb87752be5ad0698d6f2ca030af320cdf71ebc9a1ebae1b9c1d3661c8 \
--hash=sha256:ddc2bbb5572722758787066f5f841745c58452e28c59ce7c13b7228be1cb48f3 \
--hash=sha256:e65e492407d7cb3b695f3f715a1cbe6f97db69eb14011b8f156fc10c758b55c7 \
--hash=sha256:ea7d6cb95e2d74049cf08fde4ca6cbf030b9bf9ef75009847bbefb35094bb4c2 \
--hash=sha256:ea8a84d3702ccc32f8dfd0917dfb95f3d1843a0b6f85131c5cbfd1480d1d31ee \
--hash=sha256:f17677e66f95f25999c959c3f5361c05e739ad4f6b70ab9fdd24b1734c3ab029 \
--hash=sha256:f2d3bdb7e525abd03366a57eabd03e0c3f3f36bbf8af2267200605b7b712763b \
--hash=sha256:f938c503fce528d9cb715314134f8900cf09ddbd7e2bea88cf54a4bad58d0d5b \
--hash=sha256:fbec94c3e91b5e03d90a2cc2e865179e5bc58673e92b03ba64b520a97a0e9219
# via readme-renderer
cockroachdb==0.3.3 \
--hash=sha256:ef7aa1baf47f1ec8b187d7850f7996566a4131457fa69cb9490536e698f4540b
# via -r third_party/py/requirements.in
cryptography==2.9.2 \
--hash=sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6 \
--hash=sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b \
--hash=sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5 \
--hash=sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf \
--hash=sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e \
--hash=sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b \
--hash=sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae \
--hash=sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b \
--hash=sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0 \
--hash=sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b \
--hash=sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d \
--hash=sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229 \
--hash=sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3 \
--hash=sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365 \
--hash=sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55 \
--hash=sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270 \
--hash=sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e \
--hash=sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785 \
--hash=sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0
cryptography==3.4.8 \
--hash=sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e \
--hash=sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b \
--hash=sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7 \
--hash=sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085 \
--hash=sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc \
--hash=sha256:3c4129fc3fdc0fa8e40861b5ac0c673315b3c902bbdc05fc176764815b43dd1d \
--hash=sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a \
--hash=sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498 \
--hash=sha256:695104a9223a7239d155d7627ad912953b540929ef97ae0c34c7b8bf30857e89 \
--hash=sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9 \
--hash=sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c \
--hash=sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7 \
--hash=sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb \
--hash=sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14 \
--hash=sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af \
--hash=sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e \
--hash=sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5 \
--hash=sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06 \
--hash=sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7
# via
# -r third_party/py/requirements.in
# fabric
# paramiko
django==2.2.28 \
--hash=sha256:0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413 \
--hash=sha256:365429d07c1336eb42ba15aa79f45e1c13a0b04d5c21569e7d596696418a6a45
# via -r third_party/py/requirements.in
# pyjwt
defusedxml==0.7.1 \
--hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \
--hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
# via python3-openid
django==3.2.16 \
--hash=sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121 \
--hash=sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d
# via
# -r third_party/py/requirements.in
# django-allauth
# django-appconf
# django-extensions
# django-haystack
# django-mailman3
# django-picklefield
# django-q
# djangorestframework
# hyperkitty
# postorius
django-allauth==0.52.0 \
--hash=sha256:e380661ceafe55734c40102819ae720403027036f28e9f9827f0faeddc24ed5f
# via django-mailman3
django-appconf==1.0.5 \
--hash=sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d \
--hash=sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4
# via django-compressor
django-compressor==4.3.1 \
--hash=sha256:2c451174acb6f083054af7c8089376599b22d6380bd60311f78ec3fed79acc8e \
--hash=sha256:68858c0da6cc099cc29a022d86c3ba8aed114da9d709eeceb0d7b8181b5f8942
# via hyperkitty
django-extensions==3.2.1 \
--hash=sha256:2a4f4d757be2563cd1ff7cfdf2e57468f5f931cc88b23cf82ca75717aae504a4 \
--hash=sha256:421464be390289513f86cb5e18eb43e5dc1de8b4c27ba9faa3b91261b0d67e09
# via hyperkitty
django-gravatar2==1.4.4 \
--hash=sha256:545a6c2c5c624c7635dec29c7bc0be1a2cb89c9b8821af8616ae9838827cc35b \
--hash=sha256:c813280967511ced93eea0359f60e5369c35b3311efe565c3e5d4ab35c10c9ee
# via
# django-mailman3
# hyperkitty
django-haystack==3.2.1 \
--hash=sha256:97e3197aefc225fe405b6f17600a2534bf827cb4d6743130c20bc1a06f7293a4
# via hyperkitty
django-mailman3==1.3.9 \
--hash=sha256:1a92355b43bd689a4b17f99c4b6de4b490d9b0febd4b6cd0cbb76b3a25819d33
# via
# hyperkitty
# postorius
django-picklefield==3.1 \
--hash=sha256:c786cbeda78d6def2b43bff4840d19787809c8909f7ad683961703060398d356 \
--hash=sha256:d77c504df7311e8ec14e8b779f10ca6fec74de6c7f8e2c136e1ef60cf955125d
# via django-q
django-q==1.3.9 \
--hash=sha256:1b74ce3a8931990b136903e3a7bc9b07243282a2b5355117246f05ed5d076e68 \
--hash=sha256:5c6b4d530aa3aabf9c6aa57376da1ca2abf89a1562b77038b7a04e52a4a0a91b
# via hyperkitty
djangorestframework==3.14.0 \
--hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \
--hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08
# via hyperkitty
docutils==0.19 \
--hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \
--hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc
# via readme-renderer
fabric==2.4.0 \
--hash=sha256:93684ceaac92e0b78faae551297e29c48370cede12ff0f853cdebf67d4b87068 \
--hash=sha256:98538f2f3f63cf52497a8d0b24d18424ae83fe67ac7611225c72afb9e67f2cf6
@ -147,6 +288,10 @@ flask-wtf==0.14.2 \
--hash=sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36 \
--hash=sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac
# via -r third_party/py/requirements.in
flufl-lock==7.1.1 \
--hash=sha256:96d2c0448ba9fd8fc65d5d681ed7217c8e1625149c1c880bba50559bb680a615 \
--hash=sha256:af14172b35bbc58687bd06b70d1693fd8d48cbf0ffde7e51a618c148ae24042d
# via hyperkitty
future==0.17.1 \
--hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8
# via -r third_party/py/requirements.in
@ -270,6 +415,9 @@ gunicorn==20.1.0 \
--hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \
--hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
# via -r third_party/py/requirements.in
hyperkitty==1.3.7 \
--hash=sha256:4d74aca3ec30546741ca62339ece72392e298f511d0a89e69bcedbfcdc8102cb
# via -r third_party/py/requirements.in
idna==2.8 \
--hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
--hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c
@ -290,6 +438,12 @@ jinja2==2.10.1 \
# via
# -r third_party/py/requirements.in
# flask
mailmanclient==3.3.5 \
--hash=sha256:63581c604ca7eac021489c15aacca06a4958eb76f66574c6fab05eac654dd857
# via
# django-mailman3
# hyperkitty
# postorius
markupsafe==1.1.1 \
--hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
--hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \
@ -346,6 +500,14 @@ markupsafe==1.1.1 \
# via
# -r third_party/py/requirements.in
# jinja2
mistune==2.0.4 \
--hash=sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d \
--hash=sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808
# via hyperkitty
networkx==3.0 \
--hash=sha256:58058d66b1818043527244fab9d41a51fcd7dcc271748015f3c181b8a90c8e2e \
--hash=sha256:9a9992345353618ae98339c2b63d8201c381c2944f38a2ab49cb45a4c667e412
# via hyperkitty
oauthlib==3.1.1 \
--hash=sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc \
--hash=sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3
@ -358,6 +520,25 @@ paramiko==2.7.2 \
# via
# -r third_party/py/requirements.in
# fabric
postorius==1.3.7 \
--hash=sha256:3d40a9d025bfdec0fdeb5d320ed2db3fbbffc77bd0dfeb619f39cf345c03a2f0
# via -r third_party/py/requirements.in
psutil==5.9.4 \
--hash=sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff \
--hash=sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1 \
--hash=sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62 \
--hash=sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549 \
--hash=sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08 \
--hash=sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7 \
--hash=sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e \
--hash=sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe \
--hash=sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24 \
--hash=sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad \
--hash=sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94 \
--hash=sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8 \
--hash=sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7 \
--hash=sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4
# via flufl-lock
psycopg2==2.9.4 \
--hash=sha256:07b90a24d5056687781ddaef0ea172fd951f2f7293f6ffdd03d4f5077801f426 \
--hash=sha256:1da77c061bdaab450581458932ae5e469cc6e36e0d62f988376e9f513f11cb5c \
@ -371,6 +552,67 @@ psycopg2==2.9.4 \
--hash=sha256:c7fa041b4acb913f6968fce10169105af5200f296028251d817ab37847c30184 \
--hash=sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f
# via -r third_party/py/requirements.in
psycopg2-binary==2.9.4 \
--hash=sha256:02cde837df012fa5d579b9cf4bc8e1feb460f38d61f7a4ab4a919d55a9f6eeef \
--hash=sha256:044b6ab68613de7ea1e63856627deea091bfea09dea5ab4f050b13250fd18cab \
--hash=sha256:0a9465f0aa36480c8e7614991cbe8ca8aa16b0517c5398a49648ce345e446c19 \
--hash=sha256:0d8e0c9eec79fe1ae66691e06e3cc714da6fbd77981209bf32fa823c03dbaff8 \
--hash=sha256:0eae72190be519bf2629062eab7ac8d4ceec5bd132953cefa1596584d86964fe \
--hash=sha256:15e0ac0ed8a85f6049e836e95ddee627766561c85be8d23f4b3edb6ddbaa7310 \
--hash=sha256:161dc52a617f0bb610a87d391cb2e77fe65b89ebfbd752f4f3217dde701ea196 \
--hash=sha256:181ac372a5a5308b4076933601a9b5f0cd139b389b0aa5e164786a2abbcdb978 \
--hash=sha256:1c22c59ab7d9dc110d409445f111f58556bf699b0548f3fc5176684a29c629c4 \
--hash=sha256:226f11be577b70a57f4910c0ee28591d4d9fcb3d455e966267179156ae2e0c41 \
--hash=sha256:24d627ed69e754c48dd142a914124858c600b4108c92546eb0ba822e63c0c6e2 \
--hash=sha256:2535f44b00f26f6af0e949c825e6aecb9adcb56c965c17af5b97137fb69f00c0 \
--hash=sha256:25e0517ad7ee3c5c3c69dbe3c1d95504c811e42f452b39a3505d0763b1f6caa0 \
--hash=sha256:2903bf90b1e6bfc9bbfc94a1db0b50ffa9830a0ca4c042fbc38d93890c02ce08 \
--hash=sha256:2f1ded23d17af0d738e7e78087f0b88a53228887845b1989b03af4dfd3fef703 \
--hash=sha256:30200b07779446760813eef06098ec6d084131e4365b4e023eb43100de758b11 \
--hash=sha256:33ac8b4754e6b6b21f3ee180da169d8526d91aee9408ec1fc573c16ab32b0207 \
--hash=sha256:34fd249275faa782c3a2016e86ac2330636ac58d731a1580e7d686e3976b9536 \
--hash=sha256:44f5dc9b4384bafca8429759ce76c8960ffc2b583fcad9e5dfb3e5f4894269e4 \
--hash=sha256:451550e0bb5889bbabbf92575a6d6eafced941cc28c86be6ae4667f81bf32d67 \
--hash=sha256:52383e932e6de5595963f9178cf2af7b9e1f3daacf5135b9c0e21aabbc5bf7c4 \
--hash=sha256:55137faec669c4277c5687c6ce7c1fbc4dece0e2f14256ee808f4a652f0a2170 \
--hash=sha256:576b9dfbcd154a0e8b5d9dae6316d037450e64a3b31df87dec71d88e2a2d5e5f \
--hash=sha256:59a3010d566a48b919490a982f6807f68842686941dc12d568e129d9cd7703d6 \
--hash=sha256:61c6a258469c66412ae8358a0501df6ccb3bb48aa9c43b56624571ff9767f91d \
--hash=sha256:63edc507f8cbfbb5903adb75bad8a99f9798981c854df9119dbebab2ec3ee0e1 \
--hash=sha256:65d5f4e70a2d3fbaa1349236968792611088f3f2dccead36c1626e1d183cc327 \
--hash=sha256:6a1618260a112a9c93504511f0b6254b4402a8c41b7130dc6d4c9e39aff3aa0c \
--hash=sha256:704f1fcdc5b606b70563ea696c69bda90caee3a2f45ffc9cee60a901b394a79f \
--hash=sha256:7751b11cd7f6b952b4b5ec5b93b5be9ce20faba786c18c25c354f5d8717a173c \
--hash=sha256:7ad9d032dc1a31a86ca7b059f43554a049a2bfda8fe32d1492ad25f6686aff03 \
--hash=sha256:7b01d07006a0ac2216921b69a220b9f0974345d0b1b36efaeabdc7550b1cc4f8 \
--hash=sha256:7b47643c45e7619788c081d42e1d9d98c7c8a4933010a9967d097cc3c4c29f41 \
--hash=sha256:80ed219ce6cb21a5b53ead0edf5b56b6d23de4cb95389ac606f47670474f4816 \
--hash=sha256:82df4a8600999c4c0cb7d6614df1bbdb3c74732f63e79f78487893ffbed3d083 \
--hash=sha256:8660112e9127a019969a23c878e1b4a419e8a6427f9a9050c19830f152628c8a \
--hash=sha256:89a86c2b35460700d04b4d6461153ab39ee85af5a5385acac9563a8310e6320a \
--hash=sha256:8d7bc25729bb6d96b44f49ad78fde0e27a1a867cb205322b7e5f5b49e04d6f1f \
--hash=sha256:97e4f3d9b17d12e7c00cb1c29c0040044135cd5146838da4274615dbe0baae78 \
--hash=sha256:a431deb6ffdfa551f7400b3a94fa4b964837e67f49e3c37aa26d90dc75970816 \
--hash=sha256:a6a2d3d75d8698dee492f4af7ad07606d0734e581edf9e2ce2f74b6fce90f42e \
--hash=sha256:ae5b41dbf7731b838021923edfbe3b5ccdec84d92d5795f5229c0d08d32509d9 \
--hash=sha256:aff258af03dda9a990960a53759d10c3a9b936837c71fe2f3b581acd356b9121 \
--hash=sha256:b216a15e13f6e763db40ac3beb74b588650bc030d10a78fde182b88d273b82b5 \
--hash=sha256:b23b25b1243576b952689966205ef7d4285688068b966a1ca0e620bcb390d483 \
--hash=sha256:b896637091cde69d170a89253dde9aee814b25ca204b7e213fd0a6462e666638 \
--hash=sha256:d5f27b1d1b56470385faa2b2636fcb823e7ac5b5b734e0aa76b14637c66eb3b7 \
--hash=sha256:d6ba33f39436191ece7ea2b3d0b4dff00af71acd5c6e6f1d6b7563aa7286e9f2 \
--hash=sha256:d6c5e1df6f427d7a82606cf8f07cf3ba9fb3f366804b01e65f1f00f8df6b54f1 \
--hash=sha256:e02f77b620ad6b36564fe41980865436912e21a3b1138cdde175cf24afde1bc5 \
--hash=sha256:e72491d72870c3cb2f0d6f4174485533caec0e9ed7e717e2859b7cc7ff2ae1c4 \
--hash=sha256:ea8d5cd689fa7225d81ae0a049ba03e0165f4ed9ca083b19a405be9ad0b36845 \
--hash=sha256:eb5341fc7c53fdd95ac2415be77b1de854ab266488cff71174ebb007baf0e675 \
--hash=sha256:edf0a66ce9517365c7dcfed597894d8dd1f27b59e550b77a089054101435213b \
--hash=sha256:f225784812b2b57d340f2eb0d2cebef989dcc82c288f5553e28ee9767c7c8344 \
--hash=sha256:f5fbb3b325c65010e04af206a9243e2df8606736c510c7f268aca6a93e5294a9 \
--hash=sha256:f78cafa25731e0b5aa16fe20bea1abf643d4e853f6bfb8a64421b06b878e2b88 \
--hash=sha256:fb639a0e65dce4a9cccbcbdd8ddd0c8c6ab10bca317b827a5c52ac3c3a4ad60a \
--hash=sha256:ffb2f288f577a748cc23c65a818290755a4c2da1f87a40d7055b61a096d31e20
# via -r third_party/py/requirements.in
pycparser==2.21 \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
@ -379,6 +621,14 @@ pyelftools==0.26 \
--hash=sha256:86ac6cee19f6c945e8dedf78c6ee74f1112bd14da5a658d8c9d4103aed5756a2 \
--hash=sha256:cc0ea0de82b240a73ef4056fce44acbb4727dca7d66759371aff2bad457ed711
# via -r third_party/py/requirements.in
pygments==2.14.0 \
--hash=sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297 \
--hash=sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717
# via readme-renderer
pyjwt[crypto]==2.6.0 \
--hash=sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd \
--hash=sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14
# via django-allauth
pynacl==1.3.0 \
--hash=sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255 \
--hash=sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c \
@ -410,29 +660,104 @@ python-dateutil==2.8.0 \
# via
# -r third_party/py/requirements.in
# arrow
# hyperkitty
python3-openid==3.2.0 \
--hash=sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf \
--hash=sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b
# via django-allauth
pytz==2022.7.1 \
--hash=sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0 \
--hash=sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a
# via
# -r third_party/py/requirements.in
# django
# django-mailman3
# djangorestframework
# hyperkitty
rcssmin==1.1.1 \
--hash=sha256:271e3d2f8614a6d4637ed8fff3d90007f03e2a654cd9444f37d888797662ba72 \
--hash=sha256:35da6a6999e9e2c5b0e691b42ed56cc479373e0ecab33ef5277dfecce625e44a \
--hash=sha256:42576d95dfad53d77df2e68dfdec95b89b10fad320f241f1af3ca1438578254a \
--hash=sha256:4f9400b4366d29f5f5446f58e78549afa8338e6a59740c73115e9f6ac413dc64 \
--hash=sha256:705c9112d0ed54ea40aecf97e7fd29bdf0f1c46d278a32d8f957f31dde90778a \
--hash=sha256:79421230dd67c37ec61ed9892813d2b839b68f2f48ef55c75f976e81701d60b4 \
--hash=sha256:868215e1fd0e92a6122e0ed5973dfc7bb8330fe1e92274d05b2585253b38c0ca \
--hash=sha256:8a26fec3c1e6b7a3765ccbaccc20fbb5c0ed3422cc381e01a2607f08d7621c44 \
--hash=sha256:8fcfd10ae2a1c4ce231a33013f2539e07c3836bf17cc945cc25cc30bf8e68e45 \
--hash=sha256:908fe072efd2432fb0975a61124609a8e05021367f6a3463d45f5e3e74c4fdda \
--hash=sha256:914e589f40573035006913861ed2adc28fbe70082a8b6bff5be7ee430b7b5c2e \
--hash=sha256:a04d58a2a21e9a089306d3f99c4b12bf5b656a79c198ef2321e80f8fd9afab06 \
--hash=sha256:a417735d4023d47d048a6288c88dbceadd20abaaf65a11bb4fda1e8458057019 \
--hash=sha256:c30f8bc839747b6da59274e0c6e4361915d66532e26448d589cb2b1846d7bf11 \
--hash=sha256:c7278c1c25bb90d8e554df92cfb3b6a1195004ead50f764653d3093933ee0877 \
--hash=sha256:c7728e3b546b1b6ea08cab721e8e21409dbcc11b881d0b87d10b0be8930af2a2 \
--hash=sha256:cf74d7ea5e191f0f344b354eed8b7c83eeafbd9a97bec3a579c3d26edf11b005 \
--hash=sha256:d0afc6e7b64ef30d6dcde88830ec1a237b9f16a39f920a8fd159928684ccf8db \
--hash=sha256:d4e263fa9428704fd94c2cb565c7519ca1d225217943f71caffe6741ab5b9df1 \
--hash=sha256:e923c105100ab70abde1c01d3196ddd6b07255e32073685542be4e3a60870c8e \
--hash=sha256:ee386bec6d62f8c814d65c011d604a7c82d24aa3f718facd66e850eea8d6a5a1 \
--hash=sha256:f15673e97f0a68b4c378c4d15b088fe96d60bc106d278c88829923118833c20f \
--hash=sha256:f7a1fcdbafaacac0530da04edca4a44303baab430ea42e7d59aece4b3f3e9a51
# via django-compressor
readme-renderer[md]==37.3 \
--hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \
--hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343
# via postorius
redis==3.5.3 \
--hash=sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2 \
--hash=sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24
# via django-q
requests==2.22.0 \
--hash=sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4 \
--hash=sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31
# via
# -r third_party/py/requirements.in
# django-allauth
# mailmanclient
# requests-oauthlib
requests-oauthlib==1.3.0 \
--hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \
--hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a
# via -r third_party/py/requirements.in
# via
# -r third_party/py/requirements.in
# django-allauth
rjsmin==1.2.1 \
--hash=sha256:113132a40ce7d03b2ced4fac215f0297338ed1c207394b739266efab7831988b \
--hash=sha256:122aa52bcf7ad9f12728d309012d1308c6ecfe4d6b09ea867a110dcad7b7728c \
--hash=sha256:145c6af8df42d8af102d0d39a6de2e5fa66aef9e38947cfb9d65377d1b9940b2 \
--hash=sha256:1f982be8e011438777a94307279b40134a3935fc0f079312ee299725b8af5411 \