Introducing Pillar Framework

Refactor of pillar-server and pillar-web into a single python package. This
simplifies the overall architecture of pillar applications.

Special thanks @sybren and @venomgfx
This commit is contained in:
2016-08-19 09:19:06 +02:00
parent a5e92e1d87
commit 2c5dc34ea2
232 changed files with 79508 additions and 2232 deletions

View File

@@ -0,0 +1,78 @@
| {% block body %}
#user-edit-container
#user-edit-header
.user-edit-name {{user.full_name}}
.user-edit-username {{user.username}}
.user-edit-email {{user.email}}
form(
id="user-edit-form",
method="POST",
enctype="multipart/form-data",
action="{{url_for('users.users_edit', user_id=user._id)}}")
| {% for field in form %}
| {% if field.name == 'csrf_token' %}
| {{ field }}
| {% else %}
| {% if field.type == 'HiddenField' %}
| {{ field }}
| {% else %}
.form-group(class="{{field.name}}{% if field.errors %} error{% endif %}")
| {{ field.label }}
| {{ field(class='form-control') }}
| {% if field.errors %}
ul.error
| {% for error in field.errors %}
li {{ error }}
| {% endfor %}
| {% endif %}
| {% endif %}
| {% endif %}
| {% endfor %}
a#button-cancel.btn.btn-default(href="#", data-user-id='{{user._id}}') Cancel
input#submit_edit_user.btn.btn-default(
data-user-id="{{user._id}}",
type="submit" value="Submit")
#user-edit-notification
script(type="text/javascript").
$('#roles').select2();
$('#user-edit-form').submit(function(e){
e.preventDefault();
//- console.log($(this).serialize());
$.post($(this).attr('action'), $(this).serialize())
.done(function(data){
$('#user-edit-notification').addClass('success').html('Success!');
})
.fail(function(data){
$('#user-edit-notification').addClass('fail').html('Houston!');
});
//- $("#user-edit-form").submit();
});
$('#button-cancel').click(function(e){
$('#user-container').html('')
});
| {% endblock %}

View File

