Skip to content

Commit

Permalink
Merge pull request #32 from ctreffe/develop
Browse files Browse the repository at this point in the history
Mortimer v0.4.2
  • Loading branch information
jobrachem authored Apr 2, 2020
2 parents e751115 + fb55ca0 commit b8f176f
Show file tree
Hide file tree
Showing 44 changed files with 1,944 additions and 1,411 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ move_to_default_alfred.conf
alfredo2_ca_chain.pem
alfred.conf
exp/
.vscode/settings.json
test.py
34 changes: 27 additions & 7 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
# Mortimer v0.4.2

## Bugfixes

* Fixed a bug introduced in v0.4.1, which led to a problem with new experiments. Their experiment ID did not get saved at the right time, which caused trouble with data export.

## Smaller changes

* Mortimer now displays its own version number and the alfred version currently running on the server.

# Mortimer v0.4.1

## Bugfixes

* Fixed a bug in `alfredo.py` that caused trouble for videos implemented via `alfred.element.WebVideoElement` in Safari (wouldn't play at all) and Chrome (forward/backward wouldn't work)

# Mortimer v0.4-beta

## Breaking changes
- **Separation of web and local experiments**. Web experiments hosted via mortimer and local experiments now save their data into different collections in the Alfred database (`web` and `local`). This means that you can be completely sure that your web and local experiments don't interfere.
- **`exp_id`** for local experiments. You need to specify a unique `exp_id` in the section `[metadata]` of your `config.conf` to be sure that you can retrieve your data from local experiments. Thankfully, Mortimer will always give you a new unique ID on its home page and on the page for local experiment creation.

* **Separation of web and local experiments**. Web experiments hosted via mortimer and local experiments now save their data into different collections in the Alfred database ( `web` and `local` ). This means that you can be completely sure that your web and local experiments don't interfere.
* **`exp_id`** for local experiments. You need to specify a unique `exp_id` in the section `[metadata]` of your `config.conf` to be sure that you can retrieve your data from local experiments. Thankfully, Mortimer will always give you a new unique ID on its home page and on the page for local experiment creation.

## New user interface
- **Fully reworked user interface**. The new interface should be quite self-explanatory. Feel free to explore!

* **Fully reworked user interface**. The new interface should be quite self-explanatory. Feel free to explore!

## New features
- **File Management**

* **File Management**
- You can create directories and subdirectories belonging to your experiment
- You can upload now multiple files at once
- No one but you can access you files
- **Configuration**. You can now configure your experiment from within Mortimer.
- **Experiment Log**. You can now view your experiment's log file from within Mortimer.
- **Futurize Scripts**. You can futurize an old script to help you port it to Alfred v1.0.
* **Configuration**. You can now configure your experiment from within Mortimer.
* **Experiment Log**. You can now view your experiment's log file from within Mortimer.
* **Futurize Scripts**. You can futurize an old script to help you port it to Alfred v1.0.

12 changes: 11 additions & 1 deletion mortimer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python

# -*- coding: utf-8 -*-

from flask import Flask
Expand All @@ -8,6 +8,9 @@
from flask_mail import Mail
from flask_dropzone import Dropzone
import pymongo
import alfred

__version__ = '0.4.2'

# the EnvironSetter sets enviroment variables for the current session
# It is not included in the GitHub repository, because it contains sensitive
Expand Down Expand Up @@ -78,6 +81,13 @@ def create_app(config_class=Config):
app.register_blueprint(local_experiments)
app.register_blueprint(errors)

# global variables for use in templates
@app.context_processor
def version_processor():
mv = __version__
from alfred import __version__ as av
return {"v_mortimer": mv, "v_alfred": av}

# bind extensions to app instance
db.init_app(app)
bcrypt.init_app(app)
Expand Down
27 changes: 12 additions & 15 deletions mortimer/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
Expand All @@ -14,29 +13,30 @@ class Config:
if os.environ.get("MONGODB_SSL") == "True":
mongodb_ssl = True
ssl_ca_certs = os.environ.get("MONGODB_SSL_CAFILE")
ssl_ca_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), ssl_ca_certs)
ssl_ca_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), ssl_ca_certs
)
else:
mongodb_ssl = False
ssl_ca_certs = None
ssl_ca_path = None

SECRET_KEY = os.environ.get("SECRET_KEY") # secret key of flask app (e.g. for encrypted session data)
PAROLE = os.environ.get("PAROLE") # Parole/Passphrase for registration
EXP_PER_PAGE = 10 # number of experiments displayed per page
SECRET_KEY = os.environ.get(
"SECRET_KEY"
) # secret key of flask app (e.g. for encrypted session data)
PAROLE = os.environ.get("PAROLE") # Parole/Passphrase for registration
EXP_PER_PAGE = 10 # number of experiments displayed per page

# Mortimer database login settings
MONGODB_SETTINGS = {
"host": os.environ.get("MONGODB_HOST"),
"port": int(os.environ.get("MONGODB_PORT")),

"db": os.environ.get("MONGODB_MORTIMER_DB"),
"username": os.environ.get("MONGODB_MORTIMER_USER"),
"password": os.environ.get("MONGODB_MORTIMER_PW"),
"authentication_source": os.environ.get("MONGODB_MORTIMER_AUTHDB"),

"ssl": mongodb_ssl, # True / False
"ssl": mongodb_ssl, # True / False
"ssl_ca_certs": ssl_ca_certs

# "ssl": False,
# "ssl_ca_certs": "mongodb_ca_file.pem" # filepath must be relative to the directory that contains config.py and __init__.py
}
Expand All @@ -45,21 +45,18 @@ class Config:
MONGODB_ALFRED_SETTINGS = {
"host": os.environ.get("MONGODB_HOST"),
"port": int(os.environ.get("MONGODB_PORT")),

"db": os.environ.get("MONGODB_ALFRED_DB"),
"username": os.environ.get("MONGODB_ALFRED_USER"),
"password": os.environ.get("MONGODB_ALFRED_PW"),
"authentication_source": os.environ.get("MONGODB_ALFRED_AUTHDB"),

"ssl": mongodb_ssl,
"ssl_ca_certs": ssl_ca_certs

# "ssl": False,
# "ssl_ca_certs": "mongodb_ca_file.pem" # filepath must be relative to the directory that contains config.py and __init__.py
}

# Mail settings
MAIL_USE = True # enable or disable password reset emails
MAIL_USE = True # enable or disable password reset emails
MAIL_SERVER = os.environ.get("MAIL_SERVER")
MAIL_PORT = os.environ.get("MAIL_PORT")
MAIL_USE_TLS = True
Expand All @@ -68,8 +65,8 @@ class Config:

# Flask-Dropzone settings:
DROPZONE_ALLOWED_FILE_CUSTOM = True
DROPZONE_ALLOWED_FILE_TYPE = '.pdf, image/*, .txt, .xml, .pem, .mp3, .mp4, .ogg'
DROPZONE_ALLOWED_FILE_TYPE = ".pdf, image/*, .txt, .xml, .pem, .mp3, .mp4, .ogg"
DROPZONE_MAX_FILE_SIZE = 10
DROPZONE_MAX_FILES = 100
DROPZONE_UPLOAD_ON_CLICK = True
DROPZONE_UPLOAD_BTN_ID = 'upload'
DROPZONE_UPLOAD_BTN_ID = "upload"
2 changes: 1 addition & 1 deletion mortimer/errors/handlers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python

# -*- coding: utf-8 -*-

from flask import Blueprint, render_template
Expand Down
92 changes: 51 additions & 41 deletions mortimer/export.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
Expand All @@ -14,7 +13,7 @@
def make_str_bytes(f):
# Creating the byteIO object from the StringIO Object
bytes_f = io.BytesIO()
bytes_f.write(f.getvalue().encode('utf-8'))
bytes_f.write(f.getvalue().encode("utf-8"))
# seeking was necessary. Python 3.5.2, Flask 0.12.2
bytes_f.seek(0)
f.close()
Expand All @@ -29,13 +28,15 @@ def to_json(cursor):
return sIO


def to_csv(cursor, none_value=None, remove_linebreaks=False, dialect='excel', **writerparams):
def to_csv(
cursor, none_value=None, remove_linebreaks=False, dialect="excel", **writerparams
):
rows = cursor_to_rows(cursor, none_value)
if remove_linebreaks:
for i in range(1, len(rows)):
for j in range(len(rows[i])):
if isinstance(rows[i][j], str) or isinstance(rows[i][j], str):
rows[i][j].replace('\n', '')
rows[i][j].replace("\n", "")
csvfile = io.StringIO()
writer = csv.writer(csvfile, dialect=dialect, **writerparams)
for row in rows:
Expand All @@ -44,35 +45,42 @@ def to_csv(cursor, none_value=None, remove_linebreaks=False, dialect='excel', **


def to_excel_csv(cursor, none_value=None, **writerparams):
return to_csv(cursor, none_value=none_value, remove_linebreaks=True,
delimiter=';', dialect='excel', **writerparams)


def to_excel(cursor, none_value=None):
from openpyxl import Workbook
wb = Workbook(encoding='utf-8')
sheet = wb.get_active_sheet()
docs = cursor_to_rows(cursor, none_value)
for i in range(len(docs)):
for j in range(len(docs[i])):
tmp = docs[i][j]
if not(isinstance(tmp, str) and isinstance(tmp, str) and
isinstance(tmp, float) and isinstance(tmp, int)):
tmp = str(tmp)
sheet.cell(row=i, column=j).value = tmp
# f = TemporaryFile()
f = io.StringIO()
wb.save(f)
f.seek(0)
return f
return to_csv(
cursor,
none_value=none_value,
remove_linebreaks=True,
delimiter=";",
dialect="excel",
**writerparams
)


# def to_excel(cursor, none_value=None):
# from openpyxl import Workbook
# wb = Workbook(encoding='utf-8')
# sheet = wb.get_active_sheet()
# docs = cursor_to_rows(cursor, none_value)
# for i in range(len(docs)):
# for j in range(len(docs[i])):
# tmp = docs[i][j]
# if not(isinstance(tmp, str) and isinstance(tmp, str) and
# isinstance(tmp, float) and isinstance(tmp, int)):
# tmp = str(tmp)
# sheet.cell(row=i, column=j).value = tmp
# # f = TemporaryFile()
# f = io.StringIO()
# wb.save(f)
# f.seek(0)
# return f


def natural_sort(l):
def convert(text):
return int(text) if text.isdigit() else text.lower()

def alphanum_key(key):
return [convert(c) for c in re.split('([0-9]+)', key[0])]
return [convert(c) for c in re.split("([0-9]+)", key[0])]

return sorted(l, key=alphanum_key)


Expand All @@ -90,7 +98,7 @@ def cursor_to_rows(cursor, none_value=None):

class Header(object):
def __init__(self, *docs):
self.tag = docs[0]['tag']
self.tag = docs[0]["tag"]
self.parent = None
self.names = []
self.children = []
Expand All @@ -104,24 +112,24 @@ def setParent(self, p):
return self

def addDoc(self, doc):
assert(self.tag == doc['tag'])
assert self.tag == doc["tag"]
for k, v in natural_sort(list(doc.items())):
if k in ['_id', 'tag', 'uid']:
if k in ["_id", "tag", "uid"]:
pass
elif k == 'subtree_data':
elif k == "subtree_data":
for subDoc in v:
if subDoc == {}:
continue
found = False
for child in self.children:
if subDoc['tag'] == child.tag:
if subDoc["tag"] == child.tag:
child.addDoc(subDoc)
found = True
break
if not found:
self.children.append(Header(subDoc).setParent(self))
elif k == 'additional_data':
v['tag'] = 'additional_data'
elif k == "additional_data":
v["tag"] = "additional_data"
if self.additional_data is None:
self.additional_data = Header(v)
else:
Expand All @@ -131,7 +139,7 @@ def addDoc(self, doc):

def getFlatHeaders(self, with_root=True, deep=True, additional_data=True):
rl = []
pre = self.tag + '.' if with_root else ''
pre = self.tag + "." if with_root else ""
for name in self.names:
rl.append(pre + name)
if deep:
Expand All @@ -145,23 +153,25 @@ def getFlatHeaders(self, with_root=True, deep=True, additional_data=True):

def getDataFromDoc(self, doc):
rv = []
assert(doc == {} or doc['tag'] == self.tag)
assert doc == {} or doc["tag"] == self.tag
headers = self.getFlatHeaders(False, False, False)
for header in headers:
rv.append(doc.get(header, None))
for child in self.children:
found = False
for subDoc in doc.get('subtree_data', []):
for subDoc in doc.get("subtree_data", []):
try:
if subDoc['tag'] == child.tag:
if subDoc["tag"] == child.tag:
found = True
break
except KeyError as e:
except KeyError:
print(subDoc)
raise Exception("break")
rv = rv + child.getDataFromDoc(subDoc if found else {})
if self.additional_data:
rv = rv + self.additional_data.getDataFromDoc(doc.get('additional_data', {}))
rv = rv + self.additional_data.getDataFromDoc(
doc.get("additional_data", {})
)
return rv

def getDataFromDocs(self, docs):
Expand All @@ -172,8 +182,8 @@ def getDataFromDocs(self, docs):

def __unicode__(self):
if self.parent:
return str(self.parent) + '.' + self.tag
return str(self.parent) + "." + self.tag
return str(self.tag)

def __str__(self):
return str(self).encode('utf-8')
return str(self).encode("utf-8")
2 changes: 1 addition & 1 deletion mortimer/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python

# -*- coding: utf-8 -*-

from flask_wtf import FlaskForm
Expand Down
4 changes: 2 additions & 2 deletions mortimer/local_experiments/routes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python

# -*- coding: utf-8 -*-

from mortimer import export
Expand Down Expand Up @@ -99,7 +99,7 @@ def experiments():
@login_required
def user_experiments(username):

page = request.args.get("page", 1, type=int)
# page = request.args.get("page", 1, type=int)
user = User.objects.get_or_404(username=username)

if user.username != current_user.username:
Expand Down
Loading

0 comments on commit b8f176f

Please sign in to comment.