Compare commits

...

20 Commits
compat ... main

Author SHA1 Message Date
phryk
a0cfe48862 Dumb fix for dumb issue. There might be more of the same still around, but fuck it, we're going for Ooze. 2022-08-22 18:58:18 +02:00
phryk
07a741f454 some old uncommited thing. probably a fix. 2022-08-15 16:30:49 +02:00
phryk
342ad493d1 hopefully fixed shit. introduced alternative mechanism to @before_first_request as that's going to be dropped with flask 2.3 2022-08-15 16:30:25 +02:00
phryk
1f573ead23 Fix for error output that should be f-string not being one. 2022-08-15 13:45:31 +02:00
phryk
d51a63ff55 Compatibility fix for Flask deprecating Flask.try_trigger_before_first_request_functions 2022-08-15 13:41:24 +02:00
phryk
baf2c983d4 compatibility fix for peewee 3.15 in FakeMetaOptions 2022-08-15 13:19:08 +02:00
phryk
f7d10f2ec4 Fix bitrot caused by flask making app.debug basically read-only 2022-05-21 21:30:34 +02:00
phryk
fa4ee3faae Made parameter for CLIs create_app lambda optional, fixing a bit of bitrot. 2022-05-07 20:33:33 +02:00
phryk
c8c4ba6221 maybe a fix, maybe a fuckup 2021-12-23 00:26:32 +01:00
phryk
fcc790ffd7 extremely unfinished and ugly 'fix' of csv import in data editor 2021-12-23 00:19:53 +01:00
phryk
ce69142949 Revert "forgot to commit this months ago? not sure."
This reverts commit c31db61749.
2021-12-22 23:13:51 +01:00
phryk
f72b6694c8 fixed bug with file form fields where I'm not sure how this ever worked… 2021-12-22 23:03:35 +01:00
phryk
50bad00c90 fixed compatbility issue with ISO 8601-1:2019 / RFC 3339. abolish time. 2021-12-22 22:29:38 +01:00
phryk
e8f554c576 subdued highlight color in svg background 2021-12-22 22:11:16 +01:00
phryk
c81a26eebb fixed issue with comment fragment in CSS 2021-12-22 22:07:37 +01:00
phryk
1100460530 minor styling improvements 2021-12-22 21:51:45 +01:00
phryk
3d01b9d6b4 fixed null defaults for cli parameters in add 2021-12-22 21:50:46 +01:00
phryk
df5682f0c2 Actually removed those backdrop-filters… :F 2021-12-22 20:02:03 +01:00
phryk
8f51170cc1 Removed backdrop filter in CSS to waste less CPU on firefox 2021-12-22 19:59:17 +01:00
phryk
0ce854bb02 Fixed tiny bug that snuck into pretty_bytes 2021-12-22 19:54:37 +01:00
15 changed files with 138 additions and 92 deletions

View File

