diff --git a/core/admin/Dockerfile b/core/admin/Dockerfile index 8dda76d2..1958ae61 100644 --- a/core/admin/Dockerfile +++ b/core/admin/Dockerfile @@ -27,7 +27,7 @@ ENV TZ Etc/UTC # python3 shared with most images RUN set -eu \ - && apk add --no-cache python3 py3-pip git bash tzdata \ + && apk add --no-cache python3 py3-pip py3-wheel git bash tzdata \ && pip3 install --upgrade pip RUN mkdir -p /app @@ -37,13 +37,15 @@ COPY requirements-prod.txt requirements.txt RUN set -eu \ && apk add --no-cache libressl curl postgresql-libs mariadb-connector-c \ && apk add --no-cache --virtual build-dep libressl-dev libffi-dev python3-dev build-base postgresql-dev mariadb-connector-c-dev cargo \ - && pip3 install -r requirements.txt \ + && pip install --upgrade pip \ + && pip install -r requirements.txt \ && apk del --no-cache build-dep COPY --from=assets static ./mailu/static COPY mailu ./mailu COPY migrations ./migrations COPY start.py /start.py +COPY audit.py /audit.py RUN pybabel compile -d mailu/translations diff --git a/core/admin/assets/app.css b/core/admin/assets/app.css index 3886b5c1..84644900 100644 --- a/core/admin/assets/app.css +++ b/core/admin/assets/app.css @@ -52,3 +52,8 @@ fieldset:disabled .form-control:disabled { .select2-container--default .select2-selection--multiple .select2-selection__choice { color: black; } + +/* range input spacing */ +.input-group-text { + margin-right: 1em; +} diff --git a/core/admin/assets/app.js b/core/admin/assets/app.js index 54602d1f..03ea6215 100644 --- a/core/admin/assets/app.js +++ b/core/admin/assets/app.js @@ -18,7 +18,7 @@ $('document').ready(function() { $.post({ url: $(this).attr('href'), success: function() { - location.reload(); + window.location = window.location.href; }, }); }); @@ -28,10 +28,10 @@ $('document').ready(function() { var fieldset = $(this).parents('fieldset'); if (this.checked) { fieldset.removeAttr('disabled'); - fieldset.find('input').not(this).removeAttr('disabled'); + fieldset.find('input,textarea').not(this).removeAttr('disabled'); } else { fieldset.attr('disabled', ''); - fieldset.find('input').not(this).attr('disabled', ''); + fieldset.find('input,textarea').not(this).attr('disabled', ''); } }); @@ -43,7 +43,9 @@ $('document').ready(function() { var infinity = $(this).data('infinity'); var step = $(this).attr('step'); $(this).on('input', function() { - value_element.text((infinity && this.value == 0) ? '∞' : (this.value/step).toFixed(2)); + var num = (infinity && this.value == 0) ? '∞' : (this.value/step).toFixed(2); + if (num.endsWith('.00')) num = num.substr(0, num.length - 3); + value_element.text(num); }).trigger('input'); } }); diff --git a/core/admin/audit.py b/core/admin/audit.py old mode 100644 new mode 100755 index db105ff4..60583f83 --- a/core/admin/audit.py +++ b/core/admin/audit.py @@ -1,14 +1,19 @@ -from mailu import app +#!/usr/bin/python3 import sys import tabulate +sys.path[0:0] = ['/app'] + +import mailu +app = mailu.create_app() + # Known endpoints without permissions known_missing_permissions = [ - "index", - "static", "bootstrap.static", - "admin.static", "admin.login" + 'index', + 'static', 'bootstrap.static', + 'admin.static', 'admin.login' ] @@ -16,7 +21,7 @@ known_missing_permissions = [ missing_permissions = [] permissions = {} for endpoint, function in app.view_functions.items(): - audit = function.__dict__.get("_audit_permissions") + audit = function.__dict__.get('_audit_permissions') if audit: handler, args = audit if args: @@ -28,16 +33,15 @@ for endpoint, function in app.view_functions.items(): elif endpoint not in known_missing_permissions: missing_permissions.append(endpoint) - -# Fail if any endpoint is missing a permission check -if missing_permissions: - print("The following endpoints are missing permission checks:") - print(missing_permissions.join(",")) - sys.exit(1) - - # Display the permissions table print(tabulate.tabulate([ [route, *permissions[route.endpoint]] for route in app.url_map.iter_rules() if route.endpoint in permissions ])) + +# Warn if any endpoint is missing a permission check +if missing_permissions: + print() + print('The following endpoints are missing permission checks:') + print(','.join(missing_permissions)) + diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index e4024e47..fe1f376c 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -33,7 +33,7 @@ def create_app_from_config(config): app.srs_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('SRS_KEY', 'utf-8'), 'sha256').digest() # Initialize list of translations - config.translations = { + app.config.translations = { str(locale): locale for locale in sorted( utils.babel.list_translations(), @@ -57,6 +57,15 @@ def create_app_from_config(config): config = app.config, ) + # Jinja filters + @app.template_filter() + def format_date(value): + return utils.flask_babel.format_date(value) if value else '' + + @app.template_filter() + def format_datetime(value): + return utils.flask_babel.format_datetime(value) if value else '' + # Import views from mailu import ui, internal, sso app.register_blueprint(ui.ui, url_prefix=app.config['WEB_ADMIN']) diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py index f4cf5c70..b60b8a3e 100644 --- a/core/admin/mailu/configuration.py +++ b/core/admin/mailu/configuration.py @@ -90,7 +90,7 @@ DEFAULT_CONFIG = { 'POD_ADDRESS_RANGE': None } -class ConfigManager(dict): +class ConfigManager: """ Naive configuration manager that uses environment only """ @@ -105,19 +105,16 @@ class ConfigManager(dict): def get_host_address(self, name): # if MYSERVICE_ADDRESS is defined, use this - if '{}_ADDRESS'.format(name) in os.environ: - return os.environ.get('{}_ADDRESS'.format(name)) + if f'{name}_ADDRESS' in os.environ: + return os.environ.get(f'{name}_ADDRESS') # otherwise use the host name and resolve it - return system.resolve_address(self.config['HOST_{}'.format(name)]) + return system.resolve_address(self.config[f'HOST_{name}']) def resolve_hosts(self): - self.config["IMAP_ADDRESS"] = self.get_host_address("IMAP") - self.config["POP3_ADDRESS"] = self.get_host_address("POP3") - self.config["AUTHSMTP_ADDRESS"] = self.get_host_address("AUTHSMTP") - self.config["SMTP_ADDRESS"] = self.get_host_address("SMTP") - self.config["REDIS_ADDRESS"] = self.get_host_address("REDIS") - if self.config["WEBMAIL"] != "none": - self.config["WEBMAIL_ADDRESS"] = self.get_host_address("WEBMAIL") + for key in ['IMAP', 'POP3', 'AUTHSMTP', 'SMTP', 'REDIS']: + self.config[f'{key}_ADDRESS'] = self.get_host_address(key) + if self.config['WEBMAIL'] != 'none': + self.config['WEBMAIL_ADDRESS'] = self.get_host_address('WEBMAIL') def __get_env(self, key, value): key_file = key + "_FILE" @@ -136,6 +133,7 @@ class ConfigManager(dict): return value def init_app(self, app): + # get current app config self.config.update(app.config) # get environment variables self.config.update({ @@ -149,9 +147,9 @@ class ConfigManager(dict): template = self.DB_TEMPLATES[self.config['DB_FLAVOR']] self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config) - self.config['RATELIMIT_STORAGE_URL'] = 'redis://{0}/2'.format(self.config['REDIS_ADDRESS']) - self.config['QUOTA_STORAGE_URL'] = 'redis://{0}/1'.format(self.config['REDIS_ADDRESS']) - self.config['SESSION_STORAGE_URL'] = 'redis://{0}/3'.format(self.config['REDIS_ADDRESS']) + self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2' + self.config['QUOTA_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/1' + self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3' self.config['SESSION_COOKIE_SAMESITE'] = 'Strict' self.config['SESSION_COOKIE_HTTPONLY'] = True self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=int(self.config['SESSION_LIFETIME'])) @@ -160,25 +158,7 @@ class ConfigManager(dict): self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s]) self.config['HOSTNAMES'] = ','.join(hostnames) self.config['HOSTNAME'] = hostnames[0] - # update the app config itself - app.config = self - def setdefault(self, key, value): - if key not in self.config: - self.config[key] = value - return self.config[key] + # update the app config + app.config.update(self.config) - def get(self, *args): - return self.config.get(*args) - - def keys(self): - return self.config.keys() - - def __getitem__(self, key): - return self.config.get(key) - - def __setitem__(self, key, value): - self.config[key] = value - - def __contains__(self, key): - return key in self.config diff --git a/core/admin/mailu/debug.py b/core/admin/mailu/debug.py index 7677901b..4d63f3c5 100644 --- a/core/admin/mailu/debug.py +++ b/core/admin/mailu/debug.py @@ -1,6 +1,6 @@ import flask_debugtoolbar -from werkzeug.contrib import profiler as werkzeug_profiler +from werkzeug.middleware.profiler import ProfilerMiddleware # Debugging toolbar @@ -10,7 +10,7 @@ toolbar = flask_debugtoolbar.DebugToolbarExtension() # Profiler class Profiler(object): def init_app(self, app): - app.wsgi_app = werkzeug_profiler.ProfilerMiddleware( + app.wsgi_app = ProfilerMiddleware( app.wsgi_app, restrictions=[30] ) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index f5fe3b5e..697e4df7 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -19,7 +19,8 @@ import os import hmac import smtplib import idna -import dns +import dns.resolver +import dns.exception from flask import current_app as app from sqlalchemy.ext import declarative @@ -38,6 +39,8 @@ class IdnaDomain(db.TypeDecorator): """ impl = db.String(80) + cache_ok = True + python_type = str def process_bind_param(self, value, dialect): """ encode unicode domain name to punycode """ @@ -47,13 +50,13 @@ class IdnaDomain(db.TypeDecorator): """ decode punycode domain name to unicode """ return idna.decode(value) - python_type = str - class IdnaEmail(db.TypeDecorator): """ Stores a Unicode string in it's IDNA representation (ASCII only) """ impl = db.String(255) + cache_ok = True + python_type = str def process_bind_param(self, value, dialect): """ encode unicode domain part of email address to punycode """ @@ -69,13 +72,13 @@ class IdnaEmail(db.TypeDecorator): localpart, domain_name = value.rsplit('@', 1) return f'{localpart}@{idna.decode(domain_name)}' - python_type = str - class CommaSeparatedList(db.TypeDecorator): """ Stores a list as a comma-separated string, compatible with Postfix. """ impl = db.String + cache_ok = True + python_type = list def process_bind_param(self, value, dialect): """ join list of items to comma separated string """ @@ -90,13 +93,13 @@ class CommaSeparatedList(db.TypeDecorator): """ split comma separated string to list """ return list(filter(bool, (item.strip() for item in value.split(',')))) if value else [] - python_type = list - class JSONEncoded(db.TypeDecorator): """ Represents an immutable structure as a json-encoded string. """ impl = db.String + cache_ok = True + python_type = str def process_bind_param(self, value, dialect): """ encode data as json """ @@ -106,8 +109,6 @@ class JSONEncoded(db.TypeDecorator): """ decode json to data """ return json.loads(value) if value else None - python_type = str - class Base(db.Model): """ Base class for all models """ diff --git a/core/admin/mailu/schemas.py b/core/admin/mailu/schemas.py index 191d01ac..00cbf464 100644 --- a/core/admin/mailu/schemas.py +++ b/core/admin/mailu/schemas.py @@ -145,6 +145,11 @@ class Logger: if history.has_changes() and history.deleted: before = history.deleted[-1] after = getattr(target, attr.key) + # we don't have ordered lists + if isinstance(before, list): + before = set(before) + if isinstance(after, list): + after = set(after) # TODO: this can be removed when comment is not nullable in model if attr.key == 'comment' and not before and not after: pass diff --git a/core/admin/mailu/sso/templates/form_sso.html b/core/admin/mailu/sso/templates/form_sso.html index b14e7600..d2451597 100644 --- a/core/admin/mailu/sso/templates/form_sso.html +++ b/core/admin/mailu/sso/templates/form_sso.html @@ -5,7 +5,7 @@
{{ macros.form_field(form.email) }} {{ macros.form_field(form.pw) }} - {{ macros.form_fields(fields, label=False, class="btn btn-default", spacing=False) }} + {{ macros.form_fields(fields, label=False, class="btn btn-default") }}
{%- endcall %} {%- endblock %} diff --git a/core/admin/mailu/sso/views/base.py b/core/admin/mailu/sso/views/base.py index 831949e7..390d5bbf 100644 --- a/core/admin/mailu/sso/views/base.py +++ b/core/admin/mailu/sso/views/base.py @@ -19,6 +19,7 @@ def login(): fields.append(form.submitAdmin) if str(app.config["WEBMAIL"]).upper() != "NONE": fields.append(form.submitWebmail) + fields = [fields] if form.validate_on_submit(): if form.submitAdmin.data: diff --git a/core/admin/mailu/ui/templates/alias/list.html b/core/admin/mailu/ui/templates/alias/list.html index 0b784d52..6b52165e 100644 --- a/core/admin/mailu/ui/templates/alias/list.html +++ b/core/admin/mailu/ui/templates/alias/list.html @@ -34,8 +34,8 @@ {{ alias }} {{ alias.destination|join(', ') or '-' }} {{ alias.comment or '' }} - {{ alias.created_at }} - {{ alias.updated_at or '' }} + {{ alias.created_at | format_date }} + {{ alias.updated_at | format_date }} {%- endfor %} diff --git a/core/admin/mailu/ui/templates/alternative/list.html b/core/admin/mailu/ui/templates/alternative/list.html index b56cd751..4ca9f3c8 100644 --- a/core/admin/mailu/ui/templates/alternative/list.html +++ b/core/admin/mailu/ui/templates/alternative/list.html @@ -19,6 +19,7 @@ {% trans %}Actions{% endtrans %} {% trans %}Name{% endtrans %} {% trans %}Created{% endtrans %} + {% trans %}Last edit{% endtrans %} @@ -28,7 +29,8 @@ {{ alternative }} - {{ alternative.created_at }} + {{ alternative.created_at | format_date }} + {{ alternative.updated_at | format_date }} {%- endfor %} diff --git a/core/admin/mailu/ui/templates/domain/list.html b/core/admin/mailu/ui/templates/domain/list.html index 6f6bc467..61c09151 100644 --- a/core/admin/mailu/ui/templates/domain/list.html +++ b/core/admin/mailu/ui/templates/domain/list.html @@ -46,8 +46,8 @@ {{ domain.users | count }} / {{ '∞' if domain.max_users == -1 else domain.max_users }} {{ domain.aliases | count }} / {{ '∞' if domain.max_aliases == -1 else domain.max_aliases }} {{ domain.comment or '' }} - {{ domain.created_at }} - {{ domain.updated_at or '' }} + {{ domain.created_at | format_date }} + {{ domain.updated_at | format_date }} {%- endfor %} diff --git a/core/admin/mailu/ui/templates/fetch/list.html b/core/admin/mailu/ui/templates/fetch/list.html index d9374fc6..60b214de 100644 --- a/core/admin/mailu/ui/templates/fetch/list.html +++ b/core/admin/mailu/ui/templates/fetch/list.html @@ -36,10 +36,10 @@ {{ fetch.protocol }}{{ 's' if fetch.tls else '' }}://{{ fetch.host }}:{{ fetch.port }} {{ fetch.username }} {% if fetch.keep %}{% trans %}yes{% endtrans %}{% else %}{% trans %}no{% endtrans %}{% endif %} - {{ fetch.last_check or '-' }} + {{ fetch.last_check | format_datetime or '-' }} {{ fetch.error or '-' }} - {{ fetch.created_at }} - {{ fetch.updated_at or '' }} + {{ fetch.created_at | format_date }} + {{ fetch.updated_at | format_date }} {%- endfor %} diff --git a/core/admin/mailu/ui/templates/macros.html b/core/admin/mailu/ui/templates/macros.html index 5143b697..0da069a9 100644 --- a/core/admin/mailu/ui/templates/macros.html +++ b/core/admin/mailu/ui/templates/macros.html @@ -18,17 +18,19 @@ {%- endif %} {%- endmacro %} -{%- macro form_fields(fields, prepend='', append='', label=True, spacing=True) %} - {%- if spacing %} +{%- macro form_fields(fields, prepend='', append='', label=True) %} {%- set width = (12 / fields|length)|int %} - {%- else %} - {%- set width = 0 %} - {% endif %}
{%- for field in fields %}
- {{ form_individual_field(field, prepend=prepend, append=append, label=label, **kwargs) }} + {%- if field.__class__.__name__ == 'list' %} + {%- for subfield in field %} + {{ form_individual_field(subfield, prepend=prepend, append=append, label=label, **kwargs) }} + {%- endfor %} + {%- else %} + {{ form_individual_field(field, prepend=prepend, append=append, label=label, **kwargs) }} + {%- endif %}
{%- endfor %}
diff --git a/core/admin/mailu/ui/templates/relay/list.html b/core/admin/mailu/ui/templates/relay/list.html index 07838273..80bc5338 100644 --- a/core/admin/mailu/ui/templates/relay/list.html +++ b/core/admin/mailu/ui/templates/relay/list.html @@ -32,8 +32,8 @@ {{ relay.name }} {{ relay.smtp or '-' }} {{ relay.comment or '' }} - {{ relay.created_at }} - {{ relay.updated_at or '' }} + {{ relay.created_at | format_date }} + {{ relay.updated_at | format_date }} {%- endfor %} diff --git a/core/admin/mailu/ui/templates/token/list.html b/core/admin/mailu/ui/templates/token/list.html index c3cc9b5c..d7c48737 100644 --- a/core/admin/mailu/ui/templates/token/list.html +++ b/core/admin/mailu/ui/templates/token/list.html @@ -20,6 +20,7 @@ {% trans %}Comment{% endtrans %} {% trans %}Authorized IP{% endtrans %} {% trans %}Created{% endtrans %} + {% trans %}Last edit{% endtrans %} @@ -30,7 +31,8 @@ {{ token.comment }} {{ token.ip or "any" }} - {{ token.created_at }} + {{ token.created_at | format_date }} + {{ token.updated_at | format_date }} {%- endfor %} diff --git a/core/admin/mailu/ui/templates/user/list.html b/core/admin/mailu/ui/templates/user/list.html index 59a06ea7..7faddab5 100644 --- a/core/admin/mailu/ui/templates/user/list.html +++ b/core/admin/mailu/ui/templates/user/list.html @@ -45,8 +45,8 @@ {{ user.quota_bytes_used | filesizeformat }} / {{ (user.quota_bytes | filesizeformat) if user.quota_bytes else '∞' }} {{ user.comment or '-' }} - {{ user.created_at }} - {{ user.updated_at or '' }} + {{ user.created_at | format_date }} + {{ user.updated_at | format_date }} {%- endfor %} diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index f394ce7d..a48bb154 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -5,7 +5,6 @@ from flask import current_app as app import flask import flask_login import wtforms_components -import dns.resolver @ui.route('/domain', methods=['GET']) diff --git a/core/admin/mailu/utils.py b/core/admin/mailu/utils.py index e46ad7d9..024c487f 100644 --- a/core/admin/mailu/utils.py +++ b/core/admin/mailu/utils.py @@ -6,18 +6,21 @@ try: except ImportError: import pickle -import dns import dns.resolver +import dns.exception +import dns.flags +import dns.rdtypes +import dns.rdatatype +import dns.rdataclass import hmac import secrets import time from multiprocessing import Value - from mailu import limiter - from flask import current_app as app + import flask import flask_login import flask_migrate @@ -28,7 +31,7 @@ import redis from flask.sessions import SessionMixin, SessionInterface from itsdangerous.encoding import want_bytes from werkzeug.datastructures import CallbackDict -from werkzeug.contrib import fixers +from werkzeug.middleware.proxy_fix import ProxyFix # Login configuration login = flask_login.LoginManager() @@ -106,7 +109,7 @@ class PrefixMiddleware(object): return self.app(environ, start_response) def init_app(self, app): - self.app = fixers.ProxyFix(app.wsgi_app, x_for=1, x_proto=1) + self.app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1) app.wsgi_app = self proxy = PrefixMiddleware() @@ -265,7 +268,7 @@ class MailuSession(CallbackDict, SessionMixin): # set uid from dict data if self._uid is None: - self._uid = self.app.session_config.gen_uid(self.get('user_id', '')) + self._uid = self.app.session_config.gen_uid(self.get('_user_id', '')) # create new session id for new or regenerated sessions and force setting the cookie if self._sid is None: diff --git a/core/admin/migrations/env.py b/core/admin/migrations/env.py index 3e45bb18..65a4e471 100755 --- a/core/admin/migrations/env.py +++ b/core/admin/migrations/env.py @@ -1,10 +1,12 @@ -from __future__ import with_statement +import logging +import tenacity + from alembic import context from sqlalchemy import engine_from_config, pool from logging.config import fileConfig -import logging -import tenacity -from tenacity import retry + +from flask import current_app +from mailu import models # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -17,20 +19,12 @@ logger = logging.getLogger('alembic.env') # add your model's MetaData object here # for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -from flask import current_app -config.set_main_option('sqlalchemy.url', - current_app.config.get('SQLALCHEMY_DATABASE_URI')) -#target_metadata = current_app.extensions['migrate'].db.metadata -from mailu import models +config.set_main_option( + 'sqlalchemy.url', + current_app.config.get('SQLALCHEMY_DATABASE_URI') +) target_metadata = models.Base.metadata -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - def run_migrations_offline(): """Run migrations in 'offline' mode. @@ -44,7 +38,7 @@ def run_migrations_offline(): script output. """ - url = config.get_main_option("sqlalchemy.url") + url = config.get_main_option('sqlalchemy.url') context.configure(url=url) with context.begin_transaction(): @@ -69,28 +63,35 @@ def run_migrations_online(): directives[:] = [] logger.info('No changes in schema detected.') - engine = engine_from_config(config.get_section(config.config_ini_section), - prefix='sqlalchemy.', - poolclass=pool.NullPool) + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix = 'sqlalchemy.', + poolclass = pool.NullPool + ) - connection = tenacity.Retrying( - stop=tenacity.stop_after_attempt(100), - wait=tenacity.wait_random(min=2, max=5), - before=tenacity.before_log(logging.getLogger("tenacity.retry"), logging.DEBUG), - before_sleep=tenacity.before_sleep_log(logging.getLogger("tenacity.retry"), logging.INFO), - after=tenacity.after_log(logging.getLogger("tenacity.retry"), logging.DEBUG) - ).call(engine.connect) + @tenacity.retry( + stop = tenacity.stop_after_attempt(100), + wait = tenacity.wait_random(min=2, max=5), + before = tenacity.before_log(logging.getLogger('tenacity.retry'), logging.DEBUG), + before_sleep = tenacity.before_sleep_log(logging.getLogger('tenacity.retry'), logging.INFO), + after = tenacity.after_log(logging.getLogger('tenacity.retry'), logging.DEBUG) + ) + def try_connect(db): + return db.connect() - context.configure(connection=connection, - target_metadata=target_metadata, - process_revision_directives=process_revision_directives, - **current_app.extensions['migrate'].configure_args) + with try_connect(engine) as connection: + + context.configure( + connection = connection, + target_metadata = target_metadata, + process_revision_directives = process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) - try: with context.begin_transaction(): context.run_migrations() - finally: - connection.close() + + connection.close() if context.is_offline_mode(): run_migrations_offline() diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index 3406122d..d6c7aca3 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -1,56 +1,75 @@ -alembic==1.0.10 -asn1crypto==0.24.0 -Babel==2.6.0 -bcrypt==3.1.6 +alembic==1.7.4 +appdirs==1.4.4 +Babel==2.9.1 +bcrypt==3.2.0 blinker==1.4 -cffi==1.12.3 -Click==7.0 -cryptography==3.4.7 -decorator==4.4.0 -dnspython==1.16.0 -dominate==2.3.5 -Flask==1.0.2 -Flask-Babel==0.12.2 +CacheControl==0.12.9 +certifi==2021.10.8 +cffi==1.15.0 +chardet==4.0.0 +click==8.0.3 +colorama==0.4.4 +contextlib2==21.6.0 +cryptography==35.0.0 +decorator==5.1.0 +# distlib==0.3.1 +# distro==1.5.0 +dnspython==2.1.0 +dominate==2.6.0 +email-validator==1.1.3 +Flask==2.0.2 +Flask-Babel==2.0.0 Flask-Bootstrap==3.3.7.1 -Flask-DebugToolbar==0.10.1 -Flask-Limiter==1.0.1 -Flask-Login==0.4.1 +Flask-DebugToolbar==0.11.0 +Flask-Limiter==1.4 +Flask-Login==0.5.0 flask-marshmallow==0.14.0 -Flask-Migrate==2.4.0 +Flask-Migrate==3.1.0 Flask-Script==2.0.6 -Flask-SQLAlchemy==2.4.0 -Flask-WTF==0.14.2 +Flask-SQLAlchemy==2.5.1 +Flask-WTF==0.15.1 +greenlet==1.1.2 gunicorn==20.1.0 -idna==2.8 -infinity==1.4 -intervals==0.8.1 -itsdangerous==1.1.0 -Jinja2==2.11.3 -limits==1.3 -Mako==1.0.9 -MarkupSafe==1.1.1 -mysqlclient==1.4.2.post1 -marshmallow==3.10.0 -marshmallow-sqlalchemy==0.24.1 +html5lib==1.1 +idna==3.3 +infinity==1.5 +intervals==0.9.2 +itsdangerous==2.0.1 +Jinja2==3.0.2 +limits==1.5.1 +lockfile==0.12.2 +Mako==1.1.5 +MarkupSafe==2.0.1 +marshmallow==3.14.0 +marshmallow-sqlalchemy==0.26.1 +msgpack==1.0.2 +mysqlclient==2.0.3 +ordered-set==4.0.2 +# packaging==20.9 passlib==1.7.4 -psycopg2==2.8.2 -pycparser==2.19 -Pygments==2.8.1 -pyOpenSSL==20.0.1 -python-dateutil==2.8.0 -python-editor==1.0.4 -pytz==2019.1 -PyYAML==5.4.1 -redis==3.2.1 -#alpine3:12 provides six==1.15.0 -#six==1.12.0 -socrate==0.1.1 -SQLAlchemy==1.3.3 +# pep517==0.10.0 +progress==1.6 +psycopg2==2.9.1 +pycparser==2.20 +Pygments==2.10.0 +pyOpenSSL==21.0.0 +pyparsing==3.0.4 +pytz==2021.3 +PyYAML==6.0 +redis==3.5.3 +requests==2.26.0 +retrying==1.3.3 +# six==1.15.0 +socrate==0.2.0 +SQLAlchemy==1.4.26 srslib==0.1.4 -tabulate==0.8.3 -tenacity==5.0.4 -validators==0.12.6 +tabulate==0.8.9 +tenacity==8.0.1 +toml==0.10.2 +urllib3==1.26.7 +validators==0.18.2 visitor==0.1.3 -Werkzeug==0.15.5 -WTForms==2.2.1 -WTForms-Components==0.10.4 +webencodings==0.5.1 +Werkzeug==2.0.2 +WTForms==2.3.3 +WTForms-Components==0.10.5 diff --git a/core/admin/requirements.txt b/core/admin/requirements.txt index e1de3b01..65130a8c 100644 --- a/core/admin/requirements.txt +++ b/core/admin/requirements.txt @@ -18,10 +18,8 @@ PyYAML PyOpenSSL Pygments dnspython -bcrypt tenacity mysqlclient -psycopg2 idna srslib marshmallow