Compare commits

..

No commits in common. "main" and "compat" have entirely different histories.
main ... compat

15 changed files with 92 additions and 138 deletions

View File

@ -440,31 +440,19 @@ 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=None: self) @click.group(cls=flask.cli.FlaskGroup, create_app=lambda x: 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)
@ -549,14 +537,6 @@ 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()
@ -566,28 +546,18 @@ class Poobrain(flask.Flask):
return True # autoescape everything return True # autoescape everything
def setup(self, f): def try_trigger_before_first_request_functions(self):
self.setup_funcs.append(f) if not self.setup in self.before_first_request_funcs:
self.before_first_request_funcs.append(self.setup)
super(Poobrain, self).try_trigger_before_first_request_functions()
return f
def run_setup(self): def 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):
@ -642,6 +612,7 @@ 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]
@ -794,7 +765,7 @@ class Poobrain(flask.Flask):
except LookupError: except LookupError:
pass pass
raise LookupError(f"Failed generating URL for {cls.__name__}[{url_params.get('handle', None)}]-{mode}. No matching route found.") 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): def get_related_view_url(self, cls, handle, related_field, add=None):

View File

@ -576,8 +576,6 @@ 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,15 +77,12 @@ 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"),
)) ))
if self.state == 'csv': elif self.state == 'csv':
self['consumer'].value = 'csv'
self['consumer'].readonly = True
csv_options = self.session['csv_options'] csv_options = self.session['csv_options']
@ -105,14 +102,13 @@ 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(table) self['table'] = poobrains.form.fields.RenderableWrapper(value=table)
self.approve = poobrains.form.Button('submit', label='Approve') self.approve = poobrains.form.Button('submit', label='Approve')
escaped = {} escaped = {}
@ -138,46 +134,21 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
def process(self, submit): def process(self, submit):
if self.state == 'csv': if submit == 'change_csv_options':
if submit == 'change_csv_options': unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape')
unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape') self.session['csv_options'] = {
self.session['csv_options'] = { 'delimiter': unescape(self['delimiter'].value),
'delimiter': unescape(self['delimiter'].value or ''), 'doublequote': self['doublequote'].value,
'doublequote': self['doublequote'].value, 'escapechar': unescape(self['escapechar'].value),
'escapechar': unescape(self['escapechar'].value or ''), 'lineterminator': unescape(self['lineterminator'].value),
'lineterminator': unescape(self['lineterminator'].value), 'quotechar': unescape(self['quotechar'].value),
'quotechar': unescape(self['quotechar'].value or ''), 'quoting': self['quoting'].value,
'quoting': self['quoting'].value, 'skipinitialspace': self['skipinitialspace'].value,
'skipinitialspace': self['skipinitialspace'].value, }
}
flash("Changed CSV dialect options.") flash("Changed CSV dialect options.")
else:
dialect = dialect_from_dict(self.session['csv_options'])
try: else: # means parent form wasn't submitting via cancel button i.e. the "load" button was clicked
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()
@ -213,7 +184,7 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
elif consumer == 'csv': elif consumer == 'csv':
self.state = 'csv' self.state = 'csv'
self.session['csv_options'] = None self.session['data_action_info']['csv_options'] = None
else: else:
flash("Unhandled", 'error') flash("Unhandled", 'error')

View File

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

View File

@ -331,13 +331,18 @@ def add(storable):
default = None default = None
if callable(field.default): if field.default:
default = field.default()
else:
default = field.default
if field.null and default is None: if callable(field.default):
default = '' # None interpreted as no default, '' treated as no value default = field.default()
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-%dT%H:%M:%S') return value.strftime('%Y-%m-%d %H:%M:%S')
elif inspect.isclass(value): elif inspect.isclass(value):
return value.__name__ return value.__name__
@ -545,8 +545,3 @@ 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-%dT%H:%M:%S') return datetime.datetime.strptime(value, '%Y-%m-%d %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-%dT%H:%M:%S.%f') return datetime.datetime.strptime(value, '%Y-%m-%d %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-%dT%H:%M:%S' or '%Y-%m-%dT%H:%M:%S.%f'") 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'")
DATETIME = DateTimeParamType() DATETIME = DateTimeParamType()

View File

@ -263,7 +263,7 @@ def pretty_bytes(bytecount):
value /= 1024.0 value /= 1024.0
return f"{value:.2f} {unit}" return "{value:.2f} {unit}"
def levenshtein_distance(s1, s2): def levenshtein_distance(s1, s2):
@ -329,7 +329,6 @@ 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.setup @app.before_first_request
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 app.run_setup to finish booting poobrains client.get('/') # first request that triggers before_first_request 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.85); $color_background_dark: rgba(8,8,8, 0.8);
$color_background_light: rgba(255,255,255, 0.85); $color_background_light: rgba(255,255,255, 0.8);
$color_highlight: rgba(0,128,255, 0.85); $color_highlight: rgba(0,128,255, 0.8);
/*$color_highlight: transparentize(rebeccapurple, 20%);*/ /*$color_highlight: transparentize(rebeccapurple, 20%);*/
$color_danger: rgba(255, 0, 128, 0.85); $color_danger: rgba(255, 0, 128, 0.8);
$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,8 +104,7 @@
@media (min-width: 56rem) { @media (min-width: 56rem) {
#logo-link { #logo-link {
display: block !important; display: flex !important;
padding: 4rem 0;
} }
body > header { body > header {
@ -114,6 +113,7 @@
.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-color: opacify($color_background_dark, 100%); /*background-image: url('/theme/test.png');*/
background-color: #333;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-attachment: fixed; background-attachment: fixed;
@ -173,6 +173,7 @@ 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;
@ -225,18 +226,25 @@ 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: auto; overflow: hidden;
&:nth-child(2) { /* means dashbar is rendered */ &:nth-child(2) { /* means dashbar is rendered */
margin-top: 3rem; margin-top: 3rem;
@ -312,8 +320,6 @@ body {
} }
#logo { #logo {
display: block;
margin: 0 auto;
height: 7.5rem; height: 7.5rem;
pointer-events: none; pointer-events: none;
} }
@ -344,6 +350,7 @@ 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;
@ -503,6 +510,7 @@ 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;
} }
@ -525,6 +533,7 @@ 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);
@ -597,6 +606,7 @@ 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 */
@ -632,6 +642,7 @@ 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;
@ -666,6 +677,7 @@ 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;
} }
} }
@ -815,6 +827,7 @@ 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 {
@ -843,6 +856,7 @@ form {
& > div.content { & > div.content {
background: $color_background_dark; background: $color_background_dark;
backdrop-filter: blur($backdrop_blur);
} }
} }
@ -892,6 +906,7 @@ 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: darken($color_highlight, 30%); stroke: $color_background_light;
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 != '': if filename is not '':
file_path = os.path.join(self.instance.path, filename) file_path = os.path.join(self.instance.path, filename)