Skip to content
Snippets Groups Projects
Commit 33ab8192 authored by Martin Burchell's avatar Martin Burchell
Browse files

Merge branch 'develop' into admin_link

parents 3ffc1b47 d50589d3
No related branches found
No related tags found
No related merge requests found
Showing
with 326 additions and 226 deletions
WSGIPythonHome /usr/local/pythonenv/baseline27
WSGISocketPrefix /var/run/wsgi
WSGIRestrictEmbedded On
<VirtualHost *:80>
ServerAdmin carers-internewshid@aptivate.org
ServerName internewshid.dev.aptivate.org
ServerAlias fen-vz-internewshid-dev.fen.aptivate.org
DocumentRoot /var/www
# Static content uploaded by users
Alias /uploads "/var/django/internewshid/current/django/website/uploads/"
<Location "/uploads">
Order allow,deny
Allow from all
SetHandler None
</Location>
Alias /robots.txt "/var/django/internewshid/current/django/website/static/robots.txt.dev_server"
# Django settings - AFTER the static media stuff
WSGIScriptAlias / /var/django/internewshid/current/wsgi/wsgi_handler.py
WSGIDaemonProcess internewshid processes=1 threads=10 display-name='%{GROUP}' deadlock-timeout=30
WSGIApplicationGroup %{GLOBAL}
WSGIProcessGroup internewshid
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
<DirectoryMatch "^/.*/\.(svn|git)/">
Order allow,deny
Deny from all
</DirectoryMatch>
# robots.txt
#Alias /robots.txt /var/www/robots.txt
</VirtualHost>
# vi: ft=apache
......@@ -49,10 +49,15 @@ django-tables2==1.0.4
#django-debug-toolbar
## ---------CSS and assets
#django-assets
# django-assets==0.10
# We need the version of django_assets that contains the following fix:
# https://github.com/miracle2k/django-assets/issues/52
git+git://github.com/miracle2k/django-assets.git@97005b92d092827cfebe798e48e00dba25f83597
#webassets
#pyScss
#cssmin
cssmin==0.2.0
jsmin==2.1.2
django-bootstrap3
# vi: filetype=conf
......@@ -67,12 +67,14 @@ test_cmd = ' manage.py test'
host_list = {
'production': ['lin-' + project_name + '.aptivate.org:48001'],
'staging': ['fen-vz-' + project_name + '-stage.fen.aptivate.org'],
'dev_server': ['fen-vz-' + project_name + '-dev.fen.aptivate.org']
}
# this is the default git branch to use on each server
default_branch = {
'production': 'master',
'staging': 'staging',
'dev_server': 'develop'
}
# where on the server the django apps are deployed
......
......@@ -6,6 +6,10 @@
margin-bottom: 20px;
}
.dashboard .panel-body {
overflow: auto;
}
.dashboard .widget-height-small {
height: 120px;
}
......
<div class='panel panel-default'>
<div class='panel-heading'>
<span class='fa fa-align-justify fa-fw'></span>
{{ title }}
<h2>{{ title }}</h2>
</div>
<div class='panel-body'>
{{ text }}
{% if html %}
{{ text|safe }}
{% else %}
{{ text }}
{% endif %}
</div>
</div>
......@@ -3,161 +3,16 @@
{% load bootstrap3 %}
{% load render_widget %}
{% block bootstrap3_extra_head %}
{% for stylesheet in css %}
<link href='{{STATIC_URL}}{{stylesheet}}' rel='stylesheet' />
{% endfor %}
{% endblock %}
{% block maincontent %}
<h1 class="page-header"><span class="fa fa-dashboard fa-fw"></span>{% trans "Dashboard" %}</h1>
{# Actual widgets #}
{% for row in rows %}
<div class='row dashboard'>
{% for widget in row %}
<div class='col-lg-{{ widget.width }} col-md-{{ widget.width }} col-sm-{{ widget.width }} widget-height-{{ widget.height }}'>
{{ widget|render_widget }}
</div>
{% endfor %}
</div>
{% endfor %}
{# "Example canned data" #}
<div class="row dashboard">
<div class="col-lg-8">
<div class="panel panel-red">
<div class="panel-heading">
<span class="fa fa-bar-chart-o fa-fw"></span>Ebola Questions this week
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<div id="morris-bar-chart"></div>
</div>
<!-- /.panel-body -->
</div>
<!-- /.panel -->
</div>
<!-- /.col-lg-8 -->
<div class="col-lg-4">
<div class="panel panel-blue">
<div class="panel-heading">
<span class="fa fa-list fa-fw"></span>Blank widget space
<h1 class="page-header"><span class="fa fa-dashboard fa-fw"></span>{% trans "Dashboard" %}</h1>
{% for row in rows %}
<div class='row dashboard'>
{% for widget in row %}
<div class='col-lg-{{ widget.width }} col-md-{{ widget.width }} col-sm-{{ widget.width }} widget-height-{{ widget.height }}'>
{{ widget|render_widget }}
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<div id="blank-panel"></div>
</div>
<!-- /.panel-body -->
</div>
<!-- /.panel -->
</div>
<!-- /.col-lg-4 -->
</div>
<!-- /.row -->
<div class="row dashboard">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<span class="fa fa-table fa-fw"></span> Recent Messages
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
Actions
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
<li><a href="#">Action</a>
</li>
<li><a href="#">Another action</a>
</li>
<li><a href="#">Something else here</a>
</li>
<li class="divider"></li>
<li><a href="#">Separated link</a>
</li>
</ul>
</div>
</div>
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<div class="row">
<div class="table-responsive">
<table class="table table-hover table-striped table-no-border">
<thead>
<tr>
<th>County</th>
<th>Date</th>
<th>Question</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr>
<td>Lofa</td>
<td>5/7/15</td>
<td>What is the cause of ebola?</td>
<td>Origin</td>
</tr>
<tr>
<td>Montserrado</td>
<td>5/7/15</td>
<td>Is ebola still here or not?</td>
<td>Updates</td>
</tr>
<tr>
<td>Margibi</td>
<td>5/7/15</td>
<td>When will Liberia be free from ebola</td>
<td>Updates</td>
</tr>
<tr>
<td>Sinoe</td>
<td>5/7/15</td>
<td>How did Ebola come to Liberia?</td>
<td>Origin</td>
</tr>
<tr>
<td>Montserrado</td>
<td>5/7/15</td>
<td>HOW DO EBOLA TREAT A PERSON</td>
<td>Measures</td>
</tr>
<tr>
<td>Montserrado</td>
<td>5/7/15</td>
<td>I have been told that very soon that Liberia will be declar ebolo free now i want to know if there is any ebola case</td>
<td>Updates</td>
</tr>
<tr>
<td>Montserrado</td>
<td>5/7/15</td>
<td>EBOLA STILL IN LIBERIA?</td>
<td>Updates</td>
</tr>
<tr>
<td>Montserrado</td>
<td>5/7/15</td>
<td>How Many Person Have Die From Ebola In Liberia?</td>
<td>Numbers</td>
</tr>
</tbody>
</table>
</div>
<!-- /.table-responsive -->
</div>
<!-- /.col-lg-4 (nested) -->
</div>
<!-- /.row -->
</div>
<!-- /.panel-body -->
{% endfor %}
</div>
</div><!-- /.col-lg-12 -->
</div>
{% endfor %}
{% endblock maincontent %}
{% block lastjs %}
{{ block.super }}
{% for library in javascript %}
<script src='{{STATIC_URL}}{{ library }}'></script>
{% endfor %}
{% endblock %}
from mock import patch
from django.test import TestCase
from dashboard.models import Dashboard
from dashboard.views import DashboardView
from dashboard.widget_pool import register_widget
from hid.assets import use_assets
class TestWidget(object):
......@@ -62,16 +65,15 @@ class TestDashboardView(TestCase):
dashboard_view = DashboardView()
view_args = {'name': 'dashboard1'}
dashboard_view.kwargs = view_args
return dashboard_view.get_context_data(**view_args)
def test_remove_duplicates(self):
""" Ensure duplicates are removed and the order is kept"""
dashboard_view = DashboardView()
test_array = [1, 2, 5, 1, 3, 2, 5, 3, 1, 4]
self.assertEqual(
dashboard_view._remove_duplicates(test_array),
[1, 2, 5, 3, 4]
)
assets = [
'file.js', 'app/file.js', 'file.css',
'app/file.css', 'some.js', 'app2/file.js',
'some.css', 'app2/file.css',
'dashboard/dashboard.css'
]
with use_assets(*assets):
context_data = dashboard_view.get_context_data(**view_args)
return context_data
def test_context_data_includes_dashboard_name(self):
self.setup_dashboard('dashboard1')
......@@ -142,7 +144,7 @@ class TestDashboardView(TestCase):
['test-widget-4', 'test-widget-2']
])
def test_context_data_includes_javascript(self):
def test_context_data_requires_assets(self):
self.setup_dashboard('dashboard1', [
{
'widget_type': 'test-widget-1',
......@@ -155,28 +157,15 @@ class TestDashboardView(TestCase):
'column': 1
}
])
context = self.get_dashboard_view_context('dashboard1')
self.assertEqual(context['javascript'], [
required_assets = []
with patch('dashboard.views.require_assets') as mock:
self.get_dashboard_view_context('dashboard1')
for call_args in mock.call_args_list:
required_assets += call_args[0]
self.assertEqual(set([
'file.js', 'app/file.js',
'some.js', 'app2/file.js'
])
def test_context_data_includes_css(self):
self.setup_dashboard('dashboard1', [
{
'widget_type': 'test-widget-1',
'row': 0,
'column': 0
},
{
'widget_type': 'test-widget-2',
'row': 0,
'column': 1
}
])
context = self.get_dashboard_view_context('dashboard1')
self.assertEqual(context['css'], [
'dashboard/dashboard.css',
'some.js', 'app2/file.js',
'file.css', 'app/file.css',
'some.css', 'app2/file.css'
])
'some.css', 'app2/file.css',
'dashboard/dashboard.css'
]), set(required_assets))
from django.views.generic import TemplateView
from hid.assets import require_assets
from dashboard.models import Dashboard
from dashboard.widget_pool import get_widget
......@@ -38,25 +40,14 @@ class DashboardView(TemplateView):
if len(current_row) > 0:
context['rows'].append(current_row)
# Get all the javascript & css dependencies
context['javascript'] = []
context['css'] = ['dashboard/dashboard.css']
# Ensure we have all the javascript & css dependencies
require_assets('dashboard/dashboard.css')
for widget in widgets:
widget_type = get_widget(widget.widget_type)
if hasattr(widget_type, 'javascript'):
context['javascript'] += widget_type.javascript
require_assets(*widget_type.javascript)
if hasattr(widget_type, 'css'):
context['css'] += widget_type.css
context['javascript'] = self._remove_duplicates(context['javascript'])
context['css'] = self._remove_duplicates(context['css'])
require_assets(*widget_type.css)
# Return the context
return context
def _remove_duplicates(self, the_list):
""" Remove duplicates whilst preseving order """
out = []
for e in the_list:
if e not in out:
out.append(e)
return out
......@@ -36,15 +36,19 @@ class BasicTextWidget(object):
Settings:
title: The widget title
text: The widget text
html: If true, the content contains html.
If false, the content should be escaped.
"""
template_name = 'dashboard/basic-text-widget.html'
def get_context_data(self, **kwargs):
title = kwargs.get('title', '(no title')
text = kwargs.get('text', '(no text)')
html = kwargs.get('html', False)
return {
'title': title,
'text': text
'text': text,
'html': html
}
register_widget('basic-text-widget', BasicTextWidget())
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('taxonomies', '0001_initial'),
('data_layer', '0002_auto_20150619_2132'),
]
operations = [
migrations.AddField(
model_name='message',
name='terms',
field=models.ManyToManyField(to='taxonomies.Term'),
),
]
from django.db import models
from taxonomies.models import Term
class DataLayerModel(models.Model):
......@@ -11,6 +12,26 @@ class DataLayerModel(models.Model):
class Message(DataLayerModel):
body = models.TextField()
timestamp = models.DateTimeField(null=True)
terms = models.ManyToManyField(Term)
def apply_term(self, term):
# TODO: test this
""" Add or replace value of term.taxonomy for current Item
If the Item has no term in the taxonomy
OR if the taxonomy.is_multiple just add it.
IF the taxonmy is optional (categories)
If the Item has a term in that taxonomy already,
replace it
"""
# It bugs me that so much of the logic applying to taxonomies is here.
# This should really be built out with an explicity through model
# in taxonomies, with a generic foreign ken to the content type
# being classified, then this logic could live there.
if term.taxonomy.is_optional:
for old_term in self.terms.filter(taxonomy=term.taxonomy):
self.terms.remove(old_term)
self.terms.add(term)
def __unicode__(self):
return "{}: '{}' @{}".format(
......
from dashboard.widget_pool import register_widget
from hid.widgets.chart import QuestionChartWidget
from hid.widgets.table import TableWidget
register_widget('question-chart-widget', QuestionChartWidget())
register_widget('table-widget', TableWidget())
""" Handle the assets for the project so we can compile all
javascript and css in a single file, which is the optimum
way of building the resources for high latency and low
bandwidth setups.
The list of assets is not dynamic - to be built for production
the list of assets in _assets must be defined in this file.
Asset type is determined by file extension:
.js: Javascript file
.css: Css file
.less: Less file
To ensure dependencies are included, and to clearly express
which components require certain css or javascript files,
views may call 'require_assets' which will raise an
AssetMissing exception if the given asset is not included
statically here.
"""
from contextlib import contextmanager
from django_assets import Bundle, register
_assets = [
# Stylesheets
'fonts/font-awesome-4.3.0/css/font-awesome.css',
'less/internews-bootstrap.less',
'dashboard/dashboard.css',
# Javascript librairies
'js/jquery.min.js',
'bootstrap/js/bootstrap.min.js',
'js/underscore.js',
'js/backbone.js',
'flot/jquery.flot.js',
'flot/jquery.flot.resize.js',
'hid/widgets/chart.js',
'hid/js/spinner.js',
'hid/js/messages.js',
'hid/js/automatic_file_upload.js'
]
class AssetMissing(Exception):
""" Exception raised when an asset is missing """
pass
def require_assets(*assets):
""" Ensure the given assets are included in the page's assets
Args:
assets: List of assets
Raises:
AssetMissing: If any of the asset is missing
"""
global _assets
for asset in assets:
if asset not in _assets:
raise AssetMissing("Missing asset: {}".format(asset))
@contextmanager
def use_assets(*assets):
""" Context manager to temporarily override the asset list.
This is used for testing purposes only - assets not defined
statically here will not be included in production
environment.
"""
global _assets
old_assets = _assets
try:
_assets = assets
yield
finally:
_assets = old_assets
register('javascript_assets', Bundle(
*[a for a in _assets if a.endswith('.js')],
filters='jsmin', output='js/javascript_assets.js'
))
register('css_assets', Bundle(
*[a for a in _assets if a.endswith('.css') or a.endswith('.less')],
filters='less, cssmin', output='css/css_assets.css',
depends=['less/*.less']
))
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
def create_question_types(apps, schema_editor):
Taxonomy = apps.get_model('taxonomies', 'Taxonomy')
taxonomy = Taxonomy(name="Ebola Questions", slug="ebola-questions")
taxonomy.save()
question_types = (
("Vaccine", "Vaccine Trial"),
("Measures", "What measures could end Ebola?"),
("Symptoms", "Symptoms/Medical"),
("Stigmatism", "Are survivors stigmatized?"),
("Victims", "Number of Victims?"),
("Schools", "Does Ebola affect schools?"),
("Updates", "Are there updates on Ebola?"),
("Origin", "What is the origin of Ebola?"),
("Others", "Others"),
("Real", "Is Ebola real?"),
)
Term = apps.get_model('taxonomies', 'Term')
new_terms = [Term(
name=qt[0],
long_name=qt[1],
taxonomy=taxonomy) for qt in question_types]
db_alias = schema_editor.connection.alias
Term.objects.using(db_alias).bulk_create(new_terms)
class Migration(migrations.Migration):
dependencies = [
('hid', '0001_initial')
]
operations = [
migrations.RunPython(create_question_types)
]
/**
* Modify file upload button behaviour to allow us to:
* - Use own markup for file buttons (eg. a tags);
* - Auto-submit form on file selection.
*
* Usage:
* - The form element should have a 'auto-upload-file' class;
* - The upload button replacement should have an 'upload-button' class
* (optional, we can also use the normal upload button).
*
* Note that this will only work if the form has one file upload element.
*
*/
(function($){
$(document).ready(function() {
$('.auto-upload-file').each(function() {
var $form = $(this);
var $button = $('.upload-button', $form);
var $file_input = $('[type="file"]', $form);
// Auto-submit
$file_input.on('change', function(e) {
e.preventDefault();
$form.submit();
});
// Alternative upload button
$button.on('click', function(e) {
e.preventDefault();
$file_input.click();
});
});
});
})(jQuery);
......@@ -3,7 +3,7 @@
* connections where loading can take time */
jQuery(function(){
jQuery('button, .btn').click(function() {
jQuery('button:not(.navbar-toggle), .btn').click(function() {
jQuery('.spinner').toggleClass('active');
});
});
\ No newline at end of file
......@@ -24,7 +24,10 @@ class ItemTable(tables.Table):
format=settings.SHORT_DATETIME_FORMAT,
)
body = tables.Column(verbose_name='Message')
category = tables.TemplateColumn(template_name='hid/categories_column.html')
category = tables.TemplateColumn(
template_name='hid/categories_column.html',
accessor='terms.0.name',
)
delete = NamedCheckBoxColumn(accessor='id', verbose_name='Delete')
def __init__(self, *args, **kwargs):
......@@ -32,10 +35,12 @@ class ItemTable(tables.Table):
super(ItemTable, self).__init__(*args, **kwargs)
def render_category(self, record, value):
# TODO: Test this
Template = loader.get_template('hid/categories_column.html')
ctx = {
'categories': self.categories,
'category': self.categories[2][0],
'category': value,
'record': record
}
return Template.render(ctx)
{% if categories %}
<select name="category-{{ record.id }}" class="form-control">
{% if not category %}
<option value="" selected="selected">---------</option>
{% endif %}
{% for cat in categories %}
<option value="{{ cat.0 }}"{% if category == cat.0 %} selected="selected"{% endif %}>{{ cat.1 }}</option>
{% endfor %}
......
......@@ -11,11 +11,11 @@
<h2><img alt="{{ source.name }}" src="{{ STATIC_URL }}images/logo-{{ source.name }}.png"/></h2>
</div>
<form action="{% url "sources-upload" %}" method="post" enctype="multipart/form-data" class="item-source-actions pull-right">
<form action="{% url "sources-upload" %}" method="post" enctype="multipart/form-data" class="item-source-actions auto-upload-file pull-right">
{% csrf_token %}
<a class="btn btn-primary btn-block btn-sm" value="View/Edit data" type="button" href="{% url "data-view" %}"><span class="fa fa-pencil fa-fw"></span> View &amp; Edit data</a>
{% bootstrap_form source.form show_label=False %}
<a class="btn btn-sm item-source-upload btn-block btn-primary" type="button" value="Upload" href="{% url "data-view" %}"><span class="fa fa-upload fa-fw"></span> Upload</a>
<a class="btn btn-sm item-source-upload upload-button btn-block btn-primary" type="button" value="Upload" href="{% url "data-view" %}"><span class="fa fa-upload fa-fw"></span> Upload</a>
</form>
</li>
{% endfor %}
......@@ -23,18 +23,3 @@
{% endblock maincontent %}
{% block lastjs %}
<script>
$(function () {
$(".item-source-upload").on("click", function (e) {
e.preventDefault();
$(this).parents("form").find('[type="file"]').click();
});
$('.item-source [type="file"]').on("change", function (e) {
e.preventDefault();
$(this).parents("form")[0].submit();
});
});
</script>
{% endblock lastjs %}
......@@ -7,6 +7,11 @@
<h1 class="page-header"><span class="fa fa-pencil fa-fw"></span>{% trans "View &amp; Edit" %}</h1>
<div class='row'>
<div class="col-lg-12">
<form action="{% url "sources-upload" %}" method="post" enctype="multipart/form-data" class="auto-upload-file header-upload-form">
{% csrf_token %}
{% bootstrap_form upload_form show_label=False %}
<a class="btn btn-sm item-source-upload btn-block btn-primary upload-button" type="button" value="Upload" href="{% url "data-view" %}"><span class="fa fa-upload fa-fw"></span>{% blocktrans %}Upload {{ type_label }}{% endblocktrans %}</a>
</form>
<form action="{% url "data-view-process" %}" method="post" class="view-items-form">{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment