Created
April 27, 2016 14:27
-
-
Save atelic/35576f1843802993daf9bbffd0bcc514 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/.gitignore b/.gitignore | |
index a9e5112..fcc5a99 100644 | |
--- a/.gitignore | |
+++ b/.gitignore | |
@@ -54,7 +54,6 @@ website/static/vendor/bower_components/ | |
node_modules | |
# Allow robots.txt to be deployment-specific | |
website/static/robots.local.txt | |
-/website/static/git_logs.txt | |
# OSF-specific | |
############## | |
diff --git a/CHANGELOG b/CHANGELOG | |
index 46c216e..4aa5055 100644 | |
--- a/CHANGELOG | |
+++ b/CHANGELOG | |
@@ -2,7 +2,7 @@ | |
Changelog | |
********* | |
-0.67.0 (unreleased) | |
+0.67.0 (2016-04-21) | |
=================== | |
- Change "Retraction" to "Withdrawal". | |
diff --git a/NOTICE b/NOTICE | |
index 4159c91..1e9aa95 100644 | |
--- a/NOTICE | |
+++ b/NOTICE | |
@@ -203,3 +203,23 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
+ | |
+ | |
+Template helper functions adapted from Knockout.punches | |
+======================================================== | |
+ | |
+Copyright (c) 2016 Michael Best | |
+ | |
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated | |
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the | |
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit | |
+persons to whom the Software is furnished to do so, subject to the following conditions: | |
+ | |
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of | |
+the Software. | |
+ | |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO | |
+THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
+SOFTWARE. | |
diff --git a/admin/base/settings/defaults.py b/admin/base/settings/defaults.py | |
index a3f1107..f1deb87 100644 | |
--- a/admin/base/settings/defaults.py | |
+++ b/admin/base/settings/defaults.py | |
@@ -133,11 +133,11 @@ LOGIN_URL = '/admin/auth/login/' | |
LOGIN_REDIRECT_URL = '/admin/' | |
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'static_root') | |
- | |
STATICFILES_DIRS = ( | |
os.path.join(BASE_DIR, 'static'), | |
) | |
+ | |
LANGUAGE_CODE = 'en-us' | |
WEBPACK_LOADER = { | |
diff --git a/admin/templates/pre_reg/edit_draft_registration.html b/admin/templates/pre_reg/edit_draft_registration.html | |
index 0d38dc0..1ee9b45 100644 | |
--- a/admin/templates/pre_reg/edit_draft_registration.html | |
+++ b/admin/templates/pre_reg/edit_draft_registration.html | |
@@ -52,7 +52,7 @@ | |
<div data-bind="if: currentPage"> | |
<div data-bind="foreach: {data: currentPage().questions, as: 'question'}"> | |
<div class="row"> | |
- <strong data-bind="attr.id: question.id, text: question.title"></strong>: | |
+ <strong data-bind="attr: {id: question.id}, text: question.title"></strong>: | |
<div class="well"> | |
<p> | |
<span data-bind="previewQuestion: $root.context(question, $root, true)"></span> | |
diff --git a/admin/templates/pre_reg/registration_editor_extensions.html b/admin/templates/pre_reg/registration_editor_extensions.html | |
index ce386af..769264d 100644 | |
--- a/admin/templates/pre_reg/registration_editor_extensions.html | |
+++ b/admin/templates/pre_reg/registration_editor_extensions.html | |
@@ -11,7 +11,7 @@ | |
style="margin-left: 5px;" | |
class="btn btn-xs btn-danger fa fa-times"></button> | |
</div> | |
- <div data-bind="attr.id: $data.id, osfUploader"></div> | |
+ <div data-bind="attr: {id: $data.id}, osfUploader"></div><!-- TODO: osfUploader attribute may not connect to anything? --> | |
</script> | |
<script type="text/html" id="osf-upload-toggle"> | |
@@ -24,6 +24,6 @@ | |
</div> | |
<a data-bind="click: toggleUploader">Attach File</a> | |
<span data-bind="visible: showUploader"> | |
- <div data-bind="attr.id: $data.id, osfUploader"></div> | |
+ <div data-bind="attr: {id: $data.id}, osfUploader"></div><!-- TODO: osfUploader attribute may not connect to anything? --> | |
</span> | |
</script> | |
diff --git a/admin/templates/pre_reg/registration_editor_templates.html b/admin/templates/pre_reg/registration_editor_templates.html | |
index 148a96f..3ba4796 100644 | |
--- a/admin/templates/pre_reg/registration_editor_templates.html | |
+++ b/admin/templates/pre_reg/registration_editor_templates.html | |
@@ -11,7 +11,7 @@ | |
<script type="text/html" id="match"> | |
<input data-bind="valueUpdate: 'keyup', | |
value: value, | |
- attr.placeholder: match" type="text" class="form-control" /> | |
+ attr: {placeholder: match}" type="text" class="form-control" /> | |
</script> | |
<script type="text/html" id="textarea"> | |
<textarea data-bind="valueUpdate: 'keyup', | |
@@ -39,7 +39,7 @@ | |
<script type="text/html" id="multiselect"> | |
<div class="col-md-12" data-bind="foreach: {data: options, as: 'option'}"> | |
<p> | |
- <input type="checkbox" data-bind="attr.value: option, | |
+ <input type="checkbox" data-bind="attr: {value: option}, | |
checked: $parent.value, | |
checkedValue: option" /> | |
<span data-bind="text: option"></span> | |
diff --git a/api/base/middleware.py b/api/base/middleware.py | |
index 03aa188..4e21770 100644 | |
--- a/api/base/middleware.py | |
+++ b/api/base/middleware.py | |
@@ -7,11 +7,11 @@ import tempfile | |
import StringIO | |
import types | |
import functools | |
-import itertools | |
-import corsheaders.middleware | |
from django.conf import settings | |
+from modularodm import Q | |
from raven.contrib.django.raven_compat.models import sentry_exception_handler | |
+import corsheaders.middleware | |
from framework.mongo.handlers import ( | |
connection_before_request, | |
@@ -33,6 +33,7 @@ from framework.transactions.handlers import ( | |
) | |
from website import models | |
from .api_globals import api_globals | |
+from api.base import settings as api_settings | |
class MongoConnectionMiddleware(object): | |
"""MongoDB Connection middleware.""" | |
@@ -104,20 +105,21 @@ class DjangoGlobalMiddleware(object): | |
return response | |
class CorsMiddleware(corsheaders.middleware.CorsMiddleware): | |
- INSTITUTION_ORIGINS_WHITELIST = tuple(domain.lower() for domain in itertools.chain(*[ | |
- institution.domains | |
- for institution in models.Institution.find() | |
- ])) | |
- | |
""" | |
Augment CORS origin white list with the Institution model's domains. | |
""" | |
+ def origin_not_found_in_white_lists(self, origin, url): | |
+ not_found = super(CorsMiddleware, self).origin_not_found_in_white_lists(origin, url) | |
+ if not_found: | |
+ not_found = models.Institution.find(Q('domains', 'eq', url.netloc.lower())).count() == 0 | |
+ return not_found | |
+ | |
def process_request(self, request): | |
def origin_not_found_in_white_lists(self, request, origin, url): | |
not_found = super(CorsMiddleware, self).origin_not_found_in_white_lists(origin, url) | |
if not_found: | |
# Check if origin is in the dynamic Institutions whitelist | |
- if url.netloc.lower() in self.INSTITUTION_ORIGINS_WHITELIST: | |
+ if url.netloc.lower() in api_settings.INSTITUTION_ORIGINS_WHITELIST: | |
return False | |
# Check if a cross-origin request using the Authorization header | |
elif not request.COOKIES: | |
diff --git a/api/base/settings/__init__.py b/api/base/settings/__init__.py | |
index 99008c8..192c2d9 100644 | |
--- a/api/base/settings/__init__.py | |
+++ b/api/base/settings/__init__.py | |
@@ -8,6 +8,7 @@ | |
''' | |
import os | |
import warnings | |
+import itertools | |
from .defaults import * # noqa | |
@@ -22,3 +23,11 @@ if not DEBUG and os.environ.get('DJANGO_SETTINGS_MODULE') == 'api.base.settings' | |
from . import defaults | |
for setting in ('JWE_SECRET', 'JWT_SECRET'): | |
assert getattr(local, setting, None) and getattr(local, setting, None) != getattr(defaults, setting, None), '{} must be specified in local.py when DEV_MODE is False'.format(setting) | |
+ | |
+def load_institutions(): | |
+ global INSTITUTION_ORIGINS_WHITELIST | |
+ from website import models | |
+ INSTITUTION_ORIGINS_WHITELIST = tuple(domain.lower() for domain in itertools.chain(*[ | |
+ institution.domains | |
+ for institution in models.Institution.find() | |
+ ])) | |
diff --git a/api/base/settings/defaults.py b/api/base/settings/defaults.py | |
index a698ba3..08bab82 100644 | |
--- a/api/base/settings/defaults.py | |
+++ b/api/base/settings/defaults.py | |
@@ -99,6 +99,8 @@ CORS_ORIGIN_WHITELIST = (urlparse(osf_settings.DOMAIN).netloc, | |
osf_settings.DOMAIN, | |
) | |
CORS_ALLOW_CREDENTIALS = True | |
+# Set dynamically on app init | |
+INSTITUTION_ORIGINS_WHITELIST = () | |
MIDDLEWARE_CLASSES = ( | |
# TokuMX transaction support | |
diff --git a/api/base/wsgi.py b/api/base/wsgi.py | |
index 854660f..98704a2 100644 | |
--- a/api/base/wsgi.py | |
+++ b/api/base/wsgi.py | |
@@ -7,6 +7,7 @@ For more information on this file, see | |
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ | |
""" | |
from website import settings | |
+from api.base import settings as api_settings | |
if not settings.DEBUG_MODE: | |
from gevent import monkey | |
@@ -50,5 +51,6 @@ Request.__getattribute__ = object.__getattribute__ | |
############# /monkeys #################### | |
init_app(set_backends=True, routes=False, attach_request_handlers=False) | |
+api_settings.load_institutions() | |
application = get_wsgi_application() | |
diff --git a/api_tests/base/test_middleware.py b/api_tests/base/test_middleware.py | |
index fc7cbab..c8c2a79 100644 | |
--- a/api_tests/base/test_middleware.py | |
+++ b/api_tests/base/test_middleware.py | |
@@ -51,7 +51,7 @@ class TestCorsMiddleware(MiddlewareTestCase): | |
institution_domains=[domain.netloc.lower()], | |
title="Institute for Sexy Lizards" | |
) | |
- CorsMiddleware.INSTITUTION_ORIGINS_WHITELIST = CorsMiddleware.INSTITUTION_ORIGINS_WHITELIST + (domain.netloc.lower(),) | |
+ settings.load_institutions() | |
request = self.request_factory.get(url, HTTP_ORIGIN=domain.geturl()) | |
response = {} | |
self.middleware.process_request(request) | |
@@ -108,4 +108,3 @@ class TestCorsMiddleware(MiddlewareTestCase): | |
self.middleware.process_request(request) | |
processed = self.middleware.process_response(request, response) | |
assert_equal(response['Access-Control-Allow-Origin'], domain.geturl()) | |
- | |
diff --git a/api_tests/institutions/views/test_institution_list.py b/api_tests/institutions/views/test_institution_list.py | |
index 19ceb0b..ea74f65 100644 | |
--- a/api_tests/institutions/views/test_institution_list.py | |
+++ b/api_tests/institutions/views/test_institution_list.py | |
@@ -3,6 +3,7 @@ from nose.tools import * # flake8: noqa | |
from tests.base import ApiTestCase | |
from tests.factories import InstitutionFactory | |
+from website.models import Node | |
from api.base.settings.defaults import API_BASE | |
class TestInstitutionList(ApiTestCase): | |
@@ -12,6 +13,10 @@ class TestInstitutionList(ApiTestCase): | |
self.institution2 = InstitutionFactory() | |
self.institution_url = '/{}institutions/'.format(API_BASE) | |
+ def tearDown(self): | |
+ super(TestInstitutionList, self).tearDown() | |
+ Node.remove() | |
+ | |
def test_return_all_institutions(self): | |
res = self.app.get(self.institution_url) | |
@@ -21,3 +26,16 @@ class TestInstitutionList(ApiTestCase): | |
assert_equal(len(res.json['data']), 2) | |
assert_in(self.institution._id, ids) | |
assert_in(self.institution2._id, ids) | |
+ | |
+ def test_does_not_return_deleted_institution(self): | |
+ self.institution.node.is_deleted = True | |
+ self.institution.node.save() | |
+ | |
+ res = self.app.get(self.institution_url) | |
+ | |
+ assert_equal(res.status_code, 200) | |
+ | |
+ ids = [each['id'] for each in res.json['data']] | |
+ assert_equal(len(res.json['data']), 1) | |
+ assert_not_in(self.institution._id, ids) | |
+ assert_in(self.institution2._id, ids) | |
diff --git a/framework/auth/exceptions.py b/framework/auth/exceptions.py | |
index 75bc8b3..aa296c5 100644 | |
--- a/framework/auth/exceptions.py | |
+++ b/framework/auth/exceptions.py | |
@@ -1,3 +1,5 @@ | |
+import markupsafe | |
+ | |
from framework.exceptions import FrameworkError | |
from website import language | |
@@ -51,9 +53,11 @@ class MergeConfirmedRequiredError(EmailConfirmTokenError): | |
@property | |
def message_long(self): | |
+ src_user = markupsafe.escape(self.user.username) | |
+ dest_user = markupsafe.escape(self.user_to_merge.username) | |
return language.MERGE_CONFIRMATION_REQUIRED_LONG.format( | |
- user=self.user, | |
- user_to_merge=self.user_to_merge, | |
+ src_user=src_user, | |
+ dest_user=dest_user, | |
) | |
diff --git a/framework/auth/forms.py b/framework/auth/forms.py | |
index fb0be2c..e071db0 100644 | |
--- a/framework/auth/forms.py | |
+++ b/framework/auth/forms.py | |
@@ -3,6 +3,8 @@ | |
# TODO: Remove me | |
from wtforms import ValidationError | |
+ | |
+from framework import auth | |
from framework.forms import ( | |
Form, | |
NoHtmlCharacters, | |
@@ -15,9 +17,6 @@ from framework.forms import ( | |
stripped, | |
lowerstripped | |
) | |
- | |
-from framework import auth | |
- | |
from website import language | |
diff --git a/framework/auth/views.py b/framework/auth/views.py | |
index 088b02b..ec1e8d6 100644 | |
--- a/framework/auth/views.py | |
+++ b/framework/auth/views.py | |
@@ -41,9 +41,10 @@ def reset_password(auth, **kwargs): | |
user_obj = get_user(verification_key=verification_key) | |
if not user_obj: | |
- error_data = {'message_short': 'Invalid url.', | |
- 'message_long': 'The verification key in the URL is invalid or ' | |
- 'has expired.'} | |
+ error_data = { | |
+ 'message_short': 'Invalid url.', | |
+ 'message_long': 'The verification key in the URL is invalid or has expired.' | |
+ } | |
raise HTTPError(400, data=error_data) | |
if request.method == 'POST' and form.validate(): | |
@@ -51,7 +52,7 @@ def reset_password(auth, **kwargs): | |
user_obj.verification_key = security.random_string(20) | |
user_obj.set_password(form.password.data) | |
user_obj.save() | |
- status.push_status_message('Password reset', 'success') | |
+ status.push_status_message('Password reset', kind='success', trust=False) | |
# Redirect to CAS and authenticate the user with a verification key. | |
return redirect(cas.get_login_url( | |
web_url_for('user_account', _absolute=True), | |
@@ -98,13 +99,15 @@ def forgot_password_post(): | |
mail=mails.FORGOT_PASSWORD, | |
reset_link=reset_link | |
) | |
- status.push_status_message(status_message, 'success') | |
+ status.push_status_message(status_message, kind='success', trust=False) | |
else: | |
user_obj.save() | |
status.push_status_message('You have recently requested to change your password. Please wait a little ' | |
- 'while before trying again.', 'error') | |
+ 'while before trying again.', | |
+ kind='error', | |
+ trust=False) | |
else: | |
- status.push_status_message(status_message, 'success') | |
+ status.push_status_message(status_message, kind='success', trust=False) | |
forms.push_errors_to_status(form.errors) | |
return auth_login(forgot_password_form=form) | |
@@ -151,14 +154,15 @@ def auth_login(auth, **kwargs): | |
# redirect user to CAS for logout, return here w/o authentication | |
return auth_logout(redirect_url=request.url) | |
if kwargs.get('first', False): | |
- status.push_status_message('You may now log in', 'info') | |
+ status.push_status_message('You may now log in', kind='info', trust=False) | |
status_message = request.args.get('status', '') | |
if status_message == 'expired': | |
- status.push_status_message('The private link you used is expired.') | |
+ status.push_status_message('The private link you used is expired.', trust=False) | |
if next_url and must_login_warning: | |
- status.push_status_message(language.MUST_LOGIN) | |
+ status.push_status_message(language.MUST_LOGIN, trust=False) | |
+ | |
# set login_url to form action, upon successful authentication specifically w/o logout=True, | |
# allows for next to be followed or a redirect to the dashboard. | |
redirect_url = web_url_for('auth_login', next=next_url, _absolute=True) | |
@@ -214,14 +218,14 @@ def confirm_email_get(token, auth=None, **kwargs): | |
campaigns.campaign_url_for(campaign) | |
) | |
if len(auth.user.emails) == 1 and len(auth.user.email_verifications) == 0: | |
- status.push_status_message(language.WELCOME_MESSAGE, 'default', jumbotron=True) | |
+ status.push_status_message(language.WELCOME_MESSAGE, kind='default', jumbotron=True, trust=True) | |
if token in auth.user.email_verifications: | |
- status.push_status_message(language.CONFIRM_ALTERNATE_EMAIL_ERROR, 'danger') | |
+ status.push_status_message(language.CONFIRM_ALTERNATE_EMAIL_ERROR, kind='danger', trust=True) | |
# Go to home page | |
return redirect(web_url_for('index')) | |
- status.push_status_message(language.MERGE_COMPLETE, 'success') | |
+ status.push_status_message(language.MERGE_COMPLETE, kind='success', trust=False) | |
return redirect(web_url_for('user_account')) | |
try: | |
@@ -350,7 +354,7 @@ def register_user(**kwargs): | |
# TODO: Remove me | |
def auth_register_post(): | |
if not settings.ALLOW_REGISTRATION: | |
- status.push_status_message(language.REGISTRATION_UNAVAILABLE) | |
+ status.push_status_message(language.REGISTRATION_UNAVAILABLE, trust=False) | |
return redirect('/') | |
form = RegistrationForm(request.form, prefix='register') | |
@@ -364,13 +368,14 @@ def auth_register_post(): | |
framework.auth.signals.user_registered.send(user) | |
except (ValidationValueError, DuplicateEmailError): | |
status.push_status_message( | |
- language.ALREADY_REGISTERED.format(email=form.username.data)) | |
+ language.ALREADY_REGISTERED.format(email=form.username.data), | |
+ trust=False) | |
return auth_login() | |
if user: | |
if settings.CONFIRM_REGISTRATIONS_BY_EMAIL: | |
send_confirm_email(user, email=user.username) | |
message = language.REGISTRATION_SUCCESS.format(email=user.username) | |
- status.push_status_message(message, 'success') | |
+ status.push_status_message(message, kind='success', trust=False) | |
return auth_login() | |
else: | |
return redirect('/login/first/') | |
@@ -399,11 +404,11 @@ def resend_confirmation(): | |
send_confirm_email(user, clean_email) | |
except KeyError: # already confirmed, redirect to dashboard | |
status_message = 'Email has already been confirmed.' | |
- type_ = 'warning' | |
+ kind = 'warning' | |
else: | |
- status_message = 'Resent email to <em>{0}</em>'.format(clean_email) | |
- type_ = 'success' | |
- status.push_status_message(status_message, type_) | |
+ status_message = 'Resent email to {0}'.format(clean_email) | |
+ kind = 'success' | |
+ status.push_status_message(status_message, kind=kind, trust=False) | |
else: | |
forms.push_errors_to_status(form.errors) | |
# Don't go anywhere | |
@@ -429,25 +434,28 @@ def merge_user_post(auth, **kwargs): | |
return merge_user_get(**kwargs) | |
master_password = form.user_password.data | |
if not master.check_password(master_password): | |
- status.push_status_message("Could not authenticate. Please check your username and password.") | |
+ status.push_status_message("Could not authenticate. Please check your username and password.", trust=False) | |
return merge_user_get(**kwargs) | |
merged_username = form.merged_username.data | |
merged_password = form.merged_password.data | |
try: | |
merged_user = User.find_one(Q("username", "eq", merged_username)) | |
except NoResultsFound: | |
- status.push_status_message("Could not find that user. Please check the username and password.") | |
+ status.push_status_message("Could not find that user. Please check the username and password.", trust=False) | |
return merge_user_get(**kwargs) | |
if master and merged_user: | |
if merged_user.check_password(merged_password): | |
master.merge_user(merged_user) | |
master.save() | |
if request.form: | |
- status.push_status_message("Successfully merged {0} with this account".format(merged_username), 'success') | |
+ status.push_status_message("Successfully merged {0} with this account".format(merged_username), | |
+ kind='success', | |
+ trust=False) | |
return redirect("/settings/") | |
return {"status": "success"} | |
else: | |
- status.push_status_message("Could not find that user. Please check the username and password.") | |
+ status.push_status_message("Could not find that user. Please check the username and password.", | |
+ trust=False) | |
return merge_user_get(**kwargs) | |
else: | |
raise HTTPError(http.BAD_REQUEST) | |
diff --git a/framework/exceptions/__init__.py b/framework/exceptions/__init__.py | |
index 155bb09..b3eda11 100644 | |
--- a/framework/exceptions/__init__.py | |
+++ b/framework/exceptions/__init__.py | |
@@ -80,9 +80,10 @@ class HTTPError(FrameworkError): | |
} | |
else: | |
data['message_short'] = 'Unable to resolve' | |
- data['message_long'] = ('OSF was unable to resolve your request. If this ' | |
- 'issue persists, please report it to ' | |
- '<a href="mailto:[email protected]">[email protected]</a>.') | |
+ data['message_long'] = ( | |
+ 'OSF was unable to resolve your request. If this issue persists, please report it to ' | |
+ '<a href="mailto:[email protected]">[email protected]</a>.' | |
+ ) | |
data.update(self.data) | |
data['code'] = self.code | |
data['referrer'] = self.referrer | |
diff --git a/framework/forms/__init__.py b/framework/forms/__init__.py | |
index 487b663..d555ee6 100644 | |
--- a/framework/forms/__init__.py | |
+++ b/framework/forms/__init__.py | |
@@ -41,10 +41,11 @@ class BootstrapTextArea(TextArea): | |
def push_errors_to_status(errors): | |
+ # TODO: Review whether errors contain custom HTML. If so this change might cause some display anomalies. | |
if errors: | |
for field, _ in errors.items(): | |
for error in errors[field]: | |
- status.push_status_message(error) | |
+ status.push_status_message(error, trust=False) | |
class NoHtmlCharacters(object): | |
@@ -54,7 +55,7 @@ class NoHtmlCharacters(object): | |
TODO: This could still post a problem if we output an email address to a | |
Javascript literal. | |
""" | |
- | |
+ # TODO: Improve this for a post-bleach world | |
def __init__(self, message=None): | |
self.message = message or u'HTML is not allowed in form field' | |
diff --git a/framework/mongo/utils.py b/framework/mongo/utils.py | |
index 4ce2624..54a6e0f 100644 | |
--- a/framework/mongo/utils.py | |
+++ b/framework/mongo/utils.py | |
@@ -1,8 +1,9 @@ | |
# -*- coding: utf-8 -*- | |
import functools | |
-import re | |
import httplib as http | |
+import re | |
+import markupsafe | |
import pymongo | |
from modularodm import Q | |
from modularodm.query import QueryBase | |
@@ -80,27 +81,29 @@ def get_or_http_error(Model, pk_or_query, allow_deleted=False, display_name=None | |
""" | |
display_name = display_name or '' | |
+ # FIXME: Not everything that uses this decorator needs to be markupsafe, but OsfWebRenderer error.mako does... | |
+ safe_name = markupsafe.escape(display_name) | |
if isinstance(pk_or_query, QueryBase): | |
try: | |
instance = Model.find_one(pk_or_query) | |
except NoResultsFound: | |
raise HTTPError(http.NOT_FOUND, data=dict( | |
- message_long="No {name} record matching that query could be found".format(name=display_name) | |
+ message_long="No {name} record matching that query could be found".format(name=safe_name) | |
)) | |
except MultipleResultsFound: | |
raise HTTPError(http.BAD_REQUEST, data=dict( | |
- message_long="The query must match exactly one {name} record".format(name=display_name) | |
+ message_long="The query must match exactly one {name} record".format(name=safe_name) | |
)) | |
else: | |
instance = Model.load(pk_or_query) | |
if not instance: | |
raise HTTPError(http.NOT_FOUND, data=dict( | |
- message_long="No {name} record with that primary key could be found".format(name=display_name) | |
+ message_long="No {name} record with that primary key could be found".format(name=safe_name) | |
)) | |
if not allow_deleted and getattr(instance, 'is_deleted', False): | |
raise HTTPError(http.GONE, data=dict( | |
- message_long="This {name} record has been deleted".format(name=display_name) | |
+ message_long="This {name} record has been deleted".format(name=safe_name) | |
)) | |
else: | |
return instance | |
diff --git a/framework/postcommit_tasks/handlers.py b/framework/postcommit_tasks/handlers.py | |
index b316491..c44a82f 100644 | |
--- a/framework/postcommit_tasks/handlers.py | |
+++ b/framework/postcommit_tasks/handlers.py | |
@@ -1,8 +1,6 @@ | |
import logging | |
import threading | |
-import gevent | |
- | |
from website import settings | |
_local = threading.local() | |
@@ -21,7 +19,8 @@ def postcommit_after_request(response, base_status_error_code=500): | |
_local.postcommit_queue = set() | |
return response | |
try: | |
- if postcommit_queue(): | |
+ if settings.ENABLE_VARNISH and postcommit_queue(): | |
+ import gevent | |
threads = [gevent.spawn(func, *args) for func, args in postcommit_queue()] | |
gevent.joinall(threads) | |
except AttributeError: | |
diff --git a/framework/routing/__init__.py b/framework/routing/__init__.py | |
index d86e3e9..7dd5ca3 100644 | |
--- a/framework/routing/__init__.py | |
+++ b/framework/routing/__init__.py | |
@@ -27,9 +27,7 @@ TEMPLATE_DIR = settings.TEMPLATES_PATH | |
_TPL_LOOKUP = TemplateLookup( | |
default_filters=[ | |
'unicode', # default filter; must set explicitly when overriding | |
- 'strip_ko', # Filter that strips out Knockout punches syntax. Can be removed if Knockout-punches is removed. | |
], | |
- imports=['from website.util.sanitize import strip_ko'], | |
directories=[ | |
TEMPLATE_DIR, | |
os.path.join(settings.BASE_PATH, 'addons/'), | |
@@ -41,11 +39,11 @@ _TPL_LOOKUP_SAFE = TemplateLookup( | |
default_filters=[ | |
'unicode', # default filter; must set explicitly when overriding | |
'temp_ampersand_fixer', # FIXME: Temporary workaround for data stored in wrong format in DB. Unescape it before it gets re-escaped by Markupsafe. | |
- 'strip_ko', # Filter that strips out Knockout punches syntax. Can be removed if Knockout-punches is removed. | |
'h', | |
], | |
- imports=['from website.util.sanitize import temp_ampersand_fixer', # FIXME: Temporary workaround for data stored in wrong format in DB. Unescape it before it gets re-escaped by Markupsafe. | |
- 'from website.util.sanitize import strip_ko'], | |
+ imports=[ | |
+ 'from website.util.sanitize import temp_ampersand_fixer', # FIXME: Temporary workaround for data stored in wrong format in DB. Unescape it before it gets re-escaped by Markupsafe. | |
+ ], | |
directories=[ | |
TEMPLATE_DIR, | |
os.path.join(settings.BASE_PATH, 'addons/'), | |
diff --git a/package.json b/package.json | |
index 8e40865..8ee0ce1 100644 | |
--- a/package.json | |
+++ b/package.json | |
@@ -13,6 +13,7 @@ | |
"bootbox": "^4.4.0", | |
"bower": "^1.3.12", | |
"c3": "^0.4.10", | |
+ "clipboard": "^1.5.10", | |
"css-loader": "^0.9.1", | |
"diff": "~2.2.2", | |
"dropzone": "git://github.com/sloria/dropzone.git#accept-directory", | |
@@ -31,7 +32,6 @@ | |
"jstimezonedetect": "^1.0.6", | |
"keen-js": "^3.0.0", | |
"knockout": "~3.2.0", | |
- "knockout.punches": "^0.5.1", | |
"knockout.validation": "^2.0.2", | |
"less": "^2.5.3", | |
"list-of-licenses": "git://github.com/samchrisinger/list-of-licenses.git#1f1d9ca8a9aba56d57fca6e27df80b8298048cc8", | |
@@ -47,6 +47,7 @@ | |
"mime-types": "~2.0.12", | |
"mithril": "0.2.0", | |
"moment": "^2.10.6", | |
+ "mongodb": "^1.4.40", | |
"morgan": "^1.5.1", | |
"object-assign": "^2.0.0", | |
"pikaday": "^1.3.2", | |
@@ -58,8 +59,8 @@ | |
"style-loader": "^0.8.3", | |
"treebeard": "git://github.com/caneruguz/treebeard.git#89895536486e3535d9980b8f20a73f72bbb64222", | |
"typeahead.js": "^0.10.5", | |
- "uuid": "^2.0.1", | |
"url-loader": "^0.5.5", | |
+ "uuid": "^2.0.1", | |
"webpack": "^1.7.2", | |
"ws": "~0.4.32" | |
}, | |
diff --git a/requirements.txt b/requirements.txt | |
index a381c9b..0174312 100644 | |
--- a/requirements.txt | |
+++ b/requirements.txt | |
@@ -1,5 +1,6 @@ | |
# Base requirements for running the OSF. | |
# NOTE: This does not include addon, development, metrics or release requirements. | |
+# NOTE: When updating pinned version, you may also need to update constraints.txt | |
# To install addon requirements: inv requirements --addons | |
# To install dev requirements: inv requirements --dev | |
# To install metrics requirements: inv requirements --metrics | |
diff --git a/requirements/constraints.txt b/requirements/constraints.txt | |
new file mode 100644 | |
index 0000000..310f79a | |
--- /dev/null | |
+++ b/requirements/constraints.txt | |
@@ -0,0 +1,14 @@ | |
+## Constraints file for resolving conflicts across addons. In general, the version specified by OSF base requirements "wins" | |
+## "Constraints files are requirements files that only control which version of a requirement is installed, not whether it is installed or not." | |
+## See https://pip-python3.readthedocs.org/en/latest/user_guide.html#constraints-files | |
+ | |
+lxml==3.4.1 | |
+pytz==2014.9 | |
+python-dateutil==2.5.0 | |
+html5lib==0.999 | |
+bleach==1.4.1 | |
+requests==2.5.3 | |
+urllib3==1.10.4 | |
+requests-oauthlib==0.5.0 | |
+oauthlib>=1.0 # Mendeley pins to a much lower version but OSF overrides | |
+git+https://github.com/jmcarp/HTTPretty@matcher-priority | |
diff --git a/scripts/analytics/logger.py b/scripts/analytics/logger.py | |
new file mode 100644 | |
index 0000000..1b6e0c4 | |
--- /dev/null | |
+++ b/scripts/analytics/logger.py | |
@@ -0,0 +1,3 @@ | |
+# -*- coding: utf-8 -*- | |
+import logging | |
+logger = logging.getLogger(__name__) | |
diff --git a/scripts/analytics/settings/defaults.py b/scripts/analytics/settings/defaults.py | |
index 7789b09..ee6862e 100644 | |
--- a/scripts/analytics/settings/defaults.py | |
+++ b/scripts/analytics/settings/defaults.py | |
@@ -2,19 +2,18 @@ | |
from dateutil.relativedelta import relativedelta | |
+ANALYTICS_LOGS_NODE_ID = None | |
+ANALYTICS_LOGS_USER_ID = None | |
-TABULATE_EMAILS_NODE_ID = 'ejnwh' # Daily updates project | |
-TABULATE_EMAILS_USER_ID = 'jm6t4' # Daily updates user | |
+TABULATE_EMAILS_NODE_ID = ANALYTICS_LOGS_NODE_ID # Daily updates project | |
+TABULATE_EMAILS_USER_ID = ANALYTICS_LOGS_USER_ID # Daily updates user | |
TABULATE_EMAILS_FILE_NAME = 'daily-users.csv' | |
TABULATE_EMAILS_CONTENT_TYPE = 'text/csv' | |
TABULATE_EMAILS_TIME_DELTA = relativedelta(days=1) | |
TABULATE_LOGS_RESULTS_COLLECTION = 'logmetrics' | |
-TABULATE_LOGS_NODE_ID = 'ejnwh' # Daily updates project | |
-TABULATE_LOGS_USER_ID = 'jm6t4' # Daily updates user | |
+TABULATE_LOGS_NODE_ID = ANALYTICS_LOGS_NODE_ID # Daily updates project | |
+TABULATE_LOGS_USER_ID = ANALYTICS_LOGS_USER_ID # Daily updates user | |
TABULATE_LOGS_FILE_NAME = 'log-counts.csv' | |
TABULATE_LOGS_CONTENT_TYPE = 'text/csv' | |
TABULATE_LOGS_TIME_OFFSET = relativedelta(days=1) | |
- | |
-ANALYTICS_LOGS_NODE_ID = 'ejnwh' | |
-ANALYTICS_LOGS_USER_ID = 'jm6t6' | |
diff --git a/scripts/analytics/tasks.py b/scripts/analytics/tasks.py | |
index b15bc3f..9e92e00 100644 | |
--- a/scripts/analytics/tasks.py | |
+++ b/scripts/analytics/tasks.py | |
@@ -1,14 +1,17 @@ | |
import matplotlib | |
from framework.celery_tasks import app as celery_app | |
+from scripts import utils as scripts_utils | |
from website.app import init_app | |
+from .logger import logger | |
@celery_app.task(name='scripts.analytics.tasks') | |
def analytics(): | |
matplotlib.use('Agg') | |
init_app(routes=False) | |
+ scripts_utils.add_file_logger(logger, __file__) | |
from scripts.analytics import ( | |
logs, addons, comments, folders, links, watch, email_invites, | |
permissions, profile, benchmarks | |
diff --git a/scripts/analytics/upload.py b/scripts/analytics/upload.py | |
index 1234612..c7c4a2e 100644 | |
--- a/scripts/analytics/upload.py | |
+++ b/scripts/analytics/upload.py | |
@@ -11,6 +11,8 @@ from scripts.analytics import utils | |
from website import models | |
from website import settings as website_settings | |
+from .logger import logger | |
+ | |
@celery_app.task(name='scripts.analytics.upload') | |
def upload(): | |
@@ -21,7 +23,7 @@ def upload(): | |
for a_file in files: | |
segments = root.split('osf.io/website/analytics') | |
if len(segments) != 2: | |
- print('I/O Error: invalid file path') | |
+ logger.error('I/O Error: invalid root path: {}'.format(segments)) | |
continue | |
path = segments[1] | |
file_name = a_file | |
@@ -74,7 +76,7 @@ def create_or_update_file(file_stream, file_name, self_id, parent_path, parent_i | |
break | |
retry += 1 | |
if retry == 3: | |
- print('response = {}'.format(resp.json())) | |
+ logger.debug('response = {}'.format(resp.json())) | |
resp.raise_for_status() | |
@@ -83,7 +85,7 @@ def waterbutler_upload(path, name, test_existence=False): | |
root = database['storedfilenode'].find({'node': settings.ANALYTICS_LOGS_NODE_ID, 'parent': None}) | |
if root.count() != 1: | |
- print('Invalid Node ID: Cannot find the project node {}.'.format(settings.ANALYTICS_LOGS_NODE_ID)) | |
+ logger.error('Invalid Node ID: Cannot find the project node {}.'.format(settings.ANALYTICS_LOGS_NODE_ID)) | |
return | |
parent_id = root[0]['_id'] | |
parent_path = [] | |
@@ -93,7 +95,7 @@ def waterbutler_upload(path, name, test_existence=False): | |
parent_path = None | |
break | |
dirs = database['storedfilenode'].find({'name': a_dir, 'parent': parent_id}) | |
- assert dirs.count() in [0,1], 'Database query failure' | |
+ assert dirs.count() in [0, 1], 'Database query failure' | |
if dirs.count() == 0: # dir does not exsit | |
if test_existence: | |
return False | |
@@ -106,25 +108,24 @@ def waterbutler_upload(path, name, test_existence=False): | |
else: | |
raise Exception('Cannot find newly created directory') | |
continue | |
- else: # dir found | |
+ else: # dir found | |
parent_id = dirs[0]['_id'] | |
parent_path.append(parent_id) | |
continue | |
file_path = '/' + '/'.join(website_settings.ANALYTICS_PATH.split('/')[1:-1]) + path + '/' + name | |
- print('file_path: {}'.format(file_path)) | |
- file_stream = open(file_path, 'r') | |
- | |
- docs = database['storedfilenode'].find({'name': name, 'parent': parent_id}) | |
- assert docs.count() in [0,1], 'Database query failure' | |
- if docs.count() == 0: | |
- if test_existence: | |
- return False | |
- else: | |
- return create_or_update_file(file_stream, name, None, parent_path, parent_id) | |
- elif docs.count() == 1: | |
- if test_existence: | |
- return True | |
- else: | |
- self_id = docs[0]['_id'] | |
- return create_or_update_file(file_stream, name, self_id, parent_path, parent_id) | |
+ logger.debug('file_path: {}'.format(file_path)) | |
+ with open(file_path, 'r') as file_stream: | |
+ docs = database['storedfilenode'].find({'name': name, 'parent': parent_id}) | |
+ assert docs.count() in [0,1], 'Database query failure' | |
+ if docs.count() == 0: | |
+ if test_existence: | |
+ return False | |
+ else: | |
+ return create_or_update_file(file_stream, name, None, parent_path, parent_id) | |
+ elif docs.count() == 1: | |
+ if test_existence: | |
+ return True | |
+ else: | |
+ self_id = docs[0]['_id'] | |
+ return create_or_update_file(file_stream, name, self_id, parent_path, parent_id) | |
diff --git a/scripts/embargo_registrations.py b/scripts/embargo_registrations.py | |
index 49e231c..0dc3d94 100644 | |
--- a/scripts/embargo_registrations.py | |
+++ b/scripts/embargo_registrations.py | |
@@ -83,7 +83,8 @@ def main(dry_run=True): | |
parent_registration.registered_from.add_log( | |
action=NodeLog.EMBARGO_COMPLETED, | |
params={ | |
- 'node': parent_registration._id, | |
+ 'node': parent_registration.registered_from_id, | |
+ 'registration': parent_registration._id, | |
'embargo_id': embargo._id, | |
}, | |
auth=None, | |
diff --git a/scripts/ensure_log_backrefs.py b/scripts/ensure_log_backrefs.py | |
index 5fa296a..c1796fc 100644 | |
--- a/scripts/ensure_log_backrefs.py | |
+++ b/scripts/ensure_log_backrefs.py | |
@@ -1,11 +1,31 @@ | |
# -*- coding: utf-8 -*- | |
+import gc | |
+ | |
from website.app import init_app | |
from website import models | |
+ | |
+from modularodm import Q | |
from modularodm.storedobject import ensure_backrefs | |
+def paginated(model, increment=200): | |
+ last_id = '' | |
+ pages = (model.find().count() / increment) + 1 | |
+ for i in xrange(pages): | |
+ q = Q('_id', 'gt', last_id) | |
+ page = list(model.find(q).limit(increment)) | |
+ for item in page: | |
+ yield item | |
+ if page: | |
+ last_id = item._id | |
+ | |
def main(): | |
init_app(routes=False) | |
- for record in models.Node.find(): | |
+ for i, record in enumerate(paginated(models.Node)): | |
+ if i % 25 == 0: | |
+ for key in ('node', 'user', 'fileversion', 'storedfilenode'): | |
+ models.Node._cache.data.get(key, {}).clear() | |
+ models.Node._object_cache.data.get(key, {}).clear() | |
+ gc.collect() | |
ensure_backrefs(record, ['logs']) | |
print('Done.') | |
diff --git a/scripts/ensure_valid_titles.py b/scripts/ensure_valid_titles.py | |
new file mode 100644 | |
index 0000000..d465693 | |
--- /dev/null | |
+++ b/scripts/ensure_valid_titles.py | |
@@ -0,0 +1,37 @@ | |
+# -*- coding: utf-8 -*- | |
+"""Ensure that all nodes will pass title validation (i.e. have <=200 characters.)""" | |
+from __future__ import unicode_literals | |
+import logging | |
+import sys | |
+ | |
+from framework.mongo import database as db | |
+from website.app import init_app | |
+from framework.transactions.context import TokuTransaction | |
+ | |
+from scripts import utils as script_utils | |
+ | |
+logger = logging.getLogger(__name__) | |
+MAX_TITLE_LENGTH = 200 | |
+ | |
+def main(): | |
+ count = 0 | |
+ for node in db.node.find({'$where': 'this.title.length > 200'}): | |
+ logger.info('Updating node {}'.format(node['_id'])) | |
+ logger.info('Old title: {}'.format(node['title'])) | |
+ new_title = node['title'][:MAX_TITLE_LENGTH] | |
+ logger.info('New title: {}'.format(new_title)) | |
+ db.node.update({'_id': node['_id']}, { | |
+ '$set': {'title': new_title} | |
+ }) | |
+ count += 1 | |
+ logger.info('Updated {} nodes'.format(count)) | |
+ | |
+if __name__ == '__main__': | |
+ dry = '--dry' in sys.argv | |
+ if not dry: | |
+ script_utils.add_file_logger(logger, __file__) | |
+ init_app(routes=False, set_backends=True) | |
+ with TokuTransaction(): | |
+ main() | |
+ if dry: | |
+ raise Exception('Dry Run -- Aborting Transaction') | |
diff --git a/scripts/fix_forked_logs.py b/scripts/fix_forked_logs.py | |
new file mode 100644 | |
index 0000000..272562c | |
--- /dev/null | |
+++ b/scripts/fix_forked_logs.py | |
@@ -0,0 +1,75 @@ | |
+# -*- coding: utf-8 -*- | |
+import sys | |
+import logging | |
+ | |
+from modularodm import Q | |
+ | |
+from framework.transactions.context import TokuTransaction | |
+from framework.mongo import database as db | |
+from website import models | |
+from website.app import init_app | |
+from scripts import utils as script_utils | |
+ | |
+logger = logging.getLogger(__name__) | |
+ | |
+def migrate(): | |
+ migrated = [] | |
+ invalid = [] | |
+ | |
+ expected_count = models.NodeLog.find( | |
+ Q('action', 'eq', models.NodeLog.NODE_FORKED) & | |
+ Q('params.registration', 'eq', None) | |
+ ).count() | |
+ | |
+ logger.info('Expecting to migrate {} logs'.format(expected_count)) | |
+ | |
+ for fork in models.Node.find(Q('is_fork', 'eq', True)): | |
+ logger.info('Migrating fork: {}'.format(fork._id)) | |
+ try: | |
+ log_ids = db['node'].find_one({'_id': fork._id})['logs'] | |
+ logs = [models.NodeLog.load(e) for e in log_ids] | |
+ forked_log = next( | |
+ e for e in logs if | |
+ e and e.action == models.NodeLog.NODE_FORKED and | |
+ e.date == fork.forked_date | |
+ ) | |
+ except StopIteration: | |
+ logger.warn('Skipping fork that has no "node_forked" log: {}'.format(fork._id)) | |
+ invalid.append(fork) | |
+ | |
+ if not forked_log.params.get('registration'): | |
+ forked_log.params['registration'] = fork._primary_key | |
+ logger.info(' * Set "registration" param on Log {} to {}'.format(forked_log._id, fork._id)) | |
+ forked_log.save() | |
+ migrated.append(fork._id) | |
+ | |
+ if invalid: | |
+ logger.warn('Skipped {} forks that have no "node_forked" log'.format(len(invalid))) | |
+ logger.warn([each._id for each in invalid]) | |
+ | |
+ logger.info('Migrated {} logs'.format(len(migrated))) | |
+ | |
+ unmigrated = models.NodeLog.find( | |
+ Q('action', 'eq', models.NodeLog.NODE_FORKED) & | |
+ Q('params.registration', 'eq', None) | |
+ ) | |
+ | |
+ logger.warn([e._id for e in unmigrated]) | |
+ if unmigrated.count(): | |
+ logger.warn('Skipped {} log(s) that have no forward ref pointing to it'.format(unmigrated.count())) | |
+ logger.warn([each._id for each in unmigrated]) | |
+ | |
+def main(): | |
+ init_app(routes=False) | |
+ dry = '--dry' in sys.argv | |
+ if not dry: | |
+ # If we're not running in dry mode log everything to a file | |
+ script_utils.add_file_logger(logger, __file__) | |
+ with TokuTransaction(): | |
+ migrate() | |
+ if dry: | |
+ raise Exception('Abort Transaction - Dry Run') | |
+ | |
+ | |
+if __name__ == "__main__": | |
+ main() | |
diff --git a/scripts/indices.py b/scripts/indices.py | |
index 7458d88..e1c48e8 100644 | |
--- a/scripts/indices.py | |
+++ b/scripts/indices.py | |
@@ -5,10 +5,6 @@ | |
from pymongo import ASCENDING, DESCENDING | |
-db['nodelog'].create_index([ | |
- ('__backrefs.logged.node.logs', ASCENDING), | |
-]) | |
- | |
db['user'].create_index([ | |
('emails', ASCENDING), | |
]) | |
diff --git a/scripts/lowercase_log_nids.py b/scripts/lowercase_log_nids.py | |
new file mode 100644 | |
index 0000000..f0ce7e9 | |
--- /dev/null | |
+++ b/scripts/lowercase_log_nids.py | |
@@ -0,0 +1,45 @@ | |
+import sys | |
+ | |
+from framework.mongo import database as db | |
+from framework.transactions.context import TokuTransaction | |
+ | |
+from website.app import init_app | |
+ | |
+ | |
+def lowercase_nids(): | |
+ for log in db.nodelog.find({'$or': [ | |
+ {'params.node': {'$regex': '[A-Z]'}}, | |
+ {'params.project': {'$regex': '[A-Z]'}}, | |
+ {'params.registration': {'$regex': '[A-Z]'}}, | |
+ {'__backrefs.logged.node.logs': {'$regex': '[A-Z]'}}, | |
+ ]}): | |
+ update = {} | |
+ if log.get('__backrefs', {}).get('logged', {}).get('node', {}).get('logs'): | |
+ update['__backrefs.logged.node.logs'] = [nid.lower() for nid in log['__backrefs']['logged']['node']['logs']] | |
+ if log['params'].get('node'): | |
+ update['params.node'] = log['params']['node'].lower() | |
+ if log['params'].get('project'): | |
+ update['params.project'] = log['params']['project'].lower() | |
+ if log['params'].get('registration'): | |
+ update['params.registration'] = log['params']['registration'].lower() | |
+ db.nodelog.update({'_id': log['_id']}, {'$set': update}) | |
+ | |
+ assert db.nodelog.find({'$or': [ | |
+ {'params.node': {'$regex': '[A-Z]'}}, | |
+ {'params.project': {'$regex': '[A-Z]'}}, | |
+ {'params.registration': {'$regex': '[A-Z]'}}, | |
+ {'__backrefs.logged.node.logs': {'$regex': '[A-Z]'}}, | |
+ ]}).count() == 0 | |
+ | |
+ | |
+def main(): | |
+ init_app(routes=False) | |
+ dry_run = '--dry' in sys.argv | |
+ with TokuTransaction(): | |
+ lowercase_nids() | |
+ if dry_run: | |
+ raise Exception('Dry run') | |
+ | |
+ | |
+if __name__ == '__main__': | |
+ main() | |
diff --git a/scripts/meta/__init__.py b/scripts/meta/__init__.py | |
new file mode 100644 | |
index 0000000..e69de29 | |
diff --git a/scripts/meta/gatherer.py b/scripts/meta/gatherer.py | |
new file mode 100644 | |
index 0000000..97a5c05 | |
--- /dev/null | |
+++ b/scripts/meta/gatherer.py | |
@@ -0,0 +1,43 @@ | |
+import json | |
+import os | |
+from website.settings import GITHUB_API_TOKEN | |
+ | |
+GIT_LOGS_FILE = os.path.join('website', 'static', 'built', 'git_logs.json') | |
+ | |
+ | |
+def gather_pr_data(): | |
+ import requests | |
+ pr_data = [] | |
+ auth_header = {'Authorization': 'token %s' % GITHUB_API_TOKEN} | |
+ res = requests.get('https://api.github.com/repos/centerforopenscience/osf.io/pulls?state=closed&per_page=100', | |
+ headers=auth_header) | |
+ | |
+ while len(pr_data)<100: | |
+ if res.status_code == 200: | |
+ for item in res.json(): | |
+ try: | |
+ sha = item['merge_commit_sha'] | |
+ except TypeError: | |
+ sha = None | |
+ if sha: | |
+ pr_data.append(item) | |
+ try: | |
+ next_link = res.links['next'] | |
+ except KeyError: | |
+ break | |
+ res = requests.get(next_link['url']) | |
+ else: | |
+ break | |
+ | |
+ return pr_data | |
+ | |
+ | |
+def main(): | |
+ if GITHUB_API_TOKEN: | |
+ pr_data = json.dumps(gather_pr_data()) | |
+ with open(GIT_LOGS_FILE, 'w') as f: | |
+ f.write(pr_data) | |
+ | |
+if __name__ == '__main__': | |
+ main() | |
+ | |
diff --git a/scripts/migrate_registration_logs.py b/scripts/migrate_registration_logs.py | |
index 6c4fe49..aba2ae5 100644 | |
--- a/scripts/migrate_registration_logs.py | |
+++ b/scripts/migrate_registration_logs.py | |
@@ -1,11 +1,17 @@ | |
+""" | |
+This migration will change params['node'] to be the node (if it isn't node already), and will set | |
+params['registration'] equal to the associated registration. | |
+""" | |
+ | |
import logging | |
from modularodm import Q | |
-from website.models import NodeLog | |
+from website.models import NodeLog, Node, RegistrationApproval | |
from website.app import init_app | |
from scripts import utils as script_utils | |
+from framework.mongo import database as db | |
from framework.transactions.context import TokuTransaction | |
logger = logging.getLogger(__name__) | |
@@ -13,24 +19,59 @@ logging.basicConfig(level=logging.INFO) | |
def get_targets(): | |
+ """ | |
+ Fetches all registration-related logs except for project_registered. | |
+ | |
+ project_registered log is not included because params already correct. | |
+ """ | |
logs = NodeLog.find( | |
- Q('action', 'eq', 'embargo_approved') | | |
- Q('action', 'eq', 'embargo_initiated') | | |
- Q('action', 'eq', 'retraction_approved') | | |
- Q('action', 'eq', 'retraction_initiated') | | |
- Q('action', 'eq', 'registration_initiated') | |
+ Q('action', 'eq', 'registration_initiated') | | |
+ Q('action', 'eq', 'registration_approved') | | |
+ Q('action', 'eq', 'registration_cancelled') | # On staging, there are a few inconsistencies with these. Majority of params['node'] are registrations, but a handful are nodes. | |
+ Q('action', 'eq', 'retraction_initiated') | | |
+ Q('action', 'eq', 'retraction_approved') | # params['node'] is already equal to node. Adds registration_field below. Will be slow. | |
+ Q('action', 'eq', 'retraction_cancelled') | | |
+ Q('action', 'eq', 'embargo_initiated') | | |
+ Q('action', 'eq', 'embargo_approved') | | |
+ Q('action', 'eq', 'embargo_completed') | | |
+ Q('action', 'eq', 'embargo_cancelled') | |
) | |
return logs | |
+def get_registered_from(registration): | |
+ """ | |
+ Gets node registration was registered from. Handles deleted registrations where registered_from is null. | |
+ | |
+ """ | |
+ if registration.registered_from: | |
+ return registration.registered_from_id | |
+ else: | |
+ first_log_id = db['node'].find_one({'_id': registration._id})['logs'][0] | |
+ log = NodeLog.load(first_log_id) | |
+ return log.params.get('node') or log.params.get('project') | |
+ | |
+ | |
def migrate_log(logs): | |
+ """ | |
+ Migrates registration logs to set params['node'] to registered_from and params['registration'] to the registration. | |
+ """ | |
+ logs_count = logs.count() | |
+ count = 0 | |
for log in logs: | |
- if log.node.registered_from: | |
- log.params['node'] = log.node.registered_from_id | |
+ count += 1 | |
+ node = log.params.get('node') or log.params.get('project') | |
+ params_node = Node.load(node) | |
+ if params_node.is_registration: | |
+ log.params['node'] = get_registered_from(params_node) | |
+ log.params['registration'] = params_node._id | |
else: | |
- log.params['node'] = log.node.logs[0].node._id | |
+ # For logs where params['node'] already equal to node (registration_approval logs, and logs with errors in registration_cancelled) | |
+ log.params['registration'] = RegistrationApproval.load(log.params['registration_approval_id'])._get_registration()._id | |
+ | |
log.save() | |
- logger.info('Finished migrate log {}: registration action {} of node {}'.format(log._id, log.action, log.node)) | |
+ logger.info('{}/{} Finished migrating log {}: registration action {}. params[node]={} and params[registration]={}'.format(count, | |
+ logs_count, log._id, log.action, log.params['node'], log.params['registration'])) | |
def main(dry_run): | |
@@ -45,7 +86,7 @@ def main(dry_run): | |
if __name__ == '__main__': | |
import sys | |
script_utils.add_file_logger(logger, __file__) | |
- dry_run = 'dry' in sys.argv | |
+ dry_run = '--dry' in sys.argv | |
init_app(set_backends=True, routes=False) | |
with TokuTransaction(): | |
- main(dry_run=dry_run) | |
\ No newline at end of file | |
+ main(dry_run=dry_run) | |
diff --git a/scripts/migration/migrate_backrefs_between_nodes_and_nodelogs.py b/scripts/migration/migrate_backrefs_between_nodes_and_nodelogs.py | |
deleted file mode 100644 | |
index 0535d7e..0000000 | |
--- a/scripts/migration/migrate_backrefs_between_nodes_and_nodelogs.py | |
+++ /dev/null | |
@@ -1,86 +0,0 @@ | |
-""" | |
-This migration will add original_node and node associated with the log to nodelogs. It will then | |
-clone each nodelog for the remaining nodes in the backref (registrations and forks), | |
-changing the node to the current node. | |
-""" | |
-from copy import deepcopy | |
-import logging | |
-import sys | |
- | |
-from framework.mongo import database as db | |
-from framework.transactions.context import TokuTransaction | |
- | |
-from website.app import init_app | |
-from scripts import utils as script_utils | |
- | |
-logger = logging.getLogger(__name__) | |
- | |
- | |
-def migrate(dry=True): | |
- init_app(routes=False) | |
- cursor = db.nodelog.find({'original_node': None}) | |
- cursor.batch_size(10000) | |
- | |
- count = cursor.count() | |
- done = 0 | |
- | |
- to_insert = [] | |
- for log in cursor: | |
- try: | |
- try: | |
- node = log['__backrefs']['logged']['node']['logs'][0] | |
- tagged = log['__backrefs']['logged']['node']['logs'][1:] | |
- except (KeyError, IndexError): | |
- # If backrefs don't exist fallback to node/project with priority to node | |
- node = log['params'].get('node') or log['params'].get('project') | |
- # If project is different from the node we've found clone the logs for it | |
- if log['params'].get('project', node) != node: | |
- tagged = [log['params']['project']] | |
- else: | |
- tagged = [] | |
- | |
- assert node is not None, 'Could not find a node for {}'.format(log) | |
- | |
- db.nodelog.update({'_id': log['_id']}, {'$set': { | |
- 'node': node, | |
- 'original_node': log['params'].get('node', node), | |
- }}) | |
- except Exception as error: | |
- if log == {'__backrefs': {}, 'params': {}, '_id': log['_id']} or log == {'__backrefs': {'logged': {'node': {'logs': []}}}, 'params': {}, '_id': log['_id']}: | |
- logger.warning('log {} is empty. Skipping.'.format(log['_id'])) | |
- else: | |
- logger.error('Could not migrate nodelog {} due to error'.format(log)) | |
- logger.exception(error) | |
- continue | |
- finally: | |
- done += 1 | |
- | |
- for other in tagged: | |
- clone = deepcopy(log) | |
- clone.pop('_id') | |
- clone.pop('__backrefs', None) | |
- clone['original_node'] = log['params'].get('node', node) | |
- clone['node'] = other | |
- to_insert.append(clone) | |
- | |
- if len(to_insert) > 9999: | |
- count += len(to_insert) | |
- result = db.nodelog.insert(to_insert) | |
- # print(result) | |
- # assert result.modified_count == 500, result | |
- to_insert = [] | |
- done += len(result) | |
- logger.info('{}/{} Logs updated'.format(done, count)) | |
- | |
- if dry: | |
- raise RuntimeError('Dry run -- transaction rolled back') | |
- | |
-def main(): | |
- dry_run = '--dry' in sys.argv | |
- if not dry_run: | |
- script_utils.add_file_logger(logger, __file__) | |
- with TokuTransaction(): | |
- migrate(dry=dry_run) | |
- | |
-if __name__ == '__main__': | |
- main() | |
diff --git a/scripts/migration/migrate_registration_extra.py b/scripts/migration/migrate_registration_extra.py | |
deleted file mode 100644 | |
index 339e312..0000000 | |
--- a/scripts/migration/migrate_registration_extra.py | |
+++ /dev/null | |
@@ -1,78 +0,0 @@ | |
-""" | |
-Changes existing question.extra on a draft to a list | |
-required for multiple files attached to a question | |
-""" | |
-import sys | |
-import logging | |
-import itertools | |
- | |
-from modularodm import Q | |
-from website.app import init_app | |
-from scripts import utils as scripts_utils | |
-from website.models import DraftRegistration, Node | |
-from website.prereg.utils import get_prereg_schema | |
-from framework.transactions.context import TokuTransaction | |
- | |
- | |
-logger = logging.getLogger(__name__) | |
-logger.setLevel(logging.INFO) | |
- | |
-def migrate_drafts(dry): | |
- | |
- PREREG_CHALLENGE_METASCHEMA = get_prereg_schema() | |
- draft_registrations = DraftRegistration.find( | |
- Q('registration_schema', 'eq', PREREG_CHALLENGE_METASCHEMA) & | |
- Q('approval', 'eq', None) & | |
- Q('registered_node', 'eq', None) | |
- ) | |
- registered_prereg = Node.find( | |
- Q('is_registration', 'eq', True) & | |
- Q(PREREG_CHALLENGE_METASCHEMA, 'in', 'registered_schemas') & | |
- Q('is_deleted', 'eq', False) | |
- ) | |
- draft_count = 0 | |
- for r in draft_registrations: | |
- data = r.registration_metadata | |
- for q, ans in data.iteritems(): | |
- files = ans['extra'] | |
- if type(files) is dict: | |
- if len(files.keys()) == 0: | |
- ans['extra'] = [] | |
- else: | |
- ans['extra'] = [files] | |
- draft_count += 1 | |
- if not dry: | |
- r.save() | |
- | |
- node_count = 0 | |
- for p in registered_prereg: | |
- data = p.registered_meta | |
- for pk, meta in data.iteritems(): | |
- for q, ans in meta.iteritems(): | |
- files = ans['extra'] | |
- if type(files) is dict: | |
- if len(files.keys()) == 0: | |
- ans['extra'] = [] | |
- else: | |
- ans['extra'] = [files] | |
- node_count += 1 | |
- if not dry: | |
- r.save() | |
- | |
- | |
- logger.info('Done with {0} drafts and {1} nodes migrated.'.format(draft_count, node_count)) | |
- | |
- | |
-def main(dry=True): | |
- init_app(set_backends=True, routes=False) | |
- scripts_utils.add_file_logger(logger, __file__) | |
- migrate_drafts(dry) | |
- | |
- | |
- | |
-if __name__ == '__main__': | |
- dry_run = 'dry' in sys.argv | |
- with TokuTransaction(): | |
- main(dry=dry_run) | |
- if dry_run: | |
- raise RuntimeError('Dry run, rolling back transaction.') | |
diff --git a/scripts/populate_conferences.py b/scripts/populate_conferences.py | |
index b86802a..0a61e4a 100644 | |
--- a/scripts/populate_conferences.py | |
+++ b/scripts/populate_conferences.py | |
@@ -721,6 +721,26 @@ MEETING_DATA = { | |
'poster': True, | |
'talk': True, | |
}, | |
+ 'Reid2016': { | |
+ 'name': 'L. Starling Reid Undergraduate Psychology Conference 2016', | |
+ 'info_url': 'http://cacsprd.web.virginia.edu/Psych/Conference', | |
+ 'logo_url': None, | |
+ 'active': True, | |
+ 'admins': [], | |
+ 'public_projects': True, | |
+ 'poster': True, | |
+ 'talk': True, | |
+ }, | |
+ 'CNS2016': { | |
+ 'name': 'The Cognitive Neuroscience Society (CNS) 2016', | |
+ 'info_url': 'http://www.cogneurosociety.org/annual-meeting/', | |
+ 'logo_url': None, | |
+ 'active': True, | |
+ 'admins': [], | |
+ 'public_projects': True, | |
+ 'poster': True, | |
+ 'talk': True, | |
+ }, | |
} | |
diff --git a/scripts/remove_nodelog_backref.py b/scripts/remove_nodelog_backref.py | |
new file mode 100644 | |
index 0000000..650d175 | |
--- /dev/null | |
+++ b/scripts/remove_nodelog_backref.py | |
@@ -0,0 +1,221 @@ | |
+# -*- coding: utf-8 -*- | |
+""" | |
+NOTE: The following scripts need to have run first: | |
+ | |
+- python -m scripts.fix_forked_logs | |
+- python -m scripts.migrate_registration_logs | |
+""" | |
+from __future__ import division | |
+import json | |
+from bson import ObjectId | |
+import sys | |
+from copy import deepcopy | |
+ | |
+from framework.mongo import database as db | |
+from framework.transactions.context import TokuTransaction | |
+ | |
+from website.app import init_app | |
+from website.models import NodeLog | |
+from scripts import utils as script_utils | |
+ | |
+import logging | |
+logger = logging.getLogger(__name__) | |
+ | |
+BACKUP_COLLECTION = 'unmigratedlogs' | |
+ | |
+def copy_log(log, node_id): | |
+ clone = deepcopy(log) | |
+ clone['_id'] = str(ObjectId()) | |
+ clone.pop('__backrefs', None) | |
+ # node_removed is the only log type where params.project is | |
+ # not the same as the node that the log is saved on | |
+ if log['action'] == NodeLog.NODE_REMOVED: | |
+ # For node_removed logs, original node should point to the | |
+ # deleted node | |
+ original_node = log['params']['project'] | |
+ else: | |
+ original_node = get_log_subject(log) | |
+ clone['original_node'] = original_node | |
+ clone['node'] = node_id.lower() | |
+ return clone | |
+ | |
+# The remaining, unmigrated logs are orphaned (missing forward and back refs) | |
+# We put them in a separate collection for now | |
+def move_to_backup_collection(log_id): | |
+ log = db.nodelog.find_one({'_id': log_id}) | |
+ assert log | |
+ db[BACKUP_COLLECTION].insert(log) | |
+ db.nodelog.remove({'_id': log_id}, just_one=True) | |
+ | |
+ | |
+def get_log_subject(log): | |
+ # node_removed logs get stored on a node's parent, which will get handled | |
+ # when we go through a log's backrefs | |
+ if log['action'] == NodeLog.NODE_REMOVED: | |
+ return None | |
+ # node_forked logs get stored on forks, and the fork's ID is in params.registration | |
+ if log['action'] == NodeLog.NODE_FORKED: | |
+ reg = log['params'].get('registration') | |
+ if reg: | |
+ return reg.lower() | |
+ else: # There is 1 orphaned log for which params.registration is None | |
+ return None | |
+ return (log['params'].get('node') or log['params']['project']).lower() | |
+ | |
+def migrate_log(log, node_id): | |
+ should_copy = False | |
+ subject = get_log_subject(log) | |
+ if subject != node_id.lower(): | |
+ should_copy = True | |
+ else: | |
+ db.nodelog.update({'_id': log['_id']}, {'$set': { | |
+ 'node': node_id.lower(), | |
+ 'original_node': log['params'].get('node', node_id).lower(), | |
+ }}) | |
+ | |
+ return should_copy | |
+ | |
+ | |
+def bulk_insert(logs, remaining): | |
+ result = db.nodelog.insert(logs) | |
+ for each in logs: | |
+ remaining.remove(each['_id']) | |
+ return result | |
+ | |
+ | |
+def migrate(dry=True): | |
+ | |
+ cursor = db.node.find({}, | |
+ {'_id': True, 'logs': True}) | |
+ cursor = cursor.batch_size(10000) | |
+ | |
+ count = db.nodelog.count() | |
+ | |
+ remaining_log_ids = set([each['_id'] for each in db.nodelog.find({}, {'_id': 1})]) | |
+ | |
+ done = 0 | |
+ to_insert = [] | |
+ | |
+ for node in cursor: | |
+ logs = db.nodelog.find({'_id': {'$in': node['logs']}}) | |
+ | |
+ for log in logs: | |
+ should_copy = migrate_log(log=log, node_id=node['_id']) | |
+ if should_copy: | |
+ clone = copy_log(log=log, node_id=node['_id']) | |
+ remaining_log_ids.add(clone['_id']) | |
+ to_insert.append(clone) | |
+ else: | |
+ remaining_log_ids.remove(log['_id']) | |
+ done += 1 | |
+ | |
+ if len(to_insert) > 9999: | |
+ count += len(to_insert) | |
+ result = bulk_insert(to_insert, remaining=remaining_log_ids) | |
+ to_insert = [] | |
+ done += len(result) | |
+ logger.info('{}/{} Logs updated ({:.2f}%)'.format(done, count, done / count * 100)) | |
+ | |
+ if len(to_insert) > 0: | |
+ count += len(to_insert) | |
+ result = bulk_insert(to_insert, remaining=remaining_log_ids) | |
+ to_insert = [] | |
+ done += len(result) | |
+ logger.info('{}/{} Logs updated ({:.2f}%)'.format(done, count, done / count * 100)) | |
+ | |
+ backref_logs = [] | |
+ no_backref = [] | |
+ for log_id in list(remaining_log_ids): | |
+ backref_logs.append(log_id) | |
+ log = db.nodelog.find_one({'_id': log_id}) | |
+ try: | |
+ node_ids = [e.lower() for e in log['__backrefs']['logged']['node']['logs']] | |
+ except KeyError: | |
+ node_ids = [] | |
+ | |
+ if not node_ids and not log.get('was_connected_to'): | |
+ logger.warn('Null or empty backref for log {}'.format(log_id)) | |
+ no_backref.append(log_id) | |
+ continue | |
+ log_subject = get_log_subject(log) | |
+ if log_subject and log_subject not in node_ids: | |
+ logger.warn('Incomplete backref for log {}: does not contain {}'.format(log['_id'], log_subject)) | |
+ node_ids.append(log_subject) | |
+ | |
+ for node_id in node_ids: | |
+ if db.node.find_one({'_id': node_id, 'logs': log['_id']}) or node_id in log.get('was_connected_to', []): | |
+ continue | |
+ should_copy = migrate_log(log=log, node_id=node_id) | |
+ if should_copy: | |
+ clone = copy_log(log=log, node_id=node_id) | |
+ remaining_log_ids.add(clone['_id']) # XX | |
+ to_insert.append(clone) | |
+ else: | |
+ remaining_log_ids.remove(log['_id']) # XX | |
+ done += 1 | |
+ | |
+ if len(to_insert) > 9999: | |
+ count += len(to_insert) | |
+ result = bulk_insert(to_insert, remaining=remaining_log_ids) | |
+ to_insert = [] | |
+ done += len(result) | |
+ logger.info('{}/{} Logs updated ({:.2f}%)'.format(done, count, done / count * 100)) | |
+ | |
+ if len(to_insert) > 0: | |
+ count += len(to_insert) | |
+ result = bulk_insert(to_insert, remaining=remaining_log_ids) | |
+ to_insert = [] | |
+ done += len(result) | |
+ logger.info('{}/{} Logs updated ({:.2f}%)'.format(done, count, done / count * 100)) | |
+ | |
+ # Logs that have was_connected_to should not be migrated, because they | |
+ # were removed from nodes and therefore are expected to have neither | |
+ # forwards nor backwards refs | |
+ expected_unmigrated = set( | |
+ [ | |
+ each['_id'] for each in | |
+ db.nodelog.find( | |
+ {'$and': [ | |
+ {'_id': {'$in': list(remaining_log_ids)}}, | |
+ {'was_connected_to': {'$ne': []}}, | |
+ {'was_connected_to': {'$exists': True}}, | |
+ ]}, | |
+ {'_id': True} | |
+ ) | |
+ ] | |
+ ) | |
+ | |
+ unexpected_unmigrated = remaining_log_ids - expected_unmigrated | |
+ | |
+ for log_id in remaining_log_ids: | |
+ move_to_backup_collection(log_id) | |
+ | |
+ if unexpected_unmigrated: | |
+ logger.warn('Unexpected unmigrated logs: {}'.format(len(remaining_log_ids))) | |
+ logger.warn(len(remaining_log_ids)) | |
+ | |
+ with open('bads.json', 'w') as fp: # XX | |
+ json.dump(list(unexpected_unmigrated), fp) # XX | |
+ | |
+ logger.info('Updated orphans:') | |
+ logger.info(len(backref_logs)) | |
+ | |
+ if no_backref: | |
+ logger.warn('Skipped logs with no backref: {}'.format(len(no_backref))) | |
+ | |
+ if dry: | |
+ raise RuntimeError('Dry run -- transaction rolled back') | |
+ | |
+ | |
+def main(): | |
+ init_app(routes=False) | |
+ dry_run = '--dry' in sys.argv | |
+ if dry_run: | |
+ logger.warn('Running a dry run') | |
+ if not dry_run: | |
+ script_utils.add_file_logger(logger, __file__) | |
+ with TokuTransaction(): | |
+ migrate(dry=dry_run) | |
+ | |
+if __name__ == '__main__': | |
+ main() | |
diff --git a/scripts/remove_wiki_title_forward_slashes.py b/scripts/remove_wiki_title_forward_slashes.py | |
new file mode 100644 | |
index 0000000..59a8c88 | |
--- /dev/null | |
+++ b/scripts/remove_wiki_title_forward_slashes.py | |
@@ -0,0 +1,62 @@ | |
+""" | |
+Remove forward slashes from wiki page titles, since it is no longer an allowed character and | |
+breaks validation. | |
+""" | |
+import logging | |
+import sys | |
+ | |
+from framework.mongo import database as db | |
+from framework.transactions.context import TokuTransaction | |
+ | |
+from website.app import init_app | |
+from website.project.model import Node | |
+from scripts import utils as script_utils | |
+ | |
+logger = logging.getLogger(__name__) | |
+ | |
+ | |
+def main(): | |
+ wiki_pages = db.nodewikipage.find({'page_name': {'$regex': '/'}}, | |
+ {'_id': True, 'page_name': True, 'node': True}) | |
+ wiki_pages = wiki_pages.batch_size(200) | |
+ fix_wiki_titles(wiki_pages) | |
+ | |
+ | |
+def fix_wiki_titles(wiki_pages): | |
+ for i, wiki in enumerate(wiki_pages): | |
+ old_name = wiki['page_name'] | |
+ new_name = wiki['page_name'].replace('/', '') | |
+ | |
+ # update wiki page name | |
+ db.nodewikipage.update({'_id': wiki['_id']}, {'$set': {'page_name': new_name}}) | |
+ logger.info('Updated wiki {} title to {}'.format(wiki['_id'], new_name)) | |
+ | |
+ node = Node.load(wiki['node']) | |
+ if not node: | |
+ logger.info('Invalid node {} for wiki {}'.format(node, wiki['_id'])) | |
+ continue | |
+ | |
+ # update node wiki page records | |
+ if old_name in node.wiki_pages_versions: | |
+ node.wiki_pages_versions[new_name] = node.wiki_pages_versions[old_name] | |
+ del node.wiki_pages_versions[old_name] | |
+ | |
+ if old_name in node.wiki_pages_current: | |
+ node.wiki_pages_current[new_name] = node.wiki_pages_current[old_name] | |
+ del node.wiki_pages_current[old_name] | |
+ | |
+ if old_name in node.wiki_private_uuids: | |
+ node.wiki_private_uuids[new_name] = node.wiki_private_uuids[old_name] | |
+ del node.wiki_private_uuids[old_name] | |
+ node.save() | |
+ | |
+ | |
+if __name__ == '__main__': | |
+ dry = '--dry' in sys.argv | |
+ if not dry: | |
+ script_utils.add_file_logger(logger, __file__) | |
+ init_app(routes=False, set_backends=True) | |
+ with TokuTransaction(): | |
+ main() | |
+ if dry: | |
+ raise Exception('Dry Run -- Aborting Transaction') | |
diff --git a/scripts/send_queued_mails.py b/scripts/send_queued_mails.py | |
index 3d408f8..2f895ea 100644 | |
--- a/scripts/send_queued_mails.py | |
+++ b/scripts/send_queued_mails.py | |
@@ -42,6 +42,8 @@ def main(dry_run=True): | |
logger.error('Email of type {0} to be sent to {1} caused an ERROR'.format(mail.email_type, mail.to_addr)) | |
logger.exception(error) | |
pass | |
+ else: | |
+ logger.info('Email of type {} will be sent to {}'.format(mail.email_type, mail.to_addr)) | |
def find_queued_mails_ready_to_be_sent(): | |
diff --git a/scripts/tests/test_migrate_registration_extra.py b/scripts/tests/test_migrate_registration_extra.py | |
deleted file mode 100644 | |
index 4e7c4d3..0000000 | |
--- a/scripts/tests/test_migrate_registration_extra.py | |
+++ /dev/null | |
@@ -1,89 +0,0 @@ | |
-from nose.tools import * # noqa | |
- | |
-from tests.base import OsfTestCase | |
-from tests.factories import UserFactory | |
-from tests.factories import DraftRegistrationFactory, NodeFactory | |
- | |
-from website.models import MetaSchema | |
-from website.project.model import ensure_schemas | |
-from website.prereg.utils import get_prereg_schema | |
-from scripts.migration.migrate_registration_extra import main | |
- | |
- | |
- | |
-class TestMigrateRegistrationExtra(OsfTestCase): | |
- def setUp(self): | |
- super(TestMigrateRegistrationExtra, self).setUp() | |
- self.user = UserFactory() | |
- MetaSchema.remove() | |
- ensure_schemas() | |
- | |
- | |
- self.file_ans = { | |
- 'file': { | |
- 'data':{ | |
- 'kind':'file', | |
- 'extra':{ | |
- 'checkout': None, | |
- 'hashes':{ | |
- 'sha256':'1fffe6116ecfa778f9938060d5caab923ba4b8db60bd2dd57f16a72e5ef06292' | |
- }, | |
- 'downloads':0, | |
- 'version':1 | |
- }, | |
- 'modified':'2016-04-15T18:10:48', | |
- 'name':'file.txt', | |
- 'provider':'osfstorage', | |
- } | |
- } | |
- } | |
- self.complex_metadata = { | |
- 'q1': { | |
- 'value': 'Answer 1', | |
- 'extra': [] | |
- }, | |
- 'q2': { | |
- 'value': 'Answer 2', | |
- 'extra': {} | |
- }, | |
- 'q3': { | |
- 'value': 'Answer 3', | |
- 'extra': self.file_ans | |
- } | |
- } | |
- self.simple_metadata = { | |
- 'Summary': 'Some airy' | |
- } | |
- self.schema = get_prereg_schema() | |
- self.draft1 = DraftRegistrationFactory( | |
- registration_metadata=self.complex_metadata, | |
- registration_schema=self.schema, | |
- approval=None, | |
- registered_node=None | |
- | |
- ) | |
- self.draft2 = DraftRegistrationFactory( | |
- registration_metadata=self.simple_metadata | |
- ) | |
- self.project = ProjectFactory(creator=self.user, is_public=True) | |
- self.registration = RegistrationFactory(project=project, is_registration=True) | |
- | |
- def test_migrate_registration_extra(self): | |
- | |
- assert_equal(type(self.draft1.registration_metadata['q1']['extra']), list) | |
- assert_equal(type(self.draft1.registration_metadata['q2']['extra']), dict) | |
- assert_equal(type(self.draft1.registration_metadata['q2']['extra']), dict) | |
- | |
- assert_equal(self.draft2.registration_metadata, self.simple_metadata) | |
- | |
- main(dry=False) | |
- self.draft1.reload() | |
- self.draft2.reload() | |
- | |
- assert_equal(type(self.draft1.registration_metadata['q1']['extra']), list) | |
- assert_equal(type(self.draft1.registration_metadata['q2']['extra']), list) | |
- assert_equal(type(self.draft1.registration_metadata['q3']['extra']), list) | |
- | |
- assert_equal(self.draft1.registration_metadata['q3']['extra'][0], self.file_ans) | |
- | |
- assert_equal(self.draft2.registration_metadata, self.simple_metadata) | |
diff --git a/scripts/tests/test_remove_wiki_title_forward_slashes.py b/scripts/tests/test_remove_wiki_title_forward_slashes.py | |
new file mode 100644 | |
index 0000000..078a22e | |
--- /dev/null | |
+++ b/scripts/tests/test_remove_wiki_title_forward_slashes.py | |
@@ -0,0 +1,38 @@ | |
+from nose.tools import * | |
+ | |
+from framework.mongo import database as db | |
+ | |
+from scripts.remove_wiki_title_forward_slashes import main | |
+ | |
+from tests.base import OsfTestCase | |
+from tests.factories import NodeWikiFactory, ProjectFactory | |
+ | |
+ | |
+class TestRemoveWikiTitleForwardSlashes(OsfTestCase): | |
+ | |
+ def test_forward_slash_is_removed_from_wiki_title(self): | |
+ project = ProjectFactory() | |
+ wiki = NodeWikiFactory(node=project, is_current=True) | |
+ | |
+ invalid_name = 'invalid/name' | |
+ db.nodewikipage.update({'_id': wiki._id}, {'$set': {'page_name': invalid_name}}) | |
+ project.wiki_pages_current['invalid/name'] = project.wiki_pages_current[wiki.page_name] | |
+ project.wiki_pages_versions['invalid/name'] = project.wiki_pages_versions[wiki.page_name] | |
+ project.save() | |
+ | |
+ main() | |
+ wiki.reload() | |
+ | |
+ assert_equal(wiki.page_name, 'invalidname') | |
+ assert_in('invalidname', project.wiki_pages_current) | |
+ assert_in('invalidname', project.wiki_pages_versions) | |
+ | |
+ def test_valid_wiki_title(self): | |
+ project = ProjectFactory() | |
+ wiki = NodeWikiFactory(node=project, is_current=True) | |
+ page_name = wiki.page_name | |
+ main() | |
+ wiki.reload() | |
+ assert_equal(page_name, wiki.page_name) | |
+ assert_in(page_name, project.wiki_pages_current) | |
+ assert_in(page_name, project.wiki_pages_versions) | |
diff --git a/scripts/utils.py b/scripts/utils.py | |
index 63ae25f..118508d 100644 | |
--- a/scripts/utils.py | |
+++ b/scripts/utils.py | |
@@ -13,6 +13,7 @@ def format_now(): | |
def add_file_logger(logger, script_name, suffix=None): | |
_, name = os.path.split(script_name) | |
+ name = name.rstrip('c') | |
if suffix is not None: | |
name = '{0}-{1}'.format(name, suffix) | |
file_handler = logging.FileHandler( | |
diff --git a/scripts/verify_log_migration.py b/scripts/verify_log_migration.py | |
new file mode 100644 | |
index 0000000..381e42f | |
--- /dev/null | |
+++ b/scripts/verify_log_migration.py | |
@@ -0,0 +1,58 @@ | |
+# -*- coding: utf-8 -*- | |
+import os | |
+import json | |
+import logging | |
+ | |
+from framework.mongo import database | |
+ | |
+from website import models | |
+from website.app import init_app | |
+ | |
+ | |
+FILE_NAME = 'logcounts.json' | |
+logger = logging.getLogger() | |
+ | |
+ | |
+def main(): | |
+ init_app() | |
+ verify = os.path.exists(FILE_NAME) | |
+ total = models.Node.find().count() | |
+ | |
+ logger.info('{}unning in verify mode'.format('R' if verify else 'Not r')) | |
+ | |
+ counts = {} | |
+ for i, node in enumerate(models.Node.find()): | |
+ if verify: | |
+ counts[node._id] = len(node.logs) | |
+ else: | |
+ counts[node._id] = len( | |
+ set(database.node.find_one({'_id': node._id}, {'logs': True})['logs']) | |
+ | set(log['_id'] for log in database.nodelog.find({'__backrefs.logged.node.logs': node._id}, {})) | |
+ ) | |
+ | |
+ if i % 5000 == 0: | |
+ print('{:.2f}% finished'.format(float(i) / total * 100)) | |
+ models.Node._cache.data.clear() | |
+ models.Node._object_cache.data.clear() | |
+ | |
+ if not verify: | |
+ with open(FILE_NAME, 'w') as fobj: | |
+ json.dump(counts, fobj) | |
+ else: | |
+ incorrect = [] | |
+ with open(FILE_NAME, 'r') as fobj: | |
+ expected = json.load(fobj) | |
+ for nid in (set(counts.keys()) | set(expected.keys())): | |
+ if expected.get(nid, 0) != counts.get(nid, 0): | |
+ incorrect.append({'id': nid, 'expected': expected.get(nid, 0), 'actual': counts.get(nid, 0)}) | |
+ logger.warning('Log count of node {} changed. Expected {} found {}'.format(nid, expected.get(nid, 0), counts.get(nid, 0))) | |
+ if incorrect: | |
+ logger.error('Found {} incorrect node log counts'.format(len(incorrect))) | |
+ with open('incorrect.json', 'w') as fp: | |
+ json.dump(incorrect, fp) | |
+ else: | |
+ logger.info('All counts match') | |
+ | |
+ | |
+if __name__ == '__main__': | |
+ main() | |
diff --git a/tasks/__init__.py b/tasks/__init__.py | |
index 8a7545f..1d0a452 100755 | |
--- a/tasks/__init__.py | |
+++ b/tasks/__init__.py | |
@@ -17,12 +17,14 @@ from invoke import run, Collection | |
from website import settings | |
from admin import tasks as admin_tasks | |
from utils import pip_install, bin_prefix | |
+from scripts.meta import gatherer | |
logging.getLogger('invoke').setLevel(logging.CRITICAL) | |
# gets the root path for all the scripts that rely on it | |
HERE = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) | |
WHEELHOUSE_PATH = os.environ.get('WHEELHOUSE') | |
+CONSTRAINTS_PATH = os.path.join(HERE, 'requirements', 'constraints.txt') | |
try: | |
__import__('rednose') | |
@@ -69,9 +71,8 @@ def server(host=None, port=5000, debug=True, live=False, gitlogs=False): | |
app.run(host=host, port=port, debug=debug, threaded=debug, extra_files=[settings.ASSET_HASH_PATH]) | |
@task | |
-def git_logs(count=100, pretty='format:"%s - %b"', grep='"Merge pull request"'): | |
- cmd = 'git log --grep={1} -n {0} --pretty={2} > website/static/git_logs.txt'.format(count, grep, pretty) | |
- run(cmd, echo=True) | |
+def git_logs(): | |
+ gatherer.main() | |
@task | |
@@ -451,17 +452,29 @@ def requirements(base=False, addons=False, release=False, dev=False, metrics=Fal | |
# "release" takes precedence | |
if release: | |
req_file = os.path.join(HERE, 'requirements', 'release.txt') | |
- run(pip_install(req_file), echo=True) | |
+ run( | |
+ pip_install(req_file, constraints_file=CONSTRAINTS_PATH), | |
+ echo=True | |
+ ) | |
else: | |
if dev: # then dev requirements | |
req_file = os.path.join(HERE, 'requirements', 'dev.txt') | |
- run(pip_install(req_file), echo=True) | |
+ run( | |
+ pip_install(req_file, constraints_file=CONSTRAINTS_PATH), | |
+ echo=True | |
+ ) | |
if metrics: # then dev requirements | |
req_file = os.path.join(HERE, 'requirements', 'metrics.txt') | |
- run(pip_install(req_file), echo=True) | |
+ run( | |
+ pip_install(req_file, constraints_file=CONSTRAINTS_PATH), | |
+ echo=True | |
+ ) | |
if base: # then base requirements | |
req_file = os.path.join(HERE, 'requirements.txt') | |
- run(pip_install(req_file), echo=True) | |
+ run( | |
+ pip_install(req_file, constraints_file=CONSTRAINTS_PATH), | |
+ echo=True | |
+ ) | |
@task | |
@@ -573,7 +586,7 @@ def karma(single=False, sauce=False, browsers=None): | |
@task | |
def wheelhouse(addons=False, release=False, dev=False, metrics=False): | |
- """Install python dependencies. | |
+ """Build wheels for python dependencies. | |
Examples: | |
@@ -607,18 +620,16 @@ def addon_requirements(): | |
"""Install all addon requirements.""" | |
for directory in os.listdir(settings.ADDON_PATH): | |
path = os.path.join(settings.ADDON_PATH, directory) | |
- if os.path.isdir(path): | |
- try: | |
- requirements_file = os.path.join(path, 'requirements.txt') | |
- open(requirements_file) | |
- print('Installing requirements for {0}'.format(directory)) | |
- cmd = 'pip install --exists-action w --upgrade -r {0}'.format(requirements_file) | |
- if WHEELHOUSE_PATH: | |
- cmd += ' --no-index --find-links={}'.format(WHEELHOUSE_PATH) | |
- run(bin_prefix(cmd)) | |
- except IOError: | |
- pass | |
- print('Finished') | |
+ | |
+ requirements_file = os.path.join(path, 'requirements.txt') | |
+ if os.path.isdir(path) and os.path.isfile(requirements_file): | |
+ print('Installing requirements for {0}'.format(directory)) | |
+ run( | |
+ pip_install(requirements_file, constraints_file=CONSTRAINTS_PATH), | |
+ echo=True | |
+ ) | |
+ | |
+ print('Finished installing addon requirements') | |
@task | |
diff --git a/tasks/utils.py b/tasks/utils.py | |
index 30fa54e..48044bc 100644 | |
--- a/tasks/utils.py | |
+++ b/tasks/utils.py | |
@@ -15,11 +15,15 @@ def bin_prefix(cmd): | |
return os.path.join(get_bin_path(), cmd) | |
-def pip_install(req_file): | |
- """Return the proper 'pip install' command for installing the dependencies | |
- defined in ``req_file``. | |
+def pip_install(req_file, constraints_file=None): | |
+ """ | |
+ Return the proper 'pip install' command for installing the dependencies | |
+ defined in ``req_file``. Optionally obey a file of constraints in case of version conflicts | |
""" | |
cmd = bin_prefix('pip install --exists-action w --upgrade -r {} '.format(req_file)) | |
+ if constraints_file: # Support added in pip 7.1 | |
+ cmd += ' -c {}'.format(constraints_file) | |
+ | |
if WHEELHOUSE_PATH: | |
cmd += ' --no-index --find-links={}'.format(WHEELHOUSE_PATH) | |
return cmd | |
diff --git a/tests/test_institution_model.py b/tests/test_institution_model.py | |
index f8601fc..c4d8d94 100644 | |
--- a/tests/test_institution_model.py | |
+++ b/tests/test_institution_model.py | |
@@ -2,11 +2,17 @@ from nose.tools import * # flake8: noqa | |
from tests.base import OsfTestCase | |
from tests.factories import InstitutionFactory | |
+from website.models import Institution, Node | |
+ | |
class TestInstitution(OsfTestCase): | |
def setUp(self): | |
super(TestInstitution, self).setUp() | |
self.institution = InstitutionFactory() | |
+ def tearDown(self): | |
+ super(TestInstitution, self).tearDown() | |
+ Node.remove() | |
+ | |
def test_institution_save_only_changes_mapped_fields_on_node(self): | |
node = self.institution.node | |
old = { | |
@@ -51,3 +57,26 @@ class TestInstitution(OsfTestCase): | |
inst = InstitutionFactory() | |
inst.logo_name = None | |
assert_is_none(inst.logo_path, None) | |
+ | |
+ def test_institution_find(self): | |
+ insts = list(Institution.find()) | |
+ | |
+ assert_equal(len(insts), 1) | |
+ assert_equal(insts[0], self.institution) | |
+ | |
+ def test_institution_find_doesnt_find_deleted(self): | |
+ self.institution.node.is_deleted = True | |
+ self.institution.node.save() | |
+ | |
+ insts = list(Institution.find()) | |
+ | |
+ assert_equal(len(insts), 0) | |
+ | |
+ def test_find_deleted(self): | |
+ self.institution.node.is_deleted = True | |
+ self.institution.node.save() | |
+ | |
+ insts = list(Institution.find(deleted=True)) | |
+ | |
+ assert_equal(len(insts), 1) | |
+ assert_equal(insts[0], self.institution) | |
diff --git a/tests/test_models.py b/tests/test_models.py | |
index 1af0e31..bcbf7b6 100644 | |
--- a/tests/test_models.py | |
+++ b/tests/test_models.py | |
@@ -3791,6 +3791,15 @@ class TestForkNode(OsfTestCase): | |
fork = self.project.fork_node(self.auth) | |
assert_false(fork.is_public) | |
+ def test_fork_log_has_correct_log(self): | |
+ fork = self.project.fork_node(self.auth) | |
+ last_log = list(fork.logs)[-1] | |
+ assert_equal(last_log.action, NodeLog.NODE_FORKED) | |
+ # Legacy 'registration' param should be the ID of the fork | |
+ assert_equal(last_log.params['registration'], fork._primary_key) | |
+ # 'node' param is the original node's ID | |
+ assert_equal(last_log.params['node'], self.project._primary_key) | |
+ | |
def test_not_fork_private_link(self): | |
link = PrivateLinkFactory() | |
link.nodes.append(self.project) | |
diff --git a/tests/test_registrations/test_archiver.py b/tests/test_registrations/test_archiver.py | |
index 9dda8ec..be7aad8 100644 | |
--- a/tests/test_registrations/test_archiver.py | |
+++ b/tests/test_registrations/test_archiver.py | |
@@ -393,12 +393,12 @@ class TestArchiverTasks(ArchiverTestCase): | |
data = { | |
('q_' + selected_file['name']): { | |
'value': fake.word(), | |
- 'extra': [{ | |
+ 'extra': { | |
'selectedFileName': selected_file['name'], | |
'nodeId': node._id, | |
'sha256': sha256, | |
'viewUrl': '/project/{0}/files/osfstorage{1}'.format(node._id, selected_file['path']) | |
- }] | |
+ } | |
} | |
for sha256, selected_file in selected_files.items() | |
} | |
@@ -407,12 +407,12 @@ class TestArchiverTasks(ArchiverTestCase): | |
'value': { | |
name_factory(): { | |
'value': fake.word(), | |
- 'extra': [{ | |
+ 'extra': { | |
'selectedFileName': selected_file['name'], | |
'nodeId': node._id, | |
'sha256': sha256, | |
'viewUrl': '/project/{0}/files/osfstorage{1}'.format(node._id, selected_file['path']) | |
- }] | |
+ } | |
}, | |
name_factory(): { | |
'value': fake.word() | |
@@ -437,14 +437,14 @@ class TestArchiverTasks(ArchiverTestCase): | |
for key, question in registration.registered_meta[prereg_schema._id].items(): | |
target = None | |
if isinstance(question['value'], dict): | |
- target = [v for v in question['value'].values() if 'extra' in v and 'sha256' in v['extra'][0]] | |
+ target = [v for v in question['value'].values() if 'extra' in v and 'sha256' in v['extra']][0] | |
elif 'extra' in question and 'hashes' in question['extra']: | |
target = question | |
if target: | |
- assert_in(registration._id, target[0]['extra'][0]['viewUrl']) | |
- assert_not_in(node._id, target[0]['extra'][0]['viewUrl']) | |
- del selected_files[target[0]['extra'][0]['sha256']] | |
+ assert_in(registration._id, target['extra']['viewUrl']) | |
+ assert_not_in(node._id, target['extra']['viewUrl']) | |
+ del selected_files[target['extra']['sha256']] | |
else: | |
# check non-file questions are unmodified | |
assert_equal(data[key]['value'], question['value']) | |
@@ -481,7 +481,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
data = { | |
('q_' + selected_file['name']): { | |
'value': fake.word(), | |
- 'extra': [{ | |
+ 'extra': { | |
'sha256': sha256, | |
'viewUrl': '/project/{0}/files/osfstorage{1}'.format( | |
selected_file_node_index[sha256], | |
@@ -489,7 +489,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
), | |
'selectedFileName': selected_file['name'], | |
'nodeId': selected_file_node_index[sha256] | |
- }] | |
+ } | |
} | |
for sha256, selected_file in selected_files.items() | |
} | |
@@ -498,7 +498,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
'value': { | |
name_factory(): { | |
'value': fake.word(), | |
- 'extra': [{ | |
+ 'extra': { | |
'sha256': sha256, | |
'viewUrl': '/project/{0}/files/osfstorage{1}'.format( | |
selected_file_node_index[sha256], | |
@@ -506,7 +506,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
), | |
'selectedFileName': selected_file['name'], | |
'nodeId': selected_file_node_index[sha256] | |
- }] | |
+ } | |
}, | |
name_factory(): { | |
'value': fake.word() | |
@@ -549,21 +549,21 @@ class TestArchiverTasks(ArchiverTestCase): | |
for key, question in registration.registered_meta[prereg_schema._id].items(): | |
target = None | |
if isinstance(question['value'], dict): | |
- target = [v for v in question['value'].values() if 'extra' in v and 'sha256' in v['extra'][0]] | |
+ target = [v for v in question['value'].values() if 'extra' in v and 'sha256' in v['extra']][0] | |
elif 'extra' in question and 'sha256' in question['extra']: | |
target = question | |
if target: | |
node_id = re.search( | |
r'^/project/(?P<node_id>\w{5}).+$', | |
- target[0]['extra'][0]['viewUrl'] | |
+ target['extra']['viewUrl'] | |
).groupdict()['node_id'] | |
assert_in( | |
node_id, | |
[r._id for r in registration.node_and_primary_descendants()] | |
) | |
- if target[0]['extra'][0]['sha256'] in selected_files: | |
- del selected_files[target[0]['extra'][0]['sha256']] | |
+ if target['extra']['sha256'] in selected_files: | |
+ del selected_files[target['extra']['sha256']] | |
else: | |
# check non-file questions are unmodified | |
assert_equal(data[key]['value'], question['value']) | |
@@ -588,7 +588,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
data = { | |
('q_' + fake_file['name']): { | |
'value': fake.word(), | |
- 'extra': [{ | |
+ 'extra': { | |
'sha256': fake_file['extra']['hashes']['sha256'], | |
'viewUrl': '/project/{0}/files/osfstorage{1}'.format( | |
node._id, | |
@@ -596,7 +596,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
), | |
'selectedFileName': fake_file['name'], | |
'nodeId': node._id | |
- }] | |
+ } | |
} | |
} | |
@@ -605,7 +605,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
job = factories.ArchiveJobFactory() | |
archive_success(registration._id, job._id) | |
for key, question in registration.registered_meta[prereg_schema._id].items(): | |
- assert_equal(question['extra'][0]['selectedFileName'], fake_file['name']) | |
+ assert_equal(question['extra']['selectedFileName'], fake_file['name']) | |
def test_archive_success_same_file_in_component(self): | |
ensure_schemas() | |
@@ -625,7 +625,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
data = { | |
('q_' + selected['name']): { | |
'value': fake.word(), | |
- 'extra': [{ | |
+ 'extra': { | |
'sha256': selected['extra']['hashes']['sha256'], | |
'viewUrl': '/project/{0}/files/osfstorage{1}'.format( | |
child._id, | |
@@ -633,7 +633,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
), | |
'selectedFileName': selected['name'], | |
'nodeId': child._id | |
- }] | |
+ } | |
} | |
} | |
@@ -643,7 +643,7 @@ class TestArchiverTasks(ArchiverTestCase): | |
archive_success(registration._id, job._id) | |
child_reg = registration.nodes[0] | |
for key, question in registration.registered_meta[prereg_schema._id].items(): | |
- assert_in(child_reg._id, question['extra'][0]['viewUrl']) | |
+ assert_in(child_reg._id, question['extra']['viewUrl']) | |
class TestArchiverUtils(ArchiverTestCase): | |
diff --git a/tests/test_views.py b/tests/test_views.py | |
index b33a959..8a4443a 100644 | |
--- a/tests/test_views.py | |
+++ b/tests/test_views.py | |
@@ -1771,7 +1771,8 @@ class TestUserAccount(OsfTestCase): | |
self.user.reload() | |
assert_false(self.user.check_password(new_password)) | |
assert_true(mock_push_status_message.called) | |
- assert_in(error_message, mock_push_status_message.mock_calls[0][1][0]) | |
+ error_strings = [e[1][0] for e in mock_push_status_message.mock_calls] | |
+ assert_in(error_message, error_strings) | |
def test_password_change_invalid_old_password(self): | |
self.test_password_change_invalid( | |
diff --git a/webpack.common.config.js b/webpack.common.config.js | |
index c18a945..f3631ea 100644 | |
--- a/webpack.common.config.js | |
+++ b/webpack.common.config.js | |
@@ -65,7 +65,6 @@ var entry = { | |
// Vendor libraries | |
'knockout', | |
'knockout.validation', | |
- 'knockout.punches', | |
'moment', | |
'bootstrap', | |
'bootbox', | |
diff --git a/website/addons/badges/routes.py b/website/addons/badges/routes.py | |
index e9b090f..658ae2c 100644 | |
--- a/website/addons/badges/routes.py | |
+++ b/website/addons/badges/routes.py | |
@@ -6,10 +6,15 @@ from . import views | |
render_routes = { | |
'rules': [ | |
- Rule([ | |
- '/project/<pid>/badges/', | |
- '/project/<pid>/node/<nid>/badges/', | |
- ], 'get', views.render.badges_page, OsfWebRenderer('../addons/badges/templates/badges_page.mako')), | |
+ Rule( | |
+ [ | |
+ '/project/<pid>/badges/', | |
+ '/project/<pid>/node/<nid>/badges/', | |
+ ], | |
+ 'get', | |
+ views.render.badges_page, | |
+ OsfWebRenderer('../addons/badges/templates/badges_page.mako', trust=False) | |
+ ), | |
], | |
} | |
@@ -45,7 +50,10 @@ guid_urls = { | |
'rules': [ | |
Rule( | |
'/badge/<bid>/', | |
- 'get', views.render.view_badge, OsfWebRenderer('../addons/badges/templates/view_badge.mako')), | |
+ 'get', | |
+ views.render.view_badge, | |
+ OsfWebRenderer('../addons/badges/templates/view_badge.mako', trust=False) | |
+ ), | |
Rule( | |
'/badge/<bid>/json/', | |
'get', views.openbadge.get_badge_json, json_renderer), | |
diff --git a/website/addons/badges/templates/badges_page.mako b/website/addons/badges/templates/badges_page.mako | |
index 8a37d21..2fe2d21 100644 | |
--- a/website/addons/badges/templates/badges_page.mako | |
+++ b/website/addons/badges/templates/badges_page.mako | |
@@ -5,12 +5,12 @@ | |
%for assertion in reversed(assertions): | |
<div class="row well well-sm assertion"> | |
<div class="col-md-2"> | |
- <img class="open-badge" badge-url="/badge/assertion/json/${assertion._id}/" src="${assertion.badge.image}" width="150px" height="150px" class="pull-left"> | |
+ <img class="open-badge pull-left" badge-url="/badge/assertion/json/${assertion._id}/" src="${assertion.badge.image}" width="150px" height="150px"> | |
</div> | |
<div class="col-md-8"> | |
<h4> <a href="/${assertion.badge._id}/">${assertion.badge.name}</a><small> ${assertion.badge.description}</small></h4> | |
- <p>${assertion.badge.criteria_list}</p> | |
+ <p>${assertion.badge.criteria_list}</p> | |
</div> | |
<div class="col-md-2 assertion-dates"> | |
diff --git a/website/addons/badges/templates/badges_user_settings.mako b/website/addons/badges/templates/badges_user_settings.mako | |
index 6f7fdaf..9e310d8 100644 | |
--- a/website/addons/badges/templates/badges_user_settings.mako | |
+++ b/website/addons/badges/templates/badges_user_settings.mako | |
@@ -96,6 +96,7 @@ You have not created any Badges. | |
//todo fetch url from server | |
var jsonToList = function(badge, id) { | |
+ // TODO: Rewrite this substantially before this feature is re-activated | |
return '<li class="media">' + | |
'<a class="pull-left" href="/' + id + '/">' + | |
'<img class="media-object" src="' + badge['imageurl'] + '" width="64px" height="64px"> </a>' + | |
diff --git a/website/addons/badges/templates/dashboard_assertions.mako b/website/addons/badges/templates/dashboard_assertions.mako | |
index 169626c..54739a1 100644 | |
--- a/website/addons/badges/templates/dashboard_assertions.mako | |
+++ b/website/addons/badges/templates/dashboard_assertions.mako | |
@@ -6,13 +6,13 @@ $script.ready('hgrid', function() { | |
%for assertion in assertions: | |
%if not assertion.revoked: | |
{ | |
- date: '${assertion.issued_date}', | |
- badge: '${assertion.badge.name}', | |
- badge_id: '${assertion.badge._id}', | |
- project: '${assertion.node.title}', | |
- project_id:'${assertion.node._id}', | |
- assertion_id: '${assertion._id}', | |
- node_api_url: '${assertion.node.api_url}' | |
+ date: ${ assertion.issued_date | sjson, n }, | |
+ badge: ${ assertion.badge.name | sjson, n }, | |
+ badge_id: ${ assertion.badge._id | sjson, n }, | |
+ project: ${ assertion.node.title | sjson, n }, | |
+ project_id: ${ assertion.node._id | sjson, n }, | |
+ assertion_id: ${ assertion._id | sjson, n }, | |
+ node_api_url: ${ assertion.node.api_url | sjson, n } | |
}, | |
%endif | |
%endfor | |
diff --git a/website/addons/badges/templates/dashboard_badges.mako b/website/addons/badges/templates/dashboard_badges.mako | |
index 705a4ca..b167212 100644 | |
--- a/website/addons/badges/templates/dashboard_badges.mako | |
+++ b/website/addons/badges/templates/dashboard_badges.mako | |
@@ -23,6 +23,7 @@ You have not created any Badges. | |
$(document).ready(function() { | |
$('#newBadge').click(function(){ | |
bootbox.dialog({ | |
+ // TODO: Rewrite substantially before reactivating addon | |
message: '<form id="badgeForm">' + | |
'<input type="text" class="form-control" name="badgeName" placeholder="Badge Name"><br />' + | |
'<input type="text" class="form-control" name="description" placeholder="Description"><br />' + | |
diff --git a/website/addons/badges/templates/view_badge.mako b/website/addons/badges/templates/view_badge.mako | |
index d226f5a..e727225 100644 | |
--- a/website/addons/badges/templates/view_badge.mako | |
+++ b/website/addons/badges/templates/view_badge.mako | |
@@ -6,7 +6,7 @@ | |
<div class="media well"> | |
%if badge.is_system_badge: | |
- <span class="pull-right" style="text-align:end;">System Badge | |
+ <span class="pull-right" style="text-align:end;">System Badge | |
%else: | |
<span class="pull-right" style="text-align:end;">Endorsed by <a href="${badge.creator.owner.profile_url}">${badge.creator.owner.fullname}</a> | |
%endif | |
@@ -32,7 +32,7 @@ | |
<script src="/static/vendor/dropzone/dropzone.js"></script> --> | |
<script> | |
$script.ready('hgrid', function() { | |
- | |
+ // TODO: Rewrite substantially before reactivating addon | |
var data = [ | |
%for assertion in assertions: | |
%if not assertion.revoked: | |
diff --git a/website/addons/base/__init__.py b/website/addons/base/__init__.py | |
index 3423d7f..00409a3 100644 | |
--- a/website/addons/base/__init__.py | |
+++ b/website/addons/base/__init__.py | |
@@ -1,24 +1,26 @@ | |
# -*- coding: utf-8 -*- | |
-import os | |
import glob | |
import importlib | |
import mimetypes | |
-from bson import ObjectId | |
-from modularodm import fields | |
-from mako.lookup import TemplateLookup | |
+import os | |
from time import sleep | |
+from bson import ObjectId | |
+from mako.lookup import TemplateLookup | |
+import markupsafe | |
import requests | |
+ | |
+from modularodm import fields | |
from modularodm import Q | |
+from framework.auth import Auth | |
from framework.auth.decorators import must_be_logged_in | |
-from framework.mongo import StoredObject | |
-from framework.routing import process_rules | |
from framework.exceptions import ( | |
PermissionsError, | |
HTTPError, | |
) | |
-from framework.auth import Auth | |
+from framework.mongo import StoredObject | |
+from framework.routing import process_rules | |
from website import settings | |
from website.addons.base import serializer, logger | |
@@ -944,9 +946,9 @@ class AddonOAuthNodeSettingsBase(AddonNodeSettingsBase): | |
u'by {user}, authentication information has been deleted.' | |
).format( | |
addon=self.config.full_name, | |
- category=node.category_display, | |
- title=node.title, | |
- user=removed.fullname | |
+ category=markupsafe.escape(node.category_display), | |
+ title=markupsafe.escape(node.title), | |
+ user=markupsafe.escape(removed.fullname) | |
) | |
if not auth or auth.user != removed: | |
diff --git a/website/addons/box/templates/log_templates.mako b/website/addons/box/templates/log_templates.mako | |
index 21fc126..728b61e 100644 | |
--- a/website/addons/box/templates/log_templates.mako | |
+++ b/website/addons/box/templates/log_templates.mako | |
@@ -1,60 +1,55 @@ | |
<script type="text/html" id="box_file_added"> | |
added file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> | |
- {{ stripSlash(params.path) }}</a> to | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> to | |
Box in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="box_folder_created"> | |
created folder | |
-<span class="overflow log-folder">{{ stripSlash(params.path) }}</span> in | |
+<span class="overflow log-folder" data-bind="text: stripSlash(params.path)"></span> in | |
Box in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="box_file_updated"> | |
updated file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> | |
- {{ stripSlash(params.path) }}</a> to | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> to | |
Box in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="box_file_removed"> | |
-removed {{ pathType(params.path) }} <span class="overflow"> | |
- {{ stripSlash(params.path) }}</span> from | |
+removed <span data-bind="text: pathType(params.path) "></span> <span class="overflow" data-bind="text: stripSlash(params.path)"></span> from | |
Box in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="box_folder_selected"> | |
linked Box folder | |
-<span class="overflow"> | |
- {{ params.folder === 'All Files' ? '/ (Full Box)' : (params.folder || '').replace('All Files','')}} | |
-</span> to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<span class="overflow" data-bind="text: (params.folder === 'All Files' ? '/ (Full Box)' : (params.folder || '').replace('All Files',''))"></span> to | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="box_node_deauthorized"> | |
deauthorized the Box addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="box_node_authorized"> | |
authorized the Box addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="box_node_deauthorized_no_user"> | |
Box addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
diff --git a/website/addons/dataverse/README.md b/website/addons/dataverse/README.md | |
index bf796cb..32e5488 100644 | |
--- a/website/addons/dataverse/README.md | |
+++ b/website/addons/dataverse/README.md | |
@@ -14,7 +14,7 @@ $ invoke encryption | |
Creating a Dataverse dataset on the test server | |
-1. Go to http://dvn-demo.iq.harvard.edu/ and create an account | |
+1. Go to https://demo.dataverse.org/ and create an account | |
2. On the homepage, click the "Create Dataverse" button to create a Dataverse | |
3. Click the options icon on the Dataverse page | |
4. On the Settings tab, set "Dataverse Publish Settings" to "Published" and save changes. | |
@@ -40,4 +40,4 @@ Notes on privacy settings: | |
- Only the most-recently published version of the dataset is accessible. | |
- If there are no published files, the dataset is not displayed. | |
- Files from the published version can be viewed or downloaded. | |
- - For non-contributors, when a node is private, there is no access to the Dataverse add-on. | |
\ No newline at end of file | |
+ - For non-contributors, when a node is private, there is no access to the Dataverse add-on. | |
diff --git a/website/addons/dataverse/client.py b/website/addons/dataverse/client.py | |
index 4fbbec9..8d6ac4e 100644 | |
--- a/website/addons/dataverse/client.py | |
+++ b/website/addons/dataverse/client.py | |
@@ -63,14 +63,12 @@ def publish_dataset(dataset): | |
if dataset.get_state() == 'RELEASED': | |
raise HTTPError(http.CONFLICT, data=dict( | |
message_short='Dataset conflict', | |
- message_long='This version of the dataset has already been ' | |
- 'published.' | |
+ message_long='This version of the dataset has already been published.' | |
)) | |
if not dataset.dataverse.is_published: | |
raise HTTPError(http.METHOD_NOT_ALLOWED, data=dict( | |
message_short='Method not allowed', | |
- message_long='A dataset cannot be published until its parent ' | |
- 'Dataverse is published.' | |
+ message_long='A dataset cannot be published until its parent Dataverse is published.' | |
)) | |
try: | |
@@ -93,8 +91,7 @@ def get_dataset(dataverse, doi): | |
if dataset and dataset.get_state() == 'DEACCESSIONED': | |
raise HTTPError(http.GONE, data=dict( | |
message_short='Dataset deaccessioned', | |
- message_long='This dataset has been deaccessioned and can no ' | |
- 'longer be linked to the OSF.' | |
+ message_long='This dataset has been deaccessioned and can no longer be linked to the OSF.' | |
)) | |
return dataset | |
except UnicodeDecodeError: | |
diff --git a/website/addons/dataverse/model.py b/website/addons/dataverse/model.py | |
index 2b10c1d..ffd8546 100644 | |
--- a/website/addons/dataverse/model.py | |
+++ b/website/addons/dataverse/model.py | |
@@ -173,6 +173,7 @@ class AddonDataverseNodeSettings(StorageAddonBase, AddonOAuthNodeSettingsBase): | |
) | |
##### Callback overrides ##### | |
+ | |
def after_delete(self, node, user): | |
self.deauthorize(Auth(user=user), add_log=True) | |
self.save() | |
diff --git a/website/addons/dataverse/routes.py b/website/addons/dataverse/routes.py | |
index 519e6b0..7c57ecb 100644 | |
--- a/website/addons/dataverse/routes.py | |
+++ b/website/addons/dataverse/routes.py | |
@@ -93,7 +93,7 @@ api_routes = { | |
], | |
'get', | |
views.dataverse_widget, | |
- OsfWebRenderer('../addons/dataverse/templates/dataverse_widget.mako'), | |
+ OsfWebRenderer('../addons/dataverse/templates/dataverse_widget.mako', trust=False), | |
), | |
Rule( | |
[ | |
diff --git a/website/addons/dataverse/static/dataverseNodeConfig.js b/website/addons/dataverse/static/dataverseNodeConfig.js | |
index 44c6288..9819862 100644 | |
--- a/website/addons/dataverse/static/dataverseNodeConfig.js | |
+++ b/website/addons/dataverse/static/dataverseNodeConfig.js | |
@@ -5,14 +5,12 @@ | |
var ko = require('knockout'); | |
var bootbox = require('bootbox'); | |
-require('knockout.punches'); | |
var Raven = require('raven-js'); | |
var $osf = require('js/osfHelpers'); | |
var $modal = $('#dataverseInputCredentials'); | |
-ko.punches.enableAll(); | |
function ViewModel(url) { | |
var self = this; | |
@@ -118,7 +116,7 @@ function ViewModel(url) { | |
}), | |
setInfoSuccess: ko.pureComputed(function() { | |
var filesUrl = window.contextVars.node.urls.web + 'files/'; | |
- return 'Successfully linked dataset \'' + self.savedDatasetTitle() + '\'. Go to the <a href="' + | |
+ return 'Successfully linked dataset \'' + $osf.htmlEscape(self.savedDatasetTitle()) + '\'. Go to the <a href="' + | |
filesUrl + '">Files page</a> to view your content.'; | |
}), | |
setDatasetError: ko.pureComputed(function() { | |
diff --git a/website/addons/dataverse/static/dataverseUserConfig.js b/website/addons/dataverse/static/dataverseUserConfig.js | |
index e4b78c7..873ce02 100644 | |
--- a/website/addons/dataverse/static/dataverseUserConfig.js | |
+++ b/website/addons/dataverse/static/dataverseUserConfig.js | |
@@ -4,8 +4,6 @@ | |
*/ | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
-ko.punches.enableAll(); | |
var $ = require('jquery'); | |
var Raven = require('raven-js'); | |
var bootbox = require('bootbox'); | |
diff --git a/website/addons/dataverse/static/dataverseWidget.js b/website/addons/dataverse/static/dataverseWidget.js | |
index 486c1ad..5c46e0f 100644 | |
--- a/website/addons/dataverse/static/dataverseWidget.js | |
+++ b/website/addons/dataverse/static/dataverseWidget.js | |
@@ -1,10 +1,7 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
-ko.punches.enableAll(); | |
- | |
var $osf = require('js/osfHelpers'); | |
var language = require('js/osfLanguage').Addons.dataverse; | |
diff --git a/website/addons/dataverse/templates/dataverse_credentials_modal.mako b/website/addons/dataverse/templates/dataverse_credentials_modal.mako | |
index b07f357..71e9b6d 100644 | |
--- a/website/addons/dataverse/templates/dataverse_credentials_modal.mako | |
+++ b/website/addons/dataverse/templates/dataverse_credentials_modal.mako | |
@@ -45,7 +45,7 @@ | |
<label for="apiToken"> | |
API Token | |
<!-- Link to API token generation page --> | |
- <a href="{{tokenUrl}}" | |
+ <a data-bind="attr: {href: tokenUrl}" | |
target="_blank" class="text-muted addon-external-link"> | |
(Get from Dataverse <i class="fa fa-external-link-square"></i>) | |
</a> | |
diff --git a/website/addons/dataverse/templates/dataverse_node_settings.mako b/website/addons/dataverse/templates/dataverse_node_settings.mako | |
index ac49a99..9d9f7d5 100644 | |
--- a/website/addons/dataverse/templates/dataverse_node_settings.mako | |
+++ b/website/addons/dataverse/templates/dataverse_node_settings.mako | |
@@ -4,14 +4,12 @@ | |
<%include file="dataverse_credentials_modal.mako"/> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src=${addon_icon_url}></img> | |
+ <img class="addon-icon" src=${addon_icon_url}> | |
${addon_full_name} | |
<small class="authorized-by"> | |
<span data-bind="if: nodeHasAuth"> | |
- authorized by <a data-bind="attr.href: urls().owner"> | |
- {{ownerName}} | |
- </a> | |
+ authorized by <a data-bind="attr: {href: urls().owner}, text: ownerName"></a> | |
% if not is_registration: | |
<a data-bind="click: deauthorize" | |
class="text-danger pull-right addon-auth">Disconnect Account</a> | |
@@ -43,7 +41,7 @@ | |
<!-- The linked Dataverse Host --> | |
<p class="break-word"> | |
<strong>Dataverse Repository:</strong> | |
- <a data-bind="attr.href: savedHostUrl()">{{ savedHost }}</a> | |
+ <a data-bind="attr: {href: savedHostUrl()}, text: savedHost"></a> | |
</p> | |
<!-- The linked dataset --> | |
@@ -51,8 +49,8 @@ | |
<strong>Current Dataset:</strong> | |
<span data-bind="ifnot: submitting"> | |
<span data-bind="if: showLinkedDataset"> | |
- <a data-bind="attr.href: savedDatasetUrl()"> {{ savedDatasetTitle }}</a> on | |
- <a data-bind="attr.href: savedDataverseUrl()"> {{ savedDataverseTitle }} Dataverse</a>. | |
+ <a data-bind="attr: {href: savedDatasetUrl()}, text: savedDatasetTitle"></a> on | |
+ <a data-bind="attr: {href: savedDataverseUrl()}, text: savedDataverseTitle || '' + Dataverse"></a>. | |
</span> | |
<span data-bind="ifnot: showLinkedDataset" class="text-muted"> | |
None | |
@@ -128,6 +126,6 @@ | |
</div> | |
<!-- Flashed Messages --> | |
<div class="help-block"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
</div> | |
diff --git a/website/addons/dataverse/templates/dataverse_user_settings.mako b/website/addons/dataverse/templates/dataverse_user_settings.mako | |
index f0c2df4..dd86481 100644 | |
--- a/website/addons/dataverse/templates/dataverse_user_settings.mako | |
+++ b/website/addons/dataverse/templates/dataverse_user_settings.mako | |
@@ -6,8 +6,8 @@ | |
<%include file="dataverse_credentials_modal.mako"/> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src=${addon_icon_url}></img> | |
- {{ properName }} | |
+ <img class="addon-icon" src=${addon_icon_url}> | |
+ <span data-bind="text: properName"></span> <!-- TODO: Can we use mako addon_full_name as some other addons do? --> | |
<small> | |
<a href="#dataverseInputCredentials" data-toggle="modal" class="pull-right text-primary">Connect Account</a> | |
</small> | |
@@ -20,14 +20,14 @@ | |
<table class="table table-hover"> | |
<thead> | |
<tr class="user-settings-addon-auth"> | |
- <th class="text-muted default-authorized-by">Authorized on <a data-bind="attr.href: dataverseUrl"><em>{{ dataverseHost }}</em></a></th> | |
+ <th class="text-muted default-authorized-by">Authorized on <a data-bind="attr: {href: dataverseUrl}"><em data-bind="text: dataverseHost"></em></a></th> | |
</tr> | |
</thead> | |
<!-- ko if: connectedNodes().length > 0 --> | |
<tbody data-bind="foreach: connectedNodes()"> | |
<tr> | |
<td class="authorized-nodes"> | |
- <!-- ko if: title --><a data-bind="attr.href: urls.view">{{ title }}</a><!-- /ko --> | |
+ <!-- ko if: title --><a data-bind="attr: {href: urls.view}, text: title"></a><!-- /ko --> | |
<!-- ko if: !title --><em>Private project</em><!-- /ko --> | |
</td> | |
<td> | |
diff --git a/website/addons/dataverse/templates/dataverse_widget.mako b/website/addons/dataverse/templates/dataverse_widget.mako | |
index e206eff..583f065 100644 | |
--- a/website/addons/dataverse/templates/dataverse_widget.mako | |
+++ b/website/addons/dataverse/templates/dataverse_widget.mako | |
@@ -10,16 +10,16 @@ | |
<dl class="dl-horizontal dl-dataverse" style="white-space: normal"> | |
<dt>Dataset</dt> | |
- <dd>{{ dataset }}</dd> | |
+ <dd data-bind="text: dataset"></dd> | |
<dt>Global ID</dt> | |
- <dd><a data-bind="attr: {href: datasetUrl}">{{ doi }}</a></dd> | |
+ <dd><a data-bind="attr: {href: datasetUrl}, text: doi"></a></dd> | |
<dt>Dataverse</dt> | |
- <dd><a data-bind="attr: {href: dataverseUrl}">{{ dataverse }} Dataverse</a></dd> | |
+ <dd><a data-bind="attr: {href: dataverseUrl}"><span data-bind="text: dataverse"></span> Dataverse</a></dd> | |
<dt>Citation</dt> | |
- <dd>{{ citation }}</dd> | |
+ <dd data-bind="text: citation"></dd> | |
</dl> | |
</span> | |
diff --git a/website/addons/dataverse/templates/log_templates.mako b/website/addons/dataverse/templates/log_templates.mako | |
index 8867f5a..76e0c7d 100644 | |
--- a/website/addons/dataverse/templates/log_templates.mako | |
+++ b/website/addons/dataverse/templates/log_templates.mako | |
@@ -1,61 +1,61 @@ | |
<script type="text/html" id="dataverse_file_added"> | |
added file | |
<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: params.filename"></a> to | |
-Dataverse dataset <span class="overflow">{{ params.dataset }}</span> | |
+Dataverse dataset <span class="overflow" data-bind="text: params.dataset"></span> | |
in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dataverse_file_removed"> | |
-removed file <span class="overflow">{{ params.filename }}</span> from | |
-Dataverse dataset <span class="overflow">{{ params.dataset }}</span> | |
+removed file <span class="overflow" data-bind="text: params.filename"></span> from | |
+Dataverse dataset <span class="overflow" data-bind="text: params.dataset"></span> | |
in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dataverse_dataset_linked"> | |
linked Dataverse dataset | |
-<span class="overflow">{{ params.dataset }}</span> to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<span class="overflow" data-bind="text: params.dataset"></span> to | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<!-- Legacy version --> | |
<script type="text/html" id="dataverse_study_linked"> | |
linked Dataverse dataset | |
-<span class="overflow">{{ params.study }}</span> to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<span class="overflow" data-bind="params.study"></span> to | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dataverse_dataset_published"> | |
published a new version of Dataverse dataset | |
-<span class="overflow">{{ params.dataset }}</span> to | |
+<span class="overflow" data-bind="text: params.dataset"></span> to | |
for | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<!-- Legacy version --> | |
<script type="text/html" id="dataverse_study_released"> | |
published a new version of Dataverse dataset | |
-<span class="overflow">{{ params.study }}</span> to | |
+<span class="overflow" data-bind="text: params.study"></span> to | |
for | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dataverse_node_authorized"> | |
authorized the Dataverse addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dataverse_node_deauthorized"> | |
deauthorized the Dataverse addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dataverse_node_deauthorized_no_user"> | |
Dataverse addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
diff --git a/website/addons/dropbox/model.py b/website/addons/dropbox/model.py | |
index c713a2e..bfbcc8e 100644 | |
--- a/website/addons/dropbox/model.py | |
+++ b/website/addons/dropbox/model.py | |
@@ -1,12 +1,14 @@ | |
# -*- coding: utf-8 -*- | |
-import os | |
import httplib as http | |
import logging | |
+import os | |
-from flask import request | |
-from modularodm import fields | |
from dropbox.client import DropboxOAuth2Flow, DropboxClient | |
from dropbox.rest import ErrorResponse | |
+from flask import request | |
+import markupsafe | |
+ | |
+from modularodm import fields | |
from framework.auth import Auth | |
from framework.exceptions import HTTPError | |
@@ -297,9 +299,9 @@ class DropboxNodeSettings(StorageAddonBase, AddonOAuthNodeSettingsBase): | |
u'Because the Dropbox add-on for {category} "{title}" was authenticated ' | |
u'by {user}, authentication information has been deleted.' | |
).format( | |
- category=node.category_display, | |
- title=node.title, | |
- user=removed.fullname | |
+ category=markupsafe.escape(node.category_display), | |
+ title=markupsafe.escape(node.title), | |
+ user=markupsafe.escape(removed.fullname) | |
) | |
if not auth or auth.user != removed: | |
diff --git a/website/addons/dropbox/templates/log_templates.mako b/website/addons/dropbox/templates/log_templates.mako | |
index d36493e..c135b45 100644 | |
--- a/website/addons/dropbox/templates/log_templates.mako | |
+++ b/website/addons/dropbox/templates/log_templates.mako | |
@@ -1,54 +1,54 @@ | |
<script type="text/html" id="dropbox_file_added"> | |
added file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect">{{ params.path }}</a> to | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: params.path"></a> to | |
Dropbox in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dropbox_folder_created"> | |
created folder | |
-<span class="overflow log-folder">{{ stripSlash(params.path) }}</span> in | |
+<span class="overflow log-folder" data-bind="text: stripSlash(params.path)"></span> in | |
Dropbox in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dropbox_file_updated"> | |
updated file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect">{{ params.path }}</a> to | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: params.path"></a> to | |
Dropbox in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dropbox_file_removed"> | |
-removed {{ pathType(params.path) }} <span class="overflow">{{ stripSlash(params.path) }}</span> from | |
+removed <span data-bind="text: pathType(params.path)"></span> <span class="overflow" data-bind="text: stripSlash(params.path)"></span> from | |
Dropbox in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dropbox_folder_selected"> | |
-linked Dropbox folder <span class="overflow">{{ params.folder === '/' ? '/ (Full Dropbox)' : params.folder }}</span> to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+linked Dropbox folder <span class="overflow" data-bind="text: params.folder === '/' ? '/ (Full Dropbox)' : params.folder"></span> to | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dropbox_node_deauthorized"> | |
deauthorized the Dropbox addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dropbox_node_authorized"> | |
authorized the Dropbox addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="dropbox_node_deauthorized_no_user"> | |
Dropbox addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
diff --git a/website/addons/dropbox/views.py b/website/addons/dropbox/views.py | |
index 008b576..39d227d 100644 | |
--- a/website/addons/dropbox/views.py | |
+++ b/website/addons/dropbox/views.py | |
@@ -42,13 +42,15 @@ def _get_folders(node_addon, folder_id): | |
}] | |
client = DropboxClient(node_addon.external_account.oauth_key) | |
- file_not_found = HTTPError(http.NOT_FOUND, data=dict(message_short='File not found', | |
- message_long='The Dropbox file ' | |
- 'you requested could not be found.')) | |
- | |
- max_retry_error = HTTPError(http.REQUEST_TIMEOUT, data=dict(message_short='Request Timeout', | |
- message_long='Dropbox could not be reached ' | |
- 'at this time.')) | |
+ file_not_found = HTTPError(http.NOT_FOUND, data={ | |
+ 'message_short': 'File not found', | |
+ 'message_long': 'The Dropbox file you requested could not be found.' | |
+ }) | |
+ | |
+ max_retry_error = HTTPError(http.REQUEST_TIMEOUT, data={ | |
+ 'message_short': 'Request Timeout', | |
+ 'message_long': 'Dropbox could not be reached at this time.' | |
+ }) | |
try: | |
metadata = client.metadata(folder_id) | |
diff --git a/website/addons/figshare/model.py b/website/addons/figshare/model.py | |
index 9972633..7ac9807 100644 | |
--- a/website/addons/figshare/model.py | |
+++ b/website/addons/figshare/model.py | |
@@ -1,5 +1,6 @@ | |
# -*- coding: utf-8 -*- | |
+import markupsafe | |
from modularodm import fields | |
from framework.auth.decorators import Auth | |
@@ -277,7 +278,8 @@ class AddonFigShareNodeSettings(StorageAddonBase, AddonNodeSettingsBase): | |
) | |
if article_permissions == 'private' and node_permissions == 'public': | |
message += messages.BEFORE_PAGE_LOAD_PUBLIC_NODE_PRIVATE_FS | |
- return [message] | |
+ # No HTML snippets, so escape message all at once | |
+ return [markupsafe.escape(message)] | |
def before_remove_contributor(self, node, removed): | |
""" | |
@@ -311,9 +313,9 @@ class AddonFigShareNodeSettings(StorageAddonBase, AddonNodeSettingsBase): | |
u'Because the FigShare add-on for {category} "{title}" was authenticated ' | |
u'by {user}, authentication information has been deleted.' | |
).format( | |
- category=node.category_display, | |
- title=node.title, | |
- user=removed.fullname | |
+ category=markupsafe.escape(node.category_display), | |
+ title=markupsafe.escape(node.title), | |
+ user=markupsafe.escape(removed.fullname) | |
) | |
if not auth or auth.user != removed: | |
diff --git a/website/addons/figshare/templates/figshare_user_settings.mako b/website/addons/figshare/templates/figshare_user_settings.mako | |
index b40715c..02c6ebd 100644 | |
--- a/website/addons/figshare/templates/figshare_user_settings.mako | |
+++ b/website/addons/figshare/templates/figshare_user_settings.mako | |
@@ -1,7 +1,7 @@ | |
<!-- Authorization --> | |
<div> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src="${addon_icon_url}"></img> | |
+ <img class="addon-icon" src="${addon_icon_url}"> | |
figshare | |
<small class="authorized-by"> | |
% if authorized: | |
diff --git a/website/addons/figshare/templates/log_templates.mako b/website/addons/figshare/templates/log_templates.mako | |
index 9526564..a028ea1 100644 | |
--- a/website/addons/figshare/templates/log_templates.mako | |
+++ b/website/addons/figshare/templates/log_templates.mako | |
@@ -6,9 +6,9 @@ figshare <span data-bind="text: params.figshare.title"></span> in | |
</script> | |
<script type="text/html" id="figshare_file_removed"> | |
-removed file <span class="overflow">{{ params.path }}</span> from | |
+removed file <span class="overflow" data-bind="text: params.path"></span> from | |
figshare in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="figshare_content_linked"> | |
@@ -24,18 +24,18 @@ unlinked figshare project /<span data-bind="text: params.figshare.title"></span> | |
<script type="text/html" id="figshare_node_authorized"> | |
authorized the figshare addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="figshare_node_deauthorized"> | |
deauthorized the figshare addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="figshare_node_deauthorized_no_user"> | |
figshare addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
diff --git a/website/addons/forward/routes.py b/website/addons/forward/routes.py | |
index 455cf47..6703f45 100644 | |
--- a/website/addons/forward/routes.py | |
+++ b/website/addons/forward/routes.py | |
@@ -36,7 +36,7 @@ api_routes = { | |
], | |
'get', | |
views.widget.forward_widget, | |
- OsfWebRenderer('../addons/forward/templates/forward_widget.mako'), | |
+ OsfWebRenderer('../addons/forward/templates/forward_widget.mako', trust=False), | |
) | |
], | |
diff --git a/website/addons/forward/static/forwardConfig.js b/website/addons/forward/static/forwardConfig.js | |
index ec20619..92fd2ac 100644 | |
--- a/website/addons/forward/static/forwardConfig.js | |
+++ b/website/addons/forward/static/forwardConfig.js | |
@@ -1,14 +1,11 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var Raven = require('raven-js'); | |
var koHelpers = require('js/koHelpers'); | |
var $osf = require('js/osfHelpers'); | |
-ko.punches.enableAll(); | |
- | |
var MESSAGE_TIMEOUT = 5000; | |
var MIN_FORWARD_TIME = 5; | |
var MAX_FORWARD_TIME = 60; | |
diff --git a/website/addons/forward/static/forwardWidget.js b/website/addons/forward/static/forwardWidget.js | |
index 1cbef3b..8049256 100644 | |
--- a/website/addons/forward/static/forwardWidget.js | |
+++ b/website/addons/forward/static/forwardWidget.js | |
@@ -1,12 +1,9 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var $osf = require('js/osfHelpers'); | |
-ko.punches.enableAll(); | |
- | |
/** | |
* Knockout view model for the Forward node settings widget. | |
*/ | |
diff --git a/website/addons/forward/templates/forward_node_settings.mako b/website/addons/forward/templates/forward_node_settings.mako | |
index e0eff5e..acc7fe1 100644 | |
--- a/website/addons/forward/templates/forward_node_settings.mako | |
+++ b/website/addons/forward/templates/forward_node_settings.mako | |
@@ -47,7 +47,7 @@ | |
<div class="row"> | |
<div class="col-md-10 overflow"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
<div class="col-md-2"> | |
<input type="submit" | |
diff --git a/website/addons/forward/templates/forward_widget.mako b/website/addons/forward/templates/forward_widget.mako | |
index aa383e1..ddff0f4 100644 | |
--- a/website/addons/forward/templates/forward_widget.mako | |
+++ b/website/addons/forward/templates/forward_widget.mako | |
@@ -6,10 +6,10 @@ | |
<div> | |
This project contains a forward to | |
- <a data-bind="attr.href: url">{{ url }}</a>. | |
+ <a data-bind="attr: {href: url}, text: url"></a>. | |
</div> | |
- <p>You will be automatically forwarded in {{ timeLeft }} seconds.</p> | |
+ <p>You will be automatically forwarded in <span data-bind="text: timeLeft"></span> seconds.</p> | |
<div class="spaced-buttons" data-bind="visible: redirecting"> | |
<a class="btn btn-default" data-bind="click: cancelRedirect">Cancel</a> | |
@@ -22,7 +22,7 @@ | |
<div> | |
This project contains a forward to | |
- <a data-bind="attr.href: url" target="_blank">{{ linkDisplay }}</a>. | |
+ <a data-bind="attr: {href: url}, text: linkDisplay" target="_blank"></a>. | |
</div> | |
<div class="spaced-buttons m-t-sm"> | |
diff --git a/website/addons/forward/templates/log_templates.mako b/website/addons/forward/templates/log_templates.mako | |
index 2c1003a..8c27b13 100644 | |
--- a/website/addons/forward/templates/log_templates.mako | |
+++ b/website/addons/forward/templates/log_templates.mako | |
@@ -1,6 +1,6 @@ | |
<script type="text/html" id="forward_url_changed"> | |
changed forward URL to | |
-<a target="_blank" class="overflow log-file-link" data-bind="attr.href: params.forward_url">{{ params.forward_url }}</a> | |
+<a target="_blank" class="overflow log-file-link" data-bind="attr: {href: params.forward_url}, text: params.forward_url"></a> | |
in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
diff --git a/website/addons/github/model.py b/website/addons/github/model.py | |
index e9d65bb..b9ec7e9 100644 | |
--- a/website/addons/github/model.py | |
+++ b/website/addons/github/model.py | |
@@ -1,10 +1,11 @@ | |
# -*- coding: utf-8 -*- | |
+import itertools | |
import os | |
import urlparse | |
-import itertools | |
from github3 import GitHubError | |
+import markupsafe | |
from modularodm import fields | |
from framework.auth import Auth | |
@@ -301,11 +302,11 @@ class GitHubNodeSettings(StorageAddonBase, AddonOAuthNodeSettingsBase): | |
message = ( | |
'Warning: This OSF {category} is {node_perm}, but the GitHub ' | |
'repo {user} / {repo} is {repo_perm}.'.format( | |
- category=node.project_or_component, | |
- node_perm=node_permissions, | |
- repo_perm=repo_permissions, | |
- user=self.user, | |
- repo=self.repo, | |
+ category=markupsafe.escape(node.project_or_component), | |
+ node_perm=markupsafe.escape(node_permissions), | |
+ repo_perm=markupsafe.escape(repo_permissions), | |
+ user=markupsafe.escape(self.user), | |
+ repo=markupsafe.escape(self.repo), | |
) | |
) | |
if repo_permissions == 'private': | |
@@ -362,9 +363,9 @@ class GitHubNodeSettings(StorageAddonBase, AddonOAuthNodeSettingsBase): | |
u'Because the GitHub add-on for {category} "{title}" was authenticated ' | |
u'by {user}, authentication information has been deleted.' | |
).format( | |
- category=node.category_display, | |
- title=node.title, | |
- user=removed.fullname | |
+ category=markupsafe.escape(node.category_display), | |
+ title=markupsafe.escape(node.title), | |
+ user=markupsafe.escape(removed.fullname) | |
) | |
if not auth or auth.user != removed: | |
diff --git a/website/addons/github/templates/github_node_settings.mako b/website/addons/github/templates/github_node_settings.mako | |
index 007cbf5..0c22360 100644 | |
--- a/website/addons/github/templates/github_node_settings.mako | |
+++ b/website/addons/github/templates/github_node_settings.mako | |
@@ -2,7 +2,7 @@ | |
<div> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src="${addon_icon_url}"></img> | |
+ <img class="addon-icon" src="${addon_icon_url}"> | |
GitHub | |
<small class="authorized-by"> | |
% if node_has_auth: | |
@@ -88,6 +88,9 @@ | |
<%def name="on_submit()"> | |
<script type="text/javascript"> | |
- window.contextVars = $.extend({}, window.contextVars, {'githubSettingsSelector': '#addonSettings${addon_short_name.capitalize()}'}); | |
+ window.contextVars = $.extend({}, window.contextVars, { | |
+ ## Short name never changes | |
+ 'githubSettingsSelector': '#addonSettings${addon_short_name.capitalize()}' | |
+ }); | |
</script> | |
</%def> | |
diff --git a/website/addons/github/templates/log_templates.mako b/website/addons/github/templates/log_templates.mako | |
index 10390ef..3a7d112 100644 | |
--- a/website/addons/github/templates/log_templates.mako | |
+++ b/website/addons/github/templates/log_templates.mako | |
@@ -9,11 +9,11 @@ GitHub repo | |
<script type="text/html" id="github_folder_created"> | |
created folder | |
-<span class="overflow log-folder">{{ stripSlash(params.path) }}</span> in | |
+<span class="overflow log-folder" data-bind="text: stripSlash(params.path)"></span> in | |
GitHub repo | |
<span data-bind="text: params.github.user"></span> / | |
<span data-bind="text: params.github.repo"></span> in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="github_file_updated"> | |
@@ -50,18 +50,18 @@ unlinked GitHub repo | |
<script type="text/html" id="github_node_authorized"> | |
authorized the GitHub addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="github_node_deauthorized"> | |
deauthorized the GitHub addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="github_node_deauthorized_no_user"> | |
GitHub addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
diff --git a/website/addons/googledrive/templates/log_templates.mako b/website/addons/googledrive/templates/log_templates.mako | |
index 1dfb182..ace8b38 100644 | |
--- a/website/addons/googledrive/templates/log_templates.mako | |
+++ b/website/addons/googledrive/templates/log_templates.mako | |
@@ -1,48 +1,48 @@ | |
<script type="text/html" id="googledrive_file_added"> | |
added file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect">{{ decodeURIComponent(params.path) }}</a> to | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: decodeURIComponent(params.path)"></a> to | |
Google Drive in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="googledrive_folder_created"> | |
created folder | |
-<span class="overflow log-folder">{{ stripSlash(decodeURIComponent(params.path)) }}</span> in | |
+<span class="overflow log-folder" data-bind="text: stripSlash(decodeURIComponent(params.path))"></span> in | |
Google Drive in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="googledrive_file_updated"> | |
updated file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect">{{ decodeURIComponent(params.path) }}</a> to | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: decodeURIComponent(params.path)"></a> to | |
Google Drive in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="googledrive_file_removed"> | |
-removed {{ pathType(params.path) }} <span class="overflow ">{{ stripSlash(decodeURIComponent(params.path)) }}</span> from | |
+removed <span data-bind="text: pathType(params.path) "></span> <span class="overflow" data-bind="text: stripSlash(decodeURIComponent(params.path))"></span> from | |
Google Drive in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="googledrive_folder_selected"> | |
-linked Google Drive folder /<span class="overflow">{{ params.folder === '/' ? '(Full Google Drive)' : decodeURIComponent(params.folder) }}</span> to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+linked Google Drive folder /<span class="overflow" data-bind="text: (params.folder === '/' ? '(Full Google Drive)' : decodeURIComponent(params.folder))"></span> to | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="googledrive_node_deauthorized"> | |
deauthorized the Google Drive addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="googledrive_node_deauthorized"> | |
Google Drive addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
@@ -50,12 +50,12 @@ Google Drive addon for | |
<script type="text/html" id="googledrive_node_authorized"> | |
authorized the Google Drive addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="googledrive_node_deauthorized_no_user"> | |
Google Drive addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
diff --git a/website/addons/mendeley/templates/log_templates.mako b/website/addons/mendeley/templates/log_templates.mako | |
index cb5f9fe..df4c38b 100644 | |
--- a/website/addons/mendeley/templates/log_templates.mako | |
+++ b/website/addons/mendeley/templates/log_templates.mako | |
@@ -1,16 +1,16 @@ | |
<script type="text/html" id="mendeley_folder_selected"> | |
-linked Mendeley folder /<span class="overflow">{{ params.folder_name }}</span> to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+linked Mendeley folder /<span class="overflow" data-bind="text: params.folder_name"></span> to | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="mendeley_node_deauthorized"> | |
deauthorized the Mendeley addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="mendeley_node_authorized"> | |
authorized the Mendeley addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
-</script> | |
\ No newline at end of file | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
+</script> | |
diff --git a/website/addons/osfstorage/templates/log_templates.mako b/website/addons/osfstorage/templates/log_templates.mako | |
index 681be83..4072b8f 100644 | |
--- a/website/addons/osfstorage/templates/log_templates.mako | |
+++ b/website/addons/osfstorage/templates/log_templates.mako | |
@@ -1,27 +1,25 @@ | |
<script type="text/html" id="osf_storage_file_added"> | |
added file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> | |
- {{ stripSlash(params.path) }}</a> to OSF Storage in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> to OSF Storage in | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="osf_storage_folder_created"> | |
created folder | |
-<span class="overflow log-folder">{{ stripSlash(params.path) }}</span> in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<span class="overflow log-folder" data-bind="text: stripSlash(params.path)"></span> in | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="osf_storage_file_updated"> | |
updated file | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> | |
- {{ stripSlash(params.path) }}</a> to OSF Storage in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> to OSF Storage in | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="osf_storage_file_removed"> | |
- removed {{ pathType(params.path) }} <span class="overflow"> | |
- {{ stripSlash(params.path) }}</span> from OSF Storage in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ removed <span data-bind="text: pathType(params.path)"></span> <span class="overflow" data-bind="stripSlash(params.path)"></span> | |
+ from OSF Storage in | |
+ <a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
diff --git a/website/addons/osfstorage/views.py b/website/addons/osfstorage/views.py | |
index 6017fa3..0e33891 100644 | |
--- a/website/addons/osfstorage/views.py | |
+++ b/website/addons/osfstorage/views.py | |
@@ -227,7 +227,7 @@ def osfstorage_download(file_node, payload, node_addon, **kwargs): | |
try: | |
version_id = int(request.args['version']) | |
except ValueError: | |
- raise make_error(httplib.BAD_REQUEST, 'Version must be an integer if not specified') | |
+ raise make_error(httplib.BAD_REQUEST, message_short='Version must be an integer if not specified') | |
version = file_node.get_version(version_id, required=True) | |
diff --git a/website/addons/s3/static/s3NodeConfig.js b/website/addons/s3/static/s3NodeConfig.js | |
index 01fda73..1cb52d9 100644 | |
--- a/website/addons/s3/static/s3NodeConfig.js | |
+++ b/website/addons/s3/static/s3NodeConfig.js | |
@@ -1,6 +1,5 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var bootbox = require('bootbox'); | |
var Raven = require('raven-js'); | |
@@ -11,8 +10,6 @@ var $modal = $('#s3InputCredentials'); | |
var s3Settings = require('json!./settings.json'); | |
-ko.punches.enableAll(); | |
- | |
var defaultSettings = { | |
url: '', | |
encryptUploads: s3Settings.encryptUploads, | |
diff --git a/website/addons/s3/static/s3UserConfig.js b/website/addons/s3/static/s3UserConfig.js | |
index ab81d7d..58963f3 100644 | |
--- a/website/addons/s3/static/s3UserConfig.js | |
+++ b/website/addons/s3/static/s3UserConfig.js | |
@@ -4,8 +4,6 @@ | |
*/ | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
-ko.punches.enableAll(); | |
var $ = require('jquery'); | |
var Raven = require('raven-js'); | |
var bootbox = require('bootbox'); | |
diff --git a/website/addons/s3/templates/log_templates.mako b/website/addons/s3/templates/log_templates.mako | |
index 54eb68e..0d2794b 100644 | |
--- a/website/addons/s3/templates/log_templates.mako | |
+++ b/website/addons/s3/templates/log_templates.mako | |
@@ -8,9 +8,9 @@ bucket | |
<script type="text/html" id="s3_folder_created"> | |
created folder | |
-<span class="overflow log-folder">{{ stripSlash(params.path) }}</span> in | |
-bucket {{ params.bucket }} in | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+<span class="overflow log-folder" data-bind="text: stripSlash(params.path)"></span> in | |
+bucket <span data-bind="text: params.bucket"></span> in | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="s3_file_updated"> | |
@@ -22,7 +22,7 @@ bucket | |
</script> | |
<script type="text/html" id="s3_file_removed"> | |
-removed {{ pathType(params.path) }} <span class="overflow">{{ stripSlash(params.path) }}</span> from | |
+removed <span data-bind="text: pathType(params.path)"></span> <span class="overflow" data-bind="text: stripSlash(params.path)"></span> from | |
bucket | |
<span data-bind="text: params.bucket"></span> in | |
<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
@@ -43,18 +43,18 @@ un-selected bucket | |
<script type="text/html" id="s3_node_authorized"> | |
authorized the Amazon S3 addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="s3_node_deauthorized"> | |
deauthorized the Amazon S3 addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="s3_node_deauthorized_no_user"> | |
Amazon S3 addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
deauthorized | |
</script> | |
diff --git a/website/addons/s3/templates/s3_node_settings.mako b/website/addons/s3/templates/s3_node_settings.mako | |
index b48b9bd..fdc993a 100644 | |
--- a/website/addons/s3/templates/s3_node_settings.mako | |
+++ b/website/addons/s3/templates/s3_node_settings.mako | |
@@ -4,13 +4,11 @@ | |
<%include file="s3_credentials_modal.mako"/> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src="${addon_icon_url}"></img> | |
+ <img class="addon-icon" src="${addon_icon_url}"> | |
Amazon S3 | |
<small class="authorized-by"> | |
<span data-bind="if: nodeHasAuth"> | |
- authorized by <a data-bind="attr.href: urls().owner"> | |
- {{ownerName}} | |
- </a> | |
+ authorized by <a data-bind="attr: {href: urls().owner}, text: ownerName"></a> | |
% if not is_registration: | |
<a data-bind="click: deauthorizeNode" class="text-danger pull-right addon-auth"> | |
Disconnect Account | |
@@ -37,15 +35,13 @@ | |
<span data-bind="ifnot: currentBucket"> | |
None | |
</span> | |
- <a data-bind="if: currentBucket, attr.href: urls().files"> | |
- {{currentBucket}} | |
- </a> | |
+ <a data-bind="if: currentBucket, attr: {href: urls().files}, text: currentBucket"></a> | |
</p> | |
- <div data-bind="attr.disabled: creating"> | |
+ <div data-bind="attr: {disabled: creating}"> | |
<button data-bind="visible: canChange, click: toggleSelect, | |
css: {active: showSelect}" class="btn btn-primary">Change</button> | |
<button data-bind="visible: showNewBucket, click: openCreateBucket, | |
- attr.disabled: creating" class="btn btn-success" id="newBucket">Create Bucket</button> | |
+ attr: {disabled: creating}" class="btn btn-success" id="newBucket">Create Bucket</button> | |
</div> | |
<br /> | |
<br /> | |
@@ -53,7 +49,7 @@ | |
<div class="form-group col-md-8"> | |
<select class="form-control" id="s3_bucket" name="s3_bucket" | |
data-bind="value: selectedBucket, | |
- attr.disabled: !loadedBucketList(), | |
+ attr: {disabled: !loadedBucketList()}, | |
options: bucketList"> </select> | |
</div> | |
## Remove comments to enable user toggling of file upload encryption | |
@@ -63,7 +59,7 @@ | |
## </div> | |
<div class="col-md-2"> | |
<button data-bind="click: selectBucket, | |
- attr.disabled: !allowSelectBucket(), | |
+ attr: {disabled: !allowSelectBucket()}, | |
text: saveButtonText" | |
class="btn btn-success"> | |
Save | |
@@ -75,6 +71,6 @@ | |
</div> | |
<!-- Flashed Messages --> | |
<div class="help-block"> | |
- <p data-bind="html: node_message, attr.class: messageClass"></p> | |
+ <p data-bind="html: node_message, attr: {class: messageClass}"></p> | |
</div> | |
</div> | |
diff --git a/website/addons/s3/templates/s3_user_settings.mako b/website/addons/s3/templates/s3_user_settings.mako | |
index e4b3731..27b21b5 100644 | |
--- a/website/addons/s3/templates/s3_user_settings.mako | |
+++ b/website/addons/s3/templates/s3_user_settings.mako | |
@@ -6,8 +6,8 @@ | |
<%include file="s3_credentials_modal.mako"/> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src=${addon_icon_url}></img> | |
- {{ properName }} | |
+ <img class="addon-icon" src=${addon_icon_url}> | |
+ <span data-bind="text: properName"></span> | |
<small> | |
<a href="#s3InputCredentials" data-toggle="modal" class="pull-right text-primary">Connect Account</a> | |
</small> | |
@@ -28,7 +28,7 @@ | |
<tbody data-bind="foreach: connectedNodes()"> | |
<tr> | |
<td class="authorized-nodes"> | |
- <!-- ko if: title --><a data-bind="attr.href: urls.view, text: title"></a><!-- /ko --> | |
+ <!-- ko if: title --><a data-bind="attr: {href: urls.view}, text: title"></a><!-- /ko --> | |
<!-- ko if: !title --><em>Private project</em><!-- /ko --> | |
</td> | |
<td> | |
diff --git a/website/addons/twofactor/templates/twofactor_user_settings.mako b/website/addons/twofactor/templates/twofactor_user_settings.mako | |
index 755196d..9e6a1fa 100644 | |
--- a/website/addons/twofactor/templates/twofactor_user_settings.mako | |
+++ b/website/addons/twofactor/templates/twofactor_user_settings.mako | |
@@ -37,7 +37,7 @@ | |
<input type="submit" value="Submit" class="btn btn-primary"> | |
</div> | |
<div class="help-block"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
</div> | |
</form> | |
diff --git a/website/addons/wiki/routes.py b/website/addons/wiki/routes.py | |
index a2974a6..d4c6fdd 100644 | |
--- a/website/addons/wiki/routes.py | |
+++ b/website/addons/wiki/routes.py | |
@@ -1,5 +1,5 @@ | |
""" | |
- | |
+Routes associated with the wiki page | |
""" | |
import os | |
@@ -32,43 +32,71 @@ page_routes = { | |
'rules': [ | |
# Home (Base) | GET | |
- Rule([ | |
- '/project/<pid>/wiki/', | |
- '/project/<pid>/node/<nid>/wiki/', | |
- ], 'get', views.project_wiki_home, OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'))), | |
+ Rule( | |
+ [ | |
+ '/project/<pid>/wiki/', | |
+ '/project/<pid>/node/<nid>/wiki/', | |
+ ], | |
+ 'get', | |
+ views.project_wiki_home, | |
+ OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'), trust=False) | |
+ ), | |
# View (Id) | GET | |
- Rule([ | |
- '/project/<pid>/wiki/id/<wid>/', | |
- '/project/<pid>/node/<nid>/wiki/id/<wid>/', | |
- ], 'get', views.project_wiki_id_page, OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'))), | |
+ Rule( | |
+ [ | |
+ '/project/<pid>/wiki/id/<wid>/', | |
+ '/project/<pid>/node/<nid>/wiki/id/<wid>/', | |
+ ], | |
+ 'get', | |
+ views.project_wiki_id_page, | |
+ OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'), trust=False) | |
+ ), | |
# Wiki | GET | |
- Rule([ | |
- '/project/<pid>/wiki/<wname>/', | |
- '/project/<pid>/node/<nid>/wiki/<wname>/', | |
- ], 'get', views.project_wiki_view, OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'))), | |
+ Rule( | |
+ [ | |
+ '/project/<pid>/wiki/<wname>/', | |
+ '/project/<pid>/node/<nid>/wiki/<wname>/', | |
+ ], | |
+ 'get', | |
+ views.project_wiki_view, | |
+ OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'), trust=False) | |
+ ), | |
# Edit | GET (legacy url, trigger redirect) | |
- Rule([ | |
- '/project/<pid>/wiki/<wname>/edit/', | |
- '/project/<pid>/node/<nid>/wiki/<wname>/edit/', | |
- ], 'get', views.project_wiki_edit, OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'))), | |
+ Rule( | |
+ [ | |
+ '/project/<pid>/wiki/<wname>/edit/', | |
+ '/project/<pid>/node/<nid>/wiki/<wname>/edit/', | |
+ ], | |
+ 'get', | |
+ views.project_wiki_edit, | |
+ OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'), trust=False) | |
+ ), | |
# Compare | GET (legacy url, trigger redirect) | |
- Rule([ | |
- '/project/<pid>/wiki/<wname>/compare/<int:wver>/', | |
- '/project/<pid>/node/<nid>/wiki/<wname>/compare/<int:wver>/', | |
- ], 'get', views.project_wiki_compare, OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'))), | |
+ Rule( | |
+ [ | |
+ '/project/<pid>/wiki/<wname>/compare/<int:wver>/', | |
+ '/project/<pid>/node/<nid>/wiki/<wname>/compare/<int:wver>/', | |
+ ], | |
+ 'get', | |
+ views.project_wiki_compare, | |
+ OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'), trust=False) | |
+ ), | |
# Edit | POST | |
- Rule([ | |
- '/project/<pid>/wiki/<wname>/', | |
- '/project/<pid>/node/<nid>/wiki/<wname>/', | |
- ], 'post', views.project_wiki_edit_post, OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'))), | |
- | |
+ Rule( | |
+ [ | |
+ '/project/<pid>/wiki/<wname>/', | |
+ '/project/<pid>/node/<nid>/wiki/<wname>/', | |
+ ], | |
+ 'post', | |
+ views.project_wiki_edit_post, | |
+ OsfWebRenderer(os.path.join(TEMPLATE_DIR, 'edit.mako'), trust=False) | |
+ ), | |
] | |
- | |
} | |
api_routes = { | |
diff --git a/website/addons/wiki/templates/add_wiki_page.mako b/website/addons/wiki/templates/add_wiki_page.mako | |
index 6024d2d..c3a0d80 100644 | |
--- a/website/addons/wiki/templates/add_wiki_page.mako | |
+++ b/website/addons/wiki/templates/add_wiki_page.mako | |
@@ -1,5 +1,3 @@ | |
-<%page expression_filter="h"/> | |
- | |
<!-- New Component Modal --> | |
<div class="modal fade" id="newWiki"> | |
<div class="modal-dialog"> | |
@@ -61,11 +59,11 @@ | |
var request = $.ajax({ | |
type: 'GET', | |
cache: false, | |
- url: '${urls['api']['base']}' + encodeURIComponent(wikiName) + '/validate/', | |
+ url: ${ urls['api']['base'] | sjson, n } + encodeURIComponent(wikiName) + '/validate/', | |
dataType: 'json' | |
}); | |
request.done(function (response) { | |
- window.location.href = '${urls['web']['base']}' + encodeURIComponent(wikiName) + '/edit/'; | |
+ window.location.href = ${ urls['web']['base'] | sjson, n } + encodeURIComponent(wikiName) + '/edit/'; | |
}); | |
request.fail(function (response, textStatus, error) { | |
if (response.status === 409) { | |
@@ -74,7 +72,7 @@ | |
else if (response.status === 403){ | |
$alert.text('You do not have permission to perform this action.'); | |
Raven.captureMessage('Unauthorized user can view wiki add button', { | |
- url: '${urls['api']['base']}' + encodeURIComponent(wikiName) + '/validate/', | |
+ url: ${ urls['api']['base'] | sjson, n } + encodeURIComponent(wikiName) + '/validate/', | |
textStatus: textStatus, | |
error: error | |
}); | |
@@ -82,7 +80,7 @@ | |
else { | |
$alert.text('Could not validate wiki page. Please try again.'+response.status); | |
Raven.captureMessage('Error occurred while validating page', { | |
- url: '${urls['api']['base']}' + encodeURIComponent(wikiName) + '/validate/', | |
+ url: ${ urls['api']['base'] | sjson, n } + encodeURIComponent(wikiName) + '/validate/', | |
textStatus: textStatus, | |
error: error | |
}); | |
diff --git a/website/addons/wiki/templates/delete_wiki_page.mako b/website/addons/wiki/templates/delete_wiki_page.mako | |
index 19024c2..5df58bd 100644 | |
--- a/website/addons/wiki/templates/delete_wiki_page.mako | |
+++ b/website/addons/wiki/templates/delete_wiki_page.mako | |
@@ -1,5 +1,3 @@ | |
-<%page expression_filter="h"/> | |
- | |
<!-- Delete Wiki Page Modal --> | |
<div class="modal fade" id="deleteWiki"> | |
<div class="modal-dialog"> | |
@@ -26,9 +24,9 @@ | |
window.contextVars.wiki.triggeredDelete = true; | |
$.ajax({ | |
type:'DELETE', | |
- url: '${urls['api']['delete']}', | |
+ url: ${ urls['api']['delete'] | sjson, n }, | |
success: function(response) { | |
- window.location.href = '${urls['web']['home']}'; | |
+ window.location.href = ${ urls['web']['home'] | sjson, n }; | |
} | |
}) | |
}); | |
diff --git a/website/addons/wiki/templates/edit.mako b/website/addons/wiki/templates/edit.mako | |
index e851d27..700849d 100644 | |
--- a/website/addons/wiki/templates/edit.mako | |
+++ b/website/addons/wiki/templates/edit.mako | |
@@ -1,6 +1,5 @@ | |
-<%page expression_filter="h"/> | |
<%inherit file="project/project_base.mako"/> | |
-<%def name="title()">${node['title'] | n} Wiki</%def> | |
+<%def name="title()">${node['title']} Wiki</%def> | |
<%def name="stylesheets()"> | |
${parent.stylesheets()} | |
@@ -25,10 +24,10 @@ | |
</div> | |
<div class="row wiki-wrapper"> | |
- <div class="panel-toggle col-sm-${'3' if 'menu' in panels_used else '1' | n}"> | |
+ <div class="panel-toggle col-sm-${'3' if 'menu' in panels_used else '1'}"> | |
<!-- Menu with toggle normal --> | |
- <div class="osf-panel panel panel-default reset-height ${'' if 'menu' in panels_used else 'hidden visible-xs' | n}" data-bind="css: { 'osf-panel-flex': !$root.singleVis() }"> | |
+ <div class="osf-panel panel panel-default reset-height ${'' if 'menu' in panels_used else 'hidden visible-xs'}" data-bind="css: { 'osf-panel-flex': !$root.singleVis() }"> | |
<div class="panel-heading wiki-panel-header clearfix" data-bind="css: { 'osf-panel-heading-flex': !$root.singleVis()}"> | |
% if user['can_edit']: | |
<div class="wiki-toolbar-icon text-success" data-toggle="modal" data-target="#newWiki"> | |
@@ -56,7 +55,7 @@ | |
</div> | |
<!-- Menu with toggle collapsed --> | |
- <div class="osf-panel panel panel-default panel-collapsed hidden-xs text-center ${'hidden' if 'menu' in panels_used else '' | n}" > | |
+ <div class="osf-panel panel panel-default panel-collapsed hidden-xs text-center ${'hidden' if 'menu' in panels_used else ''}" > | |
<div class="panel-heading pointer"> | |
<i class="fa fa-list"> </i> | |
<i class="fa fa-angle-right"> </i> | |
@@ -68,12 +67,12 @@ | |
</div> | |
<div class="wiki" id="wikiPageContext"> | |
- <div class="panel-expand col-sm-${'9' if 'menu' in panels_used else '11' | n}"> | |
+ <div class="panel-expand col-sm-${'9' if 'menu' in panels_used else '11'}"> | |
<div class="row"> | |
<div data-osf-panel="View" | |
- class="${'col-sm-{0}'.format(12 / num_columns) | n}" | |
- style="${'' if 'view' in panels_used else 'display: none' | n}"> | |
+ class="${'col-sm-{0}'.format(12 / num_columns)}" | |
+ style="${'' if 'view' in panels_used else 'display: none'}"> | |
<div class="osf-panel panel panel-default no-border" data-bind="css: { 'no-border reset-height': $root.singleVis() === 'view', 'osf-panel-flex': $root.singleVis() !== 'view' }"> | |
<div class="panel-heading wiki-panel-header wiki-single-heading" data-bind="css: { 'osf-panel-heading-flex': $root.singleVis() !== 'view', 'wiki-single-heading': $root.singleVis() === 'view' }"> | |
<div class="row"> | |
@@ -109,7 +108,7 @@ | |
<div id="wikiViewPanel" class="panel-body" data-bind="css: { 'osf-panel-body-flex': $root.singleVis() !== 'view' }"> | |
<div id="wikiViewRender" data-bind="html: renderedView, mathjaxify: renderedView, anchorScroll : { buffer: 50, elem : '#wikiViewPanel'}" class=" markdown-it-view"> | |
% if wiki_content: | |
- ${wiki_content | n} | |
+ ${wiki_content} | |
% else: | |
<p><em>No wiki content</em></p> | |
% endif | |
@@ -121,7 +120,7 @@ | |
% if user['can_edit_wiki_body']: | |
<div data-bind="with: $root.editVM.wikiEditor.viewModel" | |
data-osf-panel="Edit" | |
- class="${'col-sm-{0}'.format(12 / num_columns) | n}" | |
+ class="${'col-sm-{0}'.format(12 / num_columns)}" | |
style="${'' if 'edit' in panels_used else 'display: none' | n}"> | |
<form id="wiki-form" action="${urls['web']['edit']}" method="POST"> | |
<div class="osf-panel panel panel-default" data-bind="css: { 'no-border': $root.singleVis() === 'edit' }"> | |
@@ -132,8 +131,8 @@ | |
</div> | |
<div class="col-md-6"> | |
<div class="pull-right"> | |
- <div class="progress no-margin pointer " data-toggle="modal" data-bind="attr: {data-target: modalTarget}" > | |
- <div role="progressbar"data-bind="attr: progressBar"> | |
+ <div class="progress no-margin pointer " data-toggle="modal" data-bind="attr: {'data-target': modalTarget}" > | |
+ <div role="progressbar" data-bind="attr: progressBar"> | |
<span class="progress-bar-content p-h-sm"> | |
<span data-bind="text: statusDisplay"></span> | |
<span class="sharejs-info-btn"> | |
@@ -151,9 +150,9 @@ | |
<div class="row"> | |
<div class="col-xs-12"> | |
<div class="form-group wmd-panel"> | |
- <ul class="list-inline" class="pull-right"> | |
+ <ul class="list-inline pull-right"> | |
<!-- ko foreach: showCollaborators --> | |
- <!-- ko ifnot: id === '${user_id}' --> | |
+ <!-- ko ifnot: id === ${ user_id | sjson, n } --> | |
<li><a data-bind="attr: { href: url }" > | |
<img data-container="body" data-bind="attr: {src: gravatar}, tooltip: {title: name, placement: 'top'}" | |
style="border: 1px solid black;" width="30px" height="30px"> | |
@@ -196,8 +195,8 @@ | |
<div data-osf-panel="Compare" | |
- class="${'col-sm-{0}'.format(12 / num_columns) | n}" | |
- style="${'' if 'compare' in panels_used else 'display: none' | n}"> | |
+ class="${'col-sm-{0}'.format(12 / num_columns)}" | |
+ style="${'' if 'compare' in panels_used else 'display: none'}"> | |
<div class="osf-panel panel panel-default osf-panel-flex" data-bind="css: { 'no-border reset-height': $root.singleVis() === 'compare', 'osf-panel-flex': $root.singleVis() !== 'compare' }"> | |
<div class="panel-heading osf-panel-heading-flex" data-bind="css: { 'osf-panel-heading-flex': $root.singleVis() !== 'compare', 'wiki-single-heading': $root.singleVis() === 'compare'}"> | |
<div class="row"> | |
@@ -354,42 +353,39 @@ | |
</div> | |
<%def name="javascript_bottom()"> | |
-<% import json %> | |
${parent.javascript_bottom()} | |
<script> | |
var canEditBody = ${user['can_edit_wiki_body'] | sjson, n}; | |
var isContributor = ${user['can_edit'] | sjson, n}; | |
- var canEditPageName = isContributor && ${json.dumps( | |
- wiki_id and wiki_name != 'home' | |
- )}; | |
+ var canEditPageName = isContributor && ${(wiki_id and wiki_name != 'home') | sjson, n }; | |
window.contextVars = window.contextVars || {}; | |
window.contextVars.wiki = { | |
canEdit: canEditBody, | |
canEditPageName: canEditPageName, | |
- usePythonRender: ${json.dumps(use_python_render)}, | |
- versionSettings: ${json.dumps(version_settings) | n}, | |
- panelsUsed: ${json.dumps(panels_used) | n}, | |
- wikiID: '${wiki_id}', | |
- wikiName: '${wiki_name}', | |
+ usePythonRender: ${ use_python_render | sjson, n }, | |
+ versionSettings: ${ version_settings | sjson, n }, | |
+ panelsUsed: ${ panels_used | sjson, n }, | |
+ wikiID: ${ wiki_id | sjson, n }, | |
+ wikiName: ${wiki_name | sjson, n }, | |
urls: { | |
- draft: '${urls['api']['draft']}', | |
- content: '${urls['api']['content']}', | |
- rename: '${urls['api']['rename']}', | |
- grid: '${urls['api']['grid']}', | |
- page: '${urls['web']['page']}', | |
- base: '${urls['web']['base']}', | |
- sharejs: '${sharejs_url}' | |
+ draft: ${ urls['api']['draft'] | sjson, n }, | |
+ content: ${urls['api']['content'] | sjson, n }, | |
+ rename: ${urls['api']['rename'] | sjson, n }, | |
+ grid: ${urls['api']['grid'] | sjson, n }, | |
+ page: ${urls['web']['page'] | sjson, n }, | |
+ base: ${urls['web']['base'] | sjson, n }, | |
+ sharejs: ${ sharejs_url | sjson, n } | |
}, | |
metadata: { | |
registration: true, | |
- docId: '${sharejs_uuid}', | |
- userId: '${user_id}', | |
- userName: '${user_full_name}', | |
- userUrl: '${user_url}', | |
- userGravatar: '${urls['gravatar']}'.replace('&', '&') | |
+ docId: ${ sharejs_uuid | sjson, n }, | |
+ userId: ${user_id | sjson, n }, | |
+ userName: ${ user_full_name | sjson, n }, | |
+ userUrl: ${ user_url | sjson, n }, | |
+ userGravatar: ${ urls['gravatar'] | sjson, n }.replace('&', '&') | |
} | |
}; | |
diff --git a/website/addons/wiki/templates/nav.mako b/website/addons/wiki/templates/nav.mako | |
index a135290..f6251e9 100644 | |
--- a/website/addons/wiki/templates/nav.mako | |
+++ b/website/addons/wiki/templates/nav.mako | |
@@ -1,5 +1,3 @@ | |
-<%page expression_filter="h"/> | |
- | |
<nav class="wiki-nav"> | |
<div class="navbar-collapse text-center"> | |
<ul class="superlist nav navbar-nav" style="float: none"> | |
diff --git a/website/addons/wiki/templates/status.mako b/website/addons/wiki/templates/status.mako | |
index 6602c93..c43bf1b 100644 | |
--- a/website/addons/wiki/templates/status.mako | |
+++ b/website/addons/wiki/templates/status.mako | |
@@ -1,7 +1,3 @@ | |
-<%page expression_filter="h"/> | |
- | |
- | |
- | |
<h3 class="wiki-title wiki-title-xs" id="wikiName"> | |
% if wiki_name == 'home': | |
<i class="fa fa-home"></i> | |
diff --git a/website/addons/wiki/templates/wiki_widget.mako b/website/addons/wiki/templates/wiki_widget.mako | |
index 4cf1ef6..ceef125 100644 | |
--- a/website/addons/wiki/templates/wiki_widget.mako | |
+++ b/website/addons/wiki/templates/wiki_widget.mako | |
@@ -1,9 +1,8 @@ | |
<%inherit file="project/addon/widget.mako"/> | |
-<%page expression_filter="h"/> | |
<div id="markdownRender" class="break-word"> | |
% if wiki_content: | |
- ${wiki_content | n} | |
+ ${wiki_content} | |
% else: | |
<p><em>No wiki content</em></p> | |
% endif | |
@@ -15,13 +14,12 @@ | |
% endif | |
</div> | |
-<% import json %> | |
<script> | |
window.contextVars = $.extend(true, {}, window.contextVars, { | |
wikiWidget: true, | |
- usePythonRender: ${json.dumps(use_python_render)}, | |
+ usePythonRender: ${ use_python_render | sjson, n }, | |
urls: { | |
- wikiContent: '${wiki_content_url}' | |
+ wikiContent: ${wiki_content_url | sjson, n } | |
} | |
}) | |
</script> | |
diff --git a/website/addons/zotero/templates/log_templates.mako b/website/addons/zotero/templates/log_templates.mako | |
index f1c1900..bb65e14 100644 | |
--- a/website/addons/zotero/templates/log_templates.mako | |
+++ b/website/addons/zotero/templates/log_templates.mako | |
@@ -1,16 +1,16 @@ | |
<script type="text/html" id="zotero_folder_selected"> | |
-linked Zotero folder /<span class="overflow">{{ params.folder_name }}</span> to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+linked Zotero folder /<span class="overflow" data-bind="text: params.folder_name"></span> to | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="zotero_node_deauthorized"> | |
deauthorized the Zotero addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="zotero_node_authorized"> | |
authorized the Zotero addon for | |
<a class="log-node-title-link overflow" | |
- data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+ data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
diff --git a/website/conferences/views.py b/website/conferences/views.py | |
index 6790af9..8437b67 100644 | |
--- a/website/conferences/views.py | |
+++ b/website/conferences/views.py | |
@@ -1,6 +1,5 @@ | |
# -*- coding: utf-8 -*- | |
-import json | |
import httplib | |
import logging | |
from datetime import datetime | |
@@ -216,7 +215,7 @@ def conference_results(meeting): | |
data = conference_data(meeting) | |
return { | |
- 'data': json.dumps(data), | |
+ 'data': data, | |
'label': meeting, | |
'meeting': conf.to_storage(), | |
# Needed in order to use base.mako namespace | |
diff --git a/website/institutions/model.py b/website/institutions/model.py | |
index 2ce4694..d99cb13 100644 | |
--- a/website/institutions/model.py | |
+++ b/website/institutions/model.py | |
@@ -93,7 +93,7 @@ class Institution(object): | |
self.node.save() | |
@classmethod | |
- def find(cls, query=None, **kwargs): | |
+ def find(cls, query=None, deleted=False, **kwargs): | |
from website.models import Node # done to prevent import error | |
if query and getattr(query, 'nodes', False): | |
for node in query.nodes: | |
@@ -103,11 +103,12 @@ class Institution(object): | |
replacement_attr = cls.attribute_map.get(query.attribute, False) | |
query.attribute = replacement_attr or query.attribute | |
query = query & Q('institution_id', 'ne', None) if query else Q('institution_id', 'ne', None) | |
+ query = query & Q('is_deleted', 'ne', True) if not deleted else query | |
nodes = Node.find(query, allow_institution=True, **kwargs) | |
return InstitutionQuerySet(nodes) | |
@classmethod | |
- def find_one(cls, query=None, **kwargs): | |
+ def find_one(cls, query=None, deleted=False, **kwargs): | |
from website.models import Node | |
if query and getattr(query, 'nodes', False): | |
for node in query.nodes: | |
@@ -117,6 +118,7 @@ class Institution(object): | |
replacement_attr = cls.attribute_map.get(query.attribute, False) | |
query.attribute = replacement_attr if replacement_attr else query.attribute | |
query = query & Q('institution_id', 'ne', None) if query else Q('institution_id', 'ne', None) | |
+ query = query & Q('is_deleted', 'ne', True) if not deleted else query | |
node = Node.find_one(query, allow_institution=True, **kwargs) | |
return cls(node) | |
diff --git a/website/language.py b/website/language.py | |
index 1daaecc..0d1c3dc 100644 | |
--- a/website/language.py | |
+++ b/website/language.py | |
@@ -21,7 +21,7 @@ REGISTRATION_SUCCESS = '''Registration successful. Please check {email} to confi | |
# Shown if registration is turned off in website.settings | |
REGISTRATION_UNAVAILABLE = 'Registration currently unavailable.' | |
-ALREADY_REGISTERED = '''The email <em>{email}</em> has already been registered.''' | |
+ALREADY_REGISTERED = u'The email {email} has already been registered.' | |
AFTER_SUBMIT_FOR_REVIEW = "Your submission has been received. You will be notified within ten business days regarding the status of your submission. If you have questions you may contact us at [email protected]." | |
@@ -49,8 +49,8 @@ LOGOUT = ''' | |
You have successfully logged out. | |
''' | |
-EMAIL_NOT_FOUND = ''' | |
-<strong>{email}</strong> was not found in our records. | |
+EMAIL_NOT_FOUND = u''' | |
+{email} was not found in our records. | |
''' | |
# Shown after an unregistered user claims an account and is redirected to the | |
@@ -84,10 +84,10 @@ MERGE_COMPLETE = 'Accounts successfully merged.' | |
MERGE_CONFIRMATION_REQUIRED_SHORT = 'Confirmation Required: Merge Accounts' | |
MERGE_CONFIRMATION_REQUIRED_LONG = ( | |
- '<p>This email is confirmed to another account. ' | |
- 'Would you like to merge <em>{user_to_merge.username}</em> with the account ' | |
- '<em>{user.username}</em>?<p>' | |
- '<a class="btn btn-primary" href="?confirm_merge">Confirm merge</a> ' | |
+ u'<p>This email is confirmed to another account. ' | |
+ u'Would you like to merge <em>{src_user}</em> with the account ' | |
+ u'<em>{dest_user}</em>?<p>' | |
+ u'<a class="btn btn-primary" href="?confirm_merge">Confirm merge</a> ' | |
) | |
# Node Actions | |
@@ -98,17 +98,17 @@ AFTER_REGISTER_ARCHIVING = ( | |
) | |
BEFORE_REGISTER_HAS_POINTERS = ( | |
- 'This {category} contains links to other projects. Links will be copied ' | |
- 'into your registration, but the projects that they link to will not be ' | |
- 'registered. If you wish to register the linked projects, you must fork ' | |
- 'them from the original project before registering.' | |
+ u'This {category} contains links to other projects. Links will be copied ' | |
+ u'into your registration, but the projects that they link to will not be ' | |
+ u'registered. If you wish to register the linked projects, you must fork ' | |
+ u'them from the original project before registering.' | |
) | |
BEFORE_FORK_HAS_POINTERS = ( | |
- 'This {category} contains links to other projects. Links will be copied ' | |
- 'into your fork, but the projects that they link to will not be forked. ' | |
- 'If you wish to fork the linked projects, they need to be forked from the ' | |
- 'original project.' | |
+ u'This {category} contains links to other projects. Links will be copied ' | |
+ u'into your fork, but the projects that they link to will not be forked. ' | |
+ u'If you wish to fork the linked projects, they need to be forked from the ' | |
+ u'original project.' | |
) | |
REGISTRATION_INFO = ''' | |
@@ -186,11 +186,11 @@ TEMPLATED_FROM_PREFIX = "Templated from " | |
# MFR Error handling | |
ERROR_PREFIX = "Unable to render. <a href='?action=download'>Download</a> file to view it." | |
-SUPPORT = "Contact [email protected] for further assistance." | |
+SUPPORT = u"Contact [email protected] for further assistance." | |
-# Custom Error Messages w/ support | |
-STATA_VERSION_ERROR = 'Version of given Stata file is not 104, 105, 108, 113 (Stata 8/9), 114 (Stata 10/11) or 115 (Stata 12)<p>{0}</p>'.format(SUPPORT) | |
-BLANK_OR_CORRUPT_TABLE_ERROR = 'Is this a valid instance of this file type?<p>{0}</p>'.format(SUPPORT) | |
+# Custom Error Messages w/ support # TODO: Where are these used? | |
+STATA_VERSION_ERROR = u'Version of given Stata file is not 104, 105, 108, 113 (Stata 8/9), 114 (Stata 10/11) or 115 (Stata 12)<p>{0}</p>'.format(SUPPORT) | |
+BLANK_OR_CORRUPT_TABLE_ERROR = u'Is this a valid instance of this file type?<p>{0}</p>'.format(SUPPORT) | |
#disk saving mode | |
DISK_SAVING_MODE = 'Forks, registrations, and uploads to OSF Storage uploads are temporarily disabled while we are undergoing a server upgrade. These features will return shortly.' | |
diff --git a/website/mails/queued_mails.py b/website/mails/queued_mails.py | |
index f891822..80a84e6 100644 | |
--- a/website/mails/queued_mails.py | |
+++ b/website/mails/queued_mails.py | |
@@ -26,6 +26,15 @@ class QueuedMail(StoredObject): | |
data = fields.DictionaryField() | |
sent_at = fields.DateTimeField(index=True) | |
+ def __repr__(self): | |
+ if self.sent_at is not None: | |
+ return '<QueuedMail {} of type {} sent to {} at {}>'.format( | |
+ self._id, self.email_type, self.to_addr, self.sent_at | |
+ ) | |
+ return '<QueuedMail {} of type {} to be sent to {} on {}>'.format( | |
+ self._id, self.email_type, self.to_addr, self.send_at | |
+ ) | |
+ | |
def send_mail(self): | |
""" | |
Grabs the data from this email, checks for user subscription to help mails, | |
diff --git a/website/profile/views.py b/website/profile/views.py | |
index 7f12ab7..ffded34 100644 | |
--- a/website/profile/views.py | |
+++ b/website/profile/views.py | |
@@ -6,6 +6,7 @@ import httplib as http # TODO: Inconsistent usage of aliased import | |
from dateutil.parser import parse as parse_date | |
from flask import request | |
+import markupsafe | |
from modularodm.exceptions import ValidationError, NoResultsFound, MultipleResultsFound | |
from modularodm import Q | |
@@ -353,9 +354,10 @@ def user_account_password(auth, **kwargs): | |
user.change_password(old_password, new_password, confirm_password) | |
user.save() | |
except ChangePasswordError as error: | |
- push_status_message('<br />'.join(error.messages) + '.', kind='warning') | |
+ for m in error.messages: | |
+ push_status_message(m, kind='warning', trust=False) | |
else: | |
- push_status_message('Password updated successfully.', kind='success') | |
+ push_status_message('Password updated successfully.', kind='success', trust=False) | |
return redirect(web_url_for('user_account')) | |
@@ -813,9 +815,9 @@ def redirect_to_twitter(twitter_handle): | |
users = User.find(Q('social.twitter', 'iexact', twitter_handle)) | |
message_long = 'There are multiple OSF accounts associated with the ' \ | |
'Twitter handle: <strong>{0}</strong>. <br /> Please ' \ | |
- 'select from the accounts below. <br /><ul>'.format(twitter_handle) | |
+ 'select from the accounts below. <br /><ul>'.format(markupsafe.escape(twitter_handle)) | |
for user in users: | |
- message_long += '<li><a href="{0}">{1}</a></li>'.format(user.url, user.fullname) | |
+ message_long += '<li><a href="{0}">{1}</a></li>'.format(user.url, markupsafe.escape(user.fullname)) | |
message_long += '</ul>' | |
raise HTTPError(http.MULTIPLE_CHOICES, data={ | |
'message_short': 'Multiple Users Found', | |
diff --git a/website/project/model.py b/website/project/model.py | |
index 03e9ebf..1ed728e 100644 | |
--- a/website/project/model.py | |
+++ b/website/project/model.py | |
@@ -730,6 +730,13 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
('institution_email_domains', pymongo.ASCENDING), | |
] | |
}, | |
+ { | |
+ 'unique': False, | |
+ 'key_or_list': [ | |
+ ('institution_id', pymongo.ASCENDING), | |
+ ('registration_approval', pymongo.ASCENDING), | |
+ ] | |
+ }, | |
] | |
# Node fields that trigger an update to Solr on save | |
@@ -945,7 +952,7 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
@property | |
def registered_from_id(self): | |
- """The ID of the user who registered this node if this is a registration, else None. | |
+ """The ID of the node that was registered, else None. | |
""" | |
if self.registered_from: | |
return self.registered_from._id | |
@@ -2143,6 +2150,7 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
save=False | |
) | |
+ # Need this save in order to access _primary_key | |
forked.save() | |
forked.add_log( | |
@@ -2150,7 +2158,8 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
params={ | |
'parent_node': original.parent_id, | |
'node': original._primary_key, | |
- 'registration': forked._primary_key, | |
+ 'registration': forked._primary_key, # TODO: Remove this in favor of 'fork' | |
+ 'fork': forked._primary_key, | |
}, | |
auth=auth, | |
log_date=when, | |
@@ -2711,6 +2720,7 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
for addon in self.get_addons(): | |
message = addon.after_remove_contributor(self, contributor, auth) | |
if message: | |
+ # Because addons can return HTML strings, addons are responsible for markupsafe-escaping any messages returned | |
status.push_status_message(message, kind='info', trust=True) | |
if log: | |
@@ -3336,6 +3346,7 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
action=NodeLog.RETRACTION_INITIATED, | |
params={ | |
'node': self.registered_from_id, | |
+ 'registration': self._id, | |
'retraction_id': retraction._id, | |
}, | |
auth=Auth(user), | |
@@ -3394,6 +3405,7 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
action=NodeLog.EMBARGO_INITIATED, | |
params={ | |
'node': self.registered_from_id, | |
+ 'registration': self._id, | |
'embargo_id': embargo._id, | |
}, | |
auth=Auth(user), | |
@@ -3474,6 +3486,7 @@ class Node(GuidStoredObject, AddonModelMixin, IdentifierMixin, Commentable): | |
action=NodeLog.REGISTRATION_APPROVAL_INITIATED, | |
params={ | |
'node': self.registered_from_id, | |
+ 'registration': self._id, | |
'registration_approval_id': approval._id, | |
}, | |
auth=Auth(user), | |
@@ -4134,7 +4147,8 @@ class Embargo(PreregCallbackMixin, EmailApprovableSanction): | |
parent_registration.registered_from.add_log( | |
action=NodeLog.EMBARGO_CANCELLED, | |
params={ | |
- 'node': parent_registration._id, | |
+ 'node': parent_registration.registered_from_id, | |
+ 'registration': parent_registration._id, | |
'embargo_id': self._id, | |
}, | |
auth=Auth(user), | |
@@ -4159,6 +4173,7 @@ class Embargo(PreregCallbackMixin, EmailApprovableSanction): | |
action=NodeLog.EMBARGO_APPROVED, | |
params={ | |
'node': parent_registration.registered_from_id, | |
+ 'registration': parent_registration._id, | |
'embargo_id': self._id, | |
}, | |
auth=Auth(self.initiated_by), | |
@@ -4262,7 +4277,8 @@ class Retraction(EmailApprovableSanction): | |
parent_registration.registered_from.add_log( | |
action=NodeLog.RETRACTION_CANCELLED, | |
params={ | |
- 'node': parent_registration._id, | |
+ 'node': parent_registration.registered_from_id, | |
+ 'registration': parent_registration._id, | |
'retraction_id': self._id, | |
}, | |
auth=Auth(user), | |
@@ -4276,6 +4292,7 @@ class Retraction(EmailApprovableSanction): | |
params={ | |
'node': parent_registration.registered_from_id, | |
'retraction_id': self._id, | |
+ 'registration': parent_registration._id | |
}, | |
auth=Auth(self.initiated_by), | |
) | |
@@ -4285,7 +4302,8 @@ class Retraction(EmailApprovableSanction): | |
parent_registration.registered_from.add_log( | |
action=NodeLog.EMBARGO_CANCELLED, | |
params={ | |
- 'node': parent_registration._id, | |
+ 'node': parent_registration.registered_from_id, | |
+ 'registration': parent_registration._id, | |
'embargo_id': parent_registration.embargo._id, | |
}, | |
auth=Auth(self.initiated_by), | |
@@ -4412,6 +4430,7 @@ class RegistrationApproval(PreregCallbackMixin, EmailApprovableSanction): | |
action=NodeLog.REGISTRATION_APPROVAL_APPROVED, | |
params={ | |
'node': registered_from._id, | |
+ 'registration': register._id, | |
'registration_approval_id': self._id, | |
}, | |
auth=auth, | |
@@ -4429,7 +4448,8 @@ class RegistrationApproval(PreregCallbackMixin, EmailApprovableSanction): | |
registered_from.add_log( | |
action=NodeLog.REGISTRATION_APPROVAL_CANCELLED, | |
params={ | |
- 'node': register._id, | |
+ 'node': registered_from._id, | |
+ 'registration': register._id, | |
'registration_approval_id': self._id, | |
}, | |
auth=Auth(user), | |
diff --git a/website/project/views/contributor.py b/website/project/views/contributor.py | |
index a762ae3..4b2f2ca 100644 | |
--- a/website/project/views/contributor.py | |
+++ b/website/project/views/contributor.py | |
@@ -558,10 +558,12 @@ def claim_user_registered(auth, node, **kwargs): | |
node.save() | |
status.push_status_message( | |
'You are now a contributor to this project.', | |
- kind='success') | |
+ kind='success', | |
+ trust=False | |
+ ) | |
return redirect(node.url) | |
else: | |
- status.push_status_message(language.LOGIN_FAILED, kind='warning', trust=True) | |
+ status.push_status_message(language.LOGIN_FAILED, kind='warning', trust=False) | |
else: | |
forms.push_errors_to_status(form.errors) | |
if is_json_request(): | |
@@ -634,8 +636,7 @@ def claim_user_form(auth, **kwargs): | |
user.verification_key = security.random_string(20) | |
user.save() | |
# Authenticate user and redirect to project page | |
- node = Node.load(pid) | |
- status.push_status_message(language.CLAIMED_CONTRIBUTOR.format(node=node), | |
+ status.push_status_message(language.CLAIMED_CONTRIBUTOR, | |
kind='success', | |
trust=True) | |
# Redirect to CAS and authenticate the user with a verification key. | |
diff --git a/website/project/views/node.py b/website/project/views/node.py | |
index c148688..f6cdd96 100644 | |
--- a/website/project/views/node.py | |
+++ b/website/project/views/node.py | |
@@ -839,7 +839,7 @@ def _get_summary(node, auth, primary=True, link_id=None, show_path=False): | |
'is_pending_retraction': node.is_pending_retraction, | |
'embargo_end_date': node.embargo_end_date.strftime("%A, %b. %d, %Y") if node.embargo_end_date else False, | |
'is_pending_embargo': node.is_pending_embargo, | |
- 'archiving': node.archiving or node.root.archiving, | |
+ 'archiving': node.archiving or getattr(node.root, 'archiving', False), | |
} | |
if node.can_view(auth): | |
diff --git a/website/routes.py b/website/routes.py | |
index c3b1a9a..be5c55e 100644 | |
--- a/website/routes.py | |
+++ b/website/routes.py | |
@@ -86,7 +86,6 @@ def get_globals(): | |
'api_v2_url': util.api_v2_url, # URL function for templates | |
'api_v2_base': util.api_v2_url(''), # Base url used by JS api helper | |
'sanitize': sanitize, | |
- 'js_str': lambda x: x.replace("'", r"\'").replace('"', r'\"'), | |
'sjson': lambda s: sanitize.safe_json(s), | |
'webpack_asset': paths.webpack_asset, | |
'waterbutler_url': settings.WATERBUTLER_URL, | |
@@ -119,7 +118,7 @@ class OsfWebRenderer(WebRenderer): | |
super(OsfWebRenderer, self).__init__(*args, **kwargs) | |
#: Use if a view only redirects or raises error | |
-notemplate = OsfWebRenderer('', renderer=render_mako_string) | |
+notemplate = OsfWebRenderer('', renderer=render_mako_string, trust=False) | |
# Static files (robots.txt, etc.) | |
@@ -151,7 +150,7 @@ def goodbye(): | |
# Redirect to dashboard if logged in | |
if _get_current_user(): | |
return redirect(util.web_url_for('index')) | |
- status.push_status_message(language.LOGOUT, 'success') | |
+ status.push_status_message(language.LOGOUT, kind='success', trust=False) | |
return {} | |
def make_url_map(app): | |
@@ -162,10 +161,18 @@ def make_url_map(app): | |
# Set default views to 404, using URL-appropriate renderers | |
process_rules(app, [ | |
- Rule('/<path:_>', ['get', 'post'], HTTPError(http.NOT_FOUND), | |
- OsfWebRenderer('', render_mako_string)), | |
- Rule('/api/v1/<path:_>', ['get', 'post'], | |
- HTTPError(http.NOT_FOUND), json_renderer), | |
+ Rule( | |
+ '/<path:_>', | |
+ ['get', 'post'], | |
+ HTTPError(http.NOT_FOUND), | |
+ OsfWebRenderer('', render_mako_string, trust=False) | |
+ ), | |
+ Rule( | |
+ '/api/v1/<path:_>', | |
+ ['get', 'post'], | |
+ HTTPError(http.NOT_FOUND), | |
+ json_renderer | |
+ ), | |
]) | |
### GUID ### | |
@@ -203,35 +210,92 @@ def make_url_map(app): | |
process_rules(app, [ | |
- Rule('/dashboard/', 'get', website_views.redirect_to_home, OsfWebRenderer('home.mako')), | |
- Rule('/myprojects/', 'get', website_views.dashboard, OsfWebRenderer('dashboard.mako')), | |
+ Rule( | |
+ '/dashboard/', | |
+ 'get', | |
+ website_views.redirect_to_home, | |
+ OsfWebRenderer('home.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/myprojects/', | |
+ 'get', | |
+ website_views.dashboard, | |
+ OsfWebRenderer('dashboard.mako', trust=False) | |
+ ), | |
- Rule('/reproducibility/', 'get', | |
- website_views.reproducibility, OsfWebRenderer('', render_mako_string)), | |
+ Rule( | |
+ '/reproducibility/', | |
+ 'get', | |
+ website_views.reproducibility, | |
+ notemplate | |
+ ), | |
- Rule('/about/', 'get', website_views.redirect_about, json_renderer,), | |
- Rule('/howosfworks/', 'get', website_views.redirect_howosfworks, json_renderer,), | |
+ Rule( | |
+ '/about/', | |
+ 'get', | |
+ website_views.redirect_about, | |
+ json_renderer | |
+ ), | |
+ Rule( | |
+ '/howosfworks/', | |
+ 'get', | |
+ website_views.redirect_howosfworks, | |
+ json_renderer | |
+ ), | |
- Rule('/faq/', 'get', {}, OsfWebRenderer('public/pages/faq.mako')), | |
- Rule('/getting-started/', 'get', {}, OsfWebRenderer('public/pages/getting_started.mako')), | |
- Rule('/getting-started/email/', 'get', website_views.redirect_meetings_analytics_link, json_renderer), | |
- Rule('/support/', 'get', {}, OsfWebRenderer('public/pages/support.mako')), | |
+ Rule( | |
+ '/faq/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('public/pages/faq.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/getting-started/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('public/pages/getting_started.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/getting-started/email/', | |
+ 'get', | |
+ website_views.redirect_meetings_analytics_link, | |
+ json_renderer | |
+ ), | |
+ Rule( | |
+ '/support/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('public/pages/support.mako', trust=False) | |
+ ), | |
- Rule('/explore/', 'get', {}, OsfWebRenderer('public/explore.mako')), | |
- Rule(['/messages/', '/help/'], 'get', {}, OsfWebRenderer('public/comingsoon.mako')), | |
+ Rule( | |
+ '/explore/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('public/explore.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ [ | |
+ '/messages/', | |
+ '/help/' | |
+ ], | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('public/comingsoon.mako', trust=False) | |
+ ), | |
Rule( | |
'/view/<meeting>/', | |
'get', | |
conference_views.conference_results, | |
- OsfWebRenderer('public/pages/meeting.mako'), | |
+ OsfWebRenderer('public/pages/meeting.mako', trust=False), | |
), | |
Rule( | |
'/view/<meeting>/plain/', | |
'get', | |
conference_views.conference_results, | |
- OsfWebRenderer('public/pages/meeting_plain.mako'), | |
+ OsfWebRenderer('public/pages/meeting_plain.mako', trust=False), | |
endpoint_suffix='__plain', | |
), | |
@@ -246,7 +310,7 @@ def make_url_map(app): | |
'/meetings/', | |
'get', | |
conference_views.conference_view, | |
- OsfWebRenderer('public/pages/meeting_landing.mako'), | |
+ OsfWebRenderer('public/pages/meeting_landing.mako', trust=False), | |
), | |
Rule( | |
@@ -263,7 +327,12 @@ def make_url_map(app): | |
json_renderer, | |
), | |
- Rule('/news/', 'get', {}, OsfWebRenderer('public/pages/news.mako')), | |
+ Rule( | |
+ '/news/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('public/pages/news.mako', trust=False) | |
+ ), | |
Rule( | |
'/prereg/', | |
@@ -324,7 +393,7 @@ def make_url_map(app): | |
'/oauth/callback/<service_name>/', | |
'get', | |
oauth_views.oauth_callback, | |
- OsfWebRenderer('util/oauth_complete.mako'), | |
+ OsfWebRenderer('util/oauth_complete.mako', trust=False), | |
), | |
]) | |
process_rules(app, [ | |
@@ -394,14 +463,14 @@ def make_url_map(app): | |
'get', | |
auth_views.confirm_email_get, | |
# View will either redirect or display error message | |
- OsfWebRenderer('error.mako', render_mako_string) | |
+ notemplate | |
), | |
Rule( | |
'/resetpassword/<verification_key>/', | |
['get', 'post'], | |
auth_views.reset_password, | |
- OsfWebRenderer('public/resetpassword.mako', render_mako_string) | |
+ OsfWebRenderer('public/resetpassword.mako', render_mako_string, trust=False) | |
), | |
# Resend confirmation URL linked to in CAS login page | |
@@ -409,38 +478,80 @@ def make_url_map(app): | |
'/resend/', | |
['get', 'post'], | |
auth_views.resend_confirmation, | |
- OsfWebRenderer('resend.mako', render_mako_string) | |
+ OsfWebRenderer('resend.mako', render_mako_string, trust=False) | |
), | |
# TODO: Remove `auth_register_post` | |
- Rule('/register/', 'post', auth_views.auth_register_post, | |
- OsfWebRenderer('public/login.mako')), | |
+ Rule( | |
+ '/register/', | |
+ 'post', | |
+ auth_views.auth_register_post, | |
+ OsfWebRenderer('public/login.mako', trust=False) | |
+ ), | |
Rule('/api/v1/register/', 'post', auth_views.register_user, json_renderer), | |
- Rule(['/login/', '/account/'], 'get', | |
- auth_views.auth_login, OsfWebRenderer('public/login.mako')), | |
- Rule('/login/first/', 'get', auth_views.auth_login, | |
- OsfWebRenderer('public/login.mako'), | |
- endpoint_suffix='__first', view_kwargs={'first': True}), | |
- Rule('/logout/', 'get', auth_views.auth_logout, notemplate), | |
- Rule('/forgotpassword/', 'get', auth_views.forgot_password_get, | |
- OsfWebRenderer('public/forgot_password.mako')), | |
- Rule('/forgotpassword/', 'post', auth_views.forgot_password_post, | |
- OsfWebRenderer('public/login.mako')), | |
+ Rule( | |
+ [ | |
+ '/login/', | |
+ '/account/' | |
+ ], | |
+ 'get', | |
+ auth_views.auth_login, | |
+ OsfWebRenderer('public/login.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/login/first/', | |
+ 'get', | |
+ auth_views.auth_login, | |
+ OsfWebRenderer('public/login.mako', trust=False), | |
+ endpoint_suffix='__first', view_kwargs={'first': True} | |
+ ), | |
+ Rule( | |
+ '/logout/', | |
+ 'get', | |
+ auth_views.auth_logout, | |
+ notemplate | |
+ ), | |
+ Rule( | |
+ '/forgotpassword/', | |
+ 'get', | |
+ auth_views.forgot_password_get, | |
+ OsfWebRenderer('public/forgot_password.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/forgotpassword/', | |
+ 'post', | |
+ auth_views.forgot_password_post, | |
+ OsfWebRenderer('public/login.mako', trust=False) | |
+ ), | |
- Rule([ | |
- '/midas/', '/summit/', '/accountbeta/', '/decline/' | |
- ], 'get', auth_views.auth_registerbeta, OsfWebRenderer('', render_mako_string)), | |
+ Rule( | |
+ [ | |
+ '/midas/', | |
+ '/summit/', | |
+ '/accountbeta/', | |
+ '/decline/' | |
+ ], | |
+ 'get', | |
+ auth_views.auth_registerbeta, | |
+ notemplate | |
+ ), | |
- Rule('/login/connected_tools/', | |
- 'get', | |
- landing_page_views.connected_tools, | |
- OsfWebRenderer('public/login_landing.mako')), | |
+ # FIXME or REDIRECTME: This redirects to settings when logged in, but gives an error (no template) when logged out | |
+ Rule( | |
+ '/login/connected_tools/', | |
+ 'get', | |
+ landing_page_views.connected_tools, | |
+ OsfWebRenderer('public/login_landing.mako', trust=False) | |
+ ), | |
- Rule('/login/enriched_profile/', | |
- 'get', | |
- landing_page_views.enriched_profile, | |
- OsfWebRenderer('public/login_landing.mako')), | |
+ # FIXME or REDIRECTME: mod-meta error when logged out: signin form not rendering for login_landing sidebar | |
+ Rule( | |
+ '/login/enriched_profile/', | |
+ 'get', | |
+ landing_page_views.enriched_profile, | |
+ OsfWebRenderer('public/login_landing.mako', trust=False) | |
+ ), | |
]) | |
@@ -462,16 +573,16 @@ def make_url_map(app): | |
OsfWebRenderer('profile.mako', trust=False) | |
), | |
Rule( | |
- ["/user/merge/"], | |
+ ['/user/merge/'], | |
'get', | |
auth_views.merge_user_get, | |
- OsfWebRenderer("merge_accounts.mako", trust=False) | |
+ OsfWebRenderer('merge_accounts.mako', trust=False) | |
), | |
Rule( | |
- ["/user/merge/"], | |
+ ['/user/merge/'], | |
'post', | |
auth_views.merge_user_post, | |
- OsfWebRenderer("merge_accounts.mako", trust=False) | |
+ OsfWebRenderer('merge_accounts.mako', trust=False) | |
), | |
# Route for claiming and setting email and password. | |
# Verification token must be querystring argument | |
@@ -708,12 +819,42 @@ def make_url_map(app): | |
process_rules(app, [ | |
- Rule('/search/', 'get', {}, OsfWebRenderer('search.mako')), | |
- Rule('/share/', 'get', {}, OsfWebRenderer('share_search.mako')), | |
- Rule('/share/registration/', 'get', {'register': settings.SHARE_REGISTRATION_URL}, OsfWebRenderer('share_registration.mako')), | |
- Rule('/share/help/', 'get', {'help': settings.SHARE_API_DOCS_URL}, OsfWebRenderer('share_api_docs.mako')), | |
- Rule('/share_dashboard/', 'get', {}, OsfWebRenderer('share_dashboard.mako')), | |
- Rule('/share/atom/', 'get', search_views.search_share_atom, xml_renderer), | |
+ Rule( | |
+ '/search/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('search.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/share/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('share_search.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/share/registration/', | |
+ 'get', | |
+ {'register': settings.SHARE_REGISTRATION_URL}, | |
+ OsfWebRenderer('share_registration.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/share/help/', | |
+ 'get', | |
+ {'help': settings.SHARE_API_DOCS_URL}, | |
+ OsfWebRenderer('share_api_docs.mako', trust=False) | |
+ ), | |
+ Rule( # FIXME: Dead route; possible that template never existed; confirm deletion candidate with ErinB | |
+ '/share_dashboard/', | |
+ 'get', | |
+ {}, | |
+ OsfWebRenderer('share_dashboard.mako', trust=False) | |
+ ), | |
+ Rule( | |
+ '/share/atom/', | |
+ 'get', | |
+ search_views.search_share_atom, | |
+ xml_renderer | |
+ ), | |
Rule('/api/v1/user/search/', 'get', search_views.search_contributor, json_renderer), | |
Rule( | |
@@ -749,8 +890,8 @@ def make_url_map(app): | |
process_rules(app, [ | |
# '/' route loads home.mako if logged in, otherwise loads landing.mako | |
- Rule('/', 'get', website_views.index, OsfWebRenderer('index.mako')), | |
- Rule('/goodbye/', 'get', goodbye, OsfWebRenderer('landing.mako')), | |
+ Rule('/', 'get', website_views.index, OsfWebRenderer('index.mako', trust=False)), | |
+ Rule('/goodbye/', 'get', goodbye, OsfWebRenderer('landing.mako', trust=False)), | |
Rule( | |
[ | |
@@ -771,7 +912,7 @@ def make_url_map(app): | |
), | |
# # TODO: Add API endpoint for tags | |
- # Rule('/tags/<tag>/', 'get', project_views.tag.project_tag, OsfWebRenderer('tags.mako')), | |
+ # Rule('/tags/<tag>/', 'get', project_views.tag.project_tag, OsfWebRenderer('tags.mako', trust=False)), | |
Rule('/project/new/<pid>/beforeTemplate/', 'get', | |
project_views.node.project_before_template, json_renderer), | |
@@ -796,14 +937,14 @@ def make_url_map(app): | |
), | |
# Permissions | |
- Rule( | |
+ Rule( # TODO: Where, if anywhere, is this route used? | |
[ | |
'/project/<pid>/permissions/<permissions>/', | |
'/project/<pid>/node/<nid>/permissions/<permissions>/', | |
], | |
'post', | |
project_views.node.project_set_privacy, | |
- OsfWebRenderer('project/project.mako') # TODO: Should this be notemplate? (post request) | |
+ OsfWebRenderer('project/project.mako', trust=False) | |
), | |
### Logs ### | |
@@ -883,7 +1024,6 @@ def make_url_map(app): | |
notemplate, | |
), | |
- # TODO: Can't create a registration locally, so can't test this one..? | |
Rule( | |
[ | |
'/project/<pid>/withdraw/', | |
diff --git a/website/settings/defaults.py b/website/settings/defaults.py | |
index 33eb9f7..81d481f 100644 | |
--- a/website/settings/defaults.py | |
+++ b/website/settings/defaults.py | |
@@ -447,3 +447,6 @@ ENABLE_VARNISH = False | |
ENABLE_ESI = False | |
VARNISH_SERVERS = [] # This should be set in local.py or cache invalidation won't work | |
ESI_MEDIA_TYPES = {'application/vnd.api+json', 'application/json'} | |
+ | |
+# Used for gathering meta information about the current build | |
+GITHUB_API_TOKEN = None | |
diff --git a/website/static/css/style.css b/website/static/css/style.css | |
index e936bce..13d7e01 100644 | |
--- a/website/static/css/style.css | |
+++ b/website/static/css/style.css | |
@@ -14,6 +14,27 @@ a { | |
cursor: pointer; | |
} | |
+/*Meta Info | |
+----------------------------------------------------*/ | |
+ | |
+#metaInfo { | |
+ text-shadow: 0 1px 0 #fff; | |
+ border-top: 1px solid #e5e5e5; | |
+ border-bottom: 1px solid #e5e5e5; | |
+ width: 100%; | |
+ color: #555; | |
+ position: relative; | |
+ padding-top:20px; | |
+ padding-bottom:20px; | |
+ z-index:10; | |
+ background-color:white; | |
+ border-top:2px solid black; | |
+} | |
+ | |
+#metaInfo th, #metaInfo td{ | |
+ padding-left: 15px; | |
+ text-align: left; | |
+} | |
/* Footer | |
-------------------------------------------------- */ | |
@@ -645,4 +666,21 @@ button.close { | |
.comma-separated:last-of-type:after { | |
content: ""; | |
+} | |
+ | |
+.action-buttons { | |
+ margin-top: 10px; | |
+ margin-bottom: 10px; | |
+} | |
+ | |
+.token-warning { | |
+ margin-bottom: 5px; | |
+ margin-top: 15px; | |
+ padding-left: 10px; | |
+ padding-top: 5px; | |
+ padding-bottom: 5px; | |
+} | |
+ | |
+#copy-button { | |
+ margin-top: 10px; | |
} | |
\ No newline at end of file | |
diff --git a/website/static/img/institutions/nd-banner.png b/website/static/img/institutions/nd-banner.png | |
index f052d05..874de80 100644 | |
Binary files a/website/static/img/institutions/nd-banner.png and b/website/static/img/institutions/nd-banner.png differ | |
diff --git a/website/static/img/institutions/nd-shield.png b/website/static/img/institutions/nd-shield.png | |
index 2594dcc..8e6b13e 100644 | |
Binary files a/website/static/img/institutions/nd-shield.png and b/website/static/img/institutions/nd-shield.png differ | |
diff --git a/website/static/js/accountSettings.js b/website/static/js/accountSettings.js | |
index e6a4ac6..cc2dd9d 100644 | |
--- a/website/static/js/accountSettings.js | |
+++ b/website/static/js/accountSettings.js | |
@@ -8,9 +8,6 @@ var oop = require('js/oop'); | |
var Raven = require('raven-js'); | |
var ChangeMessageMixin = require('js/changeMessage'); | |
-require('knockout.punches'); | |
-ko.punches.enableAll(); | |
- | |
var UserEmail = oop.defclass({ | |
constructor: function(params) { | |
diff --git a/website/static/js/addonNodeConfig.js b/website/static/js/addonNodeConfig.js | |
index 1bc8087..25fdc7f 100644 | |
--- a/website/static/js/addonNodeConfig.js | |
+++ b/website/static/js/addonNodeConfig.js | |
@@ -5,7 +5,6 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var Raven = require('raven-js'); | |
@@ -15,7 +14,6 @@ var $osf = require('js/osfHelpers'); | |
var oop = require('js/oop'); | |
var FolderPickerViewModel = require('js/folderPickerNodeConfig'); | |
-ko.punches.enableAll(); | |
/** | |
* View model to support instances of AddonNodeConfig (folder picker widget) | |
diff --git a/website/static/js/citationsNodeConfig.js b/website/static/js/citationsNodeConfig.js | |
index 6eda926..e9a7957 100644 | |
--- a/website/static/js/citationsNodeConfig.js | |
+++ b/website/static/js/citationsNodeConfig.js | |
@@ -5,7 +5,6 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var Raven = require('raven-js'); | |
var bootbox = require('bootbox'); | |
@@ -15,7 +14,6 @@ var $osf = require('js/osfHelpers'); | |
var oop = require('js/oop'); | |
var FolderPickerViewModel = require('js/folderPickerNodeConfig'); | |
-ko.punches.enableAll(); | |
/** | |
* View model to support instances of CitationsNodeConfig (folder picker widget) | |
diff --git a/website/static/js/comment.js b/website/static/js/comment.js | |
index e90293f..6dc7c38 100644 | |
--- a/website/static/js/comment.js | |
+++ b/website/static/js/comment.js | |
@@ -8,9 +8,7 @@ var ko = require('knockout'); | |
var moment = require('moment'); | |
var Raven = require('raven-js'); | |
var koHelpers = require('./koHelpers'); | |
-require('knockout.punches'); | |
require('jquery-autosize'); | |
-ko.punches.enableAll(); | |
var osfHelpers = require('js/osfHelpers'); | |
var CommentPane = require('js/commentpane'); | |
diff --git a/website/static/js/devModeControls.js b/website/static/js/devModeControls.js | |
new file mode 100644 | |
index 0000000..b6f05b3 | |
--- /dev/null | |
+++ b/website/static/js/devModeControls.js | |
@@ -0,0 +1,41 @@ | |
+var $ = require('jquery'); | |
+var ko = require('knockout'); | |
+var $osf = require('js/osfHelpers'); | |
+ | |
+var DevModeModel = function(source_file) { | |
+ self = this; | |
+ self.source_file = source_file; | |
+ self.pullRequests = ko.observableArray([]); | |
+ self.showMetaInfo = ko.observable(false); | |
+ self.showHideMetaInfo = function() { | |
+ if(self.pullRequests().length === 0) { | |
+ if(!self.showMetaInfo()) { | |
+ $.getJSON(self.source_file) | |
+ .done(function (data) { | |
+ dataLength = data.length; | |
+ for (i = 0; i < dataLength; i++) { | |
+ self.pullRequests.push(new PullRequestItem(data[i])); | |
+ } | |
+ self.showMetaInfo(true); | |
+ }); | |
+ } | |
+ } else { | |
+ self.showMetaInfo(!self.showMetaInfo()); | |
+ } | |
+ }; | |
+}; | |
+ | |
+var PullRequestItem = function(prItem) { | |
+ this.url = prItem.html_url || ''; | |
+ this.number = prItem.number || ''; | |
+ this.title = prItem.title || ''; | |
+ this.mergedAt = prItem.merged_at || ''; | |
+ | |
+}; | |
+ | |
+var DevModeControls = function(selector, source_file) { | |
+ this.viewModel = new DevModeModel(source_file); | |
+ $osf.applyBindings(this.viewModel, selector); | |
+}; | |
+ | |
+module.exports = DevModeControls; | |
\ No newline at end of file | |
diff --git a/website/static/js/folderPickerNodeConfig.js b/website/static/js/folderPickerNodeConfig.js | |
index 71995a4..0983748 100644 | |
--- a/website/static/js/folderPickerNodeConfig.js | |
+++ b/website/static/js/folderPickerNodeConfig.js | |
@@ -5,7 +5,6 @@ | |
'use strict'; | |
require('css/addon_folderpicker.css'); | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var bootbox = require('bootbox'); | |
var Raven = require('raven-js'); | |
@@ -19,7 +18,6 @@ var $osf = require('js/osfHelpers'); | |
var oop = require('js/oop'); | |
-ko.punches.enableAll(); | |
/** | |
* @class FolderPickerViewModel | |
diff --git a/website/static/js/koHelpers.js b/website/static/js/koHelpers.js | |
index 2d9b4b6..cf01518 100644 | |
--- a/website/static/js/koHelpers.js | |
+++ b/website/static/js/koHelpers.js | |
@@ -209,6 +209,46 @@ ko.bindingHandlers.fadeVisible = { | |
} | |
}; | |
+var fitHelper = function(value, length, replacement, trimWhere) { | |
+ if (length && ('' + value).length > length) { | |
+ replacement = '' + (replacement || '...'); | |
+ length = length - replacement.length; | |
+ value = '' + value; | |
+ switch (trimWhere) { | |
+ case 'left': | |
+ return replacement + value.slice(-length); | |
+ case 'middle': | |
+ var leftLen = Math.ceil(length / 2); | |
+ return value.substr(0, leftLen) + replacement + value.slice(leftLen-length); | |
+ default: | |
+ return value.substr(0, length) + replacement; | |
+ } | |
+ } else { | |
+ return value; | |
+ } | |
+}; | |
+/** | |
+ Trim the text to a specified width. Adapted from knockout.punches "fit" filter | |
+ Behavior can be modified by the presence of additional related bindings on the same element: | |
+ @param value {Object} A hash of options describing the text to truncate, and how | |
+ @param value.text {String} The string to truncate | |
+ @param value.length {Integer} Specifies the maximum length of the truncated string (default no limit) | |
+ @param [value.replacement='...'] {String} Specifies the sequence to use in place of trimmed characters | |
+ @param [value.trimWhere='right'] {String} Trim extra characters from the left, middle, or right side | |
+*/ | |
+ko.bindingHandlers.fitText = { | |
+ update: function(element, valueAccessor, allBindings) { | |
+ var value = ko.unwrap(valueAccessor()); | |
+ var trimValue = fitHelper( | |
+ value.text, | |
+ value.length, | |
+ value.replacement, | |
+ value.trimWhere | |
+ ); | |
+ $(element).text(trimValue); | |
+ } | |
+}; | |
+ | |
var tooltip = function(el, valueAccessor) { | |
var params = valueAccessor(); | |
$(el).tooltip(params); | |
@@ -433,6 +473,7 @@ ko.virtualElements.allowedBindings.stopBinding = true; | |
module.exports = { | |
makeExtender: makeExtender, | |
addExtender: addExtender, | |
+ _fitHelper: fitHelper, | |
makeRegexValidator: makeRegexValidator, | |
sanitizedObservable: sanitizedObservable, | |
mapJStoKO: mapJStoKO | |
diff --git a/website/static/js/logFeed.js b/website/static/js/logFeed.js | |
index 3ac20fb..71fd0a9 100644 | |
--- a/website/static/js/logFeed.js | |
+++ b/website/static/js/logFeed.js | |
@@ -8,13 +8,10 @@ var $ = require('jquery'); | |
var moment = require('moment'); | |
var Paginator = require('js/paginator'); | |
var oop = require('js/oop'); | |
-require('knockout.punches'); | |
var $osf = require('js/osfHelpers'); // Injects 'listing' binding handler to to Knockout | |
var nodeCategories = require('json!built/nodeCategories.json'); | |
-ko.punches.enableAll(); // Enable knockout punches | |
- | |
/** | |
* Log model. | |
*/ | |
diff --git a/website/static/js/nodeControl.js b/website/static/js/nodeControl.js | |
index cacbfde..14df092 100644 | |
--- a/website/static/js/nodeControl.js | |
+++ b/website/static/js/nodeControl.js | |
@@ -10,8 +10,6 @@ var ko = require('knockout'); | |
var bootbox = require('bootbox'); | |
var Raven = require('raven-js'); | |
require('bootstrap-editable'); | |
-require('knockout.punches'); | |
-ko.punches.enableAll(); | |
var osfHelpers = require('js/osfHelpers'); | |
var NodeActions = require('js/project.js'); | |
diff --git a/website/static/js/notificationsConfig.js b/website/static/js/notificationsConfig.js | |
index e6e2470..c3ae6ad 100644 | |
--- a/website/static/js/notificationsConfig.js | |
+++ b/website/static/js/notificationsConfig.js | |
@@ -1,10 +1,9 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var $osf = require('js/osfHelpers'); | |
-ko.punches.enableAll(); | |
+ | |
var ViewModel = function(list) { | |
var self = this; | |
diff --git a/website/static/js/oauthAddonNodeConfig.js b/website/static/js/oauthAddonNodeConfig.js | |
index 7a8ec67..2500f95 100644 | |
--- a/website/static/js/oauthAddonNodeConfig.js | |
+++ b/website/static/js/oauthAddonNodeConfig.js | |
@@ -5,7 +5,6 @@ | |
'use strict'; | |
var ko = require('knockout'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var Raven = require('raven-js'); | |
var bootbox = require('bootbox'); | |
@@ -14,7 +13,6 @@ var $osf = require('js/osfHelpers'); | |
var oop = require('js/oop'); | |
var FolderPickerViewModel = require('js/folderPickerNodeConfig'); | |
-ko.punches.enableAll(); | |
/** | |
* View model to support instances of AddonNodeConfig (folder picker widget) | |
diff --git a/website/static/js/pages/base-page.js b/website/static/js/pages/base-page.js | |
index 809db19..6e275fd 100644 | |
--- a/website/static/js/pages/base-page.js | |
+++ b/website/static/js/pages/base-page.js | |
@@ -20,6 +20,7 @@ var NavbarControl = require('js/navbarControl'); | |
var Raven = require('raven-js'); | |
var moment = require('moment'); | |
var KeenTracker = require('js/keen'); | |
+var DevModeControls = require('js/devModeControls'); | |
// Prevent IE from caching responses | |
$.ajaxSetup({cache: false}); | |
@@ -111,6 +112,7 @@ $(function() { | |
$osf.initializeResponsiveAffix(); | |
} | |
new NavbarControl('.osf-nav-wrapper'); | |
+ new DevModeControls('#devModeControls', '/static/built/git_logs.json'); | |
if(window.contextVars.keenProjectId){ | |
var params = {}; | |
params.currentUser = window.contextVars.currentUser; | |
@@ -120,6 +122,5 @@ $(function() { | |
if(!(/PhantomJS/.test(navigator.userAgent))){ | |
new KeenTracker(window.contextVars.keenProjectId, window.contextVars.keenWriteKey, params); | |
} | |
- | |
} | |
}); | |
diff --git a/website/static/js/pages/profile-settings-addons-page.js b/website/static/js/pages/profile-settings-addons-page.js | |
index 539a759..281a264 100644 | |
--- a/website/static/js/pages/profile-settings-addons-page.js | |
+++ b/website/static/js/pages/profile-settings-addons-page.js | |
@@ -10,8 +10,6 @@ var $osf = require('js/osfHelpers'); | |
var AddonPermissionsTable = require('js/addonPermissions'); | |
var addonSettings = require('js/addonSettings'); | |
-ko.punches.enableAll(); | |
- | |
// Show capabilities modal on selecting an addon; unselect if user | |
// rejects terms | |
diff --git a/website/static/js/pages/profile-settings-personal-tokens-detail-page.js b/website/static/js/pages/profile-settings-personal-tokens-detail-page.js | |
index 3409759..f0caa24 100644 | |
--- a/website/static/js/pages/profile-settings-personal-tokens-detail-page.js | |
+++ b/website/static/js/pages/profile-settings-personal-tokens-detail-page.js | |
@@ -4,3 +4,7 @@ var viewModels = require('../apiPersonalToken'); | |
var ctx = window.contextVars; | |
var apiPersonalToken = new viewModels.TokenDetail('#tokenDetail', ctx.urls); | |
+ | |
+var Clipboard = require('clipboard'); | |
+ | |
+new Clipboard('#copy-button'); | |
diff --git a/website/static/js/paginator.js b/website/static/js/paginator.js | |
index 743c728..5b3621b 100644 | |
--- a/website/static/js/paginator.js | |
+++ b/website/static/js/paginator.js | |
@@ -25,7 +25,7 @@ var Paginator = oop.defclass({ | |
self.paginators.push({ | |
style: (self.currentPage() === 0) ? 'disabled' : '', | |
handler: self.previousPage.bind(self), | |
- text: '<' | |
+ text: '<' | |
}); /* jshint ignore:line */ | |
/* functions defined inside loop */ | |
@@ -132,7 +132,7 @@ var Paginator = oop.defclass({ | |
self.paginators.push({ | |
style: (self.currentPage() === self.numberOfPages() - 1) ? 'disabled' : '', | |
handler: self.nextPage.bind(self), | |
- text: '>' | |
+ text: '>' | |
}); | |
} | |
}, | |
diff --git a/website/static/js/profile.js b/website/static/js/profile.js | |
index 6977021..6d743b7 100644 | |
--- a/website/static/js/profile.js | |
+++ b/website/static/js/profile.js | |
@@ -5,8 +5,6 @@ var $ = require('jquery'); | |
var ko = require('knockout'); | |
var bootbox = require('bootbox'); | |
require('knockout.validation'); | |
-require('knockout.punches'); | |
-ko.punches.enableAll(); | |
require('knockout-sortable'); | |
var $osf = require('./osfHelpers'); | |
diff --git a/website/static/js/registrationEditorExtensions.js b/website/static/js/registrationEditorExtensions.js | |
index 1773498..2bcf207 100644 | |
--- a/website/static/js/registrationEditorExtensions.js | |
+++ b/website/static/js/registrationEditorExtensions.js | |
@@ -231,7 +231,7 @@ var Uploader = function(question) { | |
var files = question.extra(); | |
var elem = ''; | |
$.each(files, function(_, file) { | |
- elem += '<a target="_blank" href="' + file.viewUrl + '">' + $osf.htmlEscape(file.selectedFileName) + ' </a>' + '</br>'; | |
+ elem += '<a target="_blank" href="' + file.viewUrl + '">' + $osf.htmlEscape(file.selectedFileName) + ' </a>'; | |
}); | |
return $(elem); | |
} | |
diff --git a/website/static/js/search.js b/website/static/js/search.js | |
index 5feba9e..1c07d3b 100644 | |
--- a/website/static/js/search.js | |
+++ b/website/static/js/search.js | |
@@ -12,8 +12,7 @@ var DEFAULT_LICENSE = siteLicenses.DEFAULT_LICENSE; | |
var OTHER_LICENSE = siteLicenses.OTHER_LICENSE; | |
var $osf = require('js/osfHelpers'); | |
-// Enable knockout punches | |
-ko.punches.enableAll(); | |
+ | |
// Disable IE Caching of JSON | |
$.ajaxSetup({ cache: false }); | |
diff --git a/website/static/js/signUp.js b/website/static/js/signUp.js | |
index 3e9bea1..859187b 100644 | |
--- a/website/static/js/signUp.js | |
+++ b/website/static/js/signUp.js | |
@@ -2,12 +2,10 @@ | |
var ko = require('knockout'); | |
require('knockout.validation'); | |
-require('knockout.punches'); | |
var $ = require('jquery'); | |
var $osf = require('./osfHelpers'); | |
-ko.punches.enableAll(); | |
var ViewModel = function(submitUrl, campaign) { | |
diff --git a/website/static/js/tests/koHelpers.test.js b/website/static/js/tests/koHelpers.test.js | |
index a530cc4..a034b75 100644 | |
--- a/website/static/js/tests/koHelpers.test.js | |
+++ b/website/static/js/tests/koHelpers.test.js | |
@@ -18,6 +18,23 @@ describe('koHelpers', () => { | |
}); | |
}); | |
+ describe('fitTextHelper', () => { | |
+ it('correctly processes various strings', () => { | |
+ // Test the underlying method; adapted from https://github.com/mbest/knockout.punches/blob/master/spec/textFilterSpec.js#L78 | |
+ var cases = [ | |
+ [{text: 'someText', length: 8}, 'someText'], | |
+ [{text: 'someText', length: 7}, 'some...'], | |
+ [{text: 'someText', length: 7, replacement: '!'}, 'someTe!'], | |
+ [{text: 'someText', length: 7, trimWhere: 'left'}, '...Text'], | |
+ [{text: 'someText', length: 7, trimWhere: 'middle'}, 'so...xt'] | |
+ ]; | |
+ cases.forEach(([{text, length, replacement, trimWhere}, expectedResult]) => { | |
+ let actualResult = koHelpers._fitHelper(text, length, replacement, trimWhere); | |
+ assert.equal(actualResult, expectedResult, text + ' is truncated correctly'); | |
+ }); | |
+ }); | |
+ }); | |
+ | |
// TODO: test custom validators | |
describe('mapJStoKO', () => { | |
diff --git a/website/static/js/tests/paginator.test.js b/website/static/js/tests/paginator.test.js | |
index 63fd7e7..1adbb5e 100644 | |
--- a/website/static/js/tests/paginator.test.js | |
+++ b/website/static/js/tests/paginator.test.js | |
@@ -106,11 +106,11 @@ describe('Paginator', () => { | |
}); | |
paginator.addNewPaginators(); | |
assert.equal(paginator.paginators().length, numberOfPages + 2); | |
- assert.equal(paginator.paginators()[0].text, '<'); | |
+ assert.equal(paginator.paginators()[0].text, '<'); | |
assert.equal(paginator.paginators()[1].text, 1); | |
assert.equal( | |
paginator.paginators()[paginator.paginators().length - 1].text, | |
- '>' | |
+ '>' | |
); | |
assert.equal( | |
paginator.paginators()[paginator.paginators().length - 2].text, | |
@@ -127,8 +127,8 @@ describe('Paginator', () => { | |
}); | |
paginator.addNewPaginators(); | |
assert.equal(paginator.paginators().length, maxPaginatorNumber); | |
- assert.equal(paginator.paginators()[0].text, '<'); | |
- assert.equal(paginator.paginators()[maxPaginatorNumber - 1].text, '>'); | |
+ assert.equal(paginator.paginators()[0].text, '<'); | |
+ assert.equal(paginator.paginators()[maxPaginatorNumber - 1].text, '>'); | |
assert.equal(paginator.paginators()[maxPaginatorNumber - 2].text, numberOfPages); | |
assert.equal(paginator.paginators()[maxPaginatorNumber - 3].text, '...'); | |
}); | |
@@ -143,10 +143,10 @@ describe('Paginator', () => { | |
}); | |
paginator.addNewPaginators(); | |
assert.equal(paginator.paginators().length, maxPaginatorNumber); | |
- assert.equal(paginator.paginators()[0].text, '<'); | |
+ assert.equal(paginator.paginators()[0].text, '<'); | |
assert.equal(paginator.paginators()[1].text, 1); | |
assert.equal(paginator.paginators()[2].text, '...'); | |
- assert.equal(paginator.paginators()[maxPaginatorNumber - 1].text, '>'); | |
+ assert.equal(paginator.paginators()[maxPaginatorNumber - 1].text, '>'); | |
assert.equal(paginator.paginators()[maxPaginatorNumber - 2].text, numberOfPages); | |
}); | |
@@ -161,10 +161,10 @@ describe('Paginator', () => { | |
}); | |
paginator.addNewPaginators(); | |
assert.equal(paginator.paginators().length, maxPaginatorNumber); | |
- assert.equal(paginator.paginators()[0].text, '<'); | |
+ assert.equal(paginator.paginators()[0].text, '<'); | |
assert.equal(paginator.paginators()[1].text, 1); | |
assert.equal(paginator.paginators()[2].text, '...'); | |
- assert.equal(paginator.paginators()[maxPaginatorNumber - 1].text, '>'); | |
+ assert.equal(paginator.paginators()[maxPaginatorNumber - 1].text, '>'); | |
assert.equal(paginator.paginators()[maxPaginatorNumber - 2].text, numberOfPages); | |
assert.equal(paginator.paginators()[maxPaginatorNumber - 3].text, '...'); | |
diff --git a/website/templates/alert.mako b/website/templates/alert.mako | |
index e170762..530ac49 100644 | |
--- a/website/templates/alert.mako | |
+++ b/website/templates/alert.mako | |
@@ -12,7 +12,7 @@ | |
<div class="jumbotron"> | |
%endif | |
% if trust: | |
- <p>${message | n}</p> | |
+ <p>${ message | unicode, n }</p> | |
% else: | |
<p>${message}</p> | |
% endif | |
diff --git a/website/templates/base.mako b/website/templates/base.mako | |
index 904eb88..9b1cbc2 100644 | |
--- a/website/templates/base.mako | |
+++ b/website/templates/base.mako | |
@@ -52,18 +52,40 @@ | |
<body data-spy="scroll" data-target=".scrollspy"> | |
% if dev_mode: | |
- <style> | |
- #devmode { | |
- position:fixed; | |
- bottom:0; | |
- left:0; | |
- border-top-right-radius:8px; | |
- background-color:red; | |
- color:white; | |
- padding:.5em; | |
- } | |
- </style> | |
- <div id='devmode'><strong>WARNING</strong>: This site is running in development mode.</div> | |
+ <div class="dev-mode-helper scripted" id="devModeControls"> | |
+ <div id="metaInfo" data-bind="visible: showMetaInfo"> | |
+ <table> | |
+ <thead> | |
+ <tr> | |
+ <th>PR</th> | |
+ <th>Title</th> | |
+ <th>Date Merged</th> | |
+ </tr> | |
+ </thead> | |
+ <tbody data-bind="foreach: pullRequests"> | |
+ <tr> | |
+ <td>#<a data-bind="attr: {href: url}, text: number"></a></td> | |
+ <td data-bind="text: title"></td> | |
+ <td data-bind="text: mergedAt"></td> | |
+ </tr> | |
+ </tbody> | |
+ </table> | |
+ </div> | |
+ <style> | |
+ #devmode { | |
+ position:fixed; | |
+ bottom:0; | |
+ left:0; | |
+ border-top-right-radius:8px; | |
+ background-color:red; | |
+ color:white; | |
+ padding:.5em; | |
+ } | |
+ </style> | |
+ <div id='devmode' data-bind='click: showHideMetaInfo'><strong>WARNING</strong>: This site is running in development mode.</div> | |
+ </div> | |
+ %else: | |
+ <div id="devModeControls"></div> | |
% endif | |
<%namespace name="nav_file" file="nav.mako"/> | |
@@ -161,14 +183,14 @@ | |
isOnRootDomain: ${domain | sjson, n } === window.location.origin + '/', | |
cookieName: ${ cookie_name | sjson, n }, | |
apiV2Prefix: ${ api_v2_base | sjson, n }, | |
- registerUrl: ${ api_url_for('register_user') | sjson, n}, | |
+ registerUrl: ${ api_url_for('register_user') | sjson, n }, | |
currentUser: { | |
id: ${ user_id | sjson, n }, | |
locale: ${ user_locale | sjson, n }, | |
timezone: ${ user_timezone | sjson, n } | |
}, | |
- popular: ${ popular_links_node | sjson, n}, | |
- newAndNoteworthy: ${ noteworthy_links_node | sjson, n} | |
+ popular: ${ popular_links_node | sjson, n }, | |
+ newAndNoteworthy: ${ noteworthy_links_node | sjson, n } | |
}); | |
</script> | |
@@ -303,5 +325,4 @@ | |
## the webpack runtime and a number of necessary stylesheets which should be loaded before the user sees | |
## content. | |
<script src="${'/static/public/js/vendor.js' | webpack_asset}"></script> | |
- | |
</%def> | |
diff --git a/website/templates/home.mako b/website/templates/home.mako | |
index ca15a3f..c4b6301 100644 | |
--- a/website/templates/home.mako | |
+++ b/website/templates/home.mako | |
@@ -7,10 +7,10 @@ | |
% if status: | |
${self.alert()} | |
% endif | |
- </div> | |
- </div><!-- end container --> | |
- ${self.content()} | |
+ </div> | |
</div><!-- end watermarked --> | |
+ | |
+ ${self.content()} | |
</%def> | |
diff --git a/website/templates/include/comment_pane_template.mako b/website/templates/include/comment_pane_template.mako | |
index efb9a93..34550e1 100644 | |
--- a/website/templates/include/comment_pane_template.mako | |
+++ b/website/templates/include/comment_pane_template.mako | |
@@ -31,12 +31,12 @@ | |
<div class="clearfix"> | |
<div class="pull-right"> | |
<a class="btn btn-default btn-sm" data-bind="click: cancelReply, css: {disabled: submittingReply}">Cancel</a> | |
- <a class="btn btn-success btn-sm" data-bind="click: submitReply, css: {disabled: submittingReply}">{{commentButtonText}}</a> | |
+ <a class="btn btn-success btn-sm" data-bind="click: submitReply, css: {disabled: submittingReply}, text: commentButtonText"></a> | |
<span data-bind="text: replyErrorMessage" class="text-danger"></span> | |
</div> | |
</div> | |
</div> | |
- <div class="text-danger">{{errorMessage}}</div> | |
+ <div class="text-danger" data-bind="text: errorMessage"></div> | |
</form> | |
</div> | |
<div data-bind="template: {name: 'commentTemplate', foreach: comments}"></div> | |
diff --git a/website/templates/include/comment_template.mako b/website/templates/include/comment_template.mako | |
index 6b79675..c323151 100644 | |
--- a/website/templates/include/comment_template.mako | |
+++ b/website/templates/include/comment_template.mako | |
@@ -36,7 +36,7 @@ | |
<div class="comment-info"> | |
<form class="form-inline"> | |
<span data-bind="if: author.gravatarUrl"> | |
- <img data-bind="css: {comment-gravatar: author.gravatarUrl}, attr: {src: author.gravatarUrl}"/> | |
+ <img data-bind="css: {'comment-gravatar': author.gravatarUrl}, attr: {src: author.gravatarUrl}"/> | |
</span> | |
<span data-bind="if: author.id"> | |
<a class="comment-author" data-bind="text: author.fullname, attr: {href: author.urls.profile}"></a> | |
@@ -83,7 +83,7 @@ | |
<div> | |
- <span class="text-danger">{{errorMessage}}</span> | |
+ <span class="text-danger" data-bind="text: errorMessage"></span> | |
<span> </span> | |
@@ -142,7 +142,7 @@ | |
<div class="clearfix"> | |
<div class="pull-right"> | |
<a class="btn btn-default btn-sm" data-bind="click: cancelReply, css: {disabled: submittingReply}"> Cancel</a> | |
- <a class="btn btn-success btn-sm" data-bind="click: submitReply, visible: replyNotEmpty, css: {disabled: submittingReply}"> {{commentButtonText}}</a> | |
+ <a class="btn btn-success btn-sm" data-bind="click: submitReply, visible: replyNotEmpty, css: {disabled: submittingReply}, text: commentButtonText"></a> | |
<span data-bind="text: replyErrorMessage" class="text-danger"></span> | |
</div> | |
</div> | |
diff --git a/website/templates/include/profile/jobs.mako b/website/templates/include/profile/jobs.mako | |
index bcb0ad6..5e71e96 100644 | |
--- a/website/templates/include/profile/jobs.mako | |
+++ b/website/templates/include/profile/jobs.mako | |
@@ -16,7 +16,7 @@ | |
<div> | |
<div class="well well-sm sort-handle"> | |
- <span>Position {{ $index() + 1 }}</span> | |
+ <span>Position <span data-bind="text: $index() + 1"></span></span> | |
<span data-bind="visible: $parent.contentsLength() > 1"> | |
[ drag to reorder ] | |
</span> | |
@@ -117,7 +117,7 @@ | |
<!-- Flashed Messages --> | |
<div class="help-block"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
</form> | |
@@ -138,16 +138,20 @@ | |
<div class="panel panel-default"> | |
<div class="panel-heading card-heading" data-bind="click: toggle(), attr: {id: 'jobHeading' + $index(), href: '#jobCard' + $index()}" role="button" data-toggle="collapse" aria-controls="card" aria-expanded="false"> | |
<div class="header-content"> | |
- <h5 class="institution">{{ institution }}</h5> | |
- <span data-bind="if: startYear()" class="subheading">{{ startMonth }} {{startYear }} - {{ endView }}</span> | |
+ <h5 class="institution" data-bind="text: institution"></h5> | |
+ <span data-bind="if: startYear()" class="subheading"> | |
+ <span data-bind="text: startMonth"></span> <span data-bind="text: startYear"></span> - <span data-bind="text: endView"></span> | |
+ </span> | |
</div> | |
<span data-bind="attr: {class: expanded() ? 'fa toggle-icon fa-angle-down' : 'fa toggle-icon fa-angle-up'}"></span> | |
</div> | |
- <div data-bind="attr: {id: 'jobCard' + $index(), aria-labelledby: 'jobHeading' + $index()}" class="panel-collapse collapse"> | |
+ <div data-bind="attr: {id: 'jobCard' + $index(), 'aria-labelledby': 'jobHeading' + $index()}" class="panel-collapse collapse"> | |
<div class="panel-body"> | |
- <span data-bind="if: department().length"><h5>Department:</h5> {{ department }}</span> | |
- <span data-bind="if: title().length"><h5>Title:</h5> {{ title }}</span> | |
- <span data-bind="if: startYear()"><h5>Dates:</h5> {{ startMonth }} {{startYear }} - {{ endView }}</span> | |
+ <span data-bind="if: department().length"><h5>Department:</h5> <span data-bind="text: department"></span></span> | |
+ <span data-bind="if: title().length"><h5>Title:</h5> <span data-bind="text: title"></span></span> | |
+ <span data-bind="if: startYear()"><h5>Dates:</h5> | |
+ <span data-bind="text: startMonth"></span> <span data-bind="text: startYear"></span> - <span data-bind="text: endView"></span> | |
+ </span> | |
</div> | |
</div> | |
</div> | |
@@ -156,7 +160,7 @@ | |
<div class="panel panel-default"> | |
<div class="panel-heading no-bottom-border"> | |
<div> | |
- <h5>{{ institution }}</h5> | |
+ <h5 data-bind="text: institution"></h5> | |
</div> | |
</div> | |
</div> | |
diff --git a/website/templates/include/profile/names.mako b/website/templates/include/profile/names.mako | |
index 1ff35b3..1855fa8 100644 | |
--- a/website/templates/include/profile/names.mako | |
+++ b/website/templates/include/profile/names.mako | |
@@ -54,11 +54,11 @@ | |
<tbody> | |
<tr> | |
<td>APA</td> | |
- <td class="overflow-block" width="30%">{{ citeApa }}</td> | |
+ <td class="overflow-block" width="30%"><span data-bind="text: citeApa"></span></td> | |
</tr> | |
<tr> | |
<td>MLA</td> | |
- <td class="overflow-block" width="30%">{{ citeMla }}</td> | |
+ <td class="overflow-block" width="30%"><span data-bind="text: citeMla"></span></td> | |
</tr> | |
</tbody> | |
</table> | |
@@ -80,7 +80,7 @@ | |
<!-- Flashed Messages --> | |
<div class="help-block"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
</form> | |
diff --git a/website/templates/include/profile/schools.mako b/website/templates/include/profile/schools.mako | |
index 1de214b..34f9bd9 100644 | |
--- a/website/templates/include/profile/schools.mako | |
+++ b/website/templates/include/profile/schools.mako | |
@@ -16,7 +16,7 @@ | |
<div> | |
<div class="well well-sm sort-handle"> | |
- <span>Position {{ $index() + 1 }}</span> | |
+ <span>Position <span data-bind="text: $index() + 1"></span></span> | |
<span data-bind="visible: $parent.contentsLength() > 1"> | |
[ drag to reorder ] | |
</span> | |
@@ -117,7 +117,7 @@ | |
<!-- Flashed Messages --> | |
<div class="help-block"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
</form> | |
@@ -137,16 +137,20 @@ | |
<div class="panel panel-default"> | |
<div class="panel-heading card-heading" data-bind="click: toggle(), attr: {id: 'schoolHeading' + $index(), href: '#schoolCard' + $index()}" role="button" data-toggle="collapse" aria-controls="card" aria-expanded="false"> | |
<div class="header-content"> | |
- <h5 class="institution">{{ institution }}</h5> | |
- <span data-bind="if: startYear()" class="subheading">{{ startMonth }} {{startYear }} - {{ endView }}</span> | |
+ <h5 class="institution" data-bind="text: institution"></h5> | |
+ <span data-bind="if: startYear()" class="subheading"> | |
+ <span data-bind="text: startMonth"></span> <span data-bind="text: startYear"></span> - <span data-bind="text: endView"></span> | |
+ </span> | |
</div> | |
<span data-bind="attr: {class: expanded() ? 'fa toggle-icon fa-angle-down' : 'fa toggle-icon fa-angle-up'}"></span> | |
</div> | |
- <div data-bind="attr: {id: 'schoolCard' + $index(), aria-labelledby: 'schoolHeading' + $index()}" class="panel-collapse collapse"> | |
+ <div data-bind="attr: {id: 'schoolCard' + $index(), 'aria-labelledby': 'schoolHeading' + $index()}" class="panel-collapse collapse"> | |
<div class="panel-body"> | |
- <span data-bind="if: department().length"><h5>Department:</h5> {{ department }}</span> | |
- <span data-bind="if: degree().length"><h5>Degree:</h5> {{ degree }}</span> | |
- <span data-bind="if: startYear()"><h5>Dates:</h5> {{ startMonth }} {{startYear }} - {{ endView }}</span> | |
+ <span data-bind="if: department().length"><h5>Department:</h5> <span data-bind="text: department"></span></span> | |
+ <span data-bind="if: degree().length"><h5>Degree:</h5> <span data-bind="text: degree"></span></span> | |
+ <span data-bind="if: startYear()"><h5>Dates:</h5> | |
+ <span data-bind="text: startMonth"></span> <span data-bind="text: startYear"></span> - <span data-bind="text: endView"></span> | |
+ </span> | |
</div> | |
</div> | |
</div> | |
@@ -155,7 +159,7 @@ | |
<div class="panel panel-default"> | |
<div class="panel-heading no-bottom-border"> | |
<div> | |
- <h5>{{ institution }}</h5> | |
+ <h5 data-bind="text: institution"></h5> | |
</div> | |
</div> | |
</div> | |
diff --git a/website/templates/include/profile/social.mako b/website/templates/include/profile/social.mako | |
index 9ec031e..8afc3e1 100644 | |
--- a/website/templates/include/profile/social.mako | |
+++ b/website/templates/include/profile/social.mako | |
@@ -137,7 +137,7 @@ | |
<!-- Flashed Messages --> | |
<div class="help-block flashed-message"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
</div> | |
@@ -153,14 +153,14 @@ | |
<tr data-bind="if: hasProfileWebsites()"> | |
<td data-bind="visible: profileWebsites().length > 1">Personal websites</td> | |
<td data-bind="visible: profileWebsites().length === 1">Personal website</td> | |
- <td data-bind="foreach: profileWebsites"><a target="_blank" data-bind="attr.href: $data">{{ $data }}</a></br></td> | |
+ <td data-bind="foreach: profileWebsites"><a target="_blank" data-bind="attr: {href: $data}, text: $data"></a><br></td> | |
</tr> | |
</tbody> | |
<tbody data-bind="foreach: values"> | |
<tr data-bind="if: value"> | |
- <td>{{ label }}</td> | |
- <td><a target="_blank" data-bind="attr.href: value">{{ text }}</a></td> | |
+ <td><span data-bind="text: label"></span></td> | |
+ <td><a target="_blank" data-bind="attr: {href: value}, text: text"></a></td> | |
</tr> | |
</tbody> | |
</table> | |
diff --git a/website/templates/institution.mako b/website/templates/institution.mako | |
index 91c967d..891439e 100644 | |
--- a/website/templates/institution.mako | |
+++ b/website/templates/institution.mako | |
@@ -11,9 +11,9 @@ | |
<div class="dashboard-header dashboard-header-institution"> | |
<div class="row" style="text-align: center"> | |
% if banner_path: | |
- <div class="col-sm-6 col-sm-offset-3"><img src="${ banner_path }"></div> | |
+ <div class="col-sm-6 col-sm-offset-3"><img height="110px" src="${ banner_path }"></div> | |
% else: | |
- <div class="col-sm-3 col-sm-offset-2"><img class="img-circle" height="110" width="110" src=${ logo_path }></div> | |
+ <div class="col-sm-3 col-sm-offset-2"><img class="img-circle" height="110px" width="110px" src=${ logo_path }></div> | |
<div class="col-sm-3"> | |
<h2>${ name }</h2> | |
% if description: | |
@@ -44,7 +44,7 @@ | |
logoPath: ${ logo_path | sjson, n}, | |
}, | |
currentUser: { | |
- 'id': '${user_id}' | |
+ 'id': ${ user_id | sjson, n } | |
} | |
}); | |
</script> | |
diff --git a/website/templates/landing.mako b/website/templates/landing.mako | |
index a003055..c585f07 100644 | |
--- a/website/templates/landing.mako | |
+++ b/website/templates/landing.mako | |
@@ -71,7 +71,7 @@ | |
<!-- Flashed Messages --> | |
<div class="help-block osf-box-lt" > | |
- <p data-bind="html: flashMessage, attr.class: flashMessageClass" class=""></p> | |
+ <p data-bind="html: flashMessage, attr: {class: flashMessageClass}" class=""></p> | |
</div> | |
<!-- ko ifnot: submitted --> | |
<div> | |
@@ -258,14 +258,14 @@ | |
</div> | |
<div class="col-xs-8"> | |
<h3>Making research reproducible & verifiable</h3> | |
- <p>The OSF helps our students understand and apply sound data management principles to their work. And since we have easy access to all of the files the students are working with, it greatly enhances our ability to offer them constructive guidance.<br/><small><em>Richard Ball, Professor of Economics, Haverford College</em></small></em></small></p> | |
+ <p>The OSF helps our students understand and apply sound data management principles to their work. And since we have easy access to all of the files the students are working with, it greatly enhances our ability to offer them constructive guidance.<br/><small><em>Richard Ball, Professor of Economics, Haverford College</em></small></p> | |
</div> | |
</div> | |
<div class="row hidden-xs hidden-sm"> | |
<div class="col-md-7 col-md-offset-2"> | |
<h3>Version control makes life easier</h3> | |
- <p>The OSF makes version control effortless. My PI, my lab mates, and I have access to previous versions of a file at any time—and the most current version is always readily available.<br/><small><em>Erica Baranski, PhD Student, Social and Personality Psychology Funder Lab, UC Riverside</em></small></em></small></p> | |
+ <p>The OSF makes version control effortless. My PI, my lab mates, and I have access to previous versions of a file at any time—and the most current version is always readily available.<br/><small><em>Erica Baranski, PhD Student, Social and Personality Psychology Funder Lab, UC Riverside</em></small></p> | |
</div> | |
<div class="col-md-3"> | |
<img src="/static/img/front-page/user3.jpg" class="img-circle img-responsive" alt="Erica Baranski" /> | |
@@ -278,7 +278,7 @@ | |
</div> | |
<div class="col-md-7"> | |
<h3>A centralized hub of information</h3> | |
- <p>The OSF creates a centralized hub of information where I can oversee a diversity of research projects across multiple classes. The centralization, organization and anywhere-access save me the time and energy necessary for managing these projects.<br/><small><em>Anne Allison, Associate Professor of Biology at Piedmont Virginia Community College</em></small></em></small></p> | |
+ <p>The OSF creates a centralized hub of information where I can oversee a diversity of research projects across multiple classes. The centralization, organization and anywhere-access save me the time and energy necessary for managing these projects.<br/><small><em>Anne Allison, Associate Professor of Biology at Piedmont Virginia Community College</em></small></p> | |
</div> | |
</div> | |
diff --git a/website/templates/log_list.mako b/website/templates/log_list.mako | |
index 4911002..8b41405 100644 | |
--- a/website/templates/log_list.mako | |
+++ b/website/templates/log_list.mako | |
@@ -74,7 +74,7 @@ | |
</span> | |
<div class='help-block absolute-bottom'> | |
<ul class="pagination pagination-sm" data-bind="foreach: paginators"> | |
- <li data-bind="css: style"><a href="#" data-bind="click: handler, html: text"></a></li> | |
+ <li data-bind="css: style"><a href="#" data-bind="click: handler, text: text"></a></li> | |
</ul> | |
</div> | |
diff --git a/website/templates/log_templates.mako b/website/templates/log_templates.mako | |
index 4a7bba5..3aa9b8b 100644 | |
--- a/website/templates/log_templates.mako | |
+++ b/website/templates/log_templates.mako | |
@@ -132,17 +132,14 @@ reordered contributors for | |
</script> | |
<script type="text/html" id="checked_in"> | |
-checked in {{ params.kind }} | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> | |
- {{ stripSlash(params.path) }}</a> | |
-from | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
+checked in <span data-bind="text: params.kind"></span> | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> | |
+from <a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="checked_out"> | |
-checked out {{ params.kind }} | |
-<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> | |
- {{ stripSlash(params.path) }}</a> | |
+checked out <span data-bind="text: params.kind"></span> | |
+<a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> | |
from | |
<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
@@ -178,23 +175,23 @@ from | |
</script> | |
<script type="text/html" id="file_tag_added"> | |
-tagged <a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> {{ stripSlash(params.path) }}</a> | |
-in <a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+tagged <a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> | |
+in <a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
as <a data-bind="attr: {href: '/search/?q=%22' + params.tag + '%22'}, text: params.tag"></a> | |
in OSF Storage | |
</script> | |
<script type="text/html" id="file_tag_removed"> | |
removed tag <a data-bind="attr: {href: '/search/?q=%22' + params.tag + '%22'}, text: params.tag"></a> | |
-from <a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect"> {{ stripSlash(params.path) }}</a> | |
-in <a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ nodeTitle }}</a> | |
+from <a class="overflow log-file-link" data-bind="click: NodeActions.addonFileRedirect, text: stripSlash(params.path)"></a> | |
+in <a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
in OSF Storage | |
</script> | |
<script type="text/html" id="edit_title"> | |
changed the title from <span class="overflow" data-bind="text: params.title_original"></span> | |
to | |
-<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}">{{ params.title_new }}</a> | |
+<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: params.title_new"></a> | |
</script> | |
<script type="text/html" id="project_registered"> | |
@@ -257,125 +254,125 @@ from | |
<script type="text/html" id="comment_added"> | |
added a comment | |
to | |
-{{#if params.file}} | |
+<!-- ko if: params.file --> | |
<a data-bind="attr: {href: params.file.url}, text: params.file.name"></a> | |
in | |
-{{/if}} | |
-{{#if params.wiki}} | |
+<!-- /ko --> | |
+<!-- ko if: params.wiki --> | |
wiki page | |
<a data-bind="attr: {href: params.wiki.url}, text: params.wiki.name"></a> | |
in | |
-{{/if}} | |
+<!-- /ko --> | |
<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="comment_updated"> | |
updated a comment | |
on | |
-{{#if params.file}} | |
+<!-- ko if: params.file --> | |
<a data-bind="attr: {href: params.file.url}, text: params.file.name"></a> | |
in | |
-{{/if}} | |
-{{#if params.wiki}} | |
+<!-- /ko --> | |
+<!-- ko: params.wiki --> | |
wiki page | |
<a data-bind="attr: {href: params.wiki.url}, text: params.wiki.name"></a> | |
in | |
-{{/if}} | |
+<!-- /ko --> | |
<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="comment_removed"> | |
deleted a comment | |
on | |
-{{#if params.file}} | |
+<!-- ko if: params.file --> | |
<a data-bind="attr: {href: params.file.url}, text: params.file.name"></a> | |
in | |
-{{/if}} | |
-{{#if params.wiki}} | |
+<!-- /ko --> | |
+<!-- ko if: params.wiki --> | |
wiki page | |
<a data-bind="attr: {href: params.wiki.url}, text: params.wiki.name"></a> | |
in | |
-{{/if}} | |
+<!-- /ko --> | |
<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="comment_restored"> | |
restored a comment | |
on | |
-{{#if params.file}} | |
+<!-- ko if: params.file --> | |
<a data-bind="attr: {href: params.file.url}, text: params.file.name"></a> | |
in | |
-{{/if}} | |
-{{#if params.wiki}} | |
+<!-- /ko --> | |
+<!-- ko if: params.wiki --> | |
wiki page | |
<a data-bind="attr: {href: params.wiki.url}, text: params.wiki.name"></a> | |
in | |
-{{/if}} | |
+<!-- /ko --> | |
<a class="log-node-title-link overflow" data-bind="attr: {href: nodeUrl}, text: nodeTitle"></a> | |
</script> | |
<script type="text/html" id="made_contributor_visible"> | |
- {{#if log.anonymous}} | |
+ <!-- ko if: log.anonymous --> | |
changed a non-bibliographic contributor to a bibliographic contributor on | |
- {{/if}} | |
- {{#ifnot log.anonymous}} | |
+ <!-- /ko --> | |
+ <!-- ko ifnot: log.anonymous --> | |
made non-bibliographic contributor | |
<span data-bind="html: displayContributors"></span> | |
a bibliographic contributor on | |
- {{/ifnot}} | |
+ <!-- /ko --> | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
</script> | |
<script type="text/html" id="made_contributor_invisible"> | |
- {{#if log.anonymous}} | |
+ <!-- ko if: log.anonymous --> | |
changed a bibliographic contributor to a non-bibliographic contributor on | |
- {{/if}} | |
- {{#ifnot log.anonymous}} | |
+ <!-- /ko --> | |
+ <!-- ko ifnot: log.anonymous --> | |
made bibliographic contributor | |
<span data-bind="html: displayContributors"></span> | |
a non-bibliographic contributor on | |
- {{/ifnot}} | |
+ <!-- /ko --> | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
</script> | |
<script type="text/html" id="addon_file_copied"> | |
- {{#if params.source.materialized.endsWith('/')}} | |
- copied <span class="overflow log-folder">{{ params.source.materialized }}</span> from {{ params.source.addon }} in | |
- <a class="log-node-title-link overflow" href="{{ params.source.node.url }}">{{ params.source.node.title }}</a> | |
- to <span class="overflow log-folder">{{ params.destination.materialized }}</span> in {{ params.destination.addon }} in | |
+ <!-- ko if: params.source.materialized.endsWith('/') --> | |
+ copied <span class="overflow log-folder" data-bind="text: params.source.materialized"></span> from <span data-bind="text: params.source.addon"></span> in | |
+ <a class="log-node-title-link overflow" data-bind="attr: {href: params.source.node.url}, text:params.source.node.title"></a> | |
+ to <span class="overflow log-folder" data-bind="text: params.destination.materialized"></span> in <span data-bind="text: params.destination.addon"></span> in | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
- {{/if}} | |
- {{#ifnot params.source.materialized.endsWith('/')}} | |
- copied <a href="{{ params.source.url }}" class="overflow">{{ params.source.materialized }}</a> from {{ params.source.addon }} in | |
- <a class="log-node-title-link overflow" href="{{ params.source.node.url }}">{{ params.source.node.title }}</a> | |
- to <a href="{{ params.destination.url }}" class="overflow">{{ params.destination.materialized }}</a> in {{ params.destination.addon }} in | |
+ <!-- /ko --> | |
+ <!-- ko ifnot: params.source.materialized.endsWith('/') --> | |
+ copied <a data-bind="attr: {href: params.source.url}, text: params.source.materialized" class="overflow"></a> from <span data-bind="text: params.source.addon"></span> in | |
+ <a class="log-node-title-link overflow" data-bind="attr: {href: params.source.node.url}, text: params.source.node.title"></a> | |
+ to <a data-bind="attr: {href: params.destination.url}, text: params.destination.materialized" class="overflow"></a> in <span data-bind="text: params.destination.addon"></span> in | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
- {{/ifnot}} | |
+ <!-- /ko --> | |
</script> | |
<script type="text/html" id="addon_file_moved"> | |
- {{#if params.source.materialized.endsWith('/')}} | |
- moved <span class="overflow">{{ params.source.materialized }}</span> from {{ params.source.addon }} in | |
- <a class="log-node-title-link overflow" href="{{ params.source.node.url }}">{{ params.source.node.title }}</a> | |
- to <span class="overflow log-folder">{{ params.destination.materialized }}</span> in {{ params.destination.addon }} in | |
+ <!-- ko if: params.source.materialized.endsWith('/') --> | |
+ moved <span class="overflow" data-bind="text: params.source.materialized"></span> from <span data-bind="text: params.source.addon"></span> in | |
+ <a class="log-node-title-link overflow" data-bind="attr: {href: params.source.node.url}, text: params.source.node.title"></a> | |
+ to <span class="overflow log-folder" data-bind="text: params.destination.materialized"></span> in <span data-bind="text: params.destination.addon"></span> in | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
- {{/if}} | |
- {{#ifnot params.source.materialized.endsWith('/')}} | |
- moved <span class="overflow">{{ params.source.materialized }}</span> from {{ params.source.addon }} in | |
- <a class="log-node-title-link overflow" href="{{ params.source.node.url }}">{{ params.source.node.title }}</a> | |
- to <a href="{{ params.destination.url }}" class="overflow">{{ params.destination.materialized }}</a> in {{ params.destination.addon }} in | |
+ <!-- /ko --> | |
+ <!-- ko ifnot: params.source.materialized.endsWith('/') --> | |
+ moved <span class="overflow" data-bind="text: params.source.materialized"></span> from <span data-bind="text: params.source.addon"></span> in | |
+ <a class="log-node-title-link overflow" data-bind="attr: {href: params.source.node.url}, text: params.source.node.title"></a> | |
+ to <a class="overflow" data-bind="attr: {href: params.destination.url}, text: params.destination.materialized"></a> in <span data-bind="text: params.destination.addon"></span> in | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
- {{/ifnot}} | |
+ <!-- /ko --> | |
</script> | |
<script type="text/html" id="addon_file_renamed"> | |
- renamed <span class="overflow">{{ params.source.materialized }}</span> | |
- {{#if params.source.materialized.endsWith('/')}} | |
- to <span class="overflow log-folder">{{ params.destination.materialized }}</span> in {{ params.destination.addon }} in | |
- {{/if}} | |
- {{#ifnot params.source.materialized.endsWith('/')}} | |
- to <a href="{{ params.destination.url }}" class="overflow">{{ params.destination.materialized }}</a> in {{ params.destination.addon }} in | |
- {{/ifnot}} | |
+ renamed <span class="overflow" data-bind="text: params.source.materialized"></span> | |
+ <!-- ko if: params.source.materialized.endsWith('/') --> | |
+ to <span class="overflow log-folder" data-bind="text: params.destination.materialized"></span> in <span data-bind="text: params.destination.addon"></span> in | |
+ <!-- /ko --> | |
+ <!-- ko ifnot: params.source.materialized.endsWith('/') --> | |
+ to <a class="overflow" data-bind="attr: {href: params.destination.url}, text: params.destination.materialized"></a> in <span data-bind="text: params.destination.addon"></span> in | |
+ <!-- /ko --> | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
</script> | |
@@ -388,40 +385,40 @@ on | |
</script> | |
<script type="text/html" id="citation_added"> | |
-added a citation ({{ params.citation.name }}) | |
+added a citation (<span data-bind="text: params.citation.name"></span>) | |
to | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
</script> | |
<script type="text/html" id="citation_edited"> | |
-{{#if params.citation.new_name}} | |
-updated a citation name from {{ params.citation.name }} to <strong>{{ params.citation.new_name }}</strong> | |
- {{#if params.citation.new_text}} | |
+<!-- ko if: params.citation.new_name --> | |
+updated a citation name from <span data-bind="text: params.citation.name"></span> to <strong data-bind="text: params.citation.new_name"></strong> | |
+ <!-- ko if: params.citation.new_text --> | |
and edited its text | |
- {{/if}} | |
-{{/if}} | |
-{{#ifnot params.citation.new_name}} | |
-edited the text of a citation ({{ params.citation.name }}) | |
-{{/ifnot}} | |
+ <!-- /ko --> | |
+<!-- /ko --> | |
+<!-- ko ifnot: params.citation.new_name --> | |
+edited the text of a citation (<span data-bind="text: params.citation.name"></span>) | |
+<!-- /ko --> | |
on | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
</script> | |
<script type="text/html" id="citation_removed"> | |
-removed a citation ({{ params.citation.name }}) | |
+removed a citation (<span data-bind="text: params.citation.name"></span>) | |
from | |
<a class="log-node-title-link overflow" data-bind="attr: {href: $parent.nodeUrl}, text: $parent.nodeTitle"></a> | |
</script> | |
<script type="text/html" id="primary_institution_changed"> | |
changed primary institution of <a class="log-node-title-link overflow" data-bind="text: nodeTitle, attr: {href: nodeUrl}"></a> | |
-{{#if params.previous_institution.name != 'None'}} | |
- from <a class="log-node-title-link overflow" href="/institutions/{{params.previous_institution.id}}">{{ params.previous_institution.name }}</a> | |
-{{/if}} | |
- to <a class="log-node-title-link overflow" href="/institutions/{{params.institution.id}}">{{ params.institution.name }}</a>. | |
+<!-- ko if: params.previous_institution.name != 'None' --><!-- TODO: Check datatypes here --> | |
+ from <a class="log-node-title-link overflow" data-bind="attr: {href: '/institutions/' + params.previous_institution.id}, text: params.previous_institution.name"></a> | |
+<!-- /ko --> | |
+ to <a class="log-node-title-link overflow" data-bind="attr: {href: '/institutions/' + params.institution.id}, text: params.institution.name"></a>. | |
</script> | |
<script type="text/html" id="primary_institution_removed"> | |
-removed <a class="log-node-title-link overflow" href="/institutions/{{params.institution.id}}">{{ params.institution.name }}</a> | |
+removed <a class="log-node-title-link overflow" data-bind="attr: {href: '/institutions/' + params.institution.id}, text: params.institution.name"></a> | |
as the primary institution of <a class="log-node-title-link overflow" data-bind="text: nodeTitle, attr: {href: nodeUrl}"></a>. | |
</script> | |
diff --git a/website/templates/prereg_landing_page.mako b/website/templates/prereg_landing_page.mako | |
index 40b673e..8936910 100644 | |
--- a/website/templates/prereg_landing_page.mako | |
+++ b/website/templates/prereg_landing_page.mako | |
@@ -23,7 +23,7 @@ | |
<%def name="existingPrereg(size=None)"> | |
<% size = size or '' %> | |
-<div id="existingPrereg${size}" class="prereg-existing-prereg p-md osf-box box-round clearfix m-b-lg" style="display:none; style="width: 100%;""> | |
+<div id="existingPrereg${size}" class="prereg-existing-prereg p-md osf-box box-round clearfix m-b-lg" style="display:none; width: 100%;"> | |
<p>Go to an existing preregistration:</p> | |
<input id="regDraftSearch${size}" class="form-control"></input> | |
<div class="p-xs"><a href="#" class="regDraftButton btn btn-primary disabled pull-right">Preregister</a></div> | |
diff --git a/website/templates/profile/account.mako b/website/templates/profile/account.mako | |
index 3367a23..06184f0 100644 | |
--- a/website/templates/profile/account.mako | |
+++ b/website/templates/profile/account.mako | |
@@ -28,7 +28,7 @@ | |
</thead> | |
<tbody> | |
<tr> | |
- <td>{{ profile().primaryEmail().address }}</td> | |
+ <td><span data-bind="text: profile().primaryEmail().address"></span></td> | |
<td></td> | |
</tr> | |
</tbody> | |
@@ -42,7 +42,7 @@ | |
</thead> | |
<tbody data-bind="foreach: profile().alternateEmails()"> | |
<tr> | |
- <td style="word-break: break-all;">{{ $data.address }}</td> | |
+ <td style="word-break: break-all;"><span data-bind="text: $data.address"></span></td> | |
<td style="width:150px;"><a data-bind="click: $parent.makeEmailPrimary">make primary</a></td> | |
<td style="width:50px;"><a data-bind="click: $parent.removeEmail"><i class="fa fa-times text-danger"></i></a></td> | |
</tr> | |
@@ -57,7 +57,7 @@ | |
<tbody> | |
<!-- ko foreach: profile().unconfirmedEmails() --> | |
<tr> | |
- <td style="word-break: break-all;">{{ $data.address }}</td> | |
+ <td style="word-break: break-all;"><span data-bind="text: $data.address"></span></td> | |
<td style="width:150px;"><a data-bind="click: $parent.resendConfirmation">resend confirmation</a></td> | |
<td style="width:50px;" ><a data-bind="click: $parent.removeEmail"><i class="fa fa-times text-danger"></i></a></td> | |
</tr> | |
diff --git a/website/templates/profile/addon_permissions.mako b/website/templates/profile/addon_permissions.mako | |
index 627e60a..28a2828 100644 | |
--- a/website/templates/profile/addon_permissions.mako | |
+++ b/website/templates/profile/addon_permissions.mako | |
@@ -28,9 +28,9 @@ | |
<script> | |
window.contextVars = $.extend(true, {}, window.contextVars, { | |
addonsWithNodes: { | |
- '${addon_short_name}': { | |
- shortName: '${addon_short_name}', | |
- fullName: '${addon_full_name}' | |
+ ${ addon_short_name | sjson, n }: { | |
+ shortName: ${ addon_short_name | sjson, n }, | |
+ fullName: ${ addon_full_name | sjson, n } | |
} | |
} | |
}); | |
diff --git a/website/templates/profile/oauth_app_detail.mako b/website/templates/profile/oauth_app_detail.mako | |
index ab79d72..beef167 100644 | |
--- a/website/templates/profile/oauth_app_detail.mako | |
+++ b/website/templates/profile/oauth_app_detail.mako | |
@@ -76,7 +76,7 @@ | |
<!-- Flashed Messages --> | |
<div class="help-block"> | |
- <p data-bind="html: $root.message, attr.class: $root.messageClass"></p> | |
+ <p data-bind="html: $root.message, attr: {class: $root.messageClass}"></p> | |
</div> | |
<div class="padded"> | |
diff --git a/website/templates/profile/personal_tokens_detail.mako b/website/templates/profile/personal_tokens_detail.mako | |
index c12c141..b8fc7f0 100644 | |
--- a/website/templates/profile/personal_tokens_detail.mako | |
+++ b/website/templates/profile/personal_tokens_detail.mako | |
@@ -37,27 +37,14 @@ | |
% for scope in scope_options: | |
<input type="checkbox" id="${scope[0]}" value="${scope[0]}" data-bind="checked: scopes"> | |
<label for="${scope[0]}">${scope[0]} </label> | |
- <i class="fa fa-info-circle text-muted" data-bind="tooltip: {title: '${scope[1]}', placement: 'bottom'}"></i> | |
+ <i class="fa fa-info-circle text-muted" data-bind="tooltip: {title: ${scope[1] | sjson, n }, placement: 'bottom'}"></i> | |
<br> | |
% endfor | |
</div> | |
<p data-bind="validationMessage: scopes, visible: $root.showMessages()" class="text-danger"></p> | |
</div> | |
- <br/> | |
- <div id="token-keys" class="border-box text-left" | |
- data-bind="visible: $root.showToken()"> | |
- <label>Token ID</label> | |
- <i class="fa fa-info-circle text-muted" data-bind="tooltip: {title:'ID used to authenticate with this token. This will be shown only once.', placement: 'bottom'}"></i> | |
- <span data-bind="text: token_id"></span> | |
- </div> | |
- | |
- <!-- Flashed Messages --> | |
- <div class="help-block"> | |
- <p data-bind="html: $root.message, attr.class: $root.messageClass"></p> | |
- </div> | |
- | |
- <div class="padded"> | |
+ <div class="padded action-buttons"> | |
<button type="reset" class="btn btn-default" | |
data-bind="click: $root.cancelChange.bind($root)">Cancel</button> | |
<button type="submit" class="btn btn-success" | |
@@ -67,6 +54,22 @@ | |
<button type="submit" class="btn btn-success" | |
data-bind="visible: !$root.isCreateView()">Save</button> | |
</div> | |
+ | |
+ <!-- Flashed Messages --> | |
+ <div class="help-block"> | |
+ <p data-bind="html: $root.message, attr: {class: $root.messageClass}"></p> | |
+ </div> | |
+ | |
+ <div id="token-keys" class="border-box text-left" | |
+ data-bind="visible: $root.showToken()"> | |
+ <div class="bg-danger f-w-xl token-warning">This is the only time your token will be displayed.</div> | |
+ <label class="f-w-xl">Token ID</label> | |
+ <i class="fa fa-info-circle text-muted" data-bind="tooltip: {title:'ID used to authenticate with this token. This will be shown only once.', placement: 'bottom'}"></i> | |
+ <span data-bind="text: token_id" id="token-id-text"></span> | |
+ <div> | |
+ <button type="button" class="btn btn-primary" data-clipboard-target="#token-id-text" id="copy-button"><i class="fa fa-copy"></i> Copy to clipboard</button> | |
+ </div> | |
+ </div> | |
</form> | |
</div> | |
diff --git a/website/templates/profile/user_settings_default.mako b/website/templates/profile/user_settings_default.mako | |
index 632fd1d..3b34eac 100644 | |
--- a/website/templates/profile/user_settings_default.mako | |
+++ b/website/templates/profile/user_settings_default.mako | |
@@ -3,7 +3,7 @@ | |
data-addon-short-name="${ addon_short_name }" | |
data-addon-name="${ addon_full_name }"> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src="${addon_icon_url}"></img> | |
+ <img class="addon-icon" src="${addon_icon_url}"> | |
<span data-bind="text:properName"></span> | |
<small> | |
<a data-bind="click: connectAccount" class="pull-right text-primary">Connect Account</a> | |
@@ -24,7 +24,7 @@ | |
<tbody data-bind="foreach: connectedNodes()"> | |
<tr> | |
<td class="authorized-nodes"> | |
- <!-- ko if: title --><a data-bind="attr.href: urls.view, text: title"></a><!-- /ko --> | |
+ <!-- ko if: title --><a data-bind="attr: {href: urls.view}, text: title"></a><!-- /ko --> | |
<!-- ko if: !title --><em>Private project</em><!-- /ko --> | |
</td> | |
<td> | |
diff --git a/website/templates/project/addon/citations_widget.mako b/website/templates/project/addon/citations_widget.mako | |
index 6271d7c..8296315 100644 | |
--- a/website/templates/project/addon/citations_widget.mako | |
+++ b/website/templates/project/addon/citations_widget.mako | |
@@ -1,17 +1,16 @@ | |
<script type="text/javascript"> | |
window.contextVars = $.extend(true, {}, window.contextVars, { | |
- ${short_name}: { | |
- folder_id: '${list_id | js_str}' | |
+ ${short_name | sjson , n }: { | |
+ folder_id: ${list_id | sjson, n } | |
} | |
}); | |
- | |
</script> | |
<div class="citation-picker"> | |
<input id="${short_name}StyleSelect" type="hidden" /> | |
</div> | |
<div id="${short_name}Widget" class="citation-widget"> | |
- <div class="spinner-loading-wrapper"> | |
- <div class="logo-spin logo-lg"></div> | |
- <p class="m-t-sm fg-load-message"> Loading citations...</p> | |
- </div> | |
-</div> | |
\ No newline at end of file | |
+ <div class="spinner-loading-wrapper"> | |
+ <div class="logo-spin logo-lg"></div> | |
+ <p class="m-t-sm fg-load-message"> Loading citations...</p> | |
+ </div> | |
+</div> | |
diff --git a/website/templates/project/addon/node_settings_default.mako b/website/templates/project/addon/node_settings_default.mako | |
index 92b792a..29ce044 100644 | |
--- a/website/templates/project/addon/node_settings_default.mako | |
+++ b/website/templates/project/addon/node_settings_default.mako | |
@@ -1,12 +1,10 @@ | |
<div id="${addon_short_name}Scope" class="scripted"> | |
<h4 class="addon-title"> | |
- <img class="addon-icon" src=${addon_icon_url}></img> | |
+ <img class="addon-icon" src=${addon_icon_url}> | |
${addon_full_name} | |
<small class="authorized-by"> | |
<span data-bind="if: nodeHasAuth"> | |
- authorized by <a data-bind="attr.href: urls().owner"> | |
- {{ownerName}} | |
- </a> | |
+ authorized by <a data-bind="attr: {href: urls().owner}, text: ownerName"></a> | |
% if not is_registration: | |
<a data-bind="click: deauthorize, visible: validCredentials" | |
class="text-danger pull-right addon-auth">Disconnect Account</a> | |
@@ -41,9 +39,9 @@ | |
<div class="col-md-12"> | |
<p class="break-word"> | |
<strong>Current Folder:</strong> | |
- <a href="{{ urls().files }}" data-bind="if: folderName"> | |
- {{ folderName }} | |
- </a> | |
+ <span data-bind="if: folderName"> | |
+ <a data-bind="attr: {href: urls().files}, text: folderName"></a> | |
+ </span> | |
<span class="text-muted" data-bind="ifnot: folderName"> | |
None | |
</span> | |
@@ -65,7 +63,7 @@ | |
<form data-bind="submit: submitSettings"> | |
<div class="break-word"> | |
<div data-bind="if: selected" class="alert alert-info ${addon_short_name}-confirm-dlg"> | |
- Connect <b>“{{ selectedFolderName }}”</b>? | |
+ Connect <b>“<span data-bind="text: selectedFolderName"></span>”</b>? | |
</div> | |
</div> | |
<div class="pull-right"> | |
@@ -84,6 +82,6 @@ | |
</div> | |
<!-- Flashed Messages --> | |
<div class="help-block"> | |
- <p data-bind="html: message, attr.class: messageClass"></p> | |
+ <p data-bind="html: message, attr: {class: messageClass}"></p> | |
</div> | |
</div> | |
diff --git a/website/templates/project/contributors.mako b/website/templates/project/contributors.mako | |
index 45d321d..6547069 100644 | |
--- a/website/templates/project/contributors.mako | |
+++ b/website/templates/project/contributors.mako | |
@@ -182,7 +182,7 @@ | |
<div> | |
<div class="btn-group"> | |
<button title="Copy to clipboard" class="btn btn-default btn-sm m-r-xs copy-button" | |
- data-bind="attr: {data-clipboard-text: linkUrl}" > | |
+ data-bind="attr: {'data-clipboard-text': linkUrl}" > | |
<i class="fa fa-copy"></i> | |
</button> | |
<input class="link-url" type="text" data-bind="value: linkUrl, attr:{readonly: readonly}, click: toggle, clickBubble: false" /> | |
@@ -314,7 +314,7 @@ | |
options: $parents[1].permissionList, | |
value: permission, | |
optionsText: optionsText.bind(permission), | |
- style: { font-weight: permissionChange() ? 'normal' : 'bold' }" | |
+ style: { 'font-weight': permissionChange() ? 'normal' : 'bold' }" | |
> | |
</select> | |
</span> | |
@@ -357,7 +357,7 @@ | |
</div> | |
% endif | |
<div data-bind="foreach: messages"> | |
- <div data-bind="css: cssClass">{{ text }}</div> | |
+ <div data-bind="css: cssClass, text: text"></div> | |
</div> | |
</%def> | |
diff --git a/website/templates/project/edit_draft_registration.mako b/website/templates/project/edit_draft_registration.mako | |
index aa36265..041e727 100644 | |
--- a/website/templates/project/edit_draft_registration.mako | |
+++ b/website/templates/project/edit_draft_registration.mako | |
@@ -33,7 +33,7 @@ | |
<div data-bind="visible: hasRequiredQuestions" class="progress progress-bar-md"> | |
<div data-bind="progress: completion"></div> | |
<div class="progress-bar progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" | |
- data-bind="attr.aria-completion: completion, | |
+ data-bind="attr: {'aria-completion': completion}, | |
style: {width: completion() + '%'}"> | |
</div> | |
</div> | |
diff --git a/website/templates/project/modal_add_contributor.mako b/website/templates/project/modal_add_contributor.mako | |
index 367e630..a2645ec 100644 | |
--- a/website/templates/project/modal_add_contributor.mako | |
+++ b/website/templates/project/modal_add_contributor.mako | |
@@ -58,7 +58,7 @@ | |
<a | |
class="btn btn-success contrib-button btn-mini" | |
data-bind="visible: !contributor.added, | |
- click:$root.add, | |
+ click:$root.add.bind($root), | |
tooltip: {title: 'Add contributor'}" | |
><i class="fa fa-plus"></i></a> | |
<div data-bind="visible: contributor.added, | |
@@ -112,10 +112,10 @@ | |
<div class='help-block'> | |
<div data-bind='if: foundResults'> | |
<ul class="pagination pagination-sm" data-bind="foreach: paginators"> | |
- <li data-bind="css: style"><a href="#" data-bind="click: handler, html: text"></a></li> | |
+ <li data-bind="css: style"><a href="#" data-bind="click: handler, text: text"></a></li> | |
</ul> | |
<p> | |
- <a href="#"data-bind="click:gotoInvite">Add <strong><em>{{query}}</em></strong> as an unregistered contributor</a>. | |
+ <a href="#" data-bind="click:gotoInvite">Add <strong><em data-bind="text: query"></em></strong> as an unregistered contributor</a>. | |
</p> | |
</div> | |
<div data-bind="if: showLoading"> | |
@@ -123,7 +123,7 @@ | |
</div> | |
<div data-bind="if: noResults"> | |
No results found. Try a more specific search or | |
- <a href="#" data-bind="click:gotoInvite">add <strong><em>{{query}}</em></strong> as an unregistered contributor</a>. | |
+ <a href="#" data-bind="click:gotoInvite">add <strong><em data-bind="text: query"></em></strong> as an unregistered contributor</a>. | |
</div> | |
</div> | |
</div><!-- ./col-md --> | |
diff --git a/website/templates/project/modal_add_pointer.mako b/website/templates/project/modal_add_pointer.mako | |
index 3dfe591..b6d3dc3 100644 | |
--- a/website/templates/project/modal_add_pointer.mako | |
+++ b/website/templates/project/modal_add_pointer.mako | |
@@ -69,7 +69,7 @@ | |
<div data-bind='if: foundResults'> | |
<ul class="pagination pagination-sm" data-bind="foreach: paginators"> | |
- <li data-bind="css: style"><a href="#" data-bind="click: handler, html: text"></a></li> | |
+ <li data-bind="css: style"><a href="#" data-bind="click: handler, text: text"></a></li> | |
</ul> | |
</div> | |
</div> | |
diff --git a/website/templates/project/modal_remove_contributor.mako b/website/templates/project/modal_remove_contributor.mako | |
index f41df7d..8ff4765 100644 | |
--- a/website/templates/project/modal_remove_contributor.mako | |
+++ b/website/templates/project/modal_remove_contributor.mako | |
@@ -13,7 +13,7 @@ | |
<div data-bind='if:page() === REMOVE'> | |
<div class="form-group"> | |
<span>Do you want to remove <b data-bind="text:removeSelf() ? 'yourself' : contributorToRemove()['fullname']"></b> from | |
- <b>{{title}}</b>, or from <b>{{title}}</b> and every component in it?</span> | |
+ <b data-bind="text: title"></b>, or from <b data-bind="text: title"></b> and every component in it?</span> | |
</div> | |
<div id="remove-page-radio-buttons" class="col-md-8" align="left"> | |
<div class="radio"> | |
@@ -34,7 +34,7 @@ | |
<!-- removeNoChildren page --> | |
<div data-bind='if:page() === REMOVE_NO_CHILDREN'> | |
<div class="form-group" data-bind="if:contributorToRemove"> | |
- <span>Remove <b data-bind="text:removeSelf() ? 'yourself' : contributorToRemove()['fullname']"></b> from {{title}}?</span> | |
+ <span>Remove <b data-bind="text:removeSelf() ? 'yourself' : contributorToRemove()['fullname']"></b> from <span data-bind="text: title"></span>?</span> | |
</div> | |
</div><!-- end removeNoChildren page --> | |
diff --git a/website/templates/project/nodes_privacy.mako b/website/templates/project/nodes_privacy.mako | |
index e9e331e..845a2fe 100644 | |
--- a/website/templates/project/nodes_privacy.mako | |
+++ b/website/templates/project/nodes_privacy.mako | |
@@ -1,4 +1,4 @@ | |
-<div id="nodesPrivacy" class="modal fade" div style="display: none;"> | |
+<div id="nodesPrivacy" class="modal fade"> | |
<div class="modal-dialog modal-md"> | |
<div style="display: none;" data-bind="visible: true"> | |
<div class="modal-content"> | |
diff --git a/website/templates/project/project.mako b/website/templates/project/project.mako | |
index 0f30b7a..3f8aed3 100644 | |
--- a/website/templates/project/project.mako | |
+++ b/website/templates/project/project.mako | |
@@ -285,7 +285,7 @@ | |
<span data-bind="text: chicago"></span> | |
<div data-bind="validationOptions: {insertMessages: false, messagesOnModified: false}, foreach: citations"> | |
<!-- ko if: view() === 'view' --> | |
- <div class="f-w-xl m-t-md">{{name}} | |
+ <div class="f-w-xl m-t-md"><span data-bind="text: name"></span> | |
% if 'admin' in user['permissions'] and not node['is_registration']: | |
<!-- ko ifnot: $parent.editing() --> | |
<button class="btn btn-default btn-sm" data-bind="click: function() {edit($parent)}"><i class="fa fa-edit"></i> Edit</button> | |
diff --git a/website/templates/project/register.mako b/website/templates/project/register.mako | |
index 768101e..98796df 100644 | |
--- a/website/templates/project/register.mako | |
+++ b/website/templates/project/register.mako | |
@@ -9,13 +9,13 @@ | |
<div class="col-md-2"> | |
<ul class="nav nav-stacked list-group" data-bind="foreach: {data: metaSchema.schema.pages, as: 'page'}, visible: metaSchema.schema.pages.length > 1"> | |
<li class="re-navbar"> | |
- <a class="registration-editor-page" id="top-nav" style="text-align: left; font-weight:bold;" data-bind="text: title, attr.href: '#' + page.id"> | |
+ <a class="registration-editor-page" id="top-nav" style="text-align: left; font-weight:bold;" data-bind="text: title, attr: {href: '#' + page.id}"> | |
</a> | |
<span class="btn-group-vertical" role="group"> | |
<ul class="list-group" data-bind="foreach: {data: Object.keys(page.questions), as: 'qid'}"> | |
<span data-bind="with: page.questions[qid]"> | |
<li class="registration-editor-question list-group-item"> | |
- <a data-bind="text: $data.nav || $data.title, attr.href: '#' + qid"></a> | |
+ <a data-bind="text: $data.nav || $data.title, attr: {href: '#' + qid}"></a> | |
</li> | |
</span> | |
</ul> | |
@@ -25,11 +25,11 @@ | |
</div> | |
<div class="col-md-9" style="padding-left: 30px"> | |
<div data-bind="foreach: {data: metaSchema.pages, as: 'page'}"> | |
- <h3 data-bind="attr.id: page.id, text: page.title"></h3> | |
+ <h3 data-bind="attr: {id: page.id}, text: page.title"></h3> | |
<div class="row"> | |
<div data-bind="foreach: {data: page.questions, as: 'question'}"> | |
<div class="row"> | |
- <h4 data-bind="attr.id: question.id, text: question.title"></h4> | |
+ <h4 data-bind="attr: {id: question.id}, text: question.title"></h4> | |
<div class="col-md-12"> | |
<p> | |
<span data-bind="previewQuestion: $root.editor.context(question, $root.editor)"></span> | |
diff --git a/website/templates/project/register_draft.mako b/website/templates/project/register_draft.mako | |
index 14c5bfa..d56f574 100644 | |
--- a/website/templates/project/register_draft.mako | |
+++ b/website/templates/project/register_draft.mako | |
@@ -12,10 +12,10 @@ | |
<div class="row"> | |
<div class="col-lg-12 large-12" style="padding-left: 30px"> | |
<div data-bind="foreach: {data: draft.pages, as: 'page'}"> | |
- <h3 data-bind="attr.id: page.id, text: page.title"></h3> | |
+ <h3 data-bind="attr: {id: page.id}, text: page.title"></h3> | |
<div data-bind="foreach: {data: page.questions, as: 'question'}"> | |
<p> | |
- <strong data-bind="attr.id: question.id, text: question.title"></strong>: | |
+ <strong data-bind="attr: {id: question.id}, text: question.title"></strong>: | |
<span data-bind="previewQuestion: $root.editor.context(question, $root.editor)"></span> | |
</p> | |
</div> | |
diff --git a/website/templates/project/registration_editor_extensions.mako b/website/templates/project/registration_editor_extensions.mako | |
index b2f8d06..42c8d4e 100644 | |
--- a/website/templates/project/registration_editor_extensions.mako | |
+++ b/website/templates/project/registration_editor_extensions.mako | |
@@ -12,7 +12,7 @@ | |
class="btn btn-xs btn-danger fa fa-times"></button> | |
</div> | |
</div> | |
- <div data-bind="attr.id: $data.uid, osfUploader"> | |
+ <div data-bind="attr: {id: $data.uid}, osfUploader"><!-- TODO: osfUploader attribute may not connect to anything? --> | |
<div class="spinner-loading-wrapper"> | |
<div class="logo-spin logo-lg"></div> | |
<p class="m-t-sm fg-load-message"> Loading files... </p> | |
@@ -31,7 +31,7 @@ | |
</div> | |
<a data-bind="click: toggleUploader">Attach File</a> | |
<span data-bind="visible: showUploader"> | |
- <div data-bind="attr.id: $data.uid, osfUploader"> | |
+ <div data-bind="attr: {id: $data.uid}, osfUploader"> | |
<div class="container"> | |
<p class="m-t-sm fg-load-message"> | |
<span class="logo-spin logo-sm"></span> Loading files... | |
diff --git a/website/templates/project/registration_editor_templates.mako b/website/templates/project/registration_editor_templates.mako | |
index 0bf38d3..f617563 100644 | |
--- a/website/templates/project/registration_editor_templates.mako | |
+++ b/website/templates/project/registration_editor_templates.mako | |
@@ -11,7 +11,7 @@ | |
<script type="text/html" id="match"> | |
<input data-bind="valueUpdate: 'keyup', | |
value: value, | |
- attr.placeholder: match" type="text" class="form-control" /> | |
+ attr: {placeholder: match}" type="text" class="form-control" /> | |
</script> | |
<script type="text/html" id="textarea"> | |
<textarea data-bind="valueUpdate: 'keyup', | |
@@ -47,7 +47,7 @@ | |
<p data-bind="if: Boolean(option.tooltip)"> | |
<input type="radio" data-bind="checked: $parent.value, | |
value: option.text"/> | |
- {{option.text}} | |
+ <label data-bind="text: option.text"></label> | |
<span data-bind="tooltip: {title: option.tooltip}" class="fa fa-info-circle"></span> | |
</p> | |
</div> | |
@@ -55,15 +55,15 @@ | |
<script type="text/html" id="multiselect"> | |
<div class="col-md-12" data-bind="foreach: {data: options, as: 'option'}"> | |
<p data-bind="if: !Boolean(option.tooltip)"> | |
- <input type="checkbox" data-bind="attr.value: option, | |
+ <input type="checkbox" data-bind="attr: {value: option}, | |
checked: $parent.value, | |
checkedValue: option" /> | |
<span data-bind="text: option"></span> | |
</p> | |
- <p data-bind="if: Boolean(option.tooltip)"> | |
- <input type="checkbox" data-bind="attr.value: option.text, | |
+ <p data-bind="if: Boolean(option.tooltip)"> <!-- TODO: Verify checkboxes --> | |
+ <input type="checkbox" data-bind="attr: {value: option.text}, | |
checked: $parent.value, | |
- checkedValue: option" | |
+ checkedValue: option"> | |
<span data-bind="text: option.text, tooltip: {title: option.tooltip}"></span> | |
</p> | |
</div> | |
diff --git a/website/templates/project/registration_preview.mako b/website/templates/project/registration_preview.mako | |
index 19a5da7..eae6ee9 100644 | |
--- a/website/templates/project/registration_preview.mako | |
+++ b/website/templates/project/registration_preview.mako | |
@@ -4,13 +4,13 @@ | |
<!-- <div class="span8 col-md-2 columns eight large-8"> | |
<ul class="nav nav-stacked list-group" data-bind="foreach: {data: schema.pages, as: 'page'}"> | |
<li class="re-navbar"> | |
- <a class="registration-editor-page" id="top-nav" style="text-align: left; font-weight:bold;" data-bind="text: title, attr.href: '#' + page.id"> | |
+ <a class="registration-editor-page" id="top-nav" style="text-align: left; font-weight:bold;" data-bind="text: title, attr: {href: '#' + page.id}"> | |
</a> | |
<span class="btn-group-vertical" role="group"> | |
<ul class="list-group" data-bind="foreach: {data: Object.keys(page.questions), as: 'qid'}"> | |
<span data-bind="with: page.questions[qid]"> | |
<li class="registration-editor-question list-group-item"> | |
- <a data-bind="text: nav, attr.href: '#' + qid"></a> | |
+ <a data-bind="text: nav, attr: {href: '#' + qid}"></a> | |
</li> | |
</span> | |
</ul> | |
@@ -20,10 +20,10 @@ | |
</div> --> | |
<div class="span8 col-md-9 columns eight large-8" style="padding-left: 30px"> | |
<div data-bind="foreach: {data: schema.pages, as: 'page'}"> | |
- <h3 data-bind="attr.id: page.id, text: page.title"></h3> | |
+ <h3 data-bind="attr: {id: page.id}, text: page.title"></h3> | |
<div data-bind="foreach: {data: Object.keys(page.questions), as: 'qid'}"> | |
<span data-bind="with: $parent.questions[qid]"> | |
- <h4 data-bind="attr.id: qid, text: title"></h4> | |
+ <h4 data-bind="attr: {id: qid}, text: title"></h4> | |
<span data-bind="text: qid.description"></span> | |
</span> | |
</div> | |
diff --git a/website/templates/project/registrations.mako b/website/templates/project/registrations.mako | |
index 65a93da..0810409 100644 | |
--- a/website/templates/project/registrations.mako | |
+++ b/website/templates/project/registrations.mako | |
@@ -71,7 +71,7 @@ | |
<h4 class="list-group-item-heading"> | |
<div data-bind="visible: hasRequiredQuestions" class="progress progress-bar-md"> | |
<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" | |
- data-bind="attr.aria-completion: completion, | |
+ data-bind="attr: {'aria-completion': completion}, | |
style: {width: completion() + '%'}"> | |
<span class="sr-only"></span> | |
</div> | |
@@ -113,7 +113,7 @@ | |
</span> | |
--> | |
<span data-bind="ifnot: requiresApproval"> | |
- <a class="btn btn-success" data-bind="attr.href: urls.register_page, | |
+ <a class="btn btn-success" data-bind="attr: {href: urls.register_page}, | |
css: {'disabled': !isApproved}">Register</a> | |
</span> | |
</div> | |
@@ -142,7 +142,7 @@ | |
<label> | |
<input type="radio" name="selectedDraftSchema" | |
data-bind="attr {value: id}, checked: $root.selectedSchemaId" /> | |
- {{ schema.title }} | |
+ <span data-bind="text: schema.title"></span> | |
<!-- ko if: schema.description --> | |
<i data-bind="tooltip: {title: schema.description}" class="fa fa-info-circle"> </i> | |
<!-- /ko --> | |
diff --git a/website/templates/project/settings.mako b/website/templates/project/settings.mako | |
index 024b940..0195f4c 100644 | |
--- a/website/templates/project/settings.mako | |
+++ b/website/templates/project/settings.mako | |
@@ -77,7 +77,7 @@ | |
<div id="projectSettings" class="panel-body"> | |
<div class="form-group"> | |
<label>Category:</label> | |
- <select data-bind="attr.disabled: disabled, options: categoryOptions, optionsValue: 'value', optionsText: 'label', value: selectedCategory"></select> | |
+ <select data-bind="attr: {disabled: disabled}, options: categoryOptions, optionsValue: 'value', optionsText: 'label', value: selectedCategory"></select> | |
<span data-bind="if: disabled" class="help-block"> | |
A top-level project's category cannot be changed | |
</span> | |
diff --git a/website/templates/project/view_file.mako b/website/templates/project/view_file.mako | |
index f0907eb..5a16b6b 100644 | |
--- a/website/templates/project/view_file.mako | |
+++ b/website/templates/project/view_file.mako | |
@@ -67,8 +67,8 @@ | |
<div class="row"> | |
<div id="externalView" class="col-sm-9"></div> | |
<div id="mfrIframeParent" class="col-sm-9"> | |
- <div id="mfrIframe" class="mfr mfr-file"></div> | |
- </div> | |
+ <div id="mfrIframe" class="mfr mfr-file"></div> | |
+ </div> | |
<!-- This section is built by mithril in revisions.js --> | |
<div class="file-view-panels col-sm-3"></div> | |
diff --git a/website/templates/public/explore.mako b/website/templates/public/explore.mako | |
index 2214469..f88536a 100644 | |
--- a/website/templates/public/explore.mako | |
+++ b/website/templates/public/explore.mako | |
@@ -13,7 +13,7 @@ | |
<div class="col-md-12"> | |
<h1>Collaborator Network for Public Projects</h1> | |
<p>Dark circles in the graph below represent users who are contributors on <strong>10</strong> or more public projects or components. Light circles represent projects or components that have <strong>7</strong> or more contributors. Connections between circles represent co-contributorship. Circle size is related to the number of projects/components contributed to (dark) or number of contributors (light).</p> | |
- <div style="font: 10px sans-serif;margin: 0px auto 22px;clear: both;width"> | |
+ <div style="font: 10px sans-serif;margin: 0px auto 22px;clear: both;"> | |
<div id='chart'></div> | |
</div> | |
</div> | |
diff --git a/website/templates/public/login.mako b/website/templates/public/login.mako | |
index 63d6af9..0d96652 100644 | |
--- a/website/templates/public/login.mako | |
+++ b/website/templates/public/login.mako | |
@@ -211,7 +211,7 @@ | |
</div> | |
<!-- Flashed Messages --> | |
<div class="help-block" > | |
- <p data-bind="html: flashMessage, attr.class: flashMessageClass"></p> | |
+ <p data-bind="html: flashMessage, attr: {class: flashMessageClass}"></p> | |
</div> | |
<div> | |
<p> By clicking "Create account", you agree to our <a href="https://github.com/CenterForOpenScience/centerforopenscience.org/blob/master/TERMS_OF_USE.md">Terms</a> and that you have read our <a href="https://github.com/CenterForOpenScience/centerforopenscience.org/blob/master/PRIVACY_POLICY.md">Privacy Policy</a>, including our information on <a href="https://github.com/CenterForOpenScience/centerforopenscience.org/blob/master/PRIVACY_POLICY.md#f-cookies">Cookie Use</a>.</p> | |
@@ -237,8 +237,8 @@ | |
${parent.javascript_bottom()} | |
<script type="text/javascript"> | |
window.contextVars = $.extend(true, {}, window.contextVars, { | |
- 'campaign': ${campaign or '' | sjson, n}, | |
- 'institution_redirect': ${institution_redirect or '' | sjson, n} | |
+ 'campaign': ${ campaign or '' | sjson, n }, | |
+ 'institution_redirect': ${ institution_redirect or '' | sjson, n } | |
}); | |
</script> | |
<script src=${"/static/public/js/login-page.js" | webpack_asset}></script> | |
diff --git a/website/templates/public/pages/getting_started.mako b/website/templates/public/pages/getting_started.mako | |
index 85682e0..d99c831 100644 | |
--- a/website/templates/public/pages/getting_started.mako | |
+++ b/website/templates/public/pages/getting_started.mako | |
@@ -73,6 +73,7 @@ | |
</ul> | |
</div> | |
</div> | |
+ </div> | |
<div class="col-sm-8 col-md-9"> | |
<div id="start" class="p-t-xl"> | |
diff --git a/website/templates/public/pages/meeting.mako b/website/templates/public/pages/meeting.mako | |
index 72af7ee..8baa661 100644 | |
--- a/website/templates/public/pages/meeting.mako | |
+++ b/website/templates/public/pages/meeting.mako | |
@@ -13,7 +13,7 @@ | |
${parent.javascript_bottom()} | |
<script type="text/javascript"> | |
window.contextVars = window.contextVars || {}; | |
- window.contextVars.meetingData = ${data}; | |
+ window.contextVars.meetingData = ${ data | sjson, n }; | |
$('#addLink').on('click', function(e) { | |
e.preventDefault(); | |
diff --git a/website/templates/public/pages/meeting_plain.mako b/website/templates/public/pages/meeting_plain.mako | |
index 2c9b2e4..449b1ae 100644 | |
--- a/website/templates/public/pages/meeting_plain.mako | |
+++ b/website/templates/public/pages/meeting_plain.mako | |
@@ -11,7 +11,7 @@ | |
<script src="/static/vendor/bower_components/raven-js/dist/raven.min.js"></script> | |
<script src="/static/vendor/bower_components/raven-js/plugins/jquery.js"></script> | |
<script> | |
- Raven.config('${ sentry_dsn_js }', {}).install(); | |
+ Raven.config(${ sentry_dsn_js | sjson, n }, {}).install(); | |
</script> | |
% else: | |
<script> | |
@@ -45,7 +45,7 @@ | |
<script type="text/javascript"> | |
window.contextVars = window.contextVars || {}; | |
- window.contextVars.meetingData = ${data}; | |
+ window.contextVars.meetingData = ${ data | sjson, n }; | |
$('#addLink').on('click', function(e) { | |
e.preventDefault(); | |
diff --git a/website/templates/resend.mako b/website/templates/resend.mako | |
index c590a65..18ea516 100644 | |
--- a/website/templates/resend.mako | |
+++ b/website/templates/resend.mako | |
@@ -12,7 +12,7 @@ | |
<form id='resendForm' method='POST' class='form' role='form'> | |
<div class='form-group'> | |
- ${form.email(placeholder='Email address', autofocus=True)} | |
+ ${form.email(placeholder='Email address', autofocus=True) | unicode, n } | |
</div> | |
<button type='submit' class='btn btn-primary'>Send</button> | |
diff --git a/website/templates/search.mako b/website/templates/search.mako | |
index 637c6df..c1cd803 100644 | |
--- a/website/templates/search.mako | |
+++ b/website/templates/search.mako | |
@@ -18,15 +18,15 @@ | |
<ul class="nav nav-pills nav-stacked" data-bind="foreach: allCategories"> | |
<!-- ko if: $parent.category().name === name --> | |
- <li class="active"> | |
- <a data-bind="click: $parent.filter.bind($data)">{{ display }}<span class="badge pull-right">{{count}}</span></a> | |
- </li> | |
- <!-- /ko --> | |
- <!-- ko if: $parent.category().name !== name --> | |
- <li> | |
- <a data-bind="click: $parent.filter.bind($data)">{{ display }}<span class="badge pull-right">{{count}}</span></a> | |
- </li> | |
- <!-- /ko --> | |
+ <li class="active"> <!-- TODO: simplify markup; only the active class really needs to be conditional --> | |
+ <a data-bind="click: $parent.filter.bind($data)"><span data-bind="text: display"></span><span class="badge pull-right" data-bind="text: count"></span></a> | |
+ </li> | |
+ <!-- /ko --> | |
+ <!-- ko if: $parent.category().name !== name --> | |
+ <li> | |
+ <a data-bind="click: $parent.filter.bind($data)"><span data-bind="text: display"></span><span class="badge pull-right" data-bind="text: count"></span></a> | |
+ </li> | |
+ <!-- /ko --> | |
</ul> | |
</div> | |
</div> | |
@@ -38,9 +38,7 @@ | |
<!-- ko if: count === $parent.tagMaxCount() && count > $parent.tagMaxCount()/2 --> | |
<span class="tag tag-big tag-container" | |
data-bind="click: $root.addTag.bind($parentContext, tag.name)"> | |
- <span class="cloud-text"> | |
- {{name}} | |
- </span> | |
+ <span class="cloud-text" data-bind="text: name"></span> | |
<i class="fa fa-times-circle remove-tag big" | |
data-bind="click: $root.removeTag.bind($parentContext, tag.name)"></i> | |
</span> | |
@@ -48,9 +46,7 @@ | |
<!-- ko if: count < $parent.tagMaxCount() && count > $parent.tagMaxCount()/2 --> | |
<span class="tag tag-med tag-container" | |
data-bind="click: $root.addTag.bind($parentContext, tag.name)"> | |
- <span class="cloud-text"> | |
- {{name}} | |
- </span> | |
+ <span class="cloud-text" data-bind="text: name"></span> | |
<i class="fa fa-times-circle remove-tag med" | |
data-bind="click: $root.removeTag.bind($parentContext, tag.name)"></i> | |
</span> | |
@@ -58,9 +54,7 @@ | |
<!-- ko if: count <= $parent.tagMaxCount()/2--> | |
<span class="tag tag-sm tag-container" | |
data-bind="click: $root.addTag.bind($parentContext, tag.name)"> | |
- <span class="cloud-text"> | |
- {{name}} | |
- </span> | |
+ <span class="cloud-text" data-bind="text: name"></span> | |
<i class="fa fa-times-circle remove-tag" | |
data-bind="click: $root.removeTag.bind($parentContext, tag.name)"></i> | |
</span> | |
@@ -70,7 +64,7 @@ | |
</div> | |
<br /> | |
<!-- /ko --> | |
- <div class="row" class="hidden-xs" data-bind="if: showLicenses" class="row"> | |
+ <div class="row hidden-xs" data-bind="if: showLicenses"> | |
<div class="col-md-12"> | |
<h4> Filter by license:</h4> | |
<span data-bind="if: licenses"> | |
@@ -78,7 +72,7 @@ | |
data-bind="foreach: {data: licenses, as: 'license'}"> | |
<li data-bind="css: {'active': license.active(), 'disabled': !license.count()}"> | |
<a data-bind="click: license.toggleActive"> | |
- <span style="display: inline-block; max-width: 85%;">{{license.name}}</span> | |
+ <span style="display: inline-block; max-width: 85%;" data-bind="text: license.name"></span> | |
<span data-bind="text: license.count" class="badge pull-right"></span> | |
</a> | |
</li> | |
@@ -118,26 +112,26 @@ | |
<script type="text/html" id="SHARE"> | |
<!-- ko if: $data.links --> | |
- <h4><a data-bind="attr.href: links[0].url">{{ title }}</a></h4> | |
+ <h4><a data-bind="attr: {href: links[0].url}, text: title"></a></h4> | |
<!-- /ko --> | |
<!-- ko ifnot: $data.links --> | |
- <h4><a data-bind="attr.href: id.url">{{ title }}</a></h4> | |
+ <h4><a data-bind="attr: {href: id.url}, text: title"></a></h4> | |
<!-- /ko --> | |
- <h5>Description: <small>{{ description | default:"No Description" | fit:500}}</small></h5> | |
+ <h5>Description: <small data-bind="fitText: {text: description || 'No Description', length: 500}"></small></h5> | |
<!-- ko if: contributors.length > 0 --> | |
<h5> | |
Contributors: <small data-bind="foreach: contributors"> | |
- <span>{{ $data.given + " " + $data.family}}</span> | |
+ <span data-bind="text: $data.given + ' ' + $data.family"></span> | |
<!-- ko if: ($index()+1) < ($parent.contributors.length) --> - <!-- /ko --> | |
</small> | |
</h5> | |
<!-- /ko --> | |
<!-- ko if: $data.source --> | |
- <h5>Source: <small>{{ source }}</small></h5> | |
+ <h5>Source: <small data-bind="text: source"></small></h5> | |
<!-- /ko --> | |
<!-- ko if: $data.isResource --> | |
@@ -146,11 +140,11 @@ | |
<!-- /ko --> | |
</script> | |
<script type="text/html" id="file"> | |
- <h4><a href="{{ deep_url }}">{{ name }}</a> (<span data-bind="if: is_registration">Registration </span>File)</h4> | |
+ <h4><a data-bind="attr: {href: deep_url}, text: name"></a> (<span data-bind="if: is_registration">Registration </span>File)</h4> | |
<h5> | |
- <!-- ko if: parent_url --> From: <a data-bind="attr.href: parent_url">{{ parent_title }} /</a> <!-- /ko --> | |
- <!-- ko if: !parent_url --> From: <span data-bind="if: parent_title">{{ parent_title }} /</span> <!-- /ko --> | |
- <a data-bind="attr.href: node_url">{{ node_title }}</a> | |
+ <!-- ko if: parent_url --> From: <a data-bind="attr: {href: parent_url}, text: parent_title || '' + ' /'"></a> <!-- /ko --> | |
+ <!-- ko if: !parent_url --> From: <span data-bind="if: parent_title"><span data-bind="text: parent_title"></span> /</span> <!-- /ko --> | |
+ <a data-bind="attr: {href: node_url}, text: node_title"></a> | |
</h5> | |
<!-- ko if: tags.length > 0 --> <div data-bind="template: 'tag-cloud'"></div> <!-- /ko --> | |
</script> | |
@@ -158,10 +152,10 @@ | |
<div class="row"> | |
<div class="col-md-2"> | |
- <img class="social-gravatar" data-bind="visible: gravatarUrl(), attr.src: gravatarUrl()"> | |
+ <img class="social-gravatar" data-bind="visible: gravatarUrl(), attr: {src: gravatarUrl()}"> | |
</div> | |
<div class="col-md-10"> | |
- <h4><a data-bind="attr.href: url"><span>{{ user }}</span></a></h4> | |
+ <h4><a data-bind="attr: {href: url}, text: user"></a></h4> | |
<p> | |
<span data-bind="visible: job_title, text: job_title"></span><!-- ko if: job_title && job --> at <!-- /ko --> | |
<span data-bind="visible: job, text: job"></span><!-- ko if: job_title || job --><br /><!-- /ko --> | |
@@ -171,58 +165,58 @@ | |
<!-- ko if: social --> | |
<ul class="list-inline"> | |
<li data-bind="visible: social.personal"> | |
- <a data-bind="attr.href: social.personal"> | |
+ <a data-bind="attr: {href: social.personal}"> | |
<i class="fa fa-globe social-icons" data-toggle="tooltip" title="Personal Website"></i> | |
</a> | |
</li> | |
<li data-bind="visible: social.twitter"> | |
- <a data-bind="attr.href: social.twitter"> | |
+ <a data-bind="attr: {href: social.twitter}"> | |
<i class="fa fa-twitter social-icons" data-toggle="tooltip" title="Twitter"></i> | |
</a> | |
</li> | |
<li data-bind="visible: social.github"> | |
- <a data-bind="attr.href: social.github"> | |
+ <a data-bind="attr: {href: social.github}"> | |
<i class="fa fa-github-alt social-icons" data-toggle="tooltip" title="Github"></i> | |
</a> | |
</li> | |
<li data-bind="visible: social.linkedIn"> | |
- <a data-bind="attr.href: social.linkedIn"> | |
+ <a data-bind="attr: {href: social.linkedIn}"> | |
<i class="fa fa-linkedin social-icons" data-toggle="tooltip" title="LinkedIn"></i> | |
</a> | |
</li> | |
<li data-bind="visible: social.scholar"> | |
- <a data-bind="attr.href: social.scholar"> | |
+ <a data-bind="attr: {href: social.scholar}"> | |
<img class="social-icons" src="/static/img/googlescholar.png"data-toggle="tooltip" title="Google Scholar"> | |
</a> | |
</li> | |
<li data-bind="visible: social.impactStory"> | |
- <a data-bind="attr.href: social.impactStory"> | |
+ <a data-bind="attr: {href: social.impactStory}"> | |
<i class="fa fa-info-circle social-icons" data-toggle="tooltip" title="ImpactStory"></i> | |
</a> | |
</li> | |
<li data-bind="visible: social.orcid"> | |
- <a data-bind="attr.href: social.orcid"> | |
+ <a data-bind="attr: {href: social.orcid}"> | |
<i class="fa social-icons" data-toggle="tooltip" title="ORCiD">iD</i> | |
</a> | |
</li> | |
<li data-bind="visible: social.researcherId"> | |
- <a data-bind="attr.href: social.researcherId"> | |
+ <a data-bind="attr: {href: social.researcherId}"> | |
<i class="fa social-icons" data-toggle="tooltip" title="ResearcherID">R</i> | |
</a> | |
</li> | |
<li data-bind="visible: social.researchGate"> | |
- <a data-bind="attr.href: social.researchGate"> | |
+ <a data-bind="attr: {href: social.researchGate}"> | |
<img class="social-icons" src="/static/img/researchgate.jpg" style="PADDING-BOTTOM: 7px" data-toggle="tooltip" title="ResearchGate"></i> | |
</a> | |
</li> | |
<li data-bind="visible: social.academiaInstitution + social.academiaProfileID"> | |
- <a data-bind="attr.href: social.academiaInstitution + social.academiaProfileID"> | |
+ <a data-bind="attr: {href: social.academiaInstitution + social.academiaProfileID}"> | |
<i class="fa social-icons" data-toggle="tooltip" title="Academia">A</i> | |
</a> | |
</li> | |
<li data-bind="visible: social.baiduScholar"> | |
- <a data-bind="attr.href: social.baiduScholar"> | |
+ <a data-bind="attr: {href: social.baiduScholar}"> | |
<img class="social-icons" src="/static/img/baiduscholar.png"data-toggle="tooltip" style="PADDING-BOTTOM: 5px" title="Baidu Scholar"> | |
</a> | |
</li> | |
@@ -234,38 +228,38 @@ | |
</script> | |
<script type="text/html" id="node"> | |
<!-- ko if: parent_url --> | |
- <h4><a data-bind="attr.href: parent_url">{{ parent_title}}</a> / <a data-bind="attr.href: url">{{title }}</a></h4> | |
+ <h4><a data-bind="attr: {href: parent_url}, text: parent_title"></a> / <a data-bind="attr: {href: url}, text: title"></a></h4> | |
<!-- /ko --> | |
<!-- ko if: !parent_url --> | |
- <h4><span data-bind="if: parent_title">{{ parent_title }} /</span> <a data-bind="attr.href: url">{{title }}</a></h4> | |
+ <h4><span data-bind="if: parent_title"><span data-bind="text: parent_title"></span> /</span> <a data-bind="attr: {href: url}, text: title"></a></h4> | |
<!-- /ko --> | |
- <p data-bind="visible: description"><strong>Description:</strong> {{ description | fit:500 }}</p> | |
+ <p data-bind="visible: description"><strong>Description:</strong> <span data-bind="fitText: {text: description, length: 500}"></span></p> | |
<!-- ko if: contributors.length > 0 --> | |
<p> | |
<strong>Contributors:</strong> <span data-bind="foreach: contributors"> | |
<!-- ko if: url --> | |
- <a data-bind="attr.href: url">{{ fullname }}</a> | |
+ <a data-bind="attr: {href: url}, text: fullname"></a> | |
<!-- /ko--> | |
<!-- ko ifnot: url --> | |
- {{ fullname }} | |
+ <span data-bind="text: fullname"></span> | |
<!-- /ko --> | |
<!-- ko if: ($index()+1) < ($parent.contributors.length) --> - <!-- /ko --> | |
</span> | |
</p> | |
<!-- /ko --> | |
<!-- ko if: primary_institution --> | |
- <p><strong>Primary institution:</strong> {{ primary_institution }} </p> | |
+ <p><strong>Primary institution:</strong> <span data-bind="text: primary_institution"></span></p> | |
<!-- /ko --> | |
<!-- ko if: tags.length > 0 --> | |
<div data-bind="template: 'tag-cloud'"></div> | |
<!-- /ko --> | |
<p><strong>Jump to:</strong> | |
<!-- ko if: n_wikis > 0 --> | |
- <a data-bind="attr.href: wikiUrl">Wiki</a> - | |
+ <a data-bind="attr: {href: wikiUrl}">Wiki</a> - | |
<!-- /ko --> | |
- <a data-bind="attr.href: filesUrl">Files</a> | |
+ <a data-bind="attr: {href: filesUrl}">Files</a> | |
</p> | |
</script> | |
<script type="text/html" id="project"> | |
@@ -276,23 +270,23 @@ | |
</script> | |
<script type="text/html" id="registration"> | |
<!-- ko if: parent_url --> | |
- <h4><a data-bind="attr.href: parent_url">{{ parent_title}}</a> / <a data-bind="attr.href: url">{{ title }}</a> (<span class="text-danger" data-bind="if: is_retracted">Withdrawn </span>Registration)</h4> | |
+ <h4><a data-bind="attr: {href: parent_url}, text: parent_title"></a> / <a data-bind="attr: {href: url}, text: title"></a> (<span class="text-danger" data-bind="if: is_retracted">Withdrawn </span>Registration)</h4> | |
<!-- /ko --> | |
<!-- ko if: !parent_url --> | |
- <h4><span data-bind="if: parent_title">{{ parent_title }} /</span> <a data-bind="attr.href: url">{{ title }}</a> (<span class="text-danger" data-bind="if: is_retracted">Withdrawn </span>Registration)</h4> | |
+ <h4><span data-bind="if: parent_title"><span data-bind="text: parent_title"></span> /</span> <a data-bind="attr: {href: url}, text: title"></a> (<span class="text-danger" data-bind="if: is_retracted">Withdrawn </span>Registration)</h4> | |
<!-- /ko --> | |
<strong><span data-bind="text: 'Date Registered: ' + dateRegistered['local'], tooltip: {title: dateRegistered['utc']}"></span></strong> | |
- <p data-bind="visible: description"><strong>Description:</strong> {{ description | fit:500 }}</p> | |
+ <p data-bind="visible: description"><strong>Description:</strong> <span data-bind="fitText: {text: description, length: 500}"></span></p> | |
<!-- ko if: contributors.length > 0 --> | |
<p> | |
<strong>Contributors:</strong> <span data-bind="foreach: contributors"> | |
<!-- ko if: url --> | |
- <a data-bind="attr.href: url">{{ fullname }}</a> | |
+ <a data-bind="attr: {href: url}, text: fullname"></a> | |
<!-- /ko--> | |
<!-- ko ifnot: url --> | |
- {{ fullname }} | |
+ <span data-bind="text: fullname"></span> | |
<!-- /ko --> | |
@@ -305,9 +299,9 @@ | |
<!-- /ko --> | |
<p><strong>Jump to:</strong> | |
<!-- ko if: n_wikis > 0 --> | |
- <a data-bind="attr.href: wikiUrl">Wiki</a> - | |
+ <a data-bind="attr: {href: wikiUrl}">Wiki</a> - | |
<!-- /ko --> | |
- <a data-bind="attr.href: filesUrl">Files</a> | |
+ <a data-bind="attr: {href: filesUrl}">Files</a> | |
</p> | |
</script> | |
<script id="tag-cloud" type="text/html"> | |
diff --git a/website/templates/share_api_docs.mako b/website/templates/share_api_docs.mako | |
index 01a6378..7c87660 100644 | |
--- a/website/templates/share_api_docs.mako | |
+++ b/website/templates/share_api_docs.mako | |
@@ -2,6 +2,6 @@ | |
<%def name="title()">SHARE Help</%def> | |
<%def name="content()"> | |
<div class="embed-responsive embed-responsive-4by3" style="margin-top: 20px;"> | |
- <iframe class="embed-responsive-item" src="${help | js_str}"></iframe> | |
+ <iframe class="embed-responsive-item" src="${help}"></iframe> | |
</div> | |
</%def> | |
diff --git a/website/templates/share_registration.mako b/website/templates/share_registration.mako | |
index e428f84..7ce7b2b 100644 | |
--- a/website/templates/share_registration.mako | |
+++ b/website/templates/share_registration.mako | |
@@ -10,7 +10,7 @@ | |
window.contextVars = $.extend(true, {}, window.contextVars, { | |
share: { | |
urls: { | |
- register: '${register | js_str}' | |
+ register: ${ register | sjson, n } | |
} | |
} | |
}); | |
diff --git a/website/templates/util/render_form.mako b/website/templates/util/render_form.mako | |
index bc98d8b..6d8c311 100644 | |
--- a/website/templates/util/render_form.mako | |
+++ b/website/templates/util/render_form.mako | |
@@ -2,14 +2,14 @@ | |
% if id: | |
id="${id}" | |
% endif | |
- name="${name}" method="${method_string}" ${"action=\""+action_string+"\"" if action_string else ""} class="${form_class}"> | |
+ name="${name}" method="${method_string}" action="${action_string}" class="${form_class}"> | |
<fieldset> | |
% for field in form: | |
<div class="form-group"> | |
${field['label'] | unicode, n } | |
<span class="help-block">${ field['description'] | unicode, n }</span> | |
% if html_replacements and (field['id'] in html_replacements): | |
- ${ html_replacements[field['id']] | unicode, n } | |
+ ${ html_replacements[field['id']] | unicode, n } <!-- possibly totally unused --> | |
% else: | |
${ field['html'] | unicode, n } | |
% endif | |
diff --git a/website/templates/util/render_keys.mako b/website/templates/util/render_keys.mako | |
index 0cfbf77..47aee77 100644 | |
--- a/website/templates/util/render_keys.mako | |
+++ b/website/templates/util/render_keys.mako | |
@@ -1,3 +1,4 @@ | |
+<!-- TODO: File is unused; candidate for deletion? --> | |
<div> | |
<h2>Keys</h2> | |
<form id="create_key" class="form-inline"> | |
@@ -23,7 +24,7 @@ | |
<script type="text/javascript"> | |
$('#create_key').on('submit', function() { | |
$.post( | |
- '/api/v1${route}create_key/', | |
+ ${ '/api/v1' + route + 'create_key/' | sjson, n }, | |
$(this).serialize(), | |
function(response) { | |
window.location.reload(); | |
@@ -33,7 +34,7 @@ | |
}); | |
$('.revoke_key').on('click', function() { | |
$.post( | |
- '/api/v1${route}revoke_key/', | |
+ ${'/api/v1' + route + 'revoke_key/'}, | |
{key : $(this).attr('data-key')}, | |
function(response) { | |
window.location.reload(); | |
diff --git a/website/templates/util/render_node.mako b/website/templates/util/render_node.mako | |
index 2fb8741..abe41ae 100644 | |
--- a/website/templates/util/render_node.mako | |
+++ b/website/templates/util/render_node.mako | |
@@ -33,7 +33,7 @@ | |
<span class="label label-primary"><strong>Archiving</strong></span> | | |
% endif | |
</span> | |
- <span data-bind="getIcon: '${summary['category']}'"></span> | |
+ <span data-bind="getIcon: ${ summary['category'] | sjson, n }"></span> | |
% if not summary['archiving']: | |
<a href="${summary['url']}">${summary['title']}</a> | |
% else: | |
diff --git a/website/tokens/handlers.py b/website/tokens/handlers.py | |
index 6b1fd95..3467fbe 100644 | |
--- a/website/tokens/handlers.py | |
+++ b/website/tokens/handlers.py | |
@@ -9,6 +9,7 @@ from framework import status | |
from website.tokens.exceptions import UnsupportedSanctionHandlerKind, TokenError | |
def registration_approval_handler(action, registration, registered_from): | |
+ # TODO: Unnecessary and duplicated dictionary. | |
status.push_status_message({ | |
'approve': 'Your registration approval has been accepted.', | |
'reject': 'Your disapproval has been accepted and the registration has been cancelled.', | |
diff --git a/website/util/sanitize.py b/website/util/sanitize.py | |
index 9d5e271..82e737e 100644 | |
--- a/website/util/sanitize.py | |
+++ b/website/util/sanitize.py | |
@@ -1,9 +1,8 @@ | |
# -*- coding: utf-8 -*- | |
import collections | |
-import re | |
+import json | |
import bleach | |
-import json | |
def strip_html(unclean): | |
@@ -65,7 +64,7 @@ def escape_html(data): | |
return data | |
-# FIXME: Not sure what this function is trying to accomplish. Candidate for deletion? | |
+# FIXME: Doesn't raise either type of exception expected, and can probably be deleted along with sole use | |
def assert_clean(data): | |
"""Ensure that data is cleaned | |
@@ -129,9 +128,3 @@ def safe_json(value): | |
:return: A JSON-formatted string that explicitly escapes forward slashes when needed | |
""" | |
return json.dumps(value).replace('</', '<\\/') # Fix injection of closing markup in strings | |
- | |
-def strip_ko(value): | |
- """Filter that prevents display of Knockout punches syntax when rendering variables. | |
- e.g. {{expression}} | |
- """ | |
- return re.sub(r'}\s*}', '', re.sub(r'{\s*{', '', value)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment