Commit fda48393 authored by Cédric Bonhomme's avatar Cédric Bonhomme
parents 89393154 1b7469bd
......@@ -27,6 +27,7 @@ src/web/static/lib/*
!src/web/static/lib/leaflet
!src/web/static/lib/dc
!src/web/static/lib/file-saver
!src/web/static/lib/bootstrap-slider
# node files
......
......@@ -18,7 +18,7 @@ python3.5 src/manager.py create_admin_user
# Import of the shelters from the CSV files
python3.5 src/manager.py import_shelters admin data/shelters/20150518_Haiti_shelters.csv
#python3.5 src/manager.py import_shelters admin data/shelters/Phil-Bangla-Burundi.csv
python3.5 src/manager.py import_shelters admin data/shelters/Phil-Bangla-Burundi.csv
# Import pictures of the shelters
python3.5 src/manager.py import_shelters_pictures data/shelters/pictures/
......
......@@ -31,6 +31,7 @@ with app.app_context():
app.register_blueprint(views.admin_bp)
app.register_blueprint(views.api_bp)
app.register_blueprint(views.apiv02_bp)
# API
from web import processors
# 'User' Web service
......
.stats {
display: inline-block;
}
.stats .table {
font-size: smaller;
}
.stats h5 {
margin-top:20px;
}
.leaflet-container a {
margin-left:0px !important;
}
.dc-chart text {
fill: black !important;
}
.dc-data-count {
float:right !important;
}
.menu-btn {
font-size: 0.9em;
margin-top: 15px;
}
.menu-href {
color: #333;
}
.minValue {
padding-left:1em
}
.advanced-content {
max-height: 300px ;
}
.advanced-content .button {
margin-top:5px !important;
}
.slider-handle {
width: 5px !important;
height: 20px !important;
margin-left: -2px !important;
}
.slider-horizontal {
width: 100px !important;
}
......@@ -810,21 +810,3 @@ section.map {
#tabs.tab2 .tabcontent2 {
display: initial; }
.stats {
display: inline-block;
}
.stats .table {
font-size: smaller;
}
.stats h5 {
margin-top:20px;
}
.leaflet-container a {
margin-left:0px !important;
}
.dc-chart text {
fill: black !important;
}
\ No newline at end of file
This diff is collapsed.
/*! =======================================================
VERSION 9.1.3
========================================================= */
/*! =========================================================
* bootstrap-slider.js
*
* Maintainers:
* Kyle Kemp
* - Twitter: @seiyria
* - Github: seiyria
* Rohit Kalkur
* - Twitter: @Rovolutionary
* - Github: rovolution
*
* =========================================================
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
.slider {
display: inline-block;
vertical-align: middle;
position: relative;
}
.slider.slider-horizontal {
width: 210px;
height: 20px;
}
.slider.slider-horizontal .slider-track {
height: 10px;
width: 100%;
margin-top: -5px;
top: 50%;
left: 0;
}
.slider.slider-horizontal .slider-selection,
.slider.slider-horizontal .slider-track-low,
.slider.slider-horizontal .slider-track-high {
height: 100%;
top: 0;
bottom: 0;
}
.slider.slider-horizontal .slider-tick,
.slider.slider-horizontal .slider-handle {
margin-left: -10px;
}
.slider.slider-horizontal .slider-tick.triangle,
.slider.slider-horizontal .slider-handle.triangle {
position: relative;
top: 50%;
transform: translateY(-50%);
border-width: 0 10px 10px 10px;
width: 0;
height: 0;
border-bottom-color: #0480be;
margin-top: 0;
}
.slider.slider-horizontal .slider-tick-container {
white-space: nowrap;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.slider.slider-horizontal .slider-tick-label-container {
white-space: nowrap;
margin-top: 20px;
}
.slider.slider-horizontal .slider-tick-label-container .slider-tick-label {
padding-top: 4px;
display: inline-block;
text-align: center;
}
.slider.slider-vertical {
height: 210px;
width: 20px;
}
.slider.slider-vertical .slider-track {
width: 10px;
height: 100%;
left: 25%;
top: 0;
}
.slider.slider-vertical .slider-selection {
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
.slider.slider-vertical .slider-track-low,
.slider.slider-vertical .slider-track-high {
width: 100%;
left: 0;
right: 0;
}
.slider.slider-vertical .slider-tick,
.slider.slider-vertical .slider-handle {
margin-top: -10px;
}
.slider.slider-vertical .slider-tick.triangle,
.slider.slider-vertical .slider-handle.triangle {
border-width: 10px 0 10px 10px;
width: 1px;
height: 1px;
border-left-color: #0480be;
margin-left: 0;
}
.slider.slider-vertical .slider-tick-label-container {
white-space: nowrap;
}
.slider.slider-vertical .slider-tick-label-container .slider-tick-label {
padding-left: 4px;
}
.slider.slider-disabled .slider-handle {
background-image: -webkit-linear-gradient(top, #dfdfdf 0%, #bebebe 100%);
background-image: -o-linear-gradient(top, #dfdfdf 0%, #bebebe 100%);
background-image: linear-gradient(to bottom, #dfdfdf 0%, #bebebe 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf', endColorstr='#ffbebebe', GradientType=0);
}
.slider.slider-disabled .slider-track {
background-image: -webkit-linear-gradient(top, #e5e5e5 0%, #e9e9e9 100%);
background-image: -o-linear-gradient(top, #e5e5e5 0%, #e9e9e9 100%);
background-image: linear-gradient(to bottom, #e5e5e5 0%, #e9e9e9 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5', endColorstr='#ffe9e9e9', GradientType=0);
cursor: not-allowed;
}
.slider input {
display: none;
}
.slider .tooltip.top {
margin-top: -36px;
}
.slider .tooltip-inner {
white-space: nowrap;
max-width: none;
}
.slider .hide {
display: none;
}
.slider-track {
position: absolute;
cursor: pointer;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #f9f9f9 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #f9f9f9 100%);
background-image: linear-gradient(to bottom, #f5f5f5 0%, #f9f9f9 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.slider-selection {
position: absolute;
background-image: -webkit-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #f9f9f9 0%, #f5f5f5 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border-radius: 4px;
}
.slider-selection.tick-slider-selection {
background-image: -webkit-linear-gradient(top, #89cdef 0%, #81bfde 100%);
background-image: -o-linear-gradient(top, #89cdef 0%, #81bfde 100%);
background-image: linear-gradient(to bottom, #89cdef 0%, #81bfde 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef', endColorstr='#ff81bfde', GradientType=0);
}
.slider-track-low,
.slider-track-high {
position: absolute;
background: transparent;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border-radius: 4px;
}
.slider-handle {
position: absolute;
top: 0;
width: 20px;
height: 20px;
background-color: #337ab7;
background-image: -webkit-linear-gradient(top, #149bdf 0%, #0480be 100%);
background-image: -o-linear-gradient(top, #149bdf 0%, #0480be 100%);
background-image: linear-gradient(to bottom, #149bdf 0%, #0480be 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
filter: none;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
border: 0px solid transparent;
}
.slider-handle.round {
border-radius: 50%;
}
.slider-handle.triangle {
background: transparent none;
}
.slider-handle.custom {
background: transparent none;
}
.slider-handle.custom::before {
line-height: 20px;
font-size: 20px;
content: '\2605';
color: #726204;
}
.slider-tick {
position: absolute;
width: 20px;
height: 20px;
background-image: -webkit-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #f9f9f9 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #f9f9f9 0%, #f5f5f5 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
filter: none;
opacity: 0.8;
border: 0px solid transparent;
}
.slider-tick.round {
border-radius: 50%;
}
.slider-tick.triangle {
background: transparent none;
}
.slider-tick.custom {
background: transparent none;
}
.slider-tick.custom::before {
line-height: 20px;
font-size: 20px;
content: '\2605';
color: #726204;
}
.slider-tick.in-selection {
background-image: -webkit-linear-gradient(top, #89cdef 0%, #81bfde 100%);
background-image: -o-linear-gradient(top, #89cdef 0%, #81bfde 100%);
background-image: linear-gradient(to bottom, #89cdef 0%, #81bfde 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef', endColorstr='#ff81bfde', GradientType=0);
opacity: 1;
}
This diff is collapsed.
......@@ -14,6 +14,8 @@
<script src="{{ url_for('static', filename = 'lib/leaflet/dc.leaflet.js') }}"></script>
<script src="{{ url_for('static', filename='lib/datatables.net/js/jquery.dataTables.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/file-saver/FileSaver.js')}}"></script>
<script src="{{ url_for('static', filename='lib/bootstrap-slider/bootstrap-slider.js')}}"></script>
<!--leaflet -->
......@@ -26,6 +28,9 @@
<!-- DC.js -->
<link href="{{ url_for('static', filename='lib/dc/dc.css') }}" rel="stylesheet" media="screen" />
<link href="{{ url_for('static', filename='lib/bootstrap-slider/bootstrap-slider.css')}}" rel="stylesheet" media="screen" />
{% endblock %}
......@@ -33,7 +38,7 @@
<section class="search">
<div class="content">
<div class="search-wrapper">
<input type="" name="" placeholder="Search Shelters.."/>
<input type="" name="" id="query" placeholder="Search Shelters.."/>
</div>
</div>
</section>
......@@ -60,12 +65,7 @@
<option value="" disabled>---</option>
</select>
</label>
<label for="materialFilter" class="button button-drop">
<select id="materialFilter">
<option value="" selected="selected">Material</option>
<option value="" disabled>---</option>
</select>
</label>
<label for="commercialFilter" class="button button-drop">
<select id="commercialFilter">
<option value="" selected="selected">Commercial</option>
......@@ -74,6 +74,8 @@
</label>
<!-- <a href="" class="button button-light button-drop">Climate</a> -->
<!-- <a href="" class="button button-drop">Continent</a> -->
<!-- <a href="" class="button button-drop">Disaster</a> -->
......@@ -105,16 +107,62 @@
</select>
</label>
<!--label for="topographyFilter" class="button button-small button-drop">
<label for="topographyFilter" class="button button-small button-drop">
<select id="topographyFilter">
<option value="" selected="selected">Topography</option>
<option value="" disabled>---</option>
</select>
</label-->
</label>
<label for="isSRURatedFilter" class="button button-small ">
<input id="isSRURatedFilter" type="checkbox" value=""> Rated by SRU
</label>
<br>
<label for="costRange" class="button button-small">
Construction cost per unit:&nbsp<input id="costRange" data-type="range" class="span2" data-slider-value="[0,1000000000]" />
<span id="costRangeMinValue" class="minValue"> 0</span> - <span id="costRangeMaxValue">0</span> $
</label>
<label for="widthRange" class="button button-small">
Width:&nbsp<input id="widthRange" data-type="range" class="span2" data-slider-value="[0,1000000000]"/>
<span id="widthRangeMinValue" class="minValue"> 0</span> - <span id="widthRangeMaxValue">0</span>m
</label>
<label for="lengthRange" class="button button-small">
Length:&nbsp<input id="lengthRange" data-type="range" class="span2" data-slider-value="[0,1000000000]" />
<span id="lengthRangeMinValue" class="minValue"> 0</span> - <span id="lengthRangeMaxValue">0</span>m
</label>
</div>
</div>
<div class="advanced container-fluid">
<div class="row">
<div class="col-md-4">
<div class="dc-data-count dc-chart" id="data-count">
Selected <span class="filter-count"></span> shelters out of <span
class="total-count"></span>
</div>
</div>
<div class="col-md-4 ">
<div class="menu-btn">
<a id="all" href="#" class="menu-href"> Reset All Filters&emsp;<i class="fa fa-times"></i></a>
</div>
</div>
<div class="col-md-4">
<div class="menu-btn">
<a id="download" class="menu-href">Download <i class="fa fa-download"></i></a>
|
<a id="share" class="menu-href">Share <i class="fa fa-share-alt"></i></a>
<div>
</div>
</div>
</div>
</form>
</div>
......@@ -135,35 +183,25 @@
</div>
<div class="tabcontent tabcontent2">
<div class="stats">
<div class="row>">
<div class="col-md-6">
<div class="row">
<div class="row">
<div class="col-md-12">
<div class="dc-data-count dc-chart" id="data-count">
<h4> Shelters
<small>
<span class="filter-count"></span> selected out of <span
class="total-count"></span>
records
|
<a id="all" href="#tabs">Reset All</a>
<h4> Shelters Statistics<br>
<small>
You can filter shelters by clicking a graph, using top menu buttons or zooming and dragging the map. <br>
Graph and menu filters are synchronized.
The list of shelters and graphs are also synchronized, displaying only shelters that match selected criteria.
To reset all filters use 'Reset All Filters' button below top menu.
</small>
</h4>
</small>
</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h5>By Year of Construction
<small><a id="year">reset</a></small>
</h5>
<div class="dc-chart" id="chart-timeline"></div>
</div>
</div>
<div class="row>">
<div class="col-md-6">
<div class="row">
<div class="col-md-4">
<h5>By Zone
......@@ -200,19 +238,22 @@
</div>
</div>
<div class="row">
<div class="col-md-12">
<h5>By Year of Construction
<small><a id="year">reset</a></small>
</h5>
<div class="dc-chart" id="chart-timeline"></div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-12" >
<h5>Matching shelters
</h5>
<h4 class="text-right">
<small>
<a id="download">Download</a>
|
<a id="share">Share</a>
</small>
</h4>
<table class="table table-bordered table-striped dc-table" id="shelters-table">
<table class="table table-bordered table-striped dc-table" id="shelters-table">
<thead>
<tr class="header">
......@@ -225,6 +266,9 @@
</tr>
</thead>
</table>
</div>
</div>
</div>
......
......@@ -18,6 +18,7 @@
<link href="{{ url_for('static', filename = 'lib/bootstrap/dist/css/bootstrap.min.css') }}" rel="stylesheet" media="screen" />
<link href="{{ url_for('static', filename = 'lib/font-awesome/css/font-awesome.min.css') }}" rel="stylesheet" media="screen" />
<link href="{{ url_for('static', filename='css/style_new.css') }}" rel="stylesheet" media="screen" />
<link href="{{ url_for('static', filename='css/style-dashboard.css')}}" rel="stylesheet" media="screen" />
<script src="{{ url_for('static', filename = 'lib/jquery/dist/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename = 'js/create-shelter.js') }}"></script>
......
This diff is collapsed.
......@@ -6,6 +6,7 @@ from web.views.page import recommendations
from web.views.admin import *
from web.views.session_mgmt import *
from web.views.shelterapi import api_bp
from web.views.shelterapiv02 import apiv02_bp
import conf
from flask import g
......
#! /usr/bin/env python
#-*- coding: utf-8 -*-
# ***** BEGIN LICENSE BLOCK *****
#
#
# ***** END LICENSE BLOCK *****
__author__ = ""
__version__ = ""
__date__ = ""
__revision__ = ""
__copyright__ = ""
__license__ = ""
#from bootstrap import db
from bootstrap import db
from sqlalchemy.sql import func, select
from flask import Blueprint, jsonify, request
from collections import defaultdict
from web.models import Shelter, Attribute, Property, Value, Association, ShelterPicture, Category, Tsvector
apiv02_bp = Blueprint('development api v0.2', __name__, url_prefix='/api/v0.2')
def tree():
return defaultdict(tree)
def queryfactory(model,join=False,filt=False,value=False):
#helper functions to construct queries
def filter_or(obj,attrib,val):
"""Construct filtering method (OR)"""
list(val)
if len(val) == 1:
return obj.filter(attrib == val[0])
else:
return obj.filter(attrib.in_(val))
def filter_and(obj,attrib,val):
"""Construct filtering methods recursively (AND)"""
list(val)
if len(val) == 1:
return obj.filter(attrib == val[0])
else:
return filter_and(obj.filter(attrib == val[len(val)-1]),attrib, val[0:len(val)-1])
if join and not filt:
return model.query.join(join)
elif filt and join:
return filter_or(model.query.join(join),filt,value)
elif filt and not join:
return filter_or(model.in_(value))
else:
return "error"
@apiv02_bp.route('/', methods=['GET'])
def apimessage():
message = tree()
message["API version"] = 0.2
message["Message"] = "This is the development API"
return jsonify(message)
@apiv02_bp.route('/attributes/<attribute_name>', methods=['GET'])
def getattributes(attribute_name, safetext=False):
"""Returns available values for a given attribute name, separated by semicolons"""
result= tree()
attributes = Attribute.query.filter(Attribute.uniqueid==attribute_name).\
first().associated_values
result[attribute_name] = ";".join([attribute.name for attribute in attributes])
return jsonify(result)
@apiv02_bp.route('/shelters', methods=['GET'])
@apiv02_bp.route('/shelters/<int:shelter_id>', methods=['GET'])
def allshelters(shelter_id=None):
"""Returns all shelters and their properties"""
result = tree()
#shelter pictures folder path
picpath = 'data/shelters/pictures'
Supercategory = db.aliased(Category)
querybase = db.session.query(Property.shelter_id, Category.name.label("category_name"), Supercategory.name.label("supercategory_name"), Attribute.name, Attribute.uniqueid,func.string_agg(Value.name,';').label("value"))\
.join(Category, Category.id==Property.category_id)\
.join(Attribute, Attribute.id==Property.attribute_id)\
.join(Supercategory, Supercategory.id==Category.parent_id)\
.join(Association, Property.id==Association.property_id)\
.join(Value, Association.value_id==Value.id)\
.group_by(Property.shelter_id, Supercategory.name, Category.name, Attribute.name, Attribute.uniqueid)
picquerybase = db.session.query(ShelterPicture.shelter_id, ShelterPicture.file_name.label("filename"), Category.name)\
.join(Category, Category.id == ShelterPicture.category_id)
##queries if no request arguments
shelter_properties = querybase
shelter_pictures = picquerybase
if shelter_id:
shelter_properties = shelter_properties.filter(Property.shelter_id==shelter_id)
shelter_pictures = shelter_pictures.filter(ShelterPicture.shelter_id==shelter_id)
if request.args.getlist('attribute'):
attribute = request.args.getlist('attribute')
subquery = db.session.query(Property.shelter_id)\
.join(Attribute, Attribute.id==Property.attribute_id)\
.filter(Attribute.uniqueid.in_(attribute))\