OSF Guidelines¶
General¶
- For node endpoints, use
node.url_for
andnode.api_url_for
for URL lookup
# Assuming a URL Rule:
# Rule(
# [
# '/project/<pid>/tags/<tid>',
# '/project/<pid>/node/<nid>/tags/<tid>/',
# ],
# 'put',
# node_views.node_tags_put,
# json_renderer,
# )
# Yes
# Pass the name of the view function and URL params as keyword arguments
url = node.api_url_for('node_tags_put', tid=tag._id)
# => /project/1rdsf/tags/mytag/
# No
url = os.path.join('/api', 'v1', node._primary_key, 'tags', tag._id)
- Use
website.utils.api_url_for
andwebsite.utils.web_url_for
for general URL lookup.
# Yes
from website.utils import api_url_for
url = api_url_for('user_settings')
# No
url = os.path.join('/user', 'settings')
- Use the above functions in Mako templates; they are available by default.
<!-- Yes -->
<p>Visit your <a href="${ web_url_for('user_settings') }">user settings</a>.
<!-- No -->
<p>Visit your <a href="/settings/">user settings</a>.
Views¶
- If a decorator injects keyword arguments, declare the keyword arguments whenever possible. Avoid pulling them from the kwargs dictionary.
# Yes
@must_be_logged_in
def user_settings_put(auth, **kwargs):
#...
@must_be_contributor_or_public
def get_project_comments(auth, node, **kwargs):
# ...
# No
@must_be_logged_in
def user_settings_put(**kwargs):
auth = kwargs['auth']
#...
- Use
framework.flask.redirect
to return redirect responses. It has the same functionality asflask.redirect
except that it will reappend querystring parameters for view-only links when necessary. Do not useflask.redirect
.
Responses¶
- Use correct HTTP status codes. You can used the constants in
httplib
to help.
# Yes
@must_be_logged_in
def user_token_post(auth, **kwargs):
#...
return serialized_settings, 201
# OR
# return serialized_settings, httplib.CREATED
# No
@must_be_logged_in
def user_token_post(auth, **kwargs):
#...
return serialized_settings # Implicitly returns 200 response
- Be consistent with your response format.
TODO: Come up with a standard format. The Dropbox add-on uses the following, though we may decide on a different convention later.
{
"result": {"name": "New Project", "id": ...} # ... the requested object(s) ,
"message": "Successfully created project" # ... an optional message
}
- Prefer namespaced representations to arbitrary prefixes in response data.
// Yes
{
'node': {
'_id': '123abc',
'urls': {
'api': '/api/v1/123abc',
'web': '/123abc/'
}
},
'urls': {
'latest': '/files/some-file-id/latest/',
'detail': '/files/some-file-id/'
}
}
// No
{
'node_id': '123abc',
'node_api_url': '/api/v1/123abc',
'node_web_url': '/123abc/',
'latest_file_url': '/files/some-file-id/latest/',
'file_detail_url': '/files/some-file-id/'
}
Running Migrations¶
Migrations are located in the scripts
directory.
To run them:
$ python -m scripts.script_name
To migrate search records:
invoke migrate_search
Error Handling¶
Server-side¶
If a view should return an error response, raise a framework.exceptions.HTTPError
, optionally passing a short and long message. This will ensure that a properly formatted HTML or JSON response is returned (depending on whether the route is an API or web route). Do NOT return a dictionary.
from framework.exceptions import HTTPError
@must_be_logged_in
def user_settings_get(auth, **kwargs):
"""Return the current user's settings."""
try:
settings = get_user_settings(auth)
except ModularOdmException:
raise HTTPError(404,
msg_short='User not found',
msg_long='The user could not be in our database.'
)
return serialized_settings(settings), 200
Client-side¶
All client-side HTTP requests should have proper error handlers. As an example, you might display an error message in a modal if a request fails.
Note
Use RavenJS (a JS client for Sentry) to log unexpected errors to our Sentry server.
var url = '/api/v1/profile';
var request = $osf.putJSON(url, {'email': 'foo@bar.com'});
request.done(function(response) { ... });
request.fail(function(jqxhr, status, error) {
bootbox.alert({
title: "Error",
message: "We're sorry. Your profile could not be updated at this time. Please try again later."
});
// Log error to Sentry
// Add context (e.g. error status, error messages) as the 2nd argument
Raven.captureMessage('Error while updating user profile', {
url: url, status: status, error: error
});
});
When appropriate, you can use the generic $osf.handleJSONError
, which will display a generic error message in a modal to the user if a failure occurs.
var $osf = require('osfHelpers');
// ...
request.fail($osf.handleJSONError);
Documentation¶
Docstrings¶
- Write function docstrings using Sphinx conventions (see here).
- For parameters that are not passed directly to the function (e.g. query string arguments, POST arguments), include the source of the parameter in the docstring:
def my_view(my_param):
"""Do something rad.
:param str my_param: My directly passed parameter
:param-query str foo: A parameter included in the query string; look me up in `request.args`
:param-post str bar: A parameter included in the POST payload; look me up in `request.form`
:param-json str baz: A parameter included in the JSON payload; look me up in `request.json`
"""
# Rad code here
Misc¶
Generating fake data¶
1. Install fake-factory
$ pip install fake-factory
- Create your an account on your local osf. Remember the email address you use.
3. Run the fake data generator script, passing in your username (email)
$ python -m scripts.create_fakes --user fred@cos.io
where fred@cos.io
is the email of the user you created.
After you run the script, you will have 3 fake projects, each with 3 fake contributors (with you as the creator).
Dialogs¶
We use Bootbox to generate modal dialogs in the OSF. When calling a bootbox
method, always pass in an object of arguments rather than positional arguments. This allows you to include a title in the dialog.
// Yes
bootbox.confirm({
title: 'Permanently delete file?',
message: 'Are you sure you want to delete this file?',
callback: function(confirmed) {
// ..
}
})
// No
bootbox.confirm('Are you sure you want to delete this file?',
function(confirmed) {
// ...
}
)