Skip to content

Commit

Permalink
Merge pull request #83 from ctreffe/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jobrachem authored May 21, 2022
2 parents 19a6156 + c5680c4 commit b807c9c
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 58 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ dist/
mortimer_venv/
.mortimer/
.venv/
.python-version
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Mortimer v0.8.13 (Released 2022-05-21)

### Changed

- (New) users are now allowed to remove documents in their own alfred3
mongoDB collections.

### Fixed

- Fixed support for url-jumping support
- Fixed #82

### Added

- Added logging capability (#78)
- Added log calls for 412 server responses (#79)

## Mortimer v0.8.12 (Released 2021-10-28)

### Added
Expand Down
10 changes: 9 additions & 1 deletion src/mortimer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@
db = MongoEngine() # mortimer database

# application factory
def create_app(instance_path=None):
def create_app(instance_path=None, logfile: str = None):
logger = logging.getLogger("mortimer")
fh = logging.FileHandler(logfile)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
fh.setFormatter(formatter)

logger.addHandler(fh)
logger.setLevel(logging.INFO)

app = Flask(__name__, instance_path=instance_path)

# apply configuration
Expand Down
2 changes: 1 addition & 1 deletion src/mortimer/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@



__version__ = "0.8.12"
__version__ = "0.8.13"
2 changes: 1 addition & 1 deletion src/mortimer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class BaseConfig:
MONGODB_SETTINGS["password"] = None
MONGODB_SETTINGS["authentication_source"] = "admin"
MONGODB_SETTINGS["ssl"] = False
MONGODB_SETTINGS["ssl_ca_certs"] = None
MONGODB_SETTINGS["tlsCAFile"] = None

# Alfred settings
ALFRED_DB = "alfred"
Expand Down
12 changes: 2 additions & 10 deletions src/mortimer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _prepare_db_role_privileges(self):
c_unlinked = {**res, **{"collection": self.alfred_col_unlinked}}
c_misc = {**res, **{"collection": self.alfred_col_misc}}

act = ["find", "insert", "update"]
act = ["find", "insert", "update", "remove"]
priv = [{"resource": res_dict, "actions": act} for res_dict in [c_exp, c_unlinked, c_misc]]
return priv

Expand Down Expand Up @@ -163,7 +163,7 @@ def mongo_saving_agent(self) -> dict:
"user": self.alfred_user,
"password": f.decrypt(self.alfred_pw).decode(),
"use_ssl": str(appdb_config.get("ssl")).lower(),
"ca_file_path": str(appdb_config.get("ssl_ca_certs")),
"ca_file_path": str(appdb_config.get("tlsCAFile")),
"activation_level": "1",
}
return {"mongo_saving_agent": mongo_config}
Expand Down Expand Up @@ -273,14 +273,6 @@ def parse_exp_secrets(self) -> ExperimentSecrets:
try:
secrets_string = f.decrypt(self.exp_secrets).decode()
except TypeError:
logger = logging.getLogger(__name__)
logger.info(
(
"Exception during secrets decryption. "
"To proceed, the value of secrets.conf was set to an empty string."
f" Exp id={str(self.id)}"
)
)
secrets_string = ""

exp_secrets = ExperimentSecrets(expdir=self.path)
Expand Down
8 changes: 4 additions & 4 deletions src/mortimer/templates/experiment.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ <h1 class="card-text"><b>{{ n["fin"] }}</b> / <span class="text-muted">{{ n["tot
</div>
<div class="card-body border-info">
<span class="text-muted">
{% if activity["last"] != 'none' %}
{{ activity["last"] }}
{% if activity["last_time"] != 'none' %}
{{ activity["last_time"] }}
{% else %}
- none -
{% endif %}
</span>
<h3 class="card-text text-info">
{% if activity["last"] != 'none' %}
{{ activity["last"] }}
{% if activity["last_date"] != 'none' %}
{{ activity["last_date"] }}
{% else %}
- none -
{% endif %}
Expand Down
102 changes: 64 additions & 38 deletions src/mortimer/web_experiments/alfredo.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@
send_from_directory,
session,
url_for,
jsonify
jsonify,
)
from flask_login import current_user

from mortimer.models import WebExperiment, User
from mortimer.utils import create_fernet, is_social_media_preview, render_social_media_preview
from mortimer.utils import (
create_fernet,
is_social_media_preview,
render_social_media_preview,
)
from alfred3 import alfredlog
import alfred3.config

Expand All @@ -45,7 +49,7 @@ def __init__(self):
self.config = None

def generate_experiment(self, config=None): # pylint: disable=method-hidden
"""Hook for the ``generate_experiment`` function extracted from
"""Hook for the ``generate_experiment`` function extracted from
the user's script.py. It is meant to be replaced in ``run.py``.
"""

Expand Down Expand Up @@ -75,7 +79,9 @@ def number_of_func_params(func):


def import_script(experiment_id):
experiment = WebExperiment.objects.get_or_404(id=experiment_id) # pylint: disable=no-member
experiment = WebExperiment.objects.get_or_404(
id=experiment_id
) # pylint: disable=no-member

# Fast path: see if the module has already been imported.
try:
Expand Down Expand Up @@ -127,22 +133,23 @@ def get(self, key):

self.lock.release()
if rv is None:
try:
print("Tried to access experiment with key %s" % key)
print("Available Keys: %s" % list(self.experiments.keys()))
except Exception:
pass
molog = logging.getLogger("mortimer")
molog.warning(
f"Tried to access experiment with session id '{key}'. Available Keys:"
f" {list(self.experiments.keys())}"
)
abort(412)
self.save(key, rv)
return rv

def remove_outdated(self):
self.lock.acquire()
current_time = int(time())
molog = logging.getLogger("mortimer")
for k in list(self.experiments.keys()):
v = self.experiments[k]
if current_time - v[0] > self.timeout:
print("delete exp with key %s and last access time %s" % (k, v[0]))
molog.warning(f"Delete exp with session id '{k}' and last access time {v[0]}")
del self.experiments[k]
self.lock.release()

Expand Down Expand Up @@ -170,17 +177,21 @@ def start(expid):
if is_social_media_preview(request.headers.get("User-Agent")):
return render_social_media_preview(config)

if not experiment.public and experiment.password != request.form.get("password", None):
if not experiment.public and experiment.password != request.form.get(
"password", None
):
exp_url = url_for("alfredo.start", expid=str(experiment.id))
return (
'<div align="center"><h1>Please enter the password</h1><form method="post" action="%s">\
<input type="password" name="password" /><button type="submit">Submit</button></form></div>'
% exp_url
'<div align="center"><h1>Please enter the password</h1><form method="post"'
' action="%s"> <input type="password" name="password" /><button'
' type="submit">Submit</button></form></div>' % exp_url
)

args = request.args.to_dict()
test_mode = args.get("test") in ["true", "True", "TRUE"]
debug_mode = args.get("debug") in ["true", "True", "TRUE"] or config.getboolean("general", "debug")
debug_mode = args.get("debug") in ["true", "True", "TRUE"] or config.getboolean(
"general", "debug"
)
if not experiment.active and not test_mode and not debug_mode:
return render_template("exp_inactive.html")

Expand All @@ -199,11 +210,8 @@ def start(expid):
try:
user_script = import_script(experiment.id)
exp_session = user_script.exp.create_session(
session_id=sid,
config=config,
secrets=secrets,
**request.args
)
session_id=sid, config=config, secrets=secrets, **request.args
)

except Exception:
msg = "Error during creation of experiment session."
Expand All @@ -215,10 +223,11 @@ def start(expid):
"web_experiments.experiment",
username=current_user.username,
exp_title=experiment.title,
))
)
)
else:
abort(500)

try:
exp_session._start()
experiment_manager.save(sid, exp_session)
Expand All @@ -237,28 +246,38 @@ def start(expid):
else:
abort(500)

page = request.args.get("page", None)
if page:
return redirect(url_for("alfredo.experiment", page=page))
return redirect(url_for("alfredo.experiment"))


@alfredo.route("/experiment", methods=["GET", "POST"])
def experiment():
molog = logging.getLogger("mortimer")
try:
sid = session["sid"]
except KeyError:
molog.warning(
"Experiment route called, but there was no session id in the session"
" cookie."
)
abort(412)

experiment = experiment_manager.get(sid)
tkey = experiment.current_page.name + sid

try:
if request.method == "GET":
url_pagename = request.args.get("page", None) # https://basepath.de/experiment?page=name
url_pagename = request.args.get(
"page", None
) # https://basepath.de/experiment?page=name
if url_pagename:
experiment.movement_manager.jump_by_name(name=url_pagename)
experiment.movement_manager.move(direction=f"jump>{url_pagename}")

token = session["page_tokens"].get(tkey, uuid4().hex)
session["page_tokens"][tkey] = token
session.modified = True # because the dict is mutable
session.modified = True # because the dict is mutable

current_page_html = make_response(experiment.ui.render_html(token))
current_page_html.cache_control.no_cache = True
Expand All @@ -269,11 +288,10 @@ def experiment():
submitted_token = request.values.get("page_token", None)

token = session["page_tokens"].pop(tkey, None)
session.modified = True # because the dict is mutable
session.modified = True # because the dict is mutable
if not token or not token == submitted_token:
return redirect(url_for("alfredo.experiment"))


data = request.values.to_dict()
data.pop("move", None)
data.pop("directjump", None)
Expand All @@ -287,9 +305,11 @@ def experiment():
else:
abort(400)
return redirect(url_for("alfredo.experiment"))

except Exception:
log = alfredlog.QueuedLoggingInterface("alfred3", f"exp.{str(experiment.exp_id)}")
log = alfredlog.QueuedLoggingInterface(
"alfred3", f"exp.{str(experiment.exp_id)}"
)
log.session_id = sid
msg = "Exception during experiment execution."
log.exception(msg)
Expand All @@ -311,9 +331,13 @@ def staticfile(identifier):
try:
sid = session["sid"]
experiment = experiment_manager.get(sid)
path, content_type = experiment.user_interface_controller.get_static_file(identifier)
path, content_type = experiment.user_interface_controller.get_static_file(
identifier
)
dirname, filename = os.path.split(path)
resp = make_response(send_from_directory(dirname, filename, mimetype=content_type))
resp = make_response(
send_from_directory(dirname, filename, mimetype=content_type)
)
# resp.cache_control.no_cache = True
return resp

Expand All @@ -326,13 +350,16 @@ def dynamicfile(identifier):
try:
sid = session["sid"]
experiment = experiment_manager.get(sid)
strIO, content_type = experiment.user_interface_controller.get_dynamic_file(identifier)
strIO, content_type = experiment.user_interface_controller.get_dynamic_file(
identifier
)
except KeyError:
abort(404)
resp = make_response(send_file(strIO, mimetype=content_type))
resp.cache_control.no_cache = True
return resp


@alfredo.route("/callable/<identifier>", methods=["GET", "POST"])
def callable(identifier):
try:
Expand All @@ -341,19 +368,18 @@ def callable(identifier):
f = experiment.user_interface_controller.get_callable(identifier)
except KeyError:
abort(404)

if request.content_type == "application/json":
values = request.get_json()
else:
values = request.values.to_dict()
values.pop("_", None) # remove argument with name "_"

values.pop("_", None) # remove argument with name "_"

rv = f(**values)
if rv is not None:
resp = jsonify(rv)
resp.cache_control.no_cache = True
return resp
else:
return (" ", 204)

9 changes: 6 additions & 3 deletions src/mortimer/web_experiments/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,16 @@ def experiment(username, exp_title):
if times:
try:
activity["first"] = datetime.fromtimestamp(times[0]["first"]).strftime('%Y-%m-%d, %H:%M')
activity["last"] = datetime.fromtimestamp(times[0]["last"]).strftime('%H:%M')
activity["last_time"] = datetime.fromtimestamp(times[0]["last"]).strftime('%H:%M')
activity["last_date"] = datetime.fromtimestamp(times[0]["last"]).strftime('%Y-%m-%d')
except TypeError:
activity["first"] = times[0]["first"]
activity["last"] = times[0]["last"]
activity["last_time"] = times[0]["last"]
activity["last_date"] = "none"
else:
activity["first"] = "none"
activity["last"] = "none"
activity["last_time"] = "none"
activity["last_date"] = "none"

# Form for script.py upload
form = NewScriptForm()
Expand Down

0 comments on commit b807c9c

Please sign in to comment.