Commit dc11a23e05d5aae5adf42adbd5e0492bcf499dcd

Authored by Adabriand Furtado
1 parent ba5232a0
Exists in master

Versão inicial do Validador.

.gitignore 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +.*
  2 +*~
  3 +!.gitignore
  4 +!.gitempty
  5 +*.log
  6 +*.pyc
  7 +settings_local.py
  8 +env/
  9 +tmp/
... ...
inicial.txt
... ... @@ -1 +0,0 @@
1   -Repositorio validador_sinais Criado
main.py 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +# -*- coding: utf-8 -*-
  2 +from flask import Flask, send_from_directory
  3 +from flask.ext.cors import CORS
  4 +from jinja2 import Environment, PackageLoader
  5 +from validador import Validador
  6 +import os
  7 +import pyutil
  8 +
  9 +app = Flask(__name__)
  10 +CORS(app)
  11 +controller = None
  12 +
  13 +@app.route("/<path:path>")
  14 +def send_static_files(path):
  15 + root_dir = os.path.abspath(os.path.dirname(__file__))
  16 + file_dir = os.path.join(root_dir, "view")
  17 + return send_from_directory(file_dir, path)
  18 +
  19 +@app.route("/update_project")
  20 +def update_project():
  21 + try:
  22 + return controller.update_project()
  23 + except:
  24 + pyutil.print_stack_trace()
  25 + raise
  26 +
  27 +@app.route("/create_project")
  28 +def create_project():
  29 + try:
  30 + return controller.create_project()
  31 + except:
  32 + pyutil.print_stack_trace()
  33 + raise
  34 +
  35 +@app.route("/finish_task", methods=["POST"])
  36 +def finish_task():
  37 + # TODO read - request.data['upload_session_id'] e request.data['sign_name']
  38 + return
  39 +
  40 +def read_settings(app):
  41 + here = os.path.abspath(__file__)
  42 + config_path = os.path.join(os.path.dirname(here), 'settings_local.py')
  43 + if os.path.exists(config_path):
  44 + app.config.from_pyfile(config_path)
  45 + app.config['HOST_ENDPOINT'] = "http://" + app.config['SERVER_HOST'] + ":" + str(app.config['SERVER_PORT'])
  46 +
  47 +def setup_controller():
  48 + global controller
  49 + read_settings(app)
  50 + env = Environment(loader=PackageLoader('main', 'view'))
  51 + controller = Validador(app.config, env)
  52 +
  53 +def run():
  54 + setup_controller()
  55 + app.run(port=app.config['SERVER_PORT'])
  56 +
  57 +if __name__ == '__main__':
  58 + try:
  59 + run()
  60 + except:
  61 + pyutil.print_stack_trace()
  62 + raise