@ -440,19 +440,31 @@ class Poobrain(flask.Flask):
peewee.DoesNotExist: 404 peewee.DoesNotExist: 404
} }
setup_funcs = None
cronjobs = None cronjobs = None
_setup_done = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if not 'root_path' in kwargs: if not 'root_path' in kwargs:
kwargs['root_path'] = str(pathlib.Path('.').absolute()) #TODO: pathlib probably isn't really needed here 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) super(Poobrain, self).__init__(*args, **kwargs)
self.setup_funcs = []
self.cronjobs = [] self.cronjobs = []
@click.group(cls=flask.cli.FlaskGroup, create_app=lambda x: self) @click.group(cls=flask.cli.FlaskGroup, create_app=lambda x=None: self)
@click.option('--database', default=f"sqlite:///{project_name}.db") @click.option('--database', default=f"sqlite:///{project_name}.db")
def cli(database): def cli(database):
self.db = db_url.connect(database) self.db = db_url.connect(database)
@ -537,6 +549,14 @@ class Poobrain(flask.Flask):
self.admin = Pooprint('admin', 'admin') self.admin = Pooprint('admin', 'admin')
def full_dispatch_request(self):
if not self._setup_done:
self.run_setup()
return super().full_dispatch_request()
def main(self): def main(self):
#self.cli(obj={}) #self.cli(obj={})
self.cli() self.cli()
@ -546,18 +566,28 @@ class Poobrain(flask.Flask):
return True # autoescape everything return True # autoescape everything
def try_trigger_before_first_request_functions(self): def setup(self, f):
if not self.setup in self.before_first_request_funcs: self.setup_funcs.append(f)
self.before_first_request_funcs.append(self.setup)
super(Poobrain, self).try_trigger_before_first_request_functions()
return f
def setup(self): def run_setup(self):
"""
Global runtime setup. Calls all @app.setup decorated functions
and registers site and admin blueprints.
Called by request_setup on first request.
"""
for f in self.setup_funcs:
f()
self.register_blueprint(self.site) self.register_blueprint(self.site)
self.register_blueprint(self.admin, url_prefix='/admin/') self.register_blueprint(self.admin, url_prefix='/admin/')
self._setup_done = True
@locked_cached_property @locked_cached_property
def theme_paths(self): def theme_paths(self):
@ -612,7 +642,6 @@ class Poobrain(flask.Flask):
except jinja2.exceptions.TemplateNotFound: except jinja2.exceptions.TemplateNotFound:
abort(404) abort(404)
else: else:
paths = [os.path.join(path, resource) for path in app.theme_paths] paths = [os.path.join(path, resource) for path in app.theme_paths]
@ -765,7 +794,7 @@ class Poobrain(flask.Flask):
except LookupError: except LookupError:
pass pass
raise LookupError("Failed generating URL for {cls.__name__}[{url_params.get('handle', None)}]-{mode}. No matching route found.") raise LookupError(f"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): def get_related_view_url(self, cls, handle, related_field, add=None):

View File

@ -576,6 +576,8 @@ class Dataset(EphemeralDataset, poobrains.commenting.Commentable):
class Meta: class Meta:
database = app.db
modes = collections.OrderedDict([ modes = collections.OrderedDict([
('add', 'create'), ('add', 'create'),
('teaser', 'read'), ('teaser', 'read'),

View File

@ -77,12 +77,15 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
self['file'] = poobrains.form.fields.File() self['file'] = poobrains.form.fields.File()
self['consumer'] = poobrains.form.fields.Select(choices=( self['consumer'] = poobrains.form.fields.Select(choices=(
('csv', "CSV"), ('csv', "CSV"),
('geojson', "geojson"), ('geojson', "geojson"),
)) ))
elif self.state == 'csv': if self.state == 'csv':
self['consumer'].value = 'csv'
self['consumer'].readonly = True
csv_options = self.session['csv_options'] csv_options = self.session['csv_options']
@ -102,13 +105,14 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
flash(f"Full error message: {e}", 'error') flash(f"Full error message: {e}", 'error')
else: else:
self.session['csv_options'] = csv_options
table = poobrains.rendering.Table(title='CSV preview') table = poobrains.rendering.Table(title='CSV preview')
for idx, row in enumerate(reader): for idx, row in enumerate(reader):
if idx == 5: if idx == 5:
break break
table.append(*row) 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') self.approve = poobrains.form.Button('submit', label='Approve')
escaped = {} escaped = {}
@ -134,21 +138,46 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
def process(self, submit): def process(self, submit):
if submit == 'change_csv_options': if self.state == 'csv':
unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape') if submit == 'change_csv_options':
self.session['csv_options'] = { unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape')
'delimiter': unescape(self['delimiter'].value), self.session['csv_options'] = {
'doublequote': self['doublequote'].value, 'delimiter': unescape(self['delimiter'].value or ''),
'escapechar': unescape(self['escapechar'].value), 'doublequote': self['doublequote'].value,
'lineterminator': unescape(self['lineterminator'].value), 'escapechar': unescape(self['escapechar'].value or ''),
'quotechar': unescape(self['quotechar'].value), 'lineterminator': unescape(self['lineterminator'].value),
'quoting': self['quoting'].value, 'quotechar': unescape(self['quotechar'].value or ''),
'skipinitialspace': self['skipinitialspace'].value, 'quoting': self['quoting'].value,
} 'skipinitialspace': self['skipinitialspace'].value,
}
flash("Changed CSV dialect options.") flash("Changed CSV dialect options.")
else:
dialect = dialect_from_dict(self.session['csv_options'])
else: # means parent form wasn't submitting via cancel button i.e. the "load" button was clicked 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')
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: # submitted via load button with file upload
consumer = self['consumer'].value consumer = self['consumer'].value
raw = self['file'].value.read() raw = self['file'].value.read()
@ -184,7 +213,7 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
elif consumer == 'csv': elif consumer == 'csv':
self.state = 'csv' self.state = 'csv'
self.session['data_action_info']['csv_options'] = None self.session['csv_options'] = None
else: else:
flash("Unhandled", 'error') flash("Unhandled", 'error')

View File

@ -39,7 +39,7 @@ def enforce_tls():
)) ))
@app.before_first_request @app.setup
def admin_setup(): def admin_setup():
if not app._got_first_request: if not app._got_first_request:

View File

@ -331,18 +331,13 @@ def add(storable):
default = None default = None
if field.default: if callable(field.default):
default = field.default()
else:
default = field.default
if callable(field.default): if field.null and default is None:
default = field.default() default = '' # None interpreted as no default, '' treated as no value
else:
default = field.default
elif field.type == types.DATETIME:
default = datetime.datetime.now()
elif field.type == types.BOOL:
default = False
value = click.prompt(field.name, type=field.type, default=default) value = click.prompt(field.name, type=field.type, default=default)

View File

@ -321,7 +321,7 @@ class BaseField(object, metaclass=poobrains.helpers.MetaCompatibility):
return 'true' if value == True else 'false' return 'true' if value == True else 'false'
elif isinstance(value, datetime.datetime): elif isinstance(value, datetime.datetime):
return value.strftime('%Y-%m-%d %H:%M:%S') return value.strftime('%Y-%m-%dT%H:%M:%S')
elif inspect.isclass(value): elif inspect.isclass(value):
return value.__name__ return value.__name__
@ -545,3 +545,8 @@ class File(Field):
class Meta: class Meta:
abstract = True abstract = True
def bind(self, value):
# simple passthrough because this is filled with object(s) from
# request.files and shouldn't be converted to a string.
self.value = value

View File

@ -49,7 +49,7 @@ class DateTimeParamType(ParamType):
return value # apparently we need this function to be idempotent. return value # apparently we need this function to be idempotent.
try: try:
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') return datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
except ValueError as e: except ValueError as e:
@ -61,7 +61,7 @@ class DateTimeParamType(ParamType):
else: else:
try: try:
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S.%f') return datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
except ValueError as e: except ValueError as e:
@ -71,7 +71,7 @@ class DateTimeParamType(ParamType):
self.fail("We dun goof'd, this field isn't working.") self.fail("We dun goof'd, this field isn't working.")
else: else:
self.fail(f"'{value}' is not a valid datetime. Expected format '%Y-%m-%d %H:%M:%S' or '%Y-%m-%d %H:%M:%S.%f'") self.fail(f"'{value}' is not a valid datetime. Expected format '%Y-%m-%dT%H:%M:%S' or '%Y-%m-%dT%H:%M:%S.%f'")
DATETIME = DateTimeParamType() DATETIME = DateTimeParamType()

View File

@ -263,7 +263,7 @@ def pretty_bytes(bytecount):
value /= 1024.0 value /= 1024.0
return "{value:.2f} {unit}" return f"{value:.2f} {unit}"
def levenshtein_distance(s1, s2): def levenshtein_distance(s1, s2):
@ -329,6 +329,7 @@ class FakeMetaOptions(object):
modes = None modes = None
permission_class = None permission_class = None
schema = None # needed by peewee.ModelBase.__new__ schema = None # needed by peewee.ModelBase.__new__
database = None # needed py peewee.ModelBase.__new__ at least from 3.15.0 on
_additional_keys = None # needed by peewee.ModelBase.__new__ _additional_keys = None # needed by peewee.ModelBase.__new__
def __init__(self): def __init__(self):

View File

@ -71,7 +71,7 @@ class SVG(poobrains.auth.Protected):
return poobrains.helpers.ThemedPassthrough(super(SVG, self).view(mode=mode, handle=handle)) return poobrains.helpers.ThemedPassthrough(super(SVG, self).view(mode=mode, handle=handle))
@app.before_first_request @app.setup
def register_svg_raw(): def register_svg_raw():
for cls in set(SVG.class_children()): for cls in set(SVG.class_children()):
rule = os.path.join("/svg/", cls.__name__.lower(), '<handle>', 'raw') rule = os.path.join("/svg/", cls.__name__.lower(), '<handle>', 'raw')

View File

@ -207,7 +207,7 @@ y
if name.isupper(): if name.isupper():
poobrains.app.config[name] = getattr(config, name) poobrains.app.config[name] = getattr(config, name)
client.get('/') # first request that triggers before_first_request to finish booting poobrains client.get('/') # first request that triggers app.run_setup to finish booting poobrains
def test_cli_minica(client): def test_cli_minica(client):

View File

@ -1,8 +1,8 @@
$color_background_dark: rgba(8,8,8, 0.8); $color_background_dark: rgba(8,8,8, 0.85);
$color_background_light: rgba(255,255,255, 0.8); $color_background_light: rgba(255,255,255, 0.85);
$color_highlight: rgba(0,128,255, 0.8); $color_highlight: rgba(0,128,255, 0.85);
/*$color_highlight: transparentize(rebeccapurple, 20%);*/ /*$color_highlight: transparentize(rebeccapurple, 20%);*/
$color_danger: rgba(255, 0, 128, 0.8); $color_danger: rgba(255, 0, 128, 0.85);
$color_font_dark: opacify($color_background_dark, 10%); $color_font_dark: opacify($color_background_dark, 10%);
$color_font_light: opacify($color_background_light, 10%); $color_font_light: opacify($color_background_light, 10%);
$color_background_form: opacify($color_background_light, -0.3); $color_background_form: opacify($color_background_light, -0.3);

View File

@ -104,7 +104,8 @@
@media (min-width: 56rem) { @media (min-width: 56rem) {
#logo-link { #logo-link {
display: flex !important; display: block !important;
padding: 4rem 0;
} }
body > header { body > header {
@ -113,7 +114,6 @@
.sticky { .sticky {
width: 13rem; width: 13rem;
top: 11.5rem !important; /* 7.5rem logo height + 2rem space at top and bottom */ top: 11.5rem !important; /* 7.5rem logo height + 2rem space at top and bottom */
margin-bottom: 4rem; /* needed at least in firefox, to avoid vertical cutoff. should be the same as vertical padding for logo */
} }
} }
@ -133,10 +133,10 @@ html {
width: 100%; width: 100%;
min-height: 100vh; min-height: 100vh;
/*overflow-y: scroll;*/ /* always show vertical scrollbar, viewport dependant widths stay the same */ /*overflow-y: scroll;*/ /* always show vertical scrollbar, viewport dependant widths stay the same */
background-image: url('/theme/bg.svg'), linear-gradient(45deg, lighten(opacify($color_background_dark, 100%), 15%), mix($color_highlight, $color_background_dark, 25%)); /*background-image: url('/theme/bg.svg'), linear-gradient(45deg, lighten(opacify($color_background_dark, 100%), 15%), mix($color_highlight, $color_background_dark, 25%));*/
background-image: url('/theme/bg.svg');
/*background-image: url('/theme/pooscape-probablyold.svg'), linear-gradient(to bottom, opacify($color_background_dark, 0.2), darken(opacify($color_highlight, 0.2), 40%) 100%);*/ /*background-image: url('/theme/pooscape-probablyold.svg'), linear-gradient(to bottom, opacify($color_background_dark, 0.2), darken(opacify($color_highlight, 0.2), 40%) 100%);*/
/*background-image: url('/theme/test.png');*/ background-color: opacify($color_background_dark, 100%);
background-color: #333;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-attachment: fixed; background-attachment: fixed;
@ -173,7 +173,6 @@ body {
line-height: 3rem; line-height: 3rem;
display: inherit; display: inherit;
background: $color_background_light; background: $color_background_light;
backdrop-filter: blur($backdrop_blur);
color: $color_font_dark; color: $color_font_dark;
font-family: 'Orbitron', sans; font-family: 'Orbitron', sans;
font-weight: 100; font-weight: 100;
@ -226,25 +225,18 @@ body {
flex-basis: 15rem; flex-basis: 15rem;
flex-shrink: 1; flex-shrink: 1;
flex-grow: 15; flex-grow: 15;
display: flex;
flex-direction: column; flex-direction: column;
align-self: stretch; align-self: stretch;
align-items: center; align-items: center;
justify-content: space-around;
max-height: 100vh; max-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
padding: 1rem; padding: 1rem;
z-index: 200; z-index: 200;
background-color: $color_background_dark; background-color: $color_background_dark;
backdrop-filter: blur($backdrop_blur);
/*border-left: 0.25rem solid;
border-right: 0.25rem solid;
border-bottom: 0.25rem solid;
border-color: $color_border;*/
/*-webkit-clip-path: polygon(0 0, 0 calc(100% - 3rem), 50% 100%, 100% calc(100% - 3rem), 100% 0);*/ /*-webkit-clip-path: polygon(0 0, 0 calc(100% - 3rem), 50% 100%, 100% calc(100% - 3rem), 100% 0);*/
overflow: hidden; overflow: auto;
&:nth-child(2) { /* means dashbar is rendered */ &:nth-child(2) { /* means dashbar is rendered */
margin-top: 3rem; margin-top: 3rem;
@ -320,6 +312,8 @@ body {
} }
#logo { #logo {
display: block;
margin: 0 auto;
height: 7.5rem; height: 7.5rem;
pointer-events: none; pointer-events: none;
} }
@ -350,7 +344,6 @@ main {
top: 0; top: 0;
padding: 2rem 1rem; padding: 2rem 1rem;
background: $color_background_light; background: $color_background_light;
backdrop-filter: blur($backdrop_blur);
color: $color_font_dark; color: $color_font_dark;
z-index: 110; z-index: 110;
@ -510,7 +503,6 @@ main {
& article.content-type-tag.mode-full > .content > .description { & article.content-type-tag.mode-full > .content > .description {
margin-top: 0.5rem; margin-top: 0.5rem;
background: $color_background_dark; background: $color_background_dark;
backdrop-filter: blur($backdrop_blur);
padding: 1rem; padding: 1rem;
} }
@ -533,7 +525,6 @@ main {
margin: 0.25rem 0; margin: 0.25rem 0;
padding: 0.5rem; padding: 0.5rem;
background: $color_background_dark; background: $color_background_dark;
backdrop-filter: blur($backdrop_blur);
&, a { &, a {
color: opacify($color_font_light, -0.3); color: opacify($color_font_light, -0.3);
@ -606,7 +597,6 @@ nav.menu {
a { a {
display: inline-block; display: inline-block;
background: $color_background_light; background: $color_background_light;
backdrop-filter: blur($backdrop_blur);
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
line-height: 2.3rem; /* +0.3rem based on fiddling around in ff inspector */ line-height: 2.3rem; /* +0.3rem based on fiddling around in ff inspector */
@ -642,7 +632,6 @@ nav.menu {
background-color: $color_background_dark; background-color: $color_background_dark;
background-size: 32px 32px; background-size: 32px 32px;
background-position: 4px 4px; background-position: 4px 4px;
backdrop-filter: blur($backdrop_blur);
font-weight: 400; font-weight: 400;
transition: background 0.3s linear, color 0.3s linear; transition: background 0.3s linear, color 0.3s linear;
@ -677,7 +666,6 @@ ul {
& > div.comment { & > div.comment {
/* comments have transparent bg by default, avoid breaking the design when showing them outside of an article */ /* comments have transparent bg by default, avoid breaking the design when showing them outside of an article */
background: $color_background_dark; background: $color_background_dark;
backdrop-filter: blur($backdrop_blur);
padding: 1rem; padding: 1rem;
} }
} }
@ -827,7 +815,6 @@ form {
& > header { & > header {
background: $color_background_light; background: $color_background_light;
backdrop-filter: blur($backdrop_blur);
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
h2 { h2 {
@ -856,7 +843,6 @@ form {
& > div.content { & > div.content {
background: $color_background_dark; background: $color_background_dark;
backdrop-filter: blur($backdrop_blur);
} }
} }
@ -906,7 +892,6 @@ article {
padding: 0.25rem 1rem; padding: 0.25rem 1rem;
background: $color_background_light; background: $color_background_light;
backdrop-filter: blur($backdrop_blur);
color: $color_font_dark; color: $color_font_dark;
&::after { &::after {

View File

@ -69,14 +69,14 @@ svg#hexascroll {
animation: decorotion 5s linear infinite; animation: decorotion 5s linear infinite;
use { use {
/*animation: scroll 10s linear infinite; /* apply the animation to every hexagon instead of the g so we don't get perspective fuckups */*/ animation: scroll 10s linear infinite; /* apply the animation to every hexagon instead of the g so we don't get perspective fuckups */
} }
} }
} }
use { use {
fill: opacify($color_background_dark, -0.2); fill: opacify($color_background_dark, -0.2);
stroke: $color_background_light; stroke: darken($color_highlight, 30%);
stroke-width: 2; stroke-width: 2;
&.hexagon { &.hexagon {

View File

@ -6,7 +6,7 @@
width="100%" width="100%"
height="100%" height="100%"
viewBox="0 0 100 100" viewBox="0 0 100% 100%"
preserveAspectRatio="xMinYMin meet" preserveAspectRatio="xMinYMin meet"
id="{{ content.dataset.ref_id }}" id="{{ content.dataset.ref_id }}"
class="plot {{ content.__class__.__name__.lower()}}" class="plot {{ content.__class__.__name__.lower()}}"

View File

@ -57,7 +57,7 @@ class UploadForm(poobrains.auth.AutoForm):
upload_file = self.fields['upload'].value upload_file = self.fields['upload'].value
filename = self.fields['filename'].value filename = self.fields['filename'].value
if filename is not '': if filename != '':
file_path = os.path.join(self.instance.path, filename) file_path = os.path.join(self.instance.path, filename)