diff --git a/.gitignore b/.gitignore index 9cafb92..ade15d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ -data/ -.*.swp -*.pyc *.egg-info/ -.coverage -.ropeproject/ *.mo *.pot -tags -debianmemberportfolio/model/keyringcache.db +*.pyc +.*.swp +.coverage .idea/ +.ropeproject/ +/dist/ +data/ +debianmemberportfolio/model/keyringcache.db +tags diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..87d3ab4 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,14 @@ +--- +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/source/conf.py + +python: + install: + - requirements: docs/doc-requirements.txt diff --git a/ChangeLog b/ChangeLog index 7cb22ef..8c45a78 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2023-06-03 Jan Dittberner + * add updated translations from Weblate + * switch to Poetry for dependency management + * describe translation workflow in developer documentation + * fix internal server error when name is missing for non Debian member (thanks Paul Wise for the report) + * fix unicode handling in urlbuilder (thanks Paul Wise for the report) + +2022-09-24 Jan Dittberner + * add updated translations from Weblate + * update dependencies + * replace gpg and pgp with OpenPGP (thanks Guillem Jover) + * remove flattr code + +2021-10-24 Jan Dittberner + * add updated translations from Weblate + * fix NM URL (thanks Diederik de Haas) + +2021-07-03 Jan Dittberner + * Add translations from Weblate + * remove a dead services (thanks Juri Grabowski) + 2020-02-22 Jan Dittberner * Update translations diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ea677b --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Debian Member Portfolio Service + +This is a service implementation that returns a set of personalized URLs as outlined in +https://wiki.debian.org/DDPortfolio. It takes the Debian Member's full name and email address as input and returns +a JSON formatted array or an HTML page of URLs. + +See https://debian-member-portfolio-service.readthedocs.org/ for more documentation (or its source in +docs/source/devdocs.rst), including how to configure a development environment. + +## Translations + +Translations for the Debian Member Portfolio service are maintained using +[Weblate](https://hosted.weblate.org/projects/debian-member-portfolio-service/translations/). Thanks to Weblate for +hosting the translation service and to all contributors of translations. diff --git a/README.txt b/README.txt deleted file mode 100644 index 1748835..0000000 --- a/README.txt +++ /dev/null @@ -1,11 +0,0 @@ - -This is the source code for the Debian Member Portfolio Service -application [0]. - -Cf. https://debian-member-portfolio-service.readthedocs.org/ for more -documentation (or its source in docs/source/devdocs.rst), including -how to configure a development environment. - - -[0] https://wiki.debian.org/DDPortfolio - diff --git a/debianmemberportfolio/forms.py b/debianmemberportfolio/forms.py index 24872ee..fc3d132 100644 --- a/debianmemberportfolio/forms.py +++ b/debianmemberportfolio/forms.py @@ -3,7 +3,7 @@ # # Debian Member Portfolio Service views # -# Copyright © 2015-2020 Jan Dittberner +# Copyright © 2015-2022 Jan Dittberner # # This file is part of the Debian Member Portfolio Service. # @@ -51,7 +51,7 @@ def gpg_fingerprint(data): class DeveloperData(FlaskForm): email = StringField('email', validators=[DataRequired(), Email()]) name = StringField('name', validators=[Optional(), DataRequired()]) - gpgfp = StringField('gpgfp', filters=[gpg_fingerprint], validators=[ + openpgpfp = StringField('openpgpfp', filters=[gpg_fingerprint], validators=[ Optional(), FingerPrint(), Length(min=32, max=40) ]) username = StringField('username', validators=[Optional(), PlainText()]) diff --git a/debianmemberportfolio/model/dddatabuilder.py b/debianmemberportfolio/model/dddatabuilder.py index c168489..1fa5be1 100644 --- a/debianmemberportfolio/model/dddatabuilder.py +++ b/debianmemberportfolio/model/dddatabuilder.py @@ -3,7 +3,7 @@ # # Debian Member Portfolio Service data builder # -# Copyright © 2009-2015 Jan Dittberner +# Copyright © 2009-2022 Jan Dittberner # # This file is part of the Debian Member Portfolio Service. # @@ -39,13 +39,13 @@ def build_data(email_address): """Build a DD data structure from a given email address.""" fields = dict([(field, func(str(email_address))) for (field, func) in - [('gpgfp', keyfinder.getFingerprintByEmail), + [('openpgpfp', keyfinder.getFingerprintByEmail), ('name', keyfinder.getRealnameByEmail), ('username', keyfinder.getLoginByEmail)]]) fields['email'] = email_address - if fields['username'] and fields['gpgfp'] and fields['name']: + if fields['username'] and fields['openpgpfp'] and fields['name']: fields['type'] = TYPE_DD - elif fields['name'] and fields['gpgfp']: + elif fields['name'] and fields['openpgpfp']: fields['type'] = TYPE_DM else: fields['type'] = TYPE_NO diff --git a/debianmemberportfolio/model/keyfinder.py b/debianmemberportfolio/model/keyfinder.py index 5129e54..044648a 100644 --- a/debianmemberportfolio/model/keyfinder.py +++ b/debianmemberportfolio/model/keyfinder.py @@ -3,7 +3,7 @@ # # Debian Member Portfolio Service key finder module # -# Copyright © 2009-2015 Jan Dittberner +# Copyright © 2009-2023 Jan Dittberner # # This file is part of the Debian Member Portfolio Service. # @@ -21,42 +21,39 @@ # along with this program. If not, see . # """ -This module provides tools for finding PGP key information from a +This module provides tools for finding OpenPGP key information from a given keyring. """ import logging -import time import sys +import time +from importlib import resources db = None -cachetimestamp = 0 +cache_timestamp = 0 def _get_keyring_cache(): - global db, cachetimestamp - if db is None or (time.time() - cachetimestamp) > 86300: + global db, cache_timestamp + if db is None or (time.time() - cache_timestamp) > 86300: import dbm - import pkg_resources import os.path - filename = pkg_resources.resource_filename(__name__, - 'keyringcache') - logging.debug('reading cache data from %s', filename) - assert ( - os.path.exists(filename + '.db') and - os.path.isfile(filename + '.db') - ) - db = dbm.open(filename, 'r') - cachetimestamp = time.time() + + dbm_filename = str(resources.files(__package__).joinpath("keyringcache.db")) + logging.debug("reading cache data from %s", dbm_filename) + assert os.path.exists(dbm_filename) and os.path.isfile(dbm_filename) + db = dbm.open(dbm_filename[: -len(".db")], "r") + cache_timestamp = time.time() return db def _get_cached(cachekey): cache = _get_keyring_cache() - logging.debug('cache lookup for %s', cachekey) + logging.debug("cache lookup for %s", cachekey) if cachekey in cache: - logging.debug('found entry %s', cache[cachekey]) - return cache[cachekey].decode('utf8') + logging.debug("found entry %s", cache[cachekey]) + return cache[cachekey].decode("utf8") return None @@ -65,7 +62,7 @@ def getFingerprintByEmail(email): Gets the fingerprints associated with the given email address if available. """ - return _get_cached('fpr:email:%s' % email) + return _get_cached("fpr:email:%s" % email) def getRealnameByEmail(email): @@ -73,7 +70,7 @@ def getRealnameByEmail(email): Gets the real names associated with the given email address if available. """ - return _get_cached('name:email:%s' % email) + return _get_cached("name:email:%s" % email) def getLoginByEmail(email): @@ -81,34 +78,34 @@ def getLoginByEmail(email): Gets the logins associated with the given email address if available. """ - return _get_cached('login:email:%s' % email) + return _get_cached("login:email:%s" % email) def getLoginByFingerprint(fpr): """ Gets the login associated with the given fingerprint if available. """ - return _get_cached('login:fpr:%s' % fpr) + return _get_cached("login:fpr:%s" % fpr) def _dump_cache(): cache = _get_keyring_cache() fprs = [] - for key in [key.decode('utf8') for key in list(cache.keys())]: - if key.startswith('email:fpr:'): - fpr = key.replace('email:fpr:', '') + for key in [key.decode("utf8") for key in list(cache.keys())]: + if key.startswith("email:fpr:"): + fpr = key.replace("email:fpr:", "") if not fpr in fprs: fprs.append(fpr) for fpr in fprs: login = getLoginByFingerprint(fpr) - email = _get_cached('email:fpr:%s' % fpr) - name = _get_cached('name:fpr:%s' % fpr) + email = _get_cached("email:fpr:%s" % fpr) + name = _get_cached("name:fpr:%s" % fpr) - print(fpr, login, ':') - print(' ', name, email) + print(fpr, login, ":") + print(" ", name, email) -if __name__ == '__main__': +if __name__ == "__main__": logging.basicConfig(stream=sys.stderr, level=logging.WARNING) _dump_cache() diff --git a/debianmemberportfolio/model/keyringanalyzer.py b/debianmemberportfolio/model/keyringanalyzer.py index 9ea3fab..0a007b8 100644 --- a/debianmemberportfolio/model/keyringanalyzer.py +++ b/debianmemberportfolio/model/keyringanalyzer.py @@ -3,7 +3,7 @@ # # Debian Member Portfolio Service application key ring analyzer tool # -# Copyright © 2009-2015 Jan Dittberner +# Copyright © 2009-2023 Jan Dittberner # # This file is part of the Debian Member Portfolio Service. # @@ -21,22 +21,21 @@ # along with this program. If not, see . # """ -This is a tool that analyzes GPG and PGP keyrings and stores the +This is a tool that analyzes GPG and PGP key rings and stores the retrieved data in a file database. The tool was inspired by Debian qa's carnivore. """ -import dbm -import pkg_resources -import glob import configparser +import dbm +import email.utils +import glob +import logging import os import os.path -import logging import subprocess import sys -import email.utils - +from importlib import resources CONFIG = configparser.ConfigParser() @@ -46,18 +45,18 @@ def _get_keyrings(): Gets the available keyring files from the keyring directory configured in portfolio.ini. """ - keyringdir = os.path.expanduser(CONFIG.get('DEFAULT', 'keyring.dir')) - logging.debug("keyring dir is %s", keyringdir) - keyrings = glob.glob(os.path.join(keyringdir, '*.gpg')) - keyrings.extend(glob.glob(os.path.join(keyringdir, '*.pgp'))) + keyring_dir = os.path.expanduser(CONFIG.get("DEFAULT", "keyring.dir")) + logging.debug("keyring dir is %s", keyring_dir) + keyrings = glob.glob(os.path.join(keyring_dir, "*.gpg")) + keyrings.extend(glob.glob(os.path.join(keyring_dir, "*.pgp"))) keyrings.sort() return keyrings def _parse_uid(uid): """ - Parse a uid of the form 'Real Name ' into email - and realname parts. + Parse an uid of the form 'Real Name ' into email + and real name parts. """ # First try with the Python library, but it doesn't always catch everything @@ -67,63 +66,65 @@ def _parse_uid(uid): if (not name) or (not mail): logging.debug("strange uid %s: '%s' - <%s>", uid, name, mail) # Try and do better than the python library - if not '@' in mail: + if "@" not in mail: uid = uid.strip() # First, strip comment - s = uid.find('(') - e = uid.find(')') + s = uid.find("(") + e = uid.find(")") if s >= 0 and e >= 0: - uid = uid[:s] + uid[e + 1:] - s = uid.find('<') - e = uid.find('>') + uid = uid[:s] + uid[e + 1 :] + s = uid.find("<") + e = uid.find(">") mail = None if s >= 0 and e >= 0: - mail = uid[s + 1:e] - uid = uid[:s] + uid[e + 1:] + mail = uid[s + 1 : e] + uid = uid[:s] + uid[e + 1 :] uid = uid.strip() - if not mail and uid.find('@') >= 0: + if not mail and uid.find("@") >= 0: mail, uid = uid, mail name = uid logging.debug("corrected: '%s' - <%s>", name, mail) - return (name, mail) + return name, mail -resultdict = {} + +result_dict = {} def _get_canonical(key): - if not key in resultdict: - resultdict[key] = [] + if key not in result_dict: + result_dict[key] = [] return key -def _add_to_result(key, newvalue): - logging.debug("adding %s: %s", key, newvalue) - thekey = _get_canonical(key) - if newvalue not in resultdict[thekey]: - resultdict[thekey].append(newvalue) +def _add_to_result(key, new_value): + logging.debug("adding %s: %s", key, new_value) + the_key = _get_canonical(key) + if new_value not in result_dict[the_key]: + result_dict[the_key].append(new_value) def _handle_mail(mail, fpr): - if mail.endswith('@debian.org'): - login = mail[0:-len('@debian.org')] - _add_to_result('login:email:%s' % mail, login) - _add_to_result('login:fpr:%s' % fpr, login) - _add_to_result('fpr:login:%s' % login, fpr) - _add_to_result('fpr:email:%s' % mail, fpr) - _add_to_result('email:fpr:%s' % fpr, mail) + if mail.endswith("@debian.org"): + login = mail[0 : -len("@debian.org")] + _add_to_result("login:email:%s" % mail, login) + _add_to_result("login:fpr:%s" % fpr, login) + _add_to_result("fpr:login:%s" % login, fpr) + _add_to_result("fpr:email:%s" % mail, fpr) + _add_to_result("email:fpr:%s" % fpr, mail) def _handle_uid(uid, fpr): + mail = None # Do stuff with 'uid' if uid: (uid, mail) = _parse_uid(uid) if mail: _handle_mail(mail, fpr) if uid: - _add_to_result('name:fpr:%s' % fpr, uid) + _add_to_result("name:fpr:%s" % fpr, uid) if mail: - _add_to_result('name:email:%s' % mail, uid) + _add_to_result("name:email:%s" % mail, uid) return fpr @@ -131,13 +132,13 @@ def process_gpg_list_keys_line(line, fpr): """ Process a line of gpg --list-keys --with-colon output. """ - items = line.split(':') - if items[0] == 'pub': + items = line.split(":") + if items[0] == "pub": return None - if items[0] == 'fpr': + if items[0] == "fpr": return items[9].strip() - if items[0] == 'uid': - if items[1] == 'r': + if items[0] == "uid": + if items[1] == "r": return fpr return _handle_uid(items[9].strip(), fpr) else: @@ -145,41 +146,54 @@ def process_gpg_list_keys_line(line, fpr): def process_keyrings(): - """Process the keyrings and store the extracted data in an anydbm - file.""" + """Process the keyrings and store the extracted data in an anydbm file.""" for keyring in _get_keyrings(): logging.debug("get data from %s", keyring) - proc = subprocess.Popen([ - "gpg", "--no-options", "--no-default-keyring", - "--homedir", os.path.expanduser( - CONFIG.get('DEFAULT', 'gnupghome')), - "--no-expensive-trust-checks", - "--keyring", keyring, "--list-keys", - "--with-colons", "--fixed-list-mode", "--with-fingerprint", - "--with-fingerprint"], - stdout=subprocess.PIPE) + proc = subprocess.Popen( + [ + "gpg", + "--no-options", + "--no-default-keyring", + "--homedir", + os.path.expanduser(CONFIG.get("DEFAULT", "gnupghome")), + "--no-expensive-trust-checks", + "--keyring", + keyring, + "--list-keys", + "--with-colons", + "--fixed-list-mode", + "--with-fingerprint", + "--with-fingerprint", + ], + stdout=subprocess.PIPE, + ) fpr = None for line in proc.stdout.readlines(): try: - line = line.decode('utf8') + line = line.decode("utf8") except UnicodeDecodeError: - line = line.decode('iso8859-1') + line = line.decode("iso8859-1") fpr = process_gpg_list_keys_line(line, fpr) - retcode = proc.wait() - if retcode != 0: - logging.error("subprocess ended with return code %d", retcode) - db = dbm.open(pkg_resources.resource_filename(__name__, - 'keyringcache'), 'c') - for key in resultdict: - db[key] = ":".join(resultdict[key]) + ret_code = proc.wait() + if ret_code != 0: + logging.error("subprocess ended with return code %d", ret_code) + dbm_filename = str( + resources.files("debianmemberportfolio.model").joinpath("keyringcache") + ) + db = dbm.open(dbm_filename, "c") + for key in result_dict: + db[key] = ":".join(result_dict[key]) db.close() -if __name__ == '__main__': +if __name__ == "__main__": logging.basicConfig(stream=sys.stderr, level=logging.WARNING) - CONFIG.read_string(pkg_resources.resource_string( - __name__, 'portfolio.ini').decode('utf8')) - gpghome = os.path.expanduser(CONFIG.get('DEFAULT', 'gnupghome')) - if not os.path.isdir(gpghome): - os.makedirs(gpghome, 0o700) + CONFIG.read_string( + resources.files("debianmemberportfolio.model") + .joinpath("portfolio.ini") + .read_text("utf8") + ) + gpg_home = os.path.expanduser(CONFIG.get("DEFAULT", "gnupghome")) + if not os.path.isdir(gpg_home): + os.makedirs(gpg_home, 0o700) process_keyrings() diff --git a/debianmemberportfolio/model/portfolio.ini b/debianmemberportfolio/model/portfolio.ini index 7cf278a..6baedad 100644 --- a/debianmemberportfolio/model/portfolio.ini +++ b/debianmemberportfolio/model/portfolio.ini @@ -1,7 +1,7 @@ # # Configuration for Debian Member Portfolio Service # -# Copyright © 2009-2020 Jan Dittberner +# Copyright © 2009-2022 Jan Dittberner # # This file is part of the Debian Member Portfolio Service. # @@ -44,13 +44,13 @@ urls=buildd buildd.pattern=https://buildd.debian.org/status/package.php?p=%(email)s&compact=compact&comaint=yes [qa] -urls=lintian,lintianfull,piuparts,patchtracker,dmd,duck,janitor +urls=lintian,lintianfull,piuparts,dmd,janitor dmd.pattern=https://udd.debian.org/dmd.cgi?email1=%(email)s lintian.pattern=https://lintian.debian.org/maintainer/%(email)s.html lintianfull.pattern=https://lintian.debian.org/full/%(email)s.html piuparts.pattern=https://piuparts.debian.org/sid/maintainer/%(firstchar)s/%(email)s.html -patchtracker.pattern=http://patch-tracker.debian.org/email/%(email)s -duck.pattern=http://duck.debian.net/persons/%(email)s.html +#patchtracker.pattern=http://patch-tracker.debian.org/email/%(email)s +#duck.pattern=http://duck.debian.net/persons/%(email)s.html janitor.pattern=https://janitor.debian.net/m/%(email)s [lists] @@ -69,15 +69,15 @@ people.pattern=https://people.debian.org/~%(username)s/ people.optional=true [membership] -urls=nm,dbfinger,db,webid,salsa,wiki,forum -nm.pattern=https://nm.debian.org/public/nmstatus/%(username)s +urls=nm,dbfinger,db,salsa,wiki,forum +nm.pattern=https://nm.debian.org/person/%(username)s dbfinger.pattern=finger %(username)s@db.debian.org dbfinger.type=finger dbfinger.optional=true db.pattern=https://db.debian.org/search.cgi?uid=%(username)s&dosearch=Search db.optional=true -webid.pattern=http://webid.debian.net/maintainers/%(username)s -webid.optional=true +#webid.pattern=http://webid.debian.net/maintainers/%(username)s +#webid.optional=true salsa.pattern=https://salsa.debian.org/%(salsausername)s salsa.optional=true wiki.pattern=https://wiki.debian.org/%(wikihomepage)s @@ -96,7 +96,7 @@ search.pattern=https://search.debian.org/cgi-bin/omega?P=%%22%(name)s%%22 gpgfinger.pattern=finger %(username)s/key@db.debian.org gpgfinger.type=finger gpgfinger.optional=true -gpgweb.pattern=https://db.debian.org/fetchkey.cgi?fingerprint=%(gpgfp)s +gpgweb.pattern=https://db.debian.org/fetchkey.cgi?fingerprint=%(openpgpfp)s gpgweb.optional=true nm.pattern=https://nm.debian.org/public/person/%(username)s contrib.pattern=https://contributors.debian.org/contributor/%(email)s @@ -119,6 +119,6 @@ groupinfo.pattern=ssh master.debian.org id %(username)s groupinfo.type=ssh groupinfo.optional=true -[ubuntu] -urls=ubuntudiff -ubuntudiff.pattern=http://ubuntudiff.debian.net/q/uploaders/%(email)s +#[ubuntu] +#urls=ubuntudiff +#ubuntudiff.pattern=http://ubuntudiff.debian.net/q/uploaders/%(email)s diff --git a/debianmemberportfolio/model/urlbuilder.py b/debianmemberportfolio/model/urlbuilder.py index cb2f33f..0f180ab 100644 --- a/debianmemberportfolio/model/urlbuilder.py +++ b/debianmemberportfolio/model/urlbuilder.py @@ -3,7 +3,7 @@ # # Debian Member Portfolio Service url builder # -# Copyright © 2009-2020 Jan Dittberner +# Copyright © 2009-2023 Jan Dittberner # # This file is part of the Debian Member Portfolio Service. # @@ -28,36 +28,40 @@ portfolio.ini. from configparser import ConfigParser, InterpolationMissingOptionError from encodings.utf_8 import StreamReader as UTF8StreamReader - -import pkg_resources -from debianmemberportfolio.model import keyfinder +from importlib import resources from urllib.parse import quote_plus -from flask_babel import gettext as _, lazy_gettext as N_ +from debianmemberportfolio.model import keyfinder +from flask_babel import gettext as _ +from flask_babel import lazy_gettext as N_ my_config = ConfigParser() -my_config.read_file(UTF8StreamReader( - pkg_resources.resource_stream(__name__, 'portfolio.ini'))) +ref = resources.files("debianmemberportfolio.model").joinpath("portfolio.ini") +with ref.open("rb") as fp: + my_config.read_file(UTF8StreamReader(fp)) _FIELDNAMES_MAP = { - 'email': N_('Email address'), - 'name': N_('Name'), - 'gpgfp': N_('GPG fingerprint'), - 'username': N_('Debian user name'), - 'nonddemail': N_('Non Debian email address'), - 'salsausername': N_('Salsa user name'), + "email": N_("Email address"), + "name": N_("Name"), + "openpgpfp": N_("OpenPGP fingerprint"), + "username": N_("Debian user name"), + "nonddemail": N_("Non Debian email address"), + "salsausername": N_("Salsa user name"), } class DDPortfolioEntry(object): def __init__(self, config, section, key): self.name = key - self.optional = config.has_option(section, key + '.optional') and \ - config.getboolean(section, key + '.optional') or False - if config.has_option(section, key + '.type'): - self.type = config.get(section, key + '.type') + self.optional = ( + config.has_option(section, key + ".optional") + and config.getboolean(section, key + ".optional") + or False + ) + if config.has_option(section, key + ".type"): + self.type = config.get(section, key + ".type") else: - self.type = 'url' + self.type = "url" def _build_quoted_fields(fields): @@ -68,19 +72,19 @@ def _build_quoted_fields(fields): for key, value in fields.items(): if value is not None: if isinstance(value, str): - qfields[key] = quote_plus(value.encode('utf8')) + qfields[key] = quote_plus(value.encode("utf8")) elif isinstance(value, str): qfields[key] = quote_plus(value) else: qfields[key] = value - qfields[key] = str(qfields[key]).replace('%', '%%') + qfields[key] = str(qfields[key]).replace("%", "%%") - if 'gpgfp' not in qfields: - fpr = keyfinder.getFingerprintByEmail(fields['email'].encode('utf8')) + if "openpgpfp" not in qfields: + fpr = keyfinder.getFingerprintByEmail(fields["email"]) if fpr: - qfields['gpgfp'] = fpr[0] - qfields['firstchar'] = fields['email'][0].encode('utf8') - qfields['emailnoq'] = fields['email'].encode('utf8') + qfields["openpgpfp"] = fpr[0] + qfields["firstchar"] = fields["email"][0] + qfields["emailnoq"] = fields["email"] return qfields @@ -88,27 +92,50 @@ def build_urls(fields): """Build personalized URLs using the developer information in fields.""" data = [] - qfields = _build_quoted_fields(fields) - for section in [section.strip() for section in - my_config.get('DEFAULT', - 'urlbuilder.sections').split(',')]: - data.append(['section', section]) - if my_config.has_option(section, 'urls'): - for entry in ([ - DDPortfolioEntry(my_config, section, url) for url in - my_config.get(section, 'urls').split(',')]): + quoted_fields = _build_quoted_fields(fields) + for section in [ + section.strip() + for section in my_config.get("DEFAULT", "urlbuilder.sections").split(",") + ]: + data.append(["section", section]) + if my_config.has_option(section, "urls"): + for entry in [ + DDPortfolioEntry(my_config, section, url) + for url in my_config.get(section, "urls").split(",") + ]: try: data.append( - ['url', section, entry, - my_config.get(section, entry.name + '.pattern', - raw=False, vars=qfields)]) + [ + "url", + section, + entry, + my_config.get( + section, + entry.name + ".pattern", + raw=False, + vars=quoted_fields, + ), + ] + ) except InterpolationMissingOptionError as e: if not entry.optional: if e.reference in _FIELDNAMES_MAP: - data.append(['error', section, entry, - _('Missing input: %s') % - _(_FIELDNAMES_MAP[e.reference])]) + data.append( + [ + "error", + section, + entry, + _("Missing input: %s") + % _(_FIELDNAMES_MAP[e.reference]), + ] + ) else: - data.append(['error', section, entry, - _('Missing input: %s') % e.reference]) + data.append( + [ + "error", + section, + entry, + _("Missing input: %s") % e.reference, + ] + ) return data diff --git a/debianmemberportfolio/templates/base.html b/debianmemberportfolio/templates/base.html index 82986b7..5409df8 100644 --- a/debianmemberportfolio/templates/base.html +++ b/debianmemberportfolio/templates/base.html @@ -2,7 +2,7 @@ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> {# vim: ft=jinja Base template for XHTML templates. -Copyright © 2009-2020 Jan Dittberner +Copyright © 2009-2022 Jan Dittberner This file is part of the Debian Member Portfolio service. @@ -38,11 +38,7 @@ with this program. If not, see . - - diff --git a/debianmemberportfolio/templates/showform.html b/debianmemberportfolio/templates/showform.html index a0d091a..6bc3191 100644 --- a/debianmemberportfolio/templates/showform.html +++ b/debianmemberportfolio/templates/showform.html @@ -2,7 +2,7 @@ {# Template for the data input form. -Copyright © 2009-2020 Jan Dittberner +Copyright © 2009-2022 Jan Dittberner This file is part of the Debian Member Portfolio service. @@ -46,12 +46,12 @@ with this program. If not, see .
{{ form.name }} -
-