... ...
pyutil.py 0 → 100644
... ... @@ -0,0 +1,68 @@
  1 +# -*- coding: UTF-8 -*-
  2 +
  3 +import datetime
  4 +import logging
  5 +import os
  6 +import shutil
  7 +import sys
  8 +
  9 +# @def funcao para obter data e hora atual do sistema
  10 +# @param string formato de data e hora
  11 +# @return string retorna data e hora do sistema no momento da chamada
  12 +def getTimeStamp(date_fmt="%Y-%m-%d %H:%M:%S.%f"):
  13 + if ("%f" in date_fmt):
  14 + # [:-3] remove 3 casas decimais dos milisegundos (ms)
  15 + return datetime.datetime.now().strftime(date_fmt)[:-3]
  16 + else:
  17 + return datetime.datetime.now().strftime(date_fmt)
  18 +
  19 +# @def funcao para gravar log dos eventos em arquivo
  20 +# @param string mensagem a ser salva
  21 +# @param int indice do tipo de log 0: apenas print, 1: debug, 2: info, 3: warn, 4: error, 5: critical
  22 +# @param string caminho completo do arquivo de logs
  23 +# @param string formato de tempo utilizado
  24 +# @return none
  25 +def log(msg="", log_level=2, log_file="events.log"):
  26 + dict_level = {
  27 + 0: ["Print", None, None],
  28 + 1: ["DEBUG", logging.DEBUG, logging.debug],
  29 + 2: ["INFO", logging.INFO, logging.info],
  30 + 3: ["WARNING", logging.WARN, logging.warn],
  31 + 4: ["ERROR", logging.ERROR, logging.error],
  32 + 5: ["CRITICAL", logging.CRITICAL, logging.critical]
  33 + }
  34 + # log_format = "[%(asctime)s.%(msecs).03d] %(levelname)s: <User: %(name)s> <Module: %(module)s> <Function: %(funcName)s>: %(message)s"
  35 + log_format = "[%(asctime)s.%(msecs).03d] %(levelname)s: %(message)s"
  36 + date_fmt = "%Y-%m-%d %H:%M:%S"
  37 + logging.basicConfig(filename=log_file, datefmt=date_fmt, format=log_format, level=dict_level[log_level][1])
  38 + logging.Formatter(fmt="%(asctime)s", datefmt=date_fmt)
  39 + log_level %= len(dict_level)
  40 + write_mode = dict_level[log_level][2]
  41 + print("[%s] %s: %s" % (getTimeStamp(), dict_level[log_level][0], msg))
  42 + if (write_mode != None):
  43 + write_mode(msg)
  44 + return
  45 +
  46 +# @def funcao para exibir excecao
  47 +# @param string deve ser passado: "__file__" para identificar em qual modulo ocorreu a excecao
  48 +# @return int retorna 1
  49 +def print_stack_trace():
  50 + error = "\n File name: %s\n Function name: %s\n Line code: %s\n Type exception: %s\n Message: %s" % (
  51 + os.path.basename(sys.exc_info()[2].tb_frame.f_code.co_filename),
  52 + sys.exc_info()[2].tb_frame.f_code.co_name,
  53 + sys.exc_info()[2].tb_lineno,
  54 + sys.exc_info()[0].__name__,
  55 + sys.exc_info()[1]
  56 + )
  57 + log(error, 4)
  58 + return 1
  59 +
  60 +def get_date_now():
  61 + return datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
  62 +
  63 +def is_int(string):
  64 + try:
  65 + int(string)
  66 + return True
  67 + except ValueError:
  68 + return False
... ...
requirements.txt 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +Flask==0.9
  2 +pybossa-client
  3 +flask-cors
0 4 \ No newline at end of file
... ...
settings_local.py.tmpl 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +# -*- coding: utf-8 -*-
  2 +# Corretor Server Configuration
  3 +SERVER_HOST = "localhost"
  4 +SERVER_PORT = 8001
  5 +
  6 +# PyBossa Configuration
  7 +PYBOSSA_APP_NAME = "Validador de Sinais"
  8 +PYBOSSA_APP_SHORT_NAME = "validador_sinais"
  9 +PYBOSSA_APP_DESCRIPTION = "Esse projeto possibilitará que especialistas aprovem sinais gerados pela comunidade."
  10 +PYBOSSA_ENDPOINT = "http://localhost:5000"
  11 +PYBOSSA_API_KEY = "my-api-key"
0 12 \ No newline at end of file
... ...
validador.py 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +from flask import request, make_response
  2 +from werkzeug import secure_filename
  3 +import pbclient
  4 +import os
  5 +import pyutil
  6 +
  7 +class Validador:
  8 +
  9 + def __init__(self, configuration, template_env):
  10 + self.config = configuration
  11 + self.env = template_env
  12 + self.__setup_pb_client()
  13 +
  14 + def __setup_pb_client(self):
  15 + pbclient.set('endpoint', self.config['PYBOSSA_ENDPOINT'])
  16 + pbclient.set('api_key', self.config['PYBOSSA_API_KEY'])
  17 +
  18 + def __find_project(self, app_short_name):
  19 + projects = pbclient.find_project(short_name=app_short_name)
  20 + return projects[0] if len(projects) > 0 else None
  21 +
  22 + def __setup_project(self, project):
  23 + self.__create_tasks(project)
  24 + self.__update_project_info(project)
  25 +
  26 + def __create_tasks(self, project):
  27 + test_signs = ["ENSINADO", "ENTANTO", "ENTENDIDO"]
  28 + for sign in test_signs:
  29 + task = dict(sign_name=sign, submission_date=pyutil.get_date_now())
  30 + pbclient.create_task(project.id, task)
  31 +
  32 + def __update_project_info(self, project):
  33 + template = self.env.get_template('template.html')
  34 + project.info['task_presenter'] = template.render(server=self.config['HOST_ENDPOINT'], app_shortname=self.config['PYBOSSA_APP_SHORT_NAME'])
  35 + project.info['thumbnail'] = self.config['HOST_ENDPOINT'] + "/img/thumbnail.png"
  36 + project.info['sched'] = "incremental"
  37 + project.allow_anonymous_contributors = False
  38 + pbclient.update_project(project)
  39 +
  40 + def create_project(self):
  41 + app_short_name = self.config['PYBOSSA_APP_SHORT_NAME']
  42 + project = self.__find_project(app_short_name)
  43 + result_msg = ""
  44 + if (project):
  45 + result_msg = "The project " + app_short_name + " was already created."
  46 + else:
  47 + project = pbclient.create_project(self.config['PYBOSSA_APP_NAME'], app_short_name, self.config['PYBOSSA_APP_DESCRIPTION'])
  48 + if (project):
  49 + self.__setup_project(project)
  50 + result_msg = "The project " + app_short_name + " was created."
  51 + else:
  52 + result_msg = "The project " + app_short_name + " couldn't be created. Check the server log for details."
  53 + pyutil.log(result_msg)
  54 + return result_msg
  55 +
  56 + def update_project(self):
  57 + app_short_name = self.config['PYBOSSA_APP_SHORT_NAME']
  58 + project = self.__find_project(app_short_name)
  59 + self.__update_project_info(project)
  60 + result_msg = "The project " + app_short_name + " was updated."
  61 + pyutil.log(result_msg)
  62 + return result_msg
... ...
view/assets/css/main.css 0 → 100755
... ... @@ -0,0 +1,96 @@
  1 +@font-face {
  2 + font-family: 'Titillium Web';
  3 + src: url('../fonts/TitilliumWeb-SemiBold.ttf') format('truetype');
  4 +}
  5 +
  6 +.video-body {
  7 + height: 50%;
  8 + width: 85%;
  9 +}
  10 +
  11 +.video-container {
  12 + padding-left: 40px;
  13 + padding-right: 0px;
  14 +}
  15 +
  16 +.row {
  17 + margin-left: 0%;
  18 + margin-right: 0%;
  19 +}
  20 +
  21 +/* Sombras */
  22 +.line-separator, .btn-default {
  23 + box-shadow: 2px 2px 2px rgba(215, 217, 221, 1.0);
  24 + -webkit-box-shadow: 2px 2px 2px rgba(215, 217, 221, 1.0);
  25 + -moz-box-shadow: 2px 2px 2px rgba(215, 217, 221, 1.0);
  26 +}
  27 +
  28 +/* Fontes */
  29 +.btn-default, .finish-task-button, h1,
  30 + h2, h3, h4, h5, h6 {
  31 + font-family: 'Titillium Web', sans-serif;
  32 +}
  33 +
  34 +/* Header */
  35 +#validador-header {
  36 + padding: 1px;
  37 + background: rgba(255, 255, 255, 1.0);
  38 +}
  39 +
  40 +/* Body */
  41 +.body-container {
  42 + background: rgba(236, 238, 242, 1.0);
  43 + padding-bottom: 10px;
  44 +}
  45 +
  46 +/* Linha */
  47 +.line-separator {
  48 + height: 2px;
  49 + width: 100%;
  50 + background-color: rgba(145, 200, 206, 1.0);
  51 +}
  52 +
  53 +/* Texto */
  54 +h1, h2, h3, h4, h5, h6 {
  55 + color: rgba(144, 164, 174, 1.0);
  56 +}
  57 +
  58 +h6 {
  59 + font-size: 20px;
  60 +}
  61 +
  62 +.btn-default {
  63 + background-color: rgba(94, 199, 189, 1.0);
  64 +}
  65 +
  66 +.btn-default {
  67 + color: rgba(255, 255, 255, 1.0);
  68 +}
  69 +
  70 +.icon {
  71 + width: 50px;
  72 + height: 50px;
  73 +}
  74 +
  75 +#finish-task-container {
  76 + padding-top: 40px;
  77 + padding-right: 40px;
  78 +}
  79 +
  80 +.finish-task-button {
  81 + float: right;
  82 + padding-bottom: 0px;
  83 + padding-left: 25px;
  84 + color: rgba(94, 199, 189, 1.0);
  85 +}
  86 +
  87 +.enabled-button:hover {
  88 + cursor: pointer;
  89 + filter: alpha(opacity = 50);
  90 + opacity: 0.5;
  91 +}
  92 +
  93 +.disabled-button {
  94 + filter: alpha(opacity = 50);
  95 + opacity: 0.5;
  96 +}
... ...
view/assets/fonts/Helvetica.otf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-Black.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-Bold.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-BoldItalic.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-ExtraLight.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-ExtraLightItalic.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-Italic.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-Light.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-LightItalic.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-Regular.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-SemiBold.ttf 0 → 100755
No preview for this file type
view/assets/fonts/TitilliumWeb-SemiBoldItalic.ttf 0 → 100755
No preview for this file type
view/img/finish.svg 0 → 100755
... ... @@ -0,0 +1,17 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  3 +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100px" height="100px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
  4 +viewBox="0 0 100 100"
  5 + xmlns:xlink="http://www.w3.org/1999/xlink">
  6 + <defs>
  7 + <style type="text/css">
  8 + <![CDATA[
  9 + .fil0 {fill:#5EC7BD;fill-rule:nonzero}
  10 + ]]>
  11 + </style>
  12 + </defs>
  13 + <g id="finish">
  14 + <metadata id="finish"/>
  15 + <path class="fil0" d="M44 79c-4,4 -10,4 -13,0l-20 -20c-2,-2 -3,-4 -3,-6 0,-3 1,-5 3,-7 3,-3 9,-3 13,0l12 12c0,1 2,1 3,0l37 -37c2,-2 5,-3 7,-3l0 0c2,0 5,1 6,3 2,1 3,4 3,6 0,3 -1,5 -3,7l-45 45z"/>
  16 + </g>
  17 +</svg>
... ...
view/img/no.png 0 → 100644

6.12 KB

view/img/skip.svg 0 → 100755
... ... @@ -0,0 +1,17 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  3 +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100px" height="100px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
  4 +viewBox="0 0 100 100"
  5 + xmlns:xlink="http://www.w3.org/1999/xlink">
  6 + <defs>
  7 + <style type="text/css">
  8 + <![CDATA[
  9 + .fil0 {fill:#5EC7BD;fill-rule:nonzero}
  10 + ]]>
  11 + </style>
  12 + </defs>
  13 + <g id="skip">
  14 + <metadata id="skip"/>
  15 + <path class="fil0" d="M10 24c1,-1 23,-17 45,0 8,6 12,15 13,26l22 0 -34 34 -34 -34 22 0c0,-1 1,-3 1,-4 -2,-29 -19,-26 -35,-22z"/>
  16 + </g>
  17 +</svg>
... ...
view/img/thumbnail.png 0 → 100644

34.6 KB

view/template.html 0 → 100755
... ... @@ -0,0 +1,171 @@
  1 +<link rel="stylesheet" href="{{ server }}/assets/css/main.css">
  2 +
  3 +<div class="row">
  4 + <div id="success" class="alert alert-success" style="display: none;">
  5 + <strong>Parabéns!</strong> <span>Tarefa concluída.</span>
  6 + </div>
  7 + <div id="finish" class="alert alert-success" style="display: none;">
  8 + <strong>Parabéns!</strong> <span> Você completou todas as
  9 + tarefas disponíveis! </span> <br />
  10 + <div class="alert-actions">
  11 + <a class="btn small" href="/">Voltar</a> <a class="btn small"
  12 + href="/project">ou, olhar outros projetos.</a>
  13 + </div>
  14 + </div>
  15 +</div>
  16 +
  17 +<div id="main-container" class="container">
  18 + <div id="validador-header">
  19 + <h2>
  20 + A animação do sinal "<span class="sign-label"></span>" está correta?
  21 + </h2>
  22 + </div>
  23 + <div class="line-separator"></div>
  24 + <div id="corretor-container" class="row body-container">
  25 + <div id="avatar-container" class="col-sm-6 video-container">
  26 + <div class="row">
  27 + <h6>
  28 + ANIMAÇÃO "<span class="sign-label"></span>":
  29 + </h6>
  30 + </div>
  31 + <div class="row">
  32 + <video id="avatar-video" src="" preload="metadata"
  33 + class="video-body" autoplay loop controls>
  34 + <source type="video/webm">
  35 + </video>
  36 + </div>
  37 + </div>
  38 + <div id="ref-container" class="col-sm-6 video-container">
  39 + <div class="row">
  40 + <h6>
  41 + VÍDEO DE REFERÊNCIA “<span class="sign-label"></span>”:
  42 + </h6>
  43 + </div>
  44 + <div class="row">
  45 + <video id="ref-video" src="" preload="metadata" class="video-body"
  46 + autoplay loop controls>
  47 + <source type="video/webm">
  48 + </video>
  49 + </div>
  50 + <div id="finish-task-container" class="row">
  51 + <div id="finish-button" class="finish-task-button enabled-button">
  52 + <span id="finish-button-text">SIM</span><img class="icon"
  53 + src="{{ server }}/img/finish.svg"></img>
  54 + </div>
  55 + <div id="no-button" class="finish-task-button enabled-button"
  56 + data-toggle="modal" data-target="#feedback-modal">
  57 + NÃO<img class="icon" src="{{ server }}/img/no.png"></img>
  58 + </div>
  59 + <div id="skip-button" class="finish-task-button enabled-button">
  60 + PULAR<img class="icon" src="{{ server }}/img/skip.svg"></img>
  61 + </div>
  62 + </div>
  63 + </div>
  64 + <div id="feedback-modal" class="modal fade" tabindex="-1"
  65 + role="dialog" aria-labelledby="myModalLabel">
  66 + <div class="modal-dialog" role="document">
  67 + <div class="modal-content">
  68 + <div class="modal-header">
  69 + <button type="button" class="close" data-dismiss="modal"
  70 + aria-label="FECHAR">
  71 + <span aria-hidden="true">&times;</span>
  72 + </button>
  73 + <h5 class="modal-title" id="myModalLabel">Feedback da
  74 + avaliação</h5>
  75 + </div>
  76 + <div class="modal-body">
  77 + <h6 style="margin-top: 0px;">O que precisa ser melhorado?</h6>
  78 + <div class="radio">
  79 + <input type="radio" name="optradio"> A animação do
  80 + avatar.
  81 + </div>
  82 + <div class="radio">
  83 + <input type="radio" name="optradio">O vídeo de
  84 + referência.
  85 + </div>
  86 + </div>
  87 + <div class="modal-footer">
  88 + <button type="button" class="btn btn-default" data-dismiss="modal">FECHAR</button>
  89 + <button id="no-finish-button" type="button"
  90 + class="btn btn-default disabled" data-dismiss="modal">FINALIZAR</button>
  91 + </div>
  92 + </div>
  93 + </div>
  94 + </div>
  95 + </div>
  96 +</div>
  97 +
  98 +<script type="text/javascript">
  99 + var base_url = "{{ server }}/videos/";
  100 + var current_task_id = -1;
  101 +
  102 + function setupButtons(task, deferred) {
  103 + $("#finish-button").off("click").on("click", function() {
  104 + console.log($(this).text().trim() == "SIM");
  105 + saveAnswer(task, deferred, "APPROVED");
  106 + });
  107 + $("#skip-button").off("click").on("click", function() {
  108 + saveAnswer(task, deferred, "SKIPPED");
  109 + });
  110 + $("#no-finish-button").off("click").on("click", function() {
  111 + saveAnswer(task, deferred, "DISAPPROVED");
  112 + });
  113 + $("#no-finish-button").addClass("disabled");
  114 + $("#feedback-modal input[name='optradio']").prop("checked", false);
  115 + $("#feedback-modal input[name='optradio']").off("click").on("click",
  116 + function() {
  117 + $("#no-finish-button").removeClass("disabled");
  118 + });
  119 + }
  120 +
  121 + function createAnswer(task, status) {
  122 + var answer = {
  123 + "status" : status,
  124 + "number_of_approval" : 0
  125 + };
  126 + var last_answer = task.info.last_answer;
  127 + var hasLastAnswer = typeof last_answer != "undefined";
  128 + if (hasLastAnswer) {
  129 + answer = last_answer;
  130 + }
  131 + if (status == "APPROVED") {
  132 + answer["number_of_approval"] = answer.number_of_approval + 1;
  133 + }
  134 + return answer;
  135 + }
  136 +
  137 + function saveAnswer(task, deferred, status) {
  138 + var answer = createAnswer(task, status);
  139 + pybossa.saveTask(task.id, answer).done(function() {
  140 + $("#success").fadeIn(500);
  141 + $("#main-container").hide();
  142 + setTimeout(function() {
  143 + deferred.resolve();
  144 + }, 2000);
  145 + });
  146 + }
  147 +
  148 + function loadTaskInfo(task) {
  149 + current_task_id = task.id;
  150 + var sign_name = task.info.sign_name;
  151 + var avatar_vid_link = base_url + sign_name + "_AVATAR.webm";
  152 + var ref_vid_link = base_url + sign_name + "_REF.webm";
  153 + $(".sign-label").text(sign_name);
  154 + $("#avatar-video").attr("src", avatar_vid_link);
  155 + $("#ref-video").attr("src", ref_vid_link);
  156 + }
  157 +
  158 + pybossa.presentTask(function(task, deferred) {
  159 + if (!$.isEmptyObject(task) && current_task_id != task.id) {
  160 + loadTaskInfo(task);
  161 + setupButtons(task, deferred);
  162 + $("#success").hide();
  163 + $("#main-container").fadeIn(500);
  164 + } else {
  165 + $("#main-container").hide();
  166 + $("#finish").fadeIn(500);
  167 + }
  168 + });
  169 +
  170 + pybossa.run('{{ app_shortname }}');
  171 +</script>
... ...
view/videos/ENSINADO_AVATAR.webm 0 → 100644
No preview for this file type
view/videos/ENSINADO_REF.webm 0 → 100644
No preview for this file type
view/videos/ENTANTO_AVATAR.webm 0 → 100644
No preview for this file type
view/videos/ENTANTO_REF.webm 0 → 100644
No preview for this file type
view/videos/ENTENDIDO_AVATAR.webm 0 → 100644
No preview for this file type
view/videos/ENTENDIDO_REF.webm 0 → 100644
No preview for this file type