2017-09-15 15:25:27 +02:00
|
|
|
import functools
|
2017-08-30 23:13:03 +02:00
|
|
|
import json
|
2017-03-10 15:36:30 +01:00
|
|
|
import logging
|
2017-09-15 15:25:27 +02:00
|
|
|
import typing
|
2017-03-10 15:36:30 +01:00
|
|
|
|
2017-09-15 15:25:27 +02:00
|
|
|
from flask_login import current_user, login_required
|
2017-12-21 15:26:23 +01:00
|
|
|
import flask
|
2017-09-15 15:25:27 +02:00
|
|
|
from flask import Blueprint, render_template, redirect, session, url_for, abort, flash
|
2017-08-30 23:13:03 +02:00
|
|
|
from pillarsdk import Node, Project, User, exceptions as sdk_exceptions, Group
|
2017-07-13 18:35:18 +02:00
|
|
|
from pillarsdk.exceptions import ResourceNotFound
|
2017-09-15 15:25:27 +02:00
|
|
|
|
|
|
|
from pillar import current_app
|
|
|
|
import pillar.api
|
|
|
|
from pillar.web.users import forms
|
2017-07-16 00:06:18 +02:00
|
|
|
from pillar.web.utils import system_util, get_file, current_user_is_authenticated
|
2017-07-26 16:51:16 +02:00
|
|
|
from pillar.web.utils import attach_project_pictures
|
2017-08-30 23:13:03 +02:00
|
|
|
from pillar.web.settings import blueprint as blueprint_settings
|
2017-09-15 15:25:27 +02:00
|
|
|
from pillar.web.nodes.routes import url_for_node
|
2018-01-20 00:45:57 +01:00
|
|
|
from pillar.web.nodes.custom.comments import render_comments_for_node
|
2018-04-16 14:38:08 +02:00
|
|
|
from pillar.web.projects.routes import render_project
|
|
|
|
from pillar.web.projects.routes import find_project_or_404
|
2018-01-20 00:45:57 +01:00
|
|
|
|
2017-09-15 15:25:27 +02:00
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
blueprint = Blueprint('cloud', __name__)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
@blueprint.route('/')
|
|
|
|
def homepage():
|
|
|
|
if current_user.is_anonymous:
|
|
|
|
return redirect(url_for('cloud.welcome'))
|
|
|
|
|
2017-09-19 13:45:10 +02:00
|
|
|
return render_template(
|
|
|
|
'homepage.html',
|
|
|
|
api=system_util.pillar_api(),
|
|
|
|
**_homepage_context(),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _homepage_context() -> dict:
|
|
|
|
"""Returns homepage template context variables."""
|
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
# Get latest blog posts
|
|
|
|
api = system_util.pillar_api()
|
|
|
|
latest_posts = Node.all({
|
2017-11-09 18:37:25 +01:00
|
|
|
'projection': {
|
2017-12-21 15:26:32 +01:00
|
|
|
'name': 1,
|
|
|
|
'project': 1,
|
|
|
|
'node_type': 1,
|
|
|
|
'picture': 1,
|
|
|
|
'properties.url': 1,
|
|
|
|
'properties.content': 1,
|
2018-09-20 19:03:57 +02:00
|
|
|
'properties.attachments': 1,
|
|
|
|
'properties.status': 1,
|
2017-12-21 15:26:32 +01:00
|
|
|
},
|
2017-11-09 18:37:25 +01:00
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
'where': {'node_type': 'post', 'properties.status': 'published'},
|
|
|
|
'embedded': {'project': 1},
|
|
|
|
'sort': '-_created',
|
2017-11-09 18:37:25 +01:00
|
|
|
'max_results': '3'
|
2017-09-19 13:45:48 +02:00
|
|
|
}, api=api)
|
2017-07-13 18:35:18 +02:00
|
|
|
|
|
|
|
# Append picture Files to last_posts
|
|
|
|
for post in latest_posts._items:
|
|
|
|
post.picture = get_file(post.picture, api=api)
|
2017-09-15 15:25:27 +02:00
|
|
|
post.url = url_for_node(node=post)
|
2017-07-13 18:35:18 +02:00
|
|
|
|
|
|
|
# Get latest assets added to any project
|
|
|
|
latest_assets = Node.latest('assets', api=api)
|
|
|
|
|
|
|
|
# Append picture Files to latest_assets
|
|
|
|
for asset in latest_assets._items:
|
|
|
|
asset.picture = get_file(asset.picture, api=api)
|
2017-09-15 15:25:27 +02:00
|
|
|
asset.url = url_for_node(node=asset)
|
2017-07-13 18:35:18 +02:00
|
|
|
|
|
|
|
# Get latest comments to any node
|
|
|
|
latest_comments = Node.latest('comments', api=api)
|
|
|
|
|
|
|
|
# Get a list of random featured assets
|
|
|
|
random_featured = get_random_featured_nodes()
|
|
|
|
|
|
|
|
# Parse results for replies
|
|
|
|
to_remove = []
|
2017-09-15 15:25:27 +02:00
|
|
|
|
|
|
|
@functools.lru_cache()
|
|
|
|
def _find_parent(parent_node_id) -> Node:
|
|
|
|
return Node.find(parent_node_id,
|
|
|
|
{'projection': {
|
|
|
|
'_id': 1,
|
|
|
|
'name': 1,
|
|
|
|
'node_type': 1,
|
|
|
|
'project': 1,
|
2017-09-15 15:46:52 +02:00
|
|
|
'properties.url': 1,
|
2017-09-15 15:25:27 +02:00
|
|
|
}},
|
|
|
|
api=api)
|
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
for idx, comment in enumerate(latest_comments._items):
|
|
|
|
if comment.properties.is_reply:
|
|
|
|
try:
|
2017-09-15 15:25:27 +02:00
|
|
|
comment.attached_to = _find_parent(comment.parent.parent)
|
2017-07-13 18:35:18 +02:00
|
|
|
except ResourceNotFound:
|
|
|
|
# Remove this comment
|
|
|
|
to_remove.append(idx)
|
|
|
|
else:
|
|
|
|
comment.attached_to = comment.parent
|
|
|
|
|
|
|
|
for idx in reversed(to_remove):
|
|
|
|
del latest_comments._items[idx]
|
|
|
|
|
2017-09-15 17:03:31 +02:00
|
|
|
for comment in latest_comments._items:
|
|
|
|
if not comment.attached_to:
|
|
|
|
continue
|
|
|
|
comment.attached_to.url = url_for_node(node=comment.attached_to)
|
2017-11-09 19:38:56 +01:00
|
|
|
comment.url = url_for_node(node=comment)
|
2017-09-15 17:03:31 +02:00
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api)
|
|
|
|
main_project.picture_header = get_file(main_project.picture_header, api=api)
|
|
|
|
|
|
|
|
# Merge latest assets and comments into one activity stream.
|
|
|
|
def sort_key(item):
|
|
|
|
return item._created
|
|
|
|
|
2017-11-09 18:37:25 +01:00
|
|
|
activity_stream = sorted(latest_assets._items, key=sort_key, reverse=True)
|
2017-07-13 18:35:18 +02:00
|
|
|
|
2017-09-15 15:25:27 +02:00
|
|
|
for node in activity_stream:
|
|
|
|
node.url = url_for_node(node=node)
|
|
|
|
|
2017-09-19 13:45:10 +02:00
|
|
|
return dict(
|
2017-07-13 18:35:18 +02:00
|
|
|
main_project=main_project,
|
|
|
|
latest_posts=latest_posts._items,
|
2017-11-09 18:37:25 +01:00
|
|
|
latest_comments=latest_comments._items,
|
2017-07-13 18:35:18 +02:00
|
|
|
activity_stream=activity_stream,
|
2017-09-19 13:45:10 +02:00
|
|
|
random_featured=random_featured)
|
2017-07-13 18:35:18 +02:00
|
|
|
|
|
|
|
|
2017-08-25 11:56:43 +02:00
|
|
|
@blueprint.route('/login')
|
|
|
|
def login():
|
2017-09-01 17:04:41 +02:00
|
|
|
from flask import request
|
2017-09-19 13:39:41 +02:00
|
|
|
|
|
|
|
if request.args.get('force'):
|
|
|
|
log.debug('Forcing logout of user before rendering login page.')
|
2017-12-12 11:25:48 +01:00
|
|
|
pillar.auth.logout_user()
|
2017-09-01 17:04:41 +02:00
|
|
|
|
|
|
|
next_after_login = request.args.get('next')
|
|
|
|
|
|
|
|
if not next_after_login:
|
2017-11-07 18:25:40 +01:00
|
|
|
next_after_login = request.referrer
|
2017-09-01 17:04:41 +02:00
|
|
|
|
|
|
|
session['next_after_login'] = next_after_login
|
2017-08-25 11:56:43 +02:00
|
|
|
return redirect(url_for('users.oauth_authorize', provider='blender-id'))
|
|
|
|
|
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
@blueprint.route('/welcome')
|
|
|
|
def welcome():
|
|
|
|
# Workaround to cache rendering of a page if user not logged in
|
2017-07-16 00:06:18 +02:00
|
|
|
@current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated)
|
2017-07-13 18:35:18 +02:00
|
|
|
def render_page():
|
|
|
|
return render_template('welcome.html')
|
2017-09-19 13:45:48 +02:00
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
return render_page()
|
|
|
|
|
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
@blueprint.route('/about')
|
|
|
|
def about():
|
|
|
|
return render_template('about.html')
|
2017-07-13 18:35:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/services')
|
|
|
|
def services():
|
|
|
|
return render_template('services.html')
|
|
|
|
|
|
|
|
|
2018-09-20 15:00:10 +02:00
|
|
|
@blueprint.route('/learn')
|
|
|
|
def learn():
|
|
|
|
return render_template('learn.html')
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/libraries')
|
|
|
|
def libraries():
|
|
|
|
return render_template('libraries.html')
|
|
|
|
|
|
|
|
|
2017-09-21 14:25:14 +02:00
|
|
|
@blueprint.route('/stats')
|
|
|
|
def stats():
|
|
|
|
return render_template('stats.html')
|
|
|
|
|
|
|
|
|
2017-07-13 18:35:18 +02:00
|
|
|
@blueprint.route('/join')
|
|
|
|
def join():
|
|
|
|
"""Join page"""
|
|
|
|
return redirect('https://store.blender.org/product/membership/')
|
|
|
|
|
|
|
|
|
2017-12-06 14:36:02 +01:00
|
|
|
@blueprint.route('/renew')
|
|
|
|
def renew_subscription():
|
|
|
|
return render_template('renew_subscription.html')
|
|
|
|
|
|
|
|
|
2017-07-26 16:51:16 +02:00
|
|
|
def get_projects(category):
|
|
|
|
"""Utility to get projects based on category. Should be moved on the API
|
|
|
|
and improved with more extensive filtering capabilities.
|
|
|
|
"""
|
|
|
|
api = system_util.pillar_api()
|
|
|
|
projects = Project.all({
|
|
|
|
'where': {
|
|
|
|
'category': category,
|
|
|
|
'is_private': False},
|
|
|
|
'sort': '-_created',
|
2017-09-19 13:45:48 +02:00
|
|
|
}, api=api)
|
2017-07-26 16:51:16 +02:00
|
|
|
for project in projects._items:
|
|
|
|
attach_project_pictures(project, api)
|
|
|
|
return projects
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/courses')
|
|
|
|
def courses():
|
|
|
|
@current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated)
|
|
|
|
def render_page():
|
|
|
|
projects = get_projects('course')
|
|
|
|
return render_template(
|
|
|
|
'projects_index_collection.html',
|
|
|
|
title='courses',
|
|
|
|
projects=projects._items,
|
|
|
|
api=system_util.pillar_api())
|
|
|
|
|
|
|
|
return render_page()
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/open-projects')
|
|
|
|
def open_projects():
|
|
|
|
@current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated)
|
|
|
|
def render_page():
|
|
|
|
projects = get_projects('film')
|
|
|
|
return render_template(
|
|
|
|
'projects_index_collection.html',
|
|
|
|
title='open-projects',
|
|
|
|
projects=projects._items,
|
|
|
|
api=system_util.pillar_api())
|
|
|
|
|
|
|
|
return render_page()
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/workshops')
|
|
|
|
def workshops():
|
|
|
|
@current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated)
|
|
|
|
def render_page():
|
|
|
|
projects = get_projects('workshop')
|
|
|
|
return render_template(
|
|
|
|
'projects_index_collection.html',
|
|
|
|
title='workshops',
|
|
|
|
projects=projects._items,
|
|
|
|
api=system_util.pillar_api())
|
|
|
|
|
|
|
|
return render_page()
|
|
|
|
|
|
|
|
|
2017-09-15 15:25:27 +02:00
|
|
|
def get_random_featured_nodes() -> typing.List[dict]:
|
|
|
|
"""Returns a list of project/node combinations for featured nodes.
|
2017-07-13 18:35:18 +02:00
|
|
|
|
2017-09-15 15:25:27 +02:00
|
|
|
A random subset of 3 featured nodes from all public projects is returned.
|
|
|
|
Assumes that the user actually has access to the public projects' nodes.
|
2017-07-13 18:35:18 +02:00
|
|
|
|
2017-09-15 15:25:27 +02:00
|
|
|
The dict is a node, with a 'project' key that contains a projected project.
|
|
|
|
"""
|
2017-07-13 18:35:18 +02:00
|
|
|
|
2017-09-15 15:25:27 +02:00
|
|
|
proj_coll = current_app.db('projects')
|
|
|
|
featured_nodes = proj_coll.aggregate([
|
|
|
|
{'$match': {'is_private': False}},
|
|
|
|
{'$project': {'nodes_featured': True,
|
|
|
|
'url': True,
|
2017-09-17 20:12:47 +02:00
|
|
|
'name': True,
|
|
|
|
'summary': True,
|
|
|
|
'picture_square': True}},
|
2017-09-15 15:25:27 +02:00
|
|
|
{'$unwind': {'path': '$nodes_featured'}},
|
|
|
|
{'$sample': {'size': 3}},
|
|
|
|
{'$lookup': {'from': 'nodes',
|
|
|
|
'localField': 'nodes_featured',
|
|
|
|
'foreignField': '_id',
|
|
|
|
'as': 'node'}},
|
|
|
|
{'$unwind': {'path': '$node'}},
|
2018-10-03 18:30:40 +02:00
|
|
|
{'$match': {'node._deleted': {'$ne': True}}},
|
2017-09-15 15:25:27 +02:00
|
|
|
{'$project': {'url': True,
|
|
|
|
'name': True,
|
2017-09-17 20:12:47 +02:00
|
|
|
'summary': True,
|
|
|
|
'picture_square': True,
|
2017-09-15 15:25:27 +02:00
|
|
|
'node._id': True,
|
|
|
|
'node.name': True,
|
|
|
|
'node.permissions': True,
|
|
|
|
'node.picture': True,
|
|
|
|
'node.properties.content_type': True,
|
2018-10-03 18:30:40 +02:00
|
|
|
'node.properties.duration_seconds': True,
|
|
|
|
'node.properties.url': True,
|
|
|
|
'node._created': True,
|
2018-10-04 12:30:05 +02:00
|
|
|
}
|
|
|
|
},
|
2017-09-15 15:25:27 +02:00
|
|
|
])
|
2017-07-13 18:35:18 +02:00
|
|
|
|
|
|
|
featured_node_documents = []
|
2017-09-15 15:25:27 +02:00
|
|
|
api = system_util.pillar_api()
|
|
|
|
for node_info in featured_nodes:
|
|
|
|
# Turn the project-with-node doc into a node-with-project doc.
|
|
|
|
node_document = node_info.pop('node')
|
|
|
|
node_document['project'] = node_info
|
2018-10-03 18:30:40 +02:00
|
|
|
node_document['_id'] = str(node_document['_id'])
|
2017-09-15 15:25:27 +02:00
|
|
|
|
|
|
|
node = Node(node_document)
|
|
|
|
node.picture = get_file(node.picture, api=api)
|
2017-09-17 20:12:47 +02:00
|
|
|
node.project.picture_square = get_file(node.project.picture_square, api=api)
|
2017-09-15 15:25:27 +02:00
|
|
|
featured_node_documents.append(node)
|
2017-07-13 18:35:18 +02:00
|
|
|
|
|
|
|
return featured_node_documents
|
2017-08-30 23:13:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
@blueprint_settings.route('/emails', methods=['GET', 'POST'])
|
|
|
|
@login_required
|
|
|
|
def 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_settings.route('/billing')
|
|
|
|
@login_required
|
|
|
|
def billing():
|
|
|
|
"""View the subscription status of a user
|
|
|
|
"""
|
2017-11-30 15:45:40 +01:00
|
|
|
from . import store
|
|
|
|
|
2018-01-25 14:01:14 +01:00
|
|
|
log.debug('START OF REQUEST')
|
|
|
|
|
2017-08-30 23:13:03 +02:00
|
|
|
if current_user.has_role('protected'):
|
|
|
|
return abort(404) # TODO: make this 403, handle template properly
|
2018-01-25 14:01:14 +01:00
|
|
|
|
|
|
|
expiration_date = 'No subscription to expire'
|
|
|
|
|
|
|
|
# Classify the user based on their roles and capabilities
|
|
|
|
cap_subs = current_user.has_cap('subscriber')
|
|
|
|
if current_user.has_role('demo'):
|
|
|
|
user_cls = 'demo'
|
|
|
|
elif not cap_subs and current_user.has_cap('can-renew-subscription'):
|
|
|
|
# This user has an inactive but renewable subscription.
|
|
|
|
user_cls = 'subscriber-expired'
|
|
|
|
elif cap_subs:
|
|
|
|
if current_user.has_role('subscriber'):
|
|
|
|
# This user pays for their own subscription. Only in this case do we need to fetch
|
|
|
|
# the expiration date from the Store.
|
|
|
|
user_cls = 'subscriber'
|
|
|
|
store_user = store.fetch_subscription_info(current_user.email)
|
|
|
|
if store_user is None:
|
|
|
|
expiration_date = 'Unable to reach Blender Store to check'
|
|
|
|
else:
|
|
|
|
expiration_date = store_user['expiration_date'][:10]
|
|
|
|
|
|
|
|
elif current_user.has_role('org-subscriber'):
|
|
|
|
# An organisation pays for this subscription.
|
|
|
|
user_cls = 'subscriber-org'
|
|
|
|
else:
|
|
|
|
# This user gets the subscription cap from somewhere else (like an organisation).
|
|
|
|
user_cls = 'subscriber-other'
|
2018-01-25 10:16:29 +01:00
|
|
|
else:
|
2018-01-25 14:01:14 +01:00
|
|
|
user_cls = 'outsider'
|
2017-08-30 23:13:03 +02:00
|
|
|
|
|
|
|
return render_template(
|
|
|
|
'users/settings/billing.html',
|
2018-01-25 14:01:14 +01:00
|
|
|
user_cls=user_cls,
|
|
|
|
expiration_date=expiration_date,
|
|
|
|
title='billing')
|
2017-09-19 13:45:10 +02:00
|
|
|
|
|
|
|
|
2017-09-21 22:17:52 +02:00
|
|
|
@blueprint.route('/terms-and-conditions')
|
|
|
|
def terms_and_conditions():
|
|
|
|
return render_template('terms_and_conditions.html')
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/privacy')
|
|
|
|
def privacy():
|
|
|
|
return render_template('privacy.html')
|
|
|
|
|
|
|
|
|
2018-09-05 17:03:49 +02:00
|
|
|
@blueprint.route('/production')
|
|
|
|
def production():
|
2018-09-19 11:20:32 +02:00
|
|
|
return render_template(
|
|
|
|
'production.html',
|
|
|
|
title='production')
|
2018-09-05 17:03:49 +02:00
|
|
|
|
|
|
|
|
2017-12-21 15:26:23 +01:00
|
|
|
@blueprint.route('/emails/welcome.send')
|
|
|
|
@login_required
|
|
|
|
def emails_welcome_send():
|
|
|
|
from cloud import email
|
|
|
|
email.queue_welcome_mail(current_user)
|
|
|
|
return f'queued mail to {current_user.email}'
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/emails/welcome.html')
|
|
|
|
@login_required
|
|
|
|
def emails_welcome_html():
|
|
|
|
return render_template('emails/welcome.html',
|
|
|
|
subject='Welcome to Blender Cloud',
|
|
|
|
user=current_user)
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route('/emails/welcome.txt')
|
|
|
|
@login_required
|
|
|
|
def emails_welcome_txt():
|
|
|
|
txt = render_template('emails/welcome.txt',
|
|
|
|
subject='Welcome to Blender Cloud',
|
|
|
|
user=current_user)
|
|
|
|
return flask.Response(txt, content_type='text/plain; charset=utf-8')
|
|
|
|
|
|
|
|
|
2018-01-20 00:45:57 +01:00
|
|
|
@blueprint.route('/nodes/<string(length=24):node_id>/comments')
|
|
|
|
def comments_for_node(node_id):
|
|
|
|
"""Overrides the default render_comments_for_node.
|
|
|
|
|
|
|
|
This is done in order to extend can_post_comments by requiring the
|
|
|
|
subscriber capability.
|
|
|
|
"""
|
|
|
|
|
|
|
|
api = system_util.pillar_api()
|
|
|
|
|
|
|
|
node = Node.find(node_id, api=api)
|
|
|
|
project = Project({'_id': node.project})
|
|
|
|
can_post_comments = project.node_type_has_method('comment', 'POST', api=api)
|
|
|
|
can_comment_override = flask.request.args.get('can_comment', 'True') == 'True'
|
|
|
|
can_post_comments = can_post_comments and can_comment_override and current_user.has_cap(
|
|
|
|
'subscriber')
|
|
|
|
|
|
|
|
return render_comments_for_node(node_id, can_post_comments=can_post_comments)
|
|
|
|
|
|
|
|
|
2018-04-16 14:38:08 +02:00
|
|
|
@blueprint.route('/p/hero')
|
|
|
|
def project_hero():
|
|
|
|
api = system_util.pillar_api()
|
|
|
|
project = find_project_or_404('hero',
|
|
|
|
embedded={'header_node': 1},
|
|
|
|
api=api)
|
|
|
|
# Load the header video file, if there is any.
|
|
|
|
header_video_file = None
|
|
|
|
header_video_node = None
|
|
|
|
if project.header_node and project.header_node.node_type == 'asset' and \
|
|
|
|
project.header_node.properties.content_type == 'video':
|
|
|
|
header_video_node = project.header_node
|
|
|
|
header_video_file = get_file(project.header_node.properties.file)
|
|
|
|
header_video_node.picture = get_file(header_video_node.picture)
|
|
|
|
|
|
|
|
pages = Node.all({
|
|
|
|
'where': {'project': project._id, 'node_type': 'page'},
|
|
|
|
'projection': {'name': 1}}, api=api)
|
|
|
|
|
|
|
|
return render_project(project, api,
|
|
|
|
extra_context={'header_video_file': header_video_file,
|
|
|
|
'header_video_node': header_video_node,
|
|
|
|
'pages': pages._items,},
|
|
|
|
template_name='projects/landing.html')
|
|
|
|
|
|
|
|
|
2017-09-19 13:45:10 +02:00
|
|
|
def setup_app(app):
|
|
|
|
global _homepage_context
|
|
|
|
cached = app.cache.cached(timeout=300)
|
|
|
|
_homepage_context = cached(_homepage_context)
|