Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a0cfe48862 | ||
|
07a741f454 | ||
|
342ad493d1 | ||
|
1f573ead23 | ||
|
d51a63ff55 | ||
|
baf2c983d4 | ||
|
f7d10f2ec4 | ||
|
fa4ee3faae | ||
|
c8c4ba6221 | ||
|
fcc790ffd7 | ||
|
ce69142949 | ||
|
f72b6694c8 | ||
|
50bad00c90 | ||
|
e8f554c576 | ||
|
c81a26eebb | ||
|
1100460530 | ||
|
3d01b9d6b4 | ||
|
df5682f0c2 | ||
|
8f51170cc1 | ||
|
0ce854bb02 |
@ -440,19 +440,31 @@ class Poobrain(flask.Flask):
|
||||
peewee.DoesNotExist: 404
|
||||
}
|
||||
|
||||
setup_funcs = None
|
||||
cronjobs = None
|
||||
|
||||
_setup_done = False
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
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.setup_funcs = []
|
||||
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")
|
||||
def cli(database):
|
||||
self.db = db_url.connect(database)
|
||||
@ -536,7 +548,15 @@ class Poobrain(flask.Flask):
|
||||
self.site = Pooprint('site', 'site')
|
||||
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):
|
||||
#self.cli(obj={})
|
||||
self.cli()
|
||||
@ -546,18 +566,28 @@ class Poobrain(flask.Flask):
|
||||
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.before_first_request_funcs.append(self.setup)
|
||||
super(Poobrain, self).try_trigger_before_first_request_functions()
|
||||
self.setup_funcs.append(f)
|
||||
|
||||
|
||||
def setup(self):
|
||||
return f
|
||||
|
||||
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.admin, url_prefix='/admin/')
|
||||
|
||||
self._setup_done = True
|
||||
|
||||
|
||||
@locked_cached_property
|
||||
def theme_paths(self):
|
||||
@ -573,7 +603,7 @@ class Poobrain(flask.Flask):
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
|
||||
def serve_theme_resources(self, resource):
|
||||
|
||||
r = False
|
||||
@ -612,7 +642,6 @@ class Poobrain(flask.Flask):
|
||||
except jinja2.exceptions.TemplateNotFound:
|
||||
abort(404)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
paths = [os.path.join(path, resource) for path in app.theme_paths]
|
||||
@ -629,12 +658,12 @@ class Poobrain(flask.Flask):
|
||||
r.cache_control.public = True
|
||||
r.cache_control.max_age = app.config['CACHE_LONG']
|
||||
return r
|
||||
|
||||
|
||||
abort(404)
|
||||
|
||||
|
||||
def request_setup(self):
|
||||
|
||||
|
||||
flask.g.boxes = {}
|
||||
flask.g.forms = {}
|
||||
#self.db.close() # fails first request and thus always on sqlite
|
||||
@ -765,7 +794,7 @@ class Poobrain(flask.Flask):
|
||||
except LookupError:
|
||||
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):
|
||||
|
||||
|
@ -576,6 +576,8 @@ class Dataset(EphemeralDataset, poobrains.commenting.Commentable):
|
||||
|
||||
class Meta:
|
||||
|
||||
database = app.db
|
||||
|
||||
modes = collections.OrderedDict([
|
||||
('add', 'create'),
|
||||
('teaser', 'read'),
|
||||
|
@ -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):
|
||||
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):
|
||||
|
||||
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.")
|
||||
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
|
||||
raw = self['file'].value.read()
|
||||
@ -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')
|
||||
|
@ -39,7 +39,7 @@ def enforce_tls():
|
||||
))
|
||||
|
||||
|
||||
@app.before_first_request
|
||||
@app.setup
|
||||
def admin_setup():
|
||||
|
||||
if not app._got_first_request:
|
||||
|
@ -331,18 +331,13 @@ def add(storable):
|
||||
|
||||
default = None
|
||||
|
||||
if field.default:
|
||||
if callable(field.default):
|
||||
default = field.default()
|
||||
else:
|
||||
default = field.default
|
||||
|
||||
if callable(field.default):
|
||||
default = field.default()
|
||||
else:
|
||||
default = field.default
|
||||
|
||||
elif field.type == types.DATETIME:
|
||||
default = datetime.datetime.now()
|
||||
|
||||
elif field.type == types.BOOL:
|
||||
default = False
|
||||
if field.null and default is None:
|
||||
default = '' # None interpreted as no default, '' treated as no value
|
||||
|
||||
value = click.prompt(field.name, type=field.type, default=default)
|
||||
|
||||
|
@ -321,7 +321,7 @@ class BaseField(object, metaclass=poobrains.helpers.MetaCompatibility):
|
||||
return 'true' if value == True else 'false'
|
||||
|
||||
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):
|
||||
return value.__name__
|
||||
@ -545,3 +545,8 @@ class File(Field):
|
||||
|
||||
class Meta:
|
||||
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
|
||||
|
@ -49,7 +49,7 @@ class DateTimeParamType(ParamType):
|
||||
return value # apparently we need this function to be idempotent.
|
||||
|
||||
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:
|
||||
|
||||
@ -61,7 +61,7 @@ class DateTimeParamType(ParamType):
|
||||
else:
|
||||
|
||||
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:
|
||||
|
||||
@ -71,7 +71,7 @@ class DateTimeParamType(ParamType):
|
||||
self.fail("We dun goof'd, this field isn't working.")
|
||||
|
||||
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()
|
||||
|
||||
|
@ -263,7 +263,7 @@ def pretty_bytes(bytecount):
|
||||
|
||||
value /= 1024.0
|
||||
|
||||
return "{value:.2f} {unit}"
|
||||
return f"{value:.2f} {unit}"
|
||||
|
||||
|
||||
def levenshtein_distance(s1, s2):
|
||||
@ -329,6 +329,7 @@ class FakeMetaOptions(object):
|
||||
modes = None
|
||||
permission_class = None
|
||||
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__
|
||||
|
||||
def __init__(self):
|
||||
|
@ -14,7 +14,7 @@ class SVG(poobrains.auth.Protected):
|
||||
|
||||
handle = None # needed so that app.expose registers a route with extra param, this is kinda hacky…
|
||||
_css_cache = None
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
modes = collections.OrderedDict([
|
||||
@ -23,7 +23,7 @@ class SVG(poobrains.auth.Protected):
|
||||
('raw', 'read'),
|
||||
('inline', 'read')
|
||||
])
|
||||
|
||||
|
||||
style = None
|
||||
|
||||
def __init__(self, handle=None, mode=None, **kwargs):
|
||||
@ -36,16 +36,16 @@ class SVG(poobrains.auth.Protected):
|
||||
else:
|
||||
self.style = Markup(app.scss_compiler.compile_string("@import 'svg';"))
|
||||
self.__class__._css_cache = self.style
|
||||
|
||||
|
||||
|
||||
|
||||
def templates(self, mode=None):
|
||||
|
||||
r = super(SVG, self).templates(mode=mode)
|
||||
return [f"svg/{template}" for template in r]
|
||||
|
||||
|
||||
|
||||
def instance_url(self, mode='full', quiet=False, **url_params):
|
||||
|
||||
|
||||
url_params['handle'] = self.handle
|
||||
|
||||
return super(SVG, self).instance_url(mode=mode, quiet=quiet, **url_params)
|
||||
@ -55,23 +55,23 @@ class SVG(poobrains.auth.Protected):
|
||||
def view(self, mode=None, handle=None):
|
||||
|
||||
if mode == 'raw':
|
||||
|
||||
|
||||
response = Response(self.render('raw'))
|
||||
response.headers['Content-Type'] = 'image/svg+xml'
|
||||
response.headers['Content-Disposition'] = f'filename="{self.__class__.__name__}.svg"'
|
||||
|
||||
|
||||
# Disable "public" mode caching downstream (nginx, varnish) in order to hopefully not leak restricted content
|
||||
response.cache_control.public = False
|
||||
response.cache_control.private = True
|
||||
response.cache_control.max_age = app.config['CACHE_LONG']
|
||||
|
||||
return response
|
||||
|
||||
|
||||
else:
|
||||
return poobrains.helpers.ThemedPassthrough(super(SVG, self).view(mode=mode, handle=handle))
|
||||
|
||||
|
||||
@app.before_first_request
|
||||
@app.setup
|
||||
def register_svg_raw():
|
||||
for cls in set(SVG.class_children()):
|
||||
rule = os.path.join("/svg/", cls.__name__.lower(), '<handle>', 'raw')
|
||||
|
@ -207,7 +207,7 @@ y
|
||||
if name.isupper():
|
||||
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):
|
||||
|
@ -1,8 +1,8 @@
|
||||
$color_background_dark: rgba(8,8,8, 0.8);
|
||||
$color_background_light: rgba(255,255,255, 0.8);
|
||||
$color_highlight: rgba(0,128,255, 0.8);
|
||||
$color_background_dark: rgba(8,8,8, 0.85);
|
||||
$color_background_light: rgba(255,255,255, 0.85);
|
||||
$color_highlight: rgba(0,128,255, 0.85);
|
||||
/*$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_light: opacify($color_background_light, 10%);
|
||||
$color_background_form: opacify($color_background_light, -0.3);
|
||||
|
@ -104,7 +104,8 @@
|
||||
@media (min-width: 56rem) {
|
||||
|
||||
#logo-link {
|
||||
display: flex !important;
|
||||
display: block !important;
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
body > header {
|
||||
@ -113,7 +114,6 @@
|
||||
.sticky {
|
||||
width: 13rem;
|
||||
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%;
|
||||
min-height: 100vh;
|
||||
/*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/test.png');*/
|
||||
background-color: #333;
|
||||
background-color: opacify($color_background_dark, 100%);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
@ -173,7 +173,6 @@ body {
|
||||
line-height: 3rem;
|
||||
display: inherit;
|
||||
background: $color_background_light;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
color: $color_font_dark;
|
||||
font-family: 'Orbitron', sans;
|
||||
font-weight: 100;
|
||||
@ -226,25 +225,18 @@ body {
|
||||
flex-basis: 15rem;
|
||||
flex-shrink: 1;
|
||||
flex-grow: 15;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: stretch;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
max-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
z-index: 200;
|
||||
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);*/
|
||||
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
|
||||
&:nth-child(2) { /* means dashbar is rendered */
|
||||
margin-top: 3rem;
|
||||
@ -320,6 +312,8 @@ body {
|
||||
}
|
||||
|
||||
#logo {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
height: 7.5rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -350,7 +344,6 @@ main {
|
||||
top: 0;
|
||||
padding: 2rem 1rem;
|
||||
background: $color_background_light;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
color: $color_font_dark;
|
||||
z-index: 110;
|
||||
|
||||
@ -510,7 +503,6 @@ main {
|
||||
& article.content-type-tag.mode-full > .content > .description {
|
||||
margin-top: 0.5rem;
|
||||
background: $color_background_dark;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
@ -533,7 +525,6 @@ main {
|
||||
margin: 0.25rem 0;
|
||||
padding: 0.5rem;
|
||||
background: $color_background_dark;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
|
||||
&, a {
|
||||
color: opacify($color_font_light, -0.3);
|
||||
@ -606,7 +597,6 @@ nav.menu {
|
||||
a {
|
||||
display: inline-block;
|
||||
background: $color_background_light;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
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-size: 32px 32px;
|
||||
background-position: 4px 4px;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
font-weight: 400;
|
||||
|
||||
transition: background 0.3s linear, color 0.3s linear;
|
||||
@ -677,7 +666,6 @@ ul {
|
||||
& > div.comment {
|
||||
/* comments have transparent bg by default, avoid breaking the design when showing them outside of an article */
|
||||
background: $color_background_dark;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
@ -827,7 +815,6 @@ form {
|
||||
& > header {
|
||||
|
||||
background: $color_background_light;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
h2 {
|
||||
@ -856,7 +843,6 @@ form {
|
||||
|
||||
& > div.content {
|
||||
background: $color_background_dark;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
}
|
||||
}
|
||||
|
||||
@ -906,7 +892,6 @@ article {
|
||||
|
||||
padding: 0.25rem 1rem;
|
||||
background: $color_background_light;
|
||||
backdrop-filter: blur($backdrop_blur);
|
||||
color: $color_font_dark;
|
||||
|
||||
&::after {
|
||||
|
@ -69,14 +69,14 @@ svg#hexascroll {
|
||||
animation: decorotion 5s linear infinite;
|
||||
|
||||
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 {
|
||||
fill: opacify($color_background_dark, -0.2);
|
||||
stroke: $color_background_light;
|
||||
stroke: darken($color_highlight, 30%);
|
||||
stroke-width: 2;
|
||||
|
||||
&.hexagon {
|
||||
|
@ -6,7 +6,7 @@
|
||||
width="100%"
|
||||
height="100%"
|
||||
|
||||
viewBox="0 0 100 100"
|
||||
viewBox="0 0 100% 100%"
|
||||
preserveAspectRatio="xMinYMin meet"
|
||||
id="{{ content.dataset.ref_id }}"
|
||||
class="plot {{ content.__class__.__name__.lower()}}"
|
||||
|
@ -57,7 +57,7 @@ class UploadForm(poobrains.auth.AutoForm):
|
||||
upload_file = self.fields['upload'].value
|
||||
filename = self.fields['filename'].value
|
||||
|
||||
if filename is not '':
|
||||
if filename != '':
|
||||
|
||||
file_path = os.path.join(self.instance.path, filename)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user