Introducing settings blueprint
Now settings live in a dedicated space, the settings blueprint can be used by Pillar applications, and the templates can be extended or overridden. Moved subscription and email settings to the blender-cloud repository.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
def setup_app(app):
|
def setup_app(app):
|
||||||
from . import main, users, projects, nodes, notifications, organizations, redirects, subquery
|
from . import main, users, projects, nodes, notifications, organizations, redirects, subquery, \
|
||||||
|
settings
|
||||||
main.setup_app(app, url_prefix=None)
|
main.setup_app(app, url_prefix=None)
|
||||||
users.setup_app(app, url_prefix=None)
|
users.setup_app(app, url_prefix=None)
|
||||||
redirects.setup_app(app, url_prefix='/r')
|
redirects.setup_app(app, url_prefix='/r')
|
||||||
@@ -8,3 +9,4 @@ def setup_app(app):
|
|||||||
notifications.setup_app(app, url_prefix='/notifications')
|
notifications.setup_app(app, url_prefix='/notifications')
|
||||||
subquery.setup_app(app)
|
subquery.setup_app(app)
|
||||||
organizations.setup_app(app, url_prefix='/orgs')
|
organizations.setup_app(app, url_prefix='/orgs')
|
||||||
|
settings.setup_app(app, url_prefix='/settings')
|
||||||
|
5
pillar/web/settings/__init__.py
Normal file
5
pillar/web/settings/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .routes import blueprint
|
||||||
|
|
||||||
|
|
||||||
|
def setup_app(app, url_prefix):
|
||||||
|
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
40
pillar/web/settings/routes.py
Normal file
40
pillar/web/settings/routes.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from flask import Blueprint, flash, render_template
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from werkzeug.exceptions import abort
|
||||||
|
|
||||||
|
from pillar.web import system_util
|
||||||
|
from pillar.web.users import forms
|
||||||
|
from pillarsdk import User, exceptions as sdk_exceptions
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
blueprint = Blueprint('settings', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/profile', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def profile():
|
||||||
|
"""Profile view and edit page. This is a temporary implementation.
|
||||||
|
"""
|
||||||
|
if current_user.has_role('protected'):
|
||||||
|
return abort(404) # TODO: make this 403, handle template properly
|
||||||
|
api = system_util.pillar_api()
|
||||||
|
user = User.find(current_user.objectid, api=api)
|
||||||
|
|
||||||
|
form = forms.UserProfileForm(
|
||||||
|
full_name=user.full_name,
|
||||||
|
username=user.username)
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
try:
|
||||||
|
user.full_name = form.full_name.data
|
||||||
|
user.username = form.username.data
|
||||||
|
user.update(api=api)
|
||||||
|
flash("Profile updated", 'success')
|
||||||
|
except sdk_exceptions.ResourceInvalid as e:
|
||||||
|
message = json.loads(e.content)
|
||||||
|
flash(message)
|
||||||
|
|
||||||
|
return render_template('users/settings/profile.html', form=form, title='profile')
|
@@ -3,4 +3,3 @@ from .routes import blueprint
|
|||||||
|
|
||||||
def setup_app(app, url_prefix):
|
def setup_app(app, url_prefix):
|
||||||
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
||||||
|
|
||||||
|
@@ -1,23 +1,21 @@
|
|||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from werkzeug import exceptions as wz_exceptions
|
from flask import abort, Blueprint, redirect, render_template, request, session, \
|
||||||
from flask import abort, Blueprint, flash, redirect, render_template, request, session,\
|
|
||||||
url_for
|
url_for
|
||||||
from flask_login import login_required, logout_user
|
from flask_login import login_required, logout_user
|
||||||
|
from werkzeug import exceptions as wz_exceptions
|
||||||
|
|
||||||
from pillarsdk import exceptions as sdk_exceptions
|
|
||||||
from pillarsdk.users import User
|
|
||||||
from pillarsdk.groups import Group
|
|
||||||
import pillar.api.blender_cloud.subscription
|
import pillar.api.blender_cloud.subscription
|
||||||
import pillar.auth
|
import pillar.auth
|
||||||
from pillar.web import system_util
|
from pillar.api.blender_cloud.subscription import update_subscription
|
||||||
from pillar.api.local_auth import generate_and_store_token, get_local_user
|
from pillar.api.local_auth import generate_and_store_token, get_local_user
|
||||||
from pillar.api.utils.authentication import find_user_in_db, upsert_user
|
from pillar.api.utils.authentication import find_user_in_db, upsert_user
|
||||||
from pillar.api.blender_cloud.subscription import update_subscription
|
|
||||||
from pillar.auth import current_user
|
from pillar.auth import current_user
|
||||||
from pillar.auth.oauth import OAuthSignIn, ProviderConfigurationMissing, ProviderNotImplemented, \
|
from pillar.auth.oauth import OAuthSignIn, ProviderConfigurationMissing, ProviderNotImplemented, \
|
||||||
OAuthCodeNotProvided
|
OAuthCodeNotProvided
|
||||||
|
from pillar.web import system_util
|
||||||
|
from pillarsdk import exceptions as sdk_exceptions
|
||||||
|
from pillarsdk.users import User
|
||||||
from . import forms
|
from . import forms
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -127,92 +125,6 @@ def switch():
|
|||||||
return redirect(blender_id.switch_user_url(next_url=next_url_after_bid_login))
|
return redirect(blender_id.switch_user_url(next_url=next_url_after_bid_login))
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/settings/profile', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def settings_profile():
|
|
||||||
"""Profile view and edit page. This is a temporary implementation.
|
|
||||||
"""
|
|
||||||
if current_user.has_role('protected'):
|
|
||||||
return abort(404) # TODO: make this 403, handle template properly
|
|
||||||
api = system_util.pillar_api()
|
|
||||||
user = User.find(current_user.objectid, api=api)
|
|
||||||
|
|
||||||
form = forms.UserProfileForm(
|
|
||||||
full_name=user.full_name,
|
|
||||||
username=user.username)
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
|
||||||
try:
|
|
||||||
user.full_name = form.full_name.data
|
|
||||||
user.username = form.username.data
|
|
||||||
user.update(api=api)
|
|
||||||
flash("Profile updated", 'success')
|
|
||||||
except sdk_exceptions.ResourceInvalid as e:
|
|
||||||
message = json.loads(e.content)
|
|
||||||
flash(message)
|
|
||||||
|
|
||||||
return render_template('users/settings/profile.html', form=form, title='profile')
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/settings/emails', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def settings_emails():
|
|
||||||
"""Main email settings.
|
|
||||||
"""
|
|
||||||
if current_user.has_role('protected'):
|
|
||||||
return abort(404) # TODO: make this 403, handle template properly
|
|
||||||
api = system_util.pillar_api()
|
|
||||||
user = User.find(current_user.objectid, api=api)
|
|
||||||
|
|
||||||
# Force creation of settings for the user (safely remove this code once
|
|
||||||
# implemented on account creation level, and after adding settings to all
|
|
||||||
# existing users)
|
|
||||||
if not user.settings:
|
|
||||||
user.settings = dict(email_communications=1)
|
|
||||||
user.update(api=api)
|
|
||||||
|
|
||||||
if user.settings.email_communications is None:
|
|
||||||
user.settings.email_communications = 1
|
|
||||||
user.update(api=api)
|
|
||||||
|
|
||||||
# Generate form
|
|
||||||
form = forms.UserSettingsEmailsForm(
|
|
||||||
email_communications=user.settings.email_communications)
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
|
||||||
try:
|
|
||||||
user.settings.email_communications = form.email_communications.data
|
|
||||||
user.update(api=api)
|
|
||||||
flash("Profile updated", 'success')
|
|
||||||
except sdk_exceptions.ResourceInvalid as e:
|
|
||||||
message = json.loads(e.content)
|
|
||||||
flash(message)
|
|
||||||
|
|
||||||
return render_template('users/settings/emails.html', form=form, title='emails')
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/settings/billing')
|
|
||||||
@login_required
|
|
||||||
def settings_billing():
|
|
||||||
"""View the subscription status of a user
|
|
||||||
"""
|
|
||||||
if current_user.has_role('protected'):
|
|
||||||
return abort(404) # TODO: make this 403, handle template properly
|
|
||||||
api = system_util.pillar_api()
|
|
||||||
user = User.find(current_user.objectid, api=api)
|
|
||||||
groups = []
|
|
||||||
if user.groups:
|
|
||||||
for group_id in user.groups:
|
|
||||||
group = Group.find(group_id, api=api)
|
|
||||||
groups.append(group.name)
|
|
||||||
|
|
||||||
store_user = pillar.api.blender_cloud.subscription.fetch_subscription_info(user.email)
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
'users/settings/billing.html',
|
|
||||||
store_user=store_user, groups=groups, title='billing')
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/u/<user_id>/edit', methods=['GET', 'POST'])
|
@blueprint.route('/u/<user_id>/edit', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def users_edit(user_id):
|
def users_edit(user_id):
|
||||||
@@ -273,3 +185,5 @@ def users_index():
|
|||||||
if not current_user.has_role('admin'):
|
if not current_user.has_role('admin'):
|
||||||
return abort(403)
|
return abort(403)
|
||||||
return render_template('users/index.html')
|
return render_template('users/index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@@ -30,13 +30,13 @@ li(class="dropdown")
|
|||||||
li.subscription-status(class="{{ subscription }}")
|
li.subscription-status(class="{{ subscription }}")
|
||||||
| {% if subscription == 'subscriber' %}
|
| {% if subscription == 'subscriber' %}
|
||||||
a.navbar-item(
|
a.navbar-item(
|
||||||
href="{{url_for('users.settings_billing')}}"
|
href="{{url_for('settings.billing')}}"
|
||||||
title="View subscription info")
|
title="View subscription info")
|
||||||
i.pi-grin
|
i.pi-grin
|
||||||
span Your subscription is active!
|
span Your subscription is active!
|
||||||
| {% elif subscription == 'demo' %}
|
| {% elif subscription == 'demo' %}
|
||||||
a.navbar-item(
|
a.navbar-item(
|
||||||
href="{{url_for('users.settings_billing')}}"
|
href="{{url_for('settings.billing')}}"
|
||||||
title="View subscription info")
|
title="View subscription info")
|
||||||
i.pi-heart-filled
|
i.pi-heart-filled
|
||||||
span You have a free account.
|
span You have a free account.
|
||||||
@@ -72,13 +72,13 @@ li(class="dropdown")
|
|||||||
| {% endif %}
|
| {% endif %}
|
||||||
li
|
li
|
||||||
a.navbar-item(
|
a.navbar-item(
|
||||||
href="{{ url_for('users.settings_profile') }}"
|
href="{{ url_for('settings.profile') }}"
|
||||||
title="Settings")
|
title="Settings")
|
||||||
i.pi-cog
|
i.pi-cog
|
||||||
| Settings
|
| Settings
|
||||||
li
|
li
|
||||||
a.navbar-item(
|
a.navbar-item(
|
||||||
href="{{ url_for('users.settings_billing') }}"
|
href="{{ url_for('settings.billing') }}"
|
||||||
title="Billing")
|
title="Billing")
|
||||||
i.pi-credit-card
|
i.pi-credit-card
|
||||||
| Subscription
|
| Subscription
|
||||||
|
@@ -4,17 +4,7 @@
|
|||||||
.settings-content
|
.settings-content
|
||||||
ul
|
ul
|
||||||
a(class="{% if title == 'profile' %}active{% endif %}",
|
a(class="{% if title == 'profile' %}active{% endif %}",
|
||||||
href="{{ url_for('users.settings_profile') }}")
|
href="{{ url_for('settings.profile') }}")
|
||||||
li
|
li
|
||||||
i.pi-vcard
|
i.pi-vcard
|
||||||
| Profile
|
| Profile
|
||||||
a(class="{% if title == 'emails' %}active{% endif %}",
|
|
||||||
href="{{ url_for('users.settings_emails') }}")
|
|
||||||
li
|
|
||||||
i.pi-email
|
|
||||||
| Emails
|
|
||||||
a(class="{% if title == 'billing' %}active{% endif %}",
|
|
||||||
href="{{ url_for('users.settings_billing') }}")
|
|
||||||
li
|
|
||||||
i.pi-credit-card
|
|
||||||
| Subscription
|
|
||||||
|
@@ -1,68 +0,0 @@
|
|||||||
| {% extends 'layout.html' %}
|
|
||||||
| {% block head %}
|
|
||||||
style(type='text/css').
|
|
||||||
button#recheck_subscription {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
| {% endblock %}
|
|
||||||
| {% block body %}
|
|
||||||
.container
|
|
||||||
#settings
|
|
||||||
include _sidebar
|
|
||||||
#settings-container
|
|
||||||
.settings-header
|
|
||||||
.settings-title Subscription
|
|
||||||
|
|
||||||
.settings-content
|
|
||||||
|
|
||||||
| {% if store_user['cloud_access'] %}
|
|
||||||
h3.subscription-active
|
|
||||||
i.pi-check
|
|
||||||
| Your subscription is active
|
|
||||||
h4 Thank you for supporting us!
|
|
||||||
|
|
||||||
hr
|
|
||||||
|
|
||||||
p Subscription expires on: <strong>{{ store_user['expiration_date'][:10] }}</strong>
|
|
||||||
a(href="https://store.blender.org/my-account/subscriptions/") Manage your subscription on Blender Store
|
|
||||||
| {% else %}
|
|
||||||
|
|
||||||
| {% if 'demo' in groups %}
|
|
||||||
h3.subscription-demo
|
|
||||||
i.pi-heart-filled
|
|
||||||
| You have a free account
|
|
||||||
|
|
||||||
hr
|
|
||||||
|
|
||||||
p You have full access to the Blender Cloud, provided by the Blender Institute. This account is meant for free evaluation of the service. Get in touch with #[a(href="mailto:cloudsupport@blender.org") cloudsupport@blender.org] if you have any questions.
|
|
||||||
|
|
||||||
| {% else %}
|
|
||||||
h3.subscription-missing
|
|
||||||
i.pi-info
|
|
||||||
| You do not have an active subscription.
|
|
||||||
h3
|
|
||||||
a(href="https://store.blender.org/product/membership/") Get full access to Blender Cloud now!
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
p
|
|
||||||
button#recheck_subscription.btn.btn-default(onclick="javascript:recheck_subscription(this)") Re-check my subscription
|
|
||||||
hr
|
|
||||||
|
|
||||||
|
|
||||||
script.
|
|
||||||
function recheck_subscription(button) {
|
|
||||||
$(button).text('Checking');
|
|
||||||
|
|
||||||
$.get('/api/bcloud/update-subscription')
|
|
||||||
.done(function() {
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
.fail(function() {
|
|
||||||
alert('Unable to update subscription, please check your internet connection.');
|
|
||||||
})
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
| {% endblock %}
|
|
@@ -1,27 +0,0 @@
|
|||||||
| {% extends 'layout.html' %}
|
|
||||||
| {% block body %}
|
|
||||||
.container
|
|
||||||
#settings
|
|
||||||
include _sidebar
|
|
||||||
#settings-container
|
|
||||||
.settings-header
|
|
||||||
.settings-title Emails
|
|
||||||
|
|
||||||
.settings-content
|
|
||||||
|
|
||||||
.settings-form
|
|
||||||
form#settings-form(method='POST', action="{{url_for('users.settings_emails')}}")
|
|
||||||
| {{ form.csrf_token }}
|
|
||||||
| {% for subfield in form.email_communications %}
|
|
||||||
.form-group.
|
|
||||||
{{ subfield }}
|
|
||||||
{{ subfield.label }}
|
|
||||||
| {% endfor %}
|
|
||||||
|
|
||||||
.buttons
|
|
||||||
button.btn.btn-default.button-submit(type='submit')
|
|
||||||
i.pi-check
|
|
||||||
| Save Changes
|
|
||||||
|
|
||||||
|
|
||||||
| {% endblock %}
|
|
@@ -2,14 +2,14 @@
|
|||||||
| {% block body %}
|
| {% block body %}
|
||||||
.container
|
.container
|
||||||
#settings
|
#settings
|
||||||
include _sidebar
|
| {% include 'users/settings/_sidebar.html'%}
|
||||||
#settings-container
|
#settings-container
|
||||||
.settings-header
|
.settings-header
|
||||||
.settings-title Profile
|
.settings-title Profile
|
||||||
|
|
||||||
.settings-content
|
.settings-content
|
||||||
.settings-form
|
.settings-form
|
||||||
form#settings-form(method='POST', action="{{url_for('users.settings_profile')}}")
|
form#settings-form(method='POST', action="{{url_for('settings.profile')}}")
|
||||||
.left
|
.left
|
||||||
.form-group
|
.form-group
|
||||||
| {{ form.full_name.label }}
|
| {{ form.full_name.label }}
|
||||||
|
Reference in New Issue
Block a user