@@ -0,0 +1,120 @@
| {% extends 'layout.html' %}
| {% block page_title %}Users{% endblock %}
| {% block body %}
#search-container
#search-sidebar
input.search-field(
type="text",
name="q",
id="q",
autocomplete="off",
spellcheck="false",
autocorrect="false",
placeholder="Search by Full Name, Username...")
.search-list-filters
#accordion.panel-group.accordion(role="tablist", aria-multiselectable="true")
#facets
#pagination
.search-list-stats
#stats
#search-list
#hits
#search-details
#search-hit-container
| {% raw %}
// Facet template
script(type="text/template", id="facet-template")
.panel.panel-default
a(data-toggle='collapse', data-parent='#accordion', href='#filter_{{ facet }}', aria-expanded='true', aria-controls='filter_{{ facet }}')
.panel-heading(role='tab')
.panel-title {{ title }}
.panel-collapse.collapse.in(id='filter_{{ facet }}', role='tabpanel', aria-labelledby='headingOne')
.panel-body
| {{#values}}
a.facet_link.toggleRefine(
class='{{#refined}}refined{{/refined}}',
data-facet='{{ facet }}',
data-value='{{ value }}',
href='#')
span
| {{ label }}
small.facet_count.text-muted.pull-right {{ count }}
| {{/values}}
// Hit template
script(type="text/template", id="hit-template")
.search-hit.users(data-user-id='{{ objectID }}')
.search-hit-name
| {{{ _highlightResult.full_name.value }}}
small ({{{ username }}})
.search-hit-roles
| {{{ roles }}}
// Pagination template
script(type="text/template", id="pagination-template")
ul.search-pagination.
<li {{^prev_page}}class="disabled"{{/prev_page}}><a href="#" {{#prev_page}} class="gotoPage" data-page="{{ prev_page }}" {{/prev_page}}><i class="pi-angle-left"></i></a></li>
{{#pages}}
<li class="{{#current}}active{{/current}}{{#disabled}}disabled{{/disabled}}"><a href="#" {{^disabled}} class="gotoPage" data-page="{{ number }}" {{/disabled}}>{{ number }}</a></li>
{{/pages}}
<li {{^next_page}}class="disabled"{{/next_page}}><a href="#" {{#next_page}} class="gotoPage" data-page="{{ next_page }}" {{/next_page}}><i class="pi-angle-right"></i></a></li>
// Stats template
script(type="text/template", id="stats-template")
h5 {{ nbHits }} result{{#nbHits_plural}}s{{/nbHits_plural}}
span ({{ processingTimeMS }}ms)
| {% endraw %}
| {% endblock %}
| {% block footer_scripts %}
script().
var APPLICATION_ID = '{{config.ALGOLIA_USER}}';
var SEARCH_ONLY_API_KEY = '{{config.ALGOLIA_PUBLIC_KEY}}';
var INDEX_NAME = '{{config.ALGOLIA_INDEX_USERS}}';
var sortByCountDesc = null;
var FACET_CONFIG = [
{ name: 'roles', title: 'Roles', disjunctive: false, sortFunction: sortByCountDesc },
];
script(src="//cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js")
script(src="//cdn.jsdelivr.net/algoliasearch.helper/2/algoliasearch.helper.min.js")
script(src="//cdn.jsdelivr.net/hogan.js/3.0.0/hogan.common.js")
script(src="{{ url_for('static_pillar', filename='assets/js/algolia_search.min.js') }}")
script(type='text/javascript', src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.select2.min.js') }}")
script(type="text/javascript").
if (typeof Ps !== 'undefined'){
Ps.initialize(document.getElementById('hits'), {suppressScrollX: true});
}
function displayUser(userId) {
var url = '/u/' + userId + '/edit?embed=1';
$.get(url, function(dataHtml){
$('#search-hit-container').html(dataHtml);
});
}
$('body').on('click', '.search-hit', function(){
displayUser($(this).data('user-id'));
});
// Remove focus from search input so that the click event bound to .user-hit
// can be fired on the first click.
$('#search-list').hover(function(){
$('#q').blur();
});
| {% endblock %}

View File

@@ -0,0 +1,45 @@
| {% extends 'layout.html' %}
| {% block body %}
.container
#login-container
.login-title Welcome back!
.login-info
| Log in using your shared username and password.
.login-form
form#login-form(method="POST", action="{{url_for('users.login_local')}}")
.form-group
| {{ form.username.label }}
| {{ form.username(class='form-control') }}
.form-group
| {{ form.password.label }}
| {{ form.password(class='form-control') }}
.buttons
.login-button-container
//a.forgot(href="https://blender.org/id/reset") forgot your password?
button.btn.btn-default.button-login(type="submit")
i.pi-log-in
| Login
//a.btn.btn-default.button-register(href="https://blender.org/id/register", target="_blank")
// i.pi-star-outline
// | Create Account
| {% endblock %}
| {% block footer_scripts %}
script.
$('.button-login').on('click', function(e){
e.preventDefault();
$(this).html('<i class="pi-spin spin"></i> Hold on...');
$('#login-form').submit();
});
| {% endblock %}
| {% block footer_container %}{% endblock %}

View File

@@ -0,0 +1,20 @@
#settings-sidebar
.settings-header
.settings-title Settings
.settings-content
ul
a(class="{% if title == 'profile' %}active{% endif %}",
href="{{ url_for('users.settings_profile') }}")
li
i.pi-vcard
| 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

View File

@@ -0,0 +1,55 @@
| {% extends 'layout.html' %}
| {% 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/") Manage your subscription on Blender Store
hr
| {# This text is confusing (refers to the total payments ever made by the user)
.settings-billing-info.
Paid balance: {{ store_user['paid_balance'] }} {{ store_user['balance_currency'] }}
| #}
| {% 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 %}
| {% endblock %}

View File

@@ -0,0 +1,27 @@
| {% 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 %}

View File

@@ -0,0 +1,45 @@
| {% extends 'layout.html' %}
| {% block body %}
.container
#settings
include _sidebar
#settings-container
.settings-header
.settings-title Profile
.settings-content
.settings-form
form#settings-form(method='POST', action="{{url_for('users.settings_profile')}}")
.left
.form-group
| {{ form.full_name.label }}
| {{ form.full_name(size=20, class='form-control') }}
| {% if form.full_name.errors %}
| {% for error in form.full_name.errors %}{{ error|e }}{% endfor %}
| {% endif %}
.form-group
| {{ form.username.label }}
| {{ form.username(size=20, class='form-control') }}
| {% if form.username.errors %}
| {% for error in form.username.errors %}{{ error|e }}{% endfor %}
| {% endif %}
.form-group.settings-password
| Change your password at the
a(href="https://blender.org/id/change") Blender ID
.right
.settings-avatar
a(href="https://gravatar.com/")
img(src="{{ current_user.gravatar }}")
span Change Gravatar
.buttons
button.btn.btn-default.button-submit(type='submit')
i.pi-check
| Save Changes
| {% endblock %}

View File

@@ -0,0 +1,170 @@
| {% extends 'layout.html' %}
| {% block header_items %}
link(href='//cdn.datatables.net/plug-ins/1.10.7/integration/bootstrap/3/dataTables.bootstrap.css', rel='stylesheet')
| {% endblock %}
| {% block body %}
#shots-main.col-md-8
table#user_tasks.table.table-striped.table-hover(
cellpadding='0', cellspacing='0', border='0')
thead
tr
th {# 0 #}
th {# 1 #}
th {# 2 #}
th {# 3 #}
th {# 4 #} Shot
th {# 5 #} Name
th {# 6 #} Description
th {# 7 #} Duration
th {# 8 #} Status
| {% endblock %}
| {% block sidebar %}
#shots-sidebar.col-md-4
#shot_details_container
#task_details_container
| {% endblock %}
| {% block footer_scripts %}
script(type='text/javascript', src='//cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js')
script().
$(document).ready(function(){
function render_timing(timing) {
var timing_text = '';
if (timing['cut_in'] && timing['cut_out']) {
timing_frames = timing['cut_out'] - timing['cut_in'];
timing_text += '<span title="';
timing_text += timing_frames;
timing_text += 'f">';
timing_text += Math.round(timing_frames / 24);
timing_text += 's</span>';
}
return timing_text;
}
function render_status_options(status) {
var selected = false;
var options = []
statuses = ['todo', 'in_progress', 'on_hold', 'review', 'approved', 'final']
$.each(statuses, function(key, value) {
selected = false;
if (status === value) {
selected = true;
};
option = $("<option />", {
value: value,
text: value,
selected: selected
});
options.push(option);
});
return options;
}
function render_status_label(task, task_name) {
switch(task.status) {
case 'todo':
label_text = 'ToDo';
break;
case 'in_progress':
label_text = 'In progress';
break;
case 'on_hold':
label_text = 'On Hold';
break;
case 'cbb':
label_text = 'Could Be Better';
break;
case 'review':
label_text = 'Review';
break;
case 'approved':
label_text = 'Approved';
break;
case 'final':
label_text = 'Final';
break;
case 'conflict':
label_text = 'Conflict';
break;
default:
label_text = task.status;
break
}
if (task.is_conflicting) {
label_text = 'Conflict';
task.status = 'conflict';
}
return tag = '<span task-edit-url="' + task.url_edit + '" class="load-task-view label label-' + task.status + '">' + label_text + '</span>'
}
var shots_table = $('#user_tasks').DataTable({
"paging": false,
"order": [[ 7, "desc" ]],
"data": {{tasks_data | safe}},
"columns": [
/* */ {"data": "_id"},
/* */ {"data": "order"},
/* 0 */ {"data": "picture", "width": "80px", "className": "shots-shot_thumbnail"},
/* 1 */ {"data": "parent.name"},
/* 2 */ {"data": "name", "className": "shots-shot_name"},
/* 3 */ {"data": "description", "className": "shots-shot_description"},
/* 4 */ {"data": null,},
/* 5 */ {"data": "status"},
/* 6 */ {"data": null}
],
"columnDefs": [
{
"targets": [0, 1],
"visible": false,
"searchable": false
},
],
"rowCallback": function ( row, data, index ) {
if ( data.picture) {
var img_tag = '<img alt="' + data.name + '" src="' + data.picture_thumbnail + '" class="table-thumbnail">';
$('td', row).eq(0).html('<a href="' + data.url_view + '">' + img_tag + '</a>');
}
$('td', row).eq(2).html('<a class="load-shot-view" shot-view-url="' + data.url_edit + '" href="' + data.url_view + '">' + data.name + '</a>');
$('td', row).eq(4).html(render_timing(data.timing));
$('td', row).eq(5).html(render_status_label(data, data.name));
var view_tag = '<span class="btn btn-default btn-xs load-shot-view" shot-view-url="' + data.url_edit + '"><i class="glyphicon glyphicon-edit"></i> View</span>';
$('td', row).eq(6).html(view_tag);
}
});
$(document).on("click", ".load-task-view", function() {
$(".task-update").off( "click" );
task_view_url = $(this).attr('task-edit-url');
$.get(task_view_url, function(data) {
$('#shot_details_container').hide();
$('#task_details_container').html(data);
$('#task_details_container').show();
});
$('.load-task-view').removeClass('active');
$(this).addClass('active');
var shots_table = $('#user_tasks').DataTable();
var row = shots_table.row($(this).closest('tr'))
// Remove class 'active' from rows, and add to current one
shots_table.rows('.active').nodes().to$().removeClass('active updated');
shots_table.row(row).nodes().to$().addClass('active');
shots_table.draw();
});
});
| {% endblock %}