Compare commits

...

25 Commits

Author SHA1 Message Date
phryk f7d10f2ec4 Fix bitrot caused by flask making app.debug basically read-only 1 month ago
phryk fa4ee3faae Made parameter for CLIs create_app lambda optional, fixing a bit of bitrot. 2 months ago
phryk c8c4ba6221 maybe a fix, maybe a fuckup 6 months ago
phryk fcc790ffd7 extremely unfinished and ugly 'fix' of csv import in data editor 6 months ago
phryk ce69142949 Revert "forgot to commit this months ago? not sure." 6 months ago
phryk f72b6694c8 fixed bug with file form fields where I'm not sure how this ever worked… 6 months ago
phryk 50bad00c90 fixed compatbility issue with ISO 8601-1:2019 / RFC 3339. abolish time. 6 months ago
phryk e8f554c576 subdued highlight color in svg background 6 months ago
phryk c81a26eebb fixed issue with comment fragment in CSS 6 months ago
phryk 1100460530 minor styling improvements 6 months ago
phryk 3d01b9d6b4 fixed null defaults for cli parameters in add 6 months ago
phryk df5682f0c2 Actually removed those backdrop-filters… :F 6 months ago
phryk 8f51170cc1 Removed backdrop filter in CSS to waste less CPU on firefox 6 months ago
phryk 0ce854bb02 Fixed tiny bug that snuck into pretty_bytes 6 months ago
phryk 83cec8f8f4 fix at least one compat issue with more recent flask 6 months ago
phryk c31db61749 forgot to commit this months ago? not sure. 6 months ago
phryk 63872632a8 updated quickstart. 1 year ago
phryk 375ea480e5 improved documentation frontpage, moved actual documentation rendering to ,view so link generation doesn't take so damn long, possibly more… 1 year ago
phryk 3ab445632a further progress on documentation renderer 1 year ago
phryk b21831f91f lots of progress on better documentation renderer; removed old unicode hints from the 2.7 days 1 year ago
phryk 764809f126 changed SMTP configuration to port 465 (explicit TLS) and STARTTLS to False 1 year ago
phryk 70eef38874 removed set_trace's introduced in last commit 1 year ago
phryk f108b1dcdd improved editing base info for datasets in data editor 1 year ago
phryk a74e23dd9e added backdrop-filter blur to design. 1 year ago
phryk e5960ddfed replace all format string substitutions with f-strings. closes #15 1 year ago
  1. 79
      poobrains/__init__.py
  2. 2
      poobrains/analysis/__init__.py
  3. 14
      poobrains/analysis/data.py
  4. 14
      poobrains/analysis/editor.py
  5. 69
      poobrains/analysis/sources.py
  6. 7
      poobrains/analysis/util.py
  7. 6
      poobrains/analysis/visualization.py
  8. 144
      poobrains/auth/__init__.py
  9. 67
      poobrains/cli/__init__.py
  10. 16
      poobrains/commenting.py
  11. 8
      poobrains/defaults.py
  12. 951
      poobrains/doc/__init__.py
  13. 160
      poobrains/doc/quickstart.md
  14. 4
      poobrains/errors.py
  15. 22
      poobrains/form/__init__.py
  16. 35
      poobrains/form/fields.py
  17. 16
      poobrains/form/types.py
  18. 2
      poobrains/formapp.py
  19. 34
      poobrains/helpers.py
  20. 2
      poobrains/mailing.py
  21. 8
      poobrains/md/__init__.py
  22. 4
      poobrains/md_default.py
  23. 16
      poobrains/profile.py
  24. 12
      poobrains/rendering.py
  25. 6
      poobrains/search.py
  26. 34
      poobrains/storage/__init__.py
  27. 6
      poobrains/storage/fields.py
  28. 8
      poobrains/svg/__init__.py
  29. 14
      poobrains/svg/color.py
  30. 2
      poobrains/svg/palettes.py
  31. 10
      poobrains/tagging.py
  32. 42
      poobrains/testing.py
  33. 8
      poobrains/themes/default/colors.scss
  34. 33
      poobrains/themes/default/documentation.scss
  35. 7
      poobrains/themes/default/editor.scss
  36. 2
      poobrains/themes/default/main.jinja
  37. 38
      poobrains/themes/default/main.scss
  38. 4
      poobrains/themes/default/palette.scss
  39. 4
      poobrains/themes/default/svg.scss
  40. 1
      poobrains/themes/default/tabs.scss
  41. 27
      poobrains/upload/__init__.py
  42. 4
      setup.py

79
poobrains/__init__.py

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" A webframework for aspiring media terrorists. """
import os
@ -449,13 +447,21 @@ class Poobrain(flask.Flask): @@ -449,13 +447,21 @@ class Poobrain(flask.Flask):
if not 'root_path' in kwargs:
kwargs['root_path'] = str(pathlib.Path('.').absolute()) #TODO: pathlib probably isn't really needed here
if 'DEBUG' in dir(config) and config.DEBUG:
# There's some shitty overriding going on based on FLASK_ENV.
# Set the env var to override the override and enforce what
# the config says.
os.environ['FLASK_ENV'] = 'development'
else:
os.environ['FLASK_ENV'] = 'production'
super(Poobrain, self).__init__(*args, **kwargs)
self.cronjobs = []
@click.group(cls=flask.cli.FlaskGroup, create_app=lambda x: self)
@click.option('--database', default="sqlite:///%s.db" % project_name)
@click.group(cls=flask.cli.FlaskGroup, create_app=lambda x=None: self)
@click.option('--database', default=f"sqlite:///{project_name}.db")
def cli(database):
self.db = db_url.connect(database)
self.cli = cli
@ -484,7 +490,7 @@ class Poobrain(flask.Flask): @@ -484,7 +490,7 @@ class Poobrain(flask.Flask):
user = os.getlogin()
group = grp.getgrgid(os.getgid()).gr_name
sys.exit("Somethings' fucky with the log file: %s. Current user/group is %s/%s." % (e,user,group))
sys.exit(f"Somethings' fucky with the log file: {e}. Current user/group is {user}/{group}.")
if self.debug:
# show SQL queries
@ -498,7 +504,7 @@ class Poobrain(flask.Flask): @@ -498,7 +504,7 @@ class Poobrain(flask.Flask):
import pudb
if hasattr(signal, 'SIGINFO'):
pudb.set_interrupt_handler(signal.SIGINFO)
print("%s: a graphical debugger can be invoked with SIGINFO (^T)" % (self.name.upper()))
print(f"{self.name}: a graphical debugger can be invoked with SIGINFO (^T)")
self.debugger = pudb
@ -521,10 +527,10 @@ class Poobrain(flask.Flask): @@ -521,10 +527,10 @@ class Poobrain(flask.Flask):
else:
import optparse # Pretty fucking ugly, but at least its in the stdlib. TODO: Can we *somehow* make this work with prompt in cli/__init__.py install command?
parser = optparse.OptionParser()
parser.add_option('--database', default="sqlite:///%s.db" % project_name, dest='database') # NOTE: If you change this, you'll also have to change the --database default in cli/__init__.py or else install will fuck up
parser.add_option('--database', default=f"sqlite:///{project_name}.db", dest='database') # NOTE: If you change this, you'll also have to change the --database default in cli/__init__.py or else install will fuck up
(options, _) = parser.parse_args()
self.logger.warning("No DATABASE in config, using generated default or --database parameter '%s'. This should only happen before the install command is executed." % options.database)
self.logger.warning(f"No DATABASE in config, using generated default or --database parameter '{options.database}'. This should only happen before the install command is executed.")
self.db = db_url.connect(options.database)
self.add_url_rule('/theme/<path:resource>', 'serve_theme_resources', self.serve_theme_resources)
@ -662,7 +668,7 @@ class Poobrain(flask.Flask): @@ -662,7 +668,7 @@ class Poobrain(flask.Flask):
flask.g.user = cert_info.user
except auth.ClientCert.DoesNotExist:
self.logger.error("httpd verified client certificate successfully, but it's not known at this site. CN: %s, digest: %s" % (cert.get_subject().CN, cert.digest('sha512')))
self.logger.error(f"httpd verified client certificate successfully, but it's not known at this site. CN: {cert.get_subject()}, digest: {cert.digest('sha512')}")
if flask.g.user == None:
try:
@ -702,12 +708,12 @@ class Poobrain(flask.Flask): @@ -702,12 +708,12 @@ class Poobrain(flask.Flask):
if not extra_modes is None:
for extra_mode in extra_modes:
self.site.add_view(cls, os.path.join(rule, '<handle>/%s' % extra_mode), mode=extra_mode, force_secure=force_secure)
self.site.add_view(cls, os.path.join(rule, f"<handle>/{extra_mode}"), mode=extra_mode, force_secure=force_secure)
for related_model, related_fields in cls._meta.model_backrefs.items(): # Add Models that are associated by ForeignKeyField, like /user/foo/userpermissions
if len(related_fields) > 1:
self.logger.debug("!!! Apparent multi-field relation for %s: %s !!!" % (related_model.__name__, related_fields))
self.logger.debug("!!! Apparent multi-field relation for {related_model.__name__}: {related_fields} !!!")
if issubclass(related_model, auth.Administerable):
self.site.add_related_view(cls, related_fields[0], os.path.join(rule, '<handle>/'))
@ -767,21 +773,18 @@ class Poobrain(flask.Flask): @@ -767,21 +773,18 @@ class Poobrain(flask.Flask):
except LookupError:
pass
raise LookupError("Failed generating URL for %s[%s]-%s. No matching route found." % (cls.__name__, url_params.get('handle', None), mode))
raise LookupError("Failed generating URL for {cls.__name__}[{url_params.get('handle', None)}]-{mode}. No matching route found.")
def get_related_view_url(self, cls, handle, related_field, add=None):
blueprint = self.blueprints[flask.request.blueprint]
return blueprint.get_related_view_url(cls, handle, related_field, add=add)
def cron(self, func):
self.cronjobs.append(func)
return func
def cron_run(self):
self.logger.info("Starting cron run.")
@ -823,9 +826,9 @@ class Pooprint(flask.Blueprint): @@ -823,9 +826,9 @@ class Pooprint(flask.Blueprint):
self.before_request(self.box_setup)
def register(self, app, options, first_registration=False):
def register(self, app, options):
super(Pooprint, self).register(app, options, first_registration=first_registration)
super(Pooprint, self).register(app, options)
self.app = app
self.db = app.db
@ -869,7 +872,7 @@ class Pooprint(flask.Blueprint): @@ -869,7 +872,7 @@ class Pooprint(flask.Blueprint):
self.related_views[cls] = collections.OrderedDict()
if not related_field in self.related_views[cls]:
url_segment = '%s:%s' % (related_model.__name__.lower(), related_field.name.lower())
url_segment = f"{related_model.__name__.lower()}:{related_field.name.lower()}"
rule = os.path.join(rule, url_segment, "") # empty string to get trailing slash
self.related_views[cls][related_field] = []
@ -890,7 +893,7 @@ class Pooprint(flask.Blueprint): @@ -890,7 +893,7 @@ class Pooprint(flask.Blueprint):
return cls.related_view_add(*args, **kwargs)
offset_rule = os.path.join(rule, '+<int:offset>')
offset_endpoint = '%s_offset' % (endpoint,)
offset_endpoint = f"{endpoint}_offset"
add_rule = os.path.join(rule, 'add')
add_endpoint = endpoint + '_add'
@ -952,7 +955,7 @@ class Pooprint(flask.Blueprint): @@ -952,7 +955,7 @@ class Pooprint(flask.Blueprint):
view_func = helpers.is_secure(view_func) # manual decoration, cause I don't know how to do this cleaner
offset_rule = rule+'+<int:offset>'
offset_endpoint = '%s_offset' % (endpoint,)
offset_endpoint = f"{endpoint}_offset"
self.add_url_rule(rule, endpoint=endpoint, view_func=view_func, **options)
self.add_url_rule(offset_rule, endpoint=offset_endpoint, view_func=view_func, **options)
@ -992,7 +995,8 @@ class Pooprint(flask.Blueprint): @@ -992,7 +995,8 @@ class Pooprint(flask.Blueprint):
if not_too_many_params and missing_all_optional:
return endpoint
raise ValueError("No fitting url rule found for all params: %s", ','.join(url_params.keys()))
nice_param_list = ', '.join(url_params.keys())
raise ValueError(f"No fitting url rule found for all params: {nice_param_list}")
def get_url(self, cls, mode=None, **url_params):
@ -1011,13 +1015,12 @@ class Pooprint(flask.Blueprint): @@ -1011,13 +1015,12 @@ class Pooprint(flask.Blueprint):
mode = 'full'
if not cls in self.views:
raise LookupError("No registered views for class %s." % (cls.__name__,))
raise LookupError(f"No registered views for class {cls.__name__}.")
if not mode in self.views[cls]:
raise LookupError("No registered views for class %s with mode %s." % (cls.__name__, mode))
raise LookupError(f"No registered views for class {cls.__name__} with mode {mode}.")
endpoints = ['%s.%s' % (self.name, x) for x in self.views[cls][mode]]
endpoints = [f"{self.name}.{x}" for x in self.views[cls][mode]]
if len(endpoints) > 1:
endpoint = self.choose_endpoint(endpoints, **url_params)
else:
@ -1046,12 +1049,12 @@ class Pooprint(flask.Blueprint): @@ -1046,12 +1049,12 @@ class Pooprint(flask.Blueprint):
offset = cls.select().where(*clauses).count() - 1
if not cls in self.listings:
raise LookupError("No registered listings for class %s." % (cls.__name__,))
raise LookupError(f"No registered listings for class {cls.__name__}.")
if not mode in self.listings[cls]:
raise LookupError("No registered listings for class %s with mode %s." % (cls.__name__, mode))
raise LookupError(f"No registered listings for class {cls.__name__} with mode {mode}.")
endpoints = ['%s.%s' % (self.name, x) for x in self.listings[cls][mode]]
endpoints = [f"{self.name}.{x}" for x in self.listings[cls][mode]]
endpoint = self.choose_endpoint(endpoints)
# if isinstance(offset, int) and offset > 0:
@ -1060,7 +1063,7 @@ class Pooprint(flask.Blueprint): @@ -1060,7 +1063,7 @@ class Pooprint(flask.Blueprint):
kw = copy.copy(url_params)
if offset > 0:
kw['offset'] = offset
endpoint = "%s_offset" % endpoint
endpoint = f"{endpoint}_offset"
return flask.url_for(endpoint, **kw)
@ -1075,12 +1078,12 @@ class Pooprint(flask.Blueprint): @@ -1075,12 +1078,12 @@ class Pooprint(flask.Blueprint):
if not cls in lookup:
raise LookupError("No registered related views for class %s." % (cls.__name__,))
raise LookupError(f"No registered related views for class {cls.__name__}.")
if not related_field in lookup[cls]:
raise LookupError("No registered related views for %s[%s]<-%s.%s." % (cls.__name__, handle, related_field.model.__name__, related_field.name))
raise LookupError(f"No registered related views for {cls.__name__}[{handle}]<-{related_field.model.__name__}.{related_field.name}.")
endpoints = ['%s.%s' % (self.name, x) for x in lookup[cls][related_field]]
endpoints = [f"{self.name}.{x}" for x in lookup[cls][related_field]]
endpoint = self.choose_endpoint(endpoints, **{'handle': handle})
return flask.url_for(endpoint, handle=handle)
@ -1088,7 +1091,7 @@ class Pooprint(flask.Blueprint): @@ -1088,7 +1091,7 @@ class Pooprint(flask.Blueprint):
def next_endpoint(self, cls, mode, context): # TODO: rename mode because it's not an applicable name for 'related' context
format = '%s_%s_%s_autogen_%%d' % (cls.__name__, context, mode)
endpoint_base = f"{cls.__name__}_{context}_{mode}"
try:
if context == 'view':
@ -1097,16 +1100,16 @@ class Pooprint(flask.Blueprint): @@ -1097,16 +1100,16 @@ class Pooprint(flask.Blueprint):
endpoints = self.listings[cls][mode]
elif context == 'related':
# mode is actually a foreign key field
format = '%s_%s_%s-%s_autogen_%%d' % (cls.__name__, context, mode.model.__name__, mode.name)
endpoint_base = f"{cls.__name__}_{context}_{mode.model.__name__}-{mode.name}_autogen"
endpoints = self.related_views[cls][mode]
except KeyError: # means no view/listing has been registered yet
endpoints = []
i = 1
endpoint = format % (i,)
endpoint = f"{endpoint_base}_{i}"
while endpoint in endpoints:
endpoint = format % (i,)
endpoint = f"{endpoint_base}_{i}"
i += 1
return endpoint
@ -1164,7 +1167,7 @@ class ErrorPage(rendering.Renderable): @@ -1164,7 +1167,7 @@ class ErrorPage(rendering.Renderable):
self.code = code
break
self.title = "Ermahgerd, %s!" % self.code
self.title = f"Ermahgerd, {self.code}!"
if isinstance(self.error, errors.ExposedError):
self.message = str(error)
@ -1187,7 +1190,7 @@ class ErrorPage(rendering.Renderable): @@ -1187,7 +1190,7 @@ class ErrorPage(rendering.Renderable):
def errorpage(error):
tb = None
app.logger.error('Error %s when accessing %s: %s' % (type(error).__name__, flask.request.path, str(error)))
app.logger.error(f"Error {type(error).__name__} when accessing {flask.request.path}: {error}")
if app.config['DEBUG']:
if hasattr(error, 'code'):

2
poobrains/analysis/__init__.py

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from poobrains import app
from . import util

14
poobrains/analysis/data.py

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import collections
import io
import re
@ -52,7 +50,7 @@ def load_dataset(handle): @@ -52,7 +50,7 @@ def load_dataset(handle):
ds.data[name] = source['function'](**parameters)
else:
raise poobrains.errors.ExposedError('Unknown dynamic dataset: %s' % name)
raise poobrains.errors.ExposedError(f'Unknown dynamic dataset: {name}')
else:
@ -354,7 +352,7 @@ class EphemeralDataset(poobrains.auth.Protected): @@ -354,7 +352,7 @@ class EphemeralDataset(poobrains.auth.Protected):
@locked_cached_property
def ref_id(self):
return "dataset-%s" % self.name
return f"dataset-{self.name}"
@locked_cached_property
def empty(self):
@ -511,7 +509,7 @@ class EphemeralDataset(poobrains.auth.Protected): @@ -511,7 +509,7 @@ class EphemeralDataset(poobrains.auth.Protected):
plot_kinds = visualization.Plot.class_children_keyed()
if not kind in plot_kinds:
raise ValueError('Unknown plot kind: %s' % kind)
raise ValueError(f'Unknown plot kind: {kind}')
return plot_kinds[kind](dataset=self, layers=plot_info['layers'], options=plot_info.get('options'))
@ -554,8 +552,8 @@ class EphemeralDataset(poobrains.auth.Protected): @@ -554,8 +552,8 @@ class EphemeralDataset(poobrains.auth.Protected):
ds.owner = owner or g.user
now = int(time.time())
ds.name = name or poobrains.helpers.clean_string("%s-%d" % (self.name, now))
ds.title = '%s@%s' % (self.title, str(datetime.datetime.fromtimestamp(now)))
ds.name = name or poobrains.helpers.clean_string(f"{self.name}-{now}")
ds.title = f'{self.title}@{str(datetime.datetime.fromtimestamp(now))}'
ds.description = self.description
ds.data = self.data
ds.save(force_insert=True)
@ -600,7 +598,7 @@ class Dataset(EphemeralDataset, poobrains.commenting.Commentable): @@ -600,7 +598,7 @@ class Dataset(EphemeralDataset, poobrains.commenting.Commentable):
@locked_cached_property
def ref_id(self):
return "dataset-%s" % self.name
return f"dataset-{self.name}"
@classmethod
def load(self, handle):

14
poobrains/analysis/editor.py

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import math
import re
@ -43,7 +41,7 @@ def datasource_choices(): @@ -43,7 +41,7 @@ def datasource_choices():
def validate_handle_free(handle):
if handle in session['apps']['DataEditor']:
if poobrains.analysis.data.Dataset.select().where(poobrains.analysis.data.Dataset.name == handle).count() == 1:
raise poobrains.errors.ValidationError("Dataset named '%s' already exists!" % handle)
raise poobrains.errors.ValidationError(f"Dataset named '{handle}' already exists!")
else:
del(session['apps']['DataEditor'][handle]) # FIXME: should technically be in a different function, i guess
@ -381,15 +379,17 @@ class EditorDatasetMetaEdit(poobrains.formapp.MultistateFieldset): @@ -381,15 +379,17 @@ class EditorDatasetMetaEdit(poobrains.formapp.MultistateFieldset):
super().build()
self['dataset_title'] = poobrains.form.fields.Text(label='Title', default=self.formapp.dataset.title)
self['dataset_description'] = poobrains.form.fields.TextArea(label='Description', default=self.formapp.dataset.description)
self['basedata_form'] = poobrains.form.ProxyFieldset(poobrains.auth.AutoForm(self.formapp.instance, mode='edit'))
#self['dataset_title'] = poobrains.form.fields.Text(label='Title', default=self.formapp.dataset.title)
#self['dataset_description'] = poobrains.form.fields.TextArea(label='Description', default=self.formapp.dataset.description)
self.apply = poobrains.form.Button('submit', label='Apply')
def process(self, submit):
if submit == 'apply':
self.formapp.dataset.title = self['dataset_title'].value
self.formapp.dataset.description = self['dataset_description'].value
#self.formapp.dataset.title = self['dataset_title'].value
#self.formapp.dataset.description = self['dataset_description'].value
self['basedata_form'].process('save')
flash("Updated dataset base info.")
self.parent.tool_active = None

69
poobrains/analysis/sources.py

@ -77,12 +77,15 @@ class FileFieldset(poobrains.formapp.MultistateFieldset): @@ -77,12 +77,15 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
self['file'] = poobrains.form.fields.File()
self['consumer'] = poobrains.form.fields.Select(choices=(
('csv', "CSV"),
('geojson', "geojson"),
))
self['consumer'] = poobrains.form.fields.Select(choices=(
('csv', "CSV"),
('geojson', "geojson"),
))
elif self.state == 'csv':
if self.state == 'csv':
self['consumer'].value = 'csv'
self['consumer'].readonly = True
csv_options = self.session['csv_options']
@ -102,13 +105,14 @@ class FileFieldset(poobrains.formapp.MultistateFieldset): @@ -102,13 +105,14 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
flash(f"Full error message: {e}", 'error')
else:
self.session['csv_options'] = csv_options
table = poobrains.rendering.Table(title='CSV preview')
for idx, row in enumerate(reader):
if idx == 5:
break
table.append(*row)
self['table'] = poobrains.form.fields.RenderableWrapper(value=table)
self['table'] = poobrains.form.fields.RenderableWrapper(table)
self.approve = poobrains.form.Button('submit', label='Approve')
escaped = {}
@ -134,21 +138,46 @@ class FileFieldset(poobrains.formapp.MultistateFieldset): @@ -134,21 +138,46 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
def process(self, submit):
if submit == 'change_csv_options':
unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape')
self.session['csv_options'] = {
'delimiter': unescape(self['delimiter'].value),
'doublequote': self['doublequote'].value,
'escapechar': unescape(self['escapechar'].value),
'lineterminator': unescape(self['lineterminator'].value),
'quotechar': unescape(self['quotechar'].value),
'quoting': self['quoting'].value,
'skipinitialspace': self['skipinitialspace'].value,
}
if self.state == 'csv':
if submit == 'change_csv_options':
unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape')
self.session['csv_options'] = {
'delimiter': unescape(self['delimiter'].value or ''),
'doublequote': self['doublequote'].value,
'escapechar': unescape(self['escapechar'].value or ''),
'lineterminator': unescape(self['lineterminator'].value),
'quotechar': unescape(self['quotechar'].value or ''),
'quoting': self['quoting'].value,
'skipinitialspace': self['skipinitialspace'].value,
}
flash("Changed CSV dialect options.")
else:
dialect = dialect_from_dict(self.session['csv_options'])
try:
reader = csv.reader(io.StringIO(self.session['raw']), dialect)
except csv.Error as e:
flash("Can't read file as CSV with supplied options.", 'error')
if app.debug:
flash(f"Full error message: {e}", 'error')
flash("Changed CSV dialect options.")
ds = self.owner()
ds['csv_col_fixme'] = {
'title': "CSV Column",
'description': 'FIXME: CSV has no type recognition, everything is float64',
'dtype': 'float64',
'color': None,
'observations': {}
}
for idx, row in enumerate(reader):
ds['csv_col_fixme']['observations'][idx] = util.float64.string_numpy(row[0]);
self.parent.session['other'] = ds.to_dict(whole=True)
self.parent.state = 'merge'
else: # means parent form wasn't submitting via cancel button – i.e. the "load" button was clicked
else: # submitted via load button with file upload
consumer = self['consumer'].value
raw = self['file'].value.read()
@ -184,7 +213,7 @@ class FileFieldset(poobrains.formapp.MultistateFieldset): @@ -184,7 +213,7 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
elif consumer == 'csv':
self.state = 'csv'
self.session['data_action_info']['csv_options'] = None
self.session['csv_options'] = None
else:
flash("Unhandled", 'error')

7
poobrains/analysis/util.py

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import functools
import datetime
import numpy
import shapely.geometry
@ -27,12 +28,14 @@ def pretty_si(number): @@ -27,12 +28,14 @@ def pretty_si(number):
value /= 1000.0
return "%.3f%s" % (value, postfix)
return f"{value:.3f}{postfix}"
def pretty_datetime64(dt64):
dt = dt64.item()
return f"{dt.day}. {dt.month}. {dt.year}{dt.hour}:{dt.minute}:{dt.second}",
__geo_lookup__ = {
geojson.Point: shapely.geometry.Point,
geojson.MultiPoint: shapely.geometry.MultiPoint,
@ -220,7 +223,7 @@ class DTypeFilterForm(DTypeOwnedMultistateFieldset): @@ -220,7 +223,7 @@ class DTypeFilterForm(DTypeOwnedMultistateFieldset):
self['op'] = poobrains.form.fields.Select(label='Operation', choices=self.dtype.filter_operations)
if issubclass(self.dtype.form_field.func, poobrains.formapp.Component): # dtype.form_field is a partial with .func being the actual class object
if isinstance(self.dtype.form_field, functools.partial) and issubclass(self.dtype.form_field.func, poobrains.formapp.Component): # dtype.form_field is a partial with .func being the actual class object for any Fieldset because that's derived from ClassOrInstanceBound
self['value'] = self.dtype.form_field(self)
else:
self['value'] = self.dtype.form_field(type=self.dtype.form_type)

6
poobrains/analysis/visualization.py

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import math
import pyproj
@ -94,10 +92,10 @@ class Plot(poobrains.svg.SVG): @@ -94,10 +92,10 @@ class Plot(poobrains.svg.SVG):
return [k for k in self.layers.keys()].index(layer_name)
def layer_id(self, layer_name):
return "dataset-%s-%s" % (self.dataset.name, poobrains.helpers.clean_string(layer_name))
return f"dataset-{self.dataset.name}-{poobrains.helpers.clean_string(layer_name)}"
def datapoint_id(self, layer_name, x):
return "dataset-%s-%s-%s" % (self.dataset.name, poobrains.helpers.clean_string(layer_name), poobrains.helpers.clean_string(str(x)))
return f"dataset-{self.dataset.name}-{poobrains.helpers.clean_string(layer_name)}-{poobrains.helpers.clean_string(str(x))}"
@locked_cached_property
def palette(self):

144
poobrains/auth/__init__.py

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
The authentication system.
@ -158,28 +156,28 @@ class AutoForm(BoundForm): @@ -158,28 +156,28 @@ class AutoForm(BoundForm):
def __init__(self, model_or_instance, mode='add', prefix=None, name=None, title=None, method=None, action=None):
if not name:
name = '%s-%s' % (self.model.__name__, self.instance.handle_string.replace('.', '-'))
name = f"{self.model.__name__}-{self.instance.handle_string.replace('.', '-')}"
super(AutoForm, self).__init__(model_or_instance, mode=mode, prefix=prefix, name=name, title=title, method=method, action=action)
if not title:
if hasattr(self.instance, 'title') and self.instance.title:
self.title = "%s %s '%s'" % (self.mode, self.model.__name__, self.instance.title)
self.title = f"{self.mode} {self.model.__name__} '{self.instance.title}'"
elif self.instance.name:
self.title = "%s %s '%s'" % (self.mode, self.model.__name__, self.instance.name)
self.title = f"{self.mode} {self.model.__name__} '{self.instance.name}'"
elif self.instance.id:
self.title = "%s %s #%d" % (self.mode, self.model.__name__, self.instance.id)
self.title = f"{self.mode} {self.model.__name__} #{self.instance.id}"
else:
try:
if self.instance._pk:
self.title = "%s %s '%s'" % (self.mode, self.model.__name__, self.instance._pk)
self.title = f"{self.mode} {self.model.__name__} '{self.instance._pk}'"
else:
self.title = "%s %s" % (self.mode, self.model.__name__)
self.title = f"{self.mode} {self.model.__name__}"
except Exception as e:
self.title = "%s %s" % (self.mode, self.model.__name__)
self.title = f"{self.mode} {self.model.__name__}"
for name, field in self.fields.items():
#if hasattr(self.instance, name): # fucks up with DoesNotExist on unfilled foreign keys
@ -218,7 +216,7 @@ class AutoForm(BoundForm): @@ -218,7 +216,7 @@ class AutoForm(BoundForm):
saved = self.instance.save()
if saved:
flash(u"Saved %s %s." % (self.model.__name__, self.instance.handle_string))
flash(f"Saved {self.model.__name__} {self.instance.handle_string}.")
try:
self.process_all_fieldsets(submit)
except Exception as e:
@ -236,7 +234,7 @@ class AutoForm(BoundForm): @@ -236,7 +234,7 @@ class AutoForm(BoundForm):
return self
else:
flash(u"Couldn't save %s." % self.model.__name__)
flash(f"Couldn't save {self.model.__name__}.")
except poobrains.errors.ValidationError as e:
@ -250,21 +248,21 @@ class AutoForm(BoundForm): @@ -250,21 +248,21 @@ class AutoForm(BoundForm):
if exceptions:
raise
flash(u'Integrity error: %s' % str(e), 'error')
app.logger.error(u"Integrity error: %s" % str(e))
flash(f'Integrity error: {str(e)}', 'error')
app.logger.error(f"Integrity error: {str(e)}")
except Exception as e:
if exceptions:
raise
flash(u"Couldn't save %s for mysterious reasons." % self.model.__name__)
app.logger.error(u"Couldn't save %s. %s: %s" % (self.model.__name__, type(e).__name__, str(e)))
flash(f"Couldn't save {self.model.__name__} for mysterious reasons.")
app.logger.error(f"Couldn't save {self.model.__name__}. {type(e).__name__}: {str(e)}")
elif submit == 'preview':
self.preview = self.instance.render('full')
else:
flash(u"Not handling readonly form '%s'." % self.name)
flash(f"Not handling readonly form '{self.name}'.")
return self
@ -277,7 +275,7 @@ class DeleteForm(BoundForm): @@ -277,7 +275,7 @@ class DeleteForm(BoundForm):
f = super(DeleteForm, cls).__new__(cls, model_or_instance, prefix=prefix, name=None, title=title, method=method, action=action)
f.title = "Delete %s" % f.instance.name
f.title = f"Delete {f.instance.name}"
f.warning = poobrains.form.fields.Message('deletion_irrevocable', value='Deletion is not revocable. Proceed?')
f.submit = poobrains.form.Button('submit', name='submit', value='delete', label=u'')
@ -287,16 +285,16 @@ class DeleteForm(BoundForm): @@ -287,16 +285,16 @@ class DeleteForm(BoundForm):
super(DeleteForm, self).__init__(model_or_instance, mode=mode, prefix=prefix, name=self.name, title=title, method=method, action=action)
if not title:
if hasattr(self.instance, 'title') and self.instance.title:
self.title = "Delete %s %s" % (self.model.__name__, self.instance.title)
self.title = f"Delete {self.model.__name__} {self.instance.title}"
else:
self.title = "Delete %s %s" % (self.model.__name__, str(self.instance._pk))
self.title = f"Delete {self.model.__name__} {str(self.instance._pk)}"
def process(self, submit):
if hasattr(self.instance, 'title') and self.instance.title:
message = "Deleted %s '%s'." % (self.model.__name__, self.instance.title)
message = f"Deleted {self.model.__name__} '{self.instance.title}'."
else:
message = "Deleted %s '%s'." % (self.model.__name__, str(self.instance._pk))
message = f"Deleted {self.model.__name__} '{str(self.instance._pk)}'."
self.instance.delete_instance(recursive=True)
flash(message)
@ -329,7 +327,7 @@ class AccessField(poobrains.form.fields.Field): @@ -329,7 +327,7 @@ class AccessField(poobrains.form.fields.Field):
if name == 'prefix':
for field in [self.read, self.update, self.delete]:
field.prefix = '%s.%s' % (value, self.name)
field.prefix = f'{value}.{self.name}'
elif name == 'value':
@ -395,7 +393,7 @@ class Permission(poobrains.helpers.ChildAware): @@ -395,7 +393,7 @@ class Permission(poobrains.helpers.ChildAware):
@classmethod
def check(cls, user):
msg = "Permission %s denied." % cls.__name__
msg = f"Permission {cls.__name__} denied."
granted = None
# check user-assigned permission state
@ -447,7 +445,7 @@ class Permission(poobrains.helpers.ChildAware): @@ -447,7 +445,7 @@ class Permission(poobrains.helpers.ChildAware):
granted = self._check_values[user]
if not granted:
raise AccessDenied("Permission %s denied." % self.__class__.__name__)
raise AccessDenied(f"Permission {self.__class__.__name__} denied.")
return granted
@ -504,8 +502,8 @@ class PermissionInjection(poobrains.helpers.MetaCompatibility): @@ -504,8 +502,8 @@ class PermissionInjection(poobrains.helpers.MetaCompatibility):
#for op in ['create', 'read', 'update', 'delete']:
for op in set(cls._meta.modes.values()):
perm_name = "%s_%s" % (cls.__name__, op)
perm_label = "%s %s" % (op.capitalize(), cls.__name__)
perm_name = f"{cls.__name__}_{op}"
perm_label = f"{op.capitalize()} {cls.__name__}"
#cls._meta.permissions[mode] = type(perm_name, (cls._meta.permission_class,), {})
perm_attrs = dict()
perm_attrs['op'] = op
@ -538,7 +536,7 @@ class PermissionParamType(poobrains.form.types.StringParamType): @@ -538,7 +536,7 @@ class PermissionParamType(poobrains.form.types.StringParamType):
try:
permission, access = cleaned_string.split('.')
except Exception as e:
self.fail('Could not split value to permission and access: %s' % cleaned_string)
self.fail(f'Could not split value to permission and access: {cleaned_string}')
return (permission, access)
@ -560,25 +558,25 @@ class FormPermissionField(poobrains.form.fields.Select): @@ -560,25 +558,25 @@ class FormPermissionField(poobrains.form.fields.Select):
permissions = Permission.class_children_keyed()
for perm_name in sorted(permissions):
perm = permissions[perm_name]
self.choices.append(([('%s.%s' % (perm_name, value), label) for (value, label) in perm.choices], perm_name))
self.choices.append(([(f'{perm_name}.{value}', label) for (value, label) in perm.choices], perm_name))
def validate(self):
permission, access = self.value
if not permission in Permission.class_children_keyed().keys():
raise poobrains.errors.ValidationError('Unknown permission: %s' % permission)
raise poobrains.errors.ValidationError(f'Unknown permission: {permission}')
perm_class = Permission.class_children_keyed()[permission]
choice_values = [t[0] for t in perm_class.choices]
if not access in choice_values:
raise poobrains.errors.ValidationError("Unknown access mode '%s' for permission '%s'." % (access, permission))
raise poobrains.errors.ValidationError(f"Unknown access mode '{access}' for permission '{permission}'.")
def admin_listing_actions(cls):
m = poobrains.rendering.Menu('actions')
if 'add' in cls._meta.modes:
m.append(cls.url('add'), 'add new %s' % (cls.__name__,))
m.append(cls.url('add'), f'add new {cls.__name__}')
return m
@ -597,7 +595,7 @@ def admin_menu(): @@ -597,7 +595,7 @@ def admin_menu():
for mode, endpoints in listings.items():
for endpoint in endpoints: # iterates through endpoints.keys()
menu.append(url_for('admin.%s' % endpoint), administerable.__name__)
menu.append(url_for(f'admin.{endpoint}'), administerable.__name__)
return menu
@ -618,11 +616,11 @@ def admin_index(): @@ -618,11 +616,11 @@ def admin_index():
for administerable, listings in app.admin.listings.items():
subcontainer = poobrains.rendering.Container(css_class='administerable-actions', mode='full')
menu = poobrains.rendering.Menu('listings-%s' % administerable.__name__)
menu = poobrains.rendering.Menu(f'listings-{administerable.__name__}')
for mode, endpoints in listings.items():
for endpoint in endpoints: # iterates through endpoints.keys()
menu.append(url_for('admin.%s' % endpoint), administerable.__name__)
menu.append(url_for(f'admin.{endpoint}'), administerable.__name__)
subcontainer.append(menu)
if administerable.__doc__:
@ -664,17 +662,16 @@ def protected(func): @@ -664,17 +662,16 @@ def protected(func):
cls = cls_or_instance
if not (issubclass(cls, Protected) or isinstance(cls_or_instance, Protected)):
raise ValueError("@protected used with non-protected class '%s'." % cls.__name__)
raise ValueError(f"@protected used with non-protected class '{cls.__name__}'.")
if not mode in cls._meta.modes:
raise AccessDenied("Unknown mode '%s' for accessing %s." % (mode, cls.__name__))
raise AccessDenied(f"Unknown mode '{mode}' for accessing {cls.__name__}.")
op = cls._meta.modes[mode]
if not op in ['create', 'read', 'update', 'delete']:
raise AccessDenied("Unknown access op '%s' for accessing %s." (op, cls.__name__))
raise AccessDenied(f"Unknown access op '{op}' for accessing {cls.__name__}.")
if not op in cls_or_instance.permissions:
raise NotImplementedError("Did not find permission for op '%s' in cls_or_instance of class '%s'." % (op, cls.__name__))
raise NotImplementedError(f"Did not find permission for op '{op}' in cls_or_instance of class '{cls.__name__}'.")
cls_or_instance.permissions[op].check(user)
@ -743,11 +740,11 @@ class ClientCertForm(poobrains.form.Form): @@ -743,11 +740,11 @@ class ClientCertForm(poobrains.form.Form):
if self.controls['tls_submit'].value:
r = app.response_class(pkcs12.export(passphrase=passphrase))
r.mimetype = 'application/pkcs-12'
flash(u"The passphrase for this delicious bundle of crypto is '%s'" % passphrase)
flash(f"The passphrase for this delicious bundle of crypto is '{passphrase}'")
else: # means pgp mail
text = "Hello %s. Here's your new set of keys to the gates of Shambala.\nYour passphrase is '%s'." % (token.user.name, passphrase)
text = f"Hello {token.user.name}. Here's your new set of keys to the gates of Shambala.\nYour passphrase is '{passphrase}'."
mail = poobrains.mailing.Mail(token.user.pgp_fingerprint)
mail['Subject'] = 'Bow before entropy'
@ -759,11 +756,10 @@ class ClientCertForm(poobrains.form.Form): @@ -759,11 +756,10 @@ class ClientCertForm(poobrains.form.Form):
mail.send()
flash(u"Your private key and client certificate have been send to '%s'." % token.user.mail)
flash(f"Your private key and client certificate have been send to '{token.user.mail}'.")
r = self
try:
cert_info.save()
@ -813,7 +809,7 @@ class OwnedPermission(Permission): @@ -813,7 +809,7 @@ class OwnedPermission(Permission):
return True
else:
app.logger.warning("Unknown access mode '%s' for User %d with Permission %s" % (access, user.id, cls.__name__))
app.logger.warning(f"Unknown access mode '{access}' for User #{user.id} with Permission {cls.__name__}")
raise AccessDenied("YOU SHALL NOT PASS!")
else:
@ -972,7 +968,7 @@ class RelatedForm(poobrains.form.Form): @@ -972,7 +968,7 @@ class RelatedForm(poobrains.form.Form):
endpoint = request.endpoint
if not endpoint.endswith('_offset'):
endpoint = '%s_offset' % (endpoint,)
endpoint = f'{endpoint}_offset'
f = super(RelatedForm, cls).__new__(cls, prefix=prefix, name=name, title=title, method=method, action=action)
@ -1011,7 +1007,7 @@ class RelatedForm(poobrains.form.Form): @@ -1011,7 +1007,7 @@ class RelatedForm(poobrains.form.Form):
self.related_model = related_model
self.related_field = related_field
self.title = "%s for %s %s" % (self.related_model.__name__, self.instance.__class__.__name__, self.instance.handle_string)
self.title = f"{self.related_model.__name__} for {self.instance.__class__.__name__} {self.instance.handle_string}"
def process(self, submit):
@ -1021,8 +1017,8 @@ class RelatedForm(poobrains.form.Form): @@ -1021,8 +1017,8 @@ class RelatedForm(poobrains.form.Form):
try:
fieldset.process(submit)
except Exception as e:
flash(u"Failed to process fieldset '%s.%s'." % (fieldset.prefix, fieldset.name))
app.logger.error("Failed to process fieldset %s.%s - %s: %s" % (fieldset.prefix, fieldset.name, type(e).__name__, str(e)))
flash(f"Failed to process fieldset '{fieldset.prefix}.{fieldset.name}'.")
app.logger.error(f"Failed to process fieldset {fieldset.prefix}.{fieldset.name} - {type(e).__name__}: {str(e)}")
return redirect(request.url)
return self
@ -1129,7 +1125,7 @@ class Protected(poobrains.rendering.Renderable, metaclass=PermissionInjection): @@ -1129,7 +1125,7 @@ class Protected(poobrains.rendering.Renderable, metaclass=PermissionInjection):
except AccessDenied:
if mode == 'inline':
return Markup(poobrains.rendering.RenderString("Access Denied for %s." % self.__class__.__name__))
return Markup(poobrains.rendering.RenderString(f"Access Denied for {self.__class__.__name__}."))
raise
@ -1253,7 +1249,7 @@ class Administerable(poobrains.storage.Storable, Protected, metaclass=BaseAdmini @@ -1253,7 +1249,7 @@ class Administerable(poobrains.storage.Storable, Protected, metaclass=BaseAdmini
actions.append(self.url(mode), mode)
except AccessDenied:
app.logger.debug("Not generating %s link for %s %s because this user is not authorized for it." % (mode, self.__class__.__name__, self.handle_string))
app.logger.debug(f"Not generating {mode} link for {self.__class__.__name__} {self.handle_string} because this user is not authorized for it.")
except LookupError:
pass
#app.logger.debug("Couldn't create %s link for %s" % (mode, self.handle_string))
@ -1291,9 +1287,9 @@ class Administerable(poobrains.storage.Storable, Protected, metaclass=BaseAdmini @@ -1291,9 +1287,9 @@ class Administerable(poobrains.storage.Storable, Protected, metaclass=BaseAdmini
def form(self, mode=None):
n = 'form_%s' % mode
n = f'form_{mode}'
if not hasattr(self, n):
raise NotImplementedError("Form class %s.%s missing." % (self.__class__.__name__, n))
raise NotImplementedError(f"Form class {self.__class__.__name__}.{n} missing.")
form_class = getattr(self, n)
return form_class(mode=mode)#, name=None, title=None, method=None, action=None)
@ -1349,7 +1345,7 @@ class Administerable(poobrains.storage.Storable, Protected, metaclass=BaseAdmini @@ -1349,7 +1345,7 @@ class Administerable(poobrains.storage.Storable, Protected, metaclass=BaseAdmini
def related_view(cls, related_field=None, handle=None, offset=0):
if related_field is None:
raise TypeError("%s.related_view needs Field instance for parameter 'related_field'. Got %s (%s) instead." % (cls.__name__, type(field).__name__, str(field)))
raise TypeError(f"{cls.__name__}.related_view needs Field instance for parameter 'related_field'. Got {type(field).__name__} ({str(field)}) instead.")
related_model = related_field.model
instance = cls.load(handle)
@ -1478,9 +1474,9 @@ class User(ProtectedNamed): @@ -1478,9 +1474,9 @@ class User(ProtectedNamed):
invalid_after = datetime.datetime.now() + datetime.timedelta(seconds=app.config['CERT_MAX_LIFETIME']) # FIXME: DRY!
if not_after > invalid_after:
raise poobrains.errors.ExposedError("not_after too far into the future, max allowed %s but got %s" % (invalid_after, not_after))
raise poobrains.errors.ExposedError(f"not_after too far into the future, max allowed {invalid_after} but got {not_after}")
common_name = '%s:%s@%s' % (self.name, name, app.config['SITE_NAME'])
common_name = f"{self.name}:{name}@{app.config['SITE_NAME']}"
fd = open(app.config['CA_KEY'], 'rb')
ca_key = openssl.crypto.load_privatekey(openssl.crypto.FILETYPE_PEM, fd.read())
@ -1556,8 +1552,8 @@ class User(ProtectedNamed): @@ -1556,8 +1552,8 @@ class User(ProtectedNamed):
NOTE: the passed model needs to have a datetime field with the name 'date'. This is needed for ordering.
"""
assert issubclass(model, Owned) and hasattr(model, 'date'), "Only Owned subclasses with a 'date' field can be @User.profile'd. %s does not qualify." % model.__name__
assert hasattr(model, 'id') and isinstance(model.id, poobrains.storage.fields.AutoField), "@on_profile model without id: %s" % model.__name__
assert issubclass(model, Owned) and hasattr(model, 'date'), f"Only Owned subclasses with a 'date' field can be @User.profile'd. {model.__name__} does not qualify."
assert hasattr(model, 'id') and isinstance(model.id, poobrains.storage.fields.AutoField), f"@on_profile model without id: {model.__name__}"
cls._on_profile.append(model)
@ -1588,7 +1584,7 @@ class User(ProtectedNamed): @@ -1588,7 +1584,7 @@ class User(ProtectedNamed):
for model in self.models_on_profile:
try:
queries.append(model.list('read', g.user, ordered=False, fields=[peewee.SQL("'%s'" % model.__name__).alias('model'), model.id, model.date.alias('date')]).where(model.owner == self))
queries.append(model.list('read', g.user, ordered=False, fields=[peewee.SQL(f"'{model.__name__}'").alias('model'), model.id, model.date.alias('date')]).where(model.owner == self))
except AccessDenied:
pass # ignore models we aren't allowed to read
@ -1653,7 +1649,7 @@ class UserPermission(Administerable): @@ -1653,7 +1649,7 @@ class UserPermission(Administerable):
return Permission.class_children_keyed()[self.permission]
except KeyError:
app.logger.error("Unknown permission '%s' associated to user #%d." % (self.permission, self.user_id)) # can't use self.user.name because dat recursion
app.logger.error(f"Unknown permission '{self.permission}' associated to user #{self.user_id}.") # can't use self.user.name because dat recursion
#TODO: Do we want to do more, like define a permission_class that always denies access?
def save(self, *args, **kwargs):
@ -1663,7 +1659,7 @@ class UserPermission(Administerable): @@ -1663,7 +1659,7 @@ class UserPermission(Administerable):
valid_permission_names.append(cls.__name__)
if self.permission not in valid_permission_names:
raise ValueError("Invalid permission name: %s" % self.permission)
raise ValueError(f"Invalid permission name: {self.permission}")
return super(UserPermission, self).save(*args, **kwargs)
@ -1732,7 +1728,7 @@ class GroupPermission(Administerable): @@ -1732,7 +1728,7 @@ class GroupPermission(Administerable):
return Permission.class_children_keyed()[self.permission]
except KeyError:
app.logger.error("Unknown permission '%s' associated to user #%d." % (self.permission, self.group_id)) # can't use self.group.name because dat recursion
app.logger.error(f"Unknown permission '{self.permission}' associated to user #{self.group_id}.") # can't use self.group.name because dat recursion
#TODO: Do we want to do more, like define a permission_class that always denies access?
def form(self, mode=None):
@ -1757,7 +1753,7 @@ class GroupPermission(Administerable): @@ -1757,7 +1753,7 @@ class GroupPermission(Administerable):
except Group.DoesNotExist:
name = 'FNORD'
return "%s-%s" % (name, self.permission)
return f"{name}-{self.permission}"
class ClientCertTokenAddForm(AutoForm):
@ -1766,7 +1762,7 @@ class ClientCertTokenAddForm(AutoForm): @@ -1766,7 +1762,7 @@ class ClientCertTokenAddForm(AutoForm):
r = super(ClientCertTokenAddForm, self).process(submit, exceptions=exceptions)
self.instance.user.notify("A new certificate token was created in your name! \n The token is *%s*. \nIt will expire on %s. \nThe certificate will be named '%s'" % (self.instance.token, self.instance.expiry_date, self.instance.cert_name)) # TODO: check if token was actually saved
self.instance.user.notify(f"A new certificate token was created in your name! \n The token is *{self.instance.token}*. \nIt will expire on {self.instance.expiry_date}. \nThe certificate will be named '{self.instance.cert_name}'") # TODO: check if token was actually saved
return r
@ -1804,18 +1800,14 @@ class ClientCertToken(Administerable, Protected): @@ -1804,18 +1800,14 @@ class ClientCertToken(Administerable, Protected):
if user_token_count >= app.config['MAX_TOKENS']:
raise ValueError( # TODO: Is ValueError really the most fitting exception there is?
"User %s already has %d out of %d client certificate tokens." % (
self.user.name,
user_token_count,
app.config['MAX_TOKENS']
)
f"User {self.user.name} already has {user_token_count} out of {app.config['MAX_TOKENS']} client certificate tokens."
)
if self.__class__.select().where(self.__class__.user == self.user, self.__class__.cert_name == self.cert_name).count():
raise ValueError("User %s already has a client certificate token for a certificate named '%s'." % (self.user.name, self.cert_name))
raise ValueError(f"User {self.user.name} already has a client certificate token for a certificate named '{self.cert_name}'.")
if ClientCert.select().where(ClientCert.user == self.user, ClientCert.name == self.cert_name).count():
raise ValueError("User %s already has a client certificate named '%s'." % (self.user.name, self.cert_name))