diff --git a/app.py b/app.py new file mode 100644 index 0000000..cee9765 --- /dev/null +++ b/app.py @@ -0,0 +1,218 @@ +import os +import yaml +import json +from requests_oauthlib import OAuth1Session +from flask import Flask, render_template, request, session, redirect, url_for, jsonify, g +from flask_babel import Babel +from oauth_wikidata import get_username, get_token +from wikidata import query_by_type, query_metadata_of_work, query_depicts_metadata,\ + api_post_request, post_search_entity, filter_by_tesauros + +__dir__ = os.path.dirname(__file__) +app = Flask(__name__) +app.config.update(yaml.safe_load(open(os.path.join(__dir__, 'config.yaml')))) + +BABEL = Babel(app) + + +############################################################## +# ESTRUTURA +############################################################## +@app.before_request +def init_profile(): + g.profiling = [] + + +@app.before_request +def global_user(): + g.user = get_username() + + +@app.route('/login') +def login(): + client_key = app.config['CONSUMER_KEY'] + client_secret = app.config['CONSUMER_SECRET'] + base_url = 'https://www.wikidata.org/w/index.php' + request_token_url = base_url + '?title=Special%3aOAuth%2finitiate' + + oauth = OAuth1Session(client_key, + client_secret=client_secret, + callback_uri='oob') + fetch_response = oauth.fetch_request_token(request_token_url) + + session['owner_key'] = fetch_response.get('oauth_token') + session['owner_secret'] = fetch_response.get('oauth_token_secret') + + base_authorization_url = 'https://www.wikidata.org/wiki/Special:OAuth/authorize' + authorization_url = oauth.authorization_url(base_authorization_url, + oauth_consumer_key=client_key) + return redirect(authorization_url) + + +@app.route("/oauth-callback", methods=["GET"]) +def oauth_callback(): + base_url = 'https://www.wikidata.org/w/index.php' + client_key = app.config['CONSUMER_KEY'] + client_secret = app.config['CONSUMER_SECRET'] + + oauth = OAuth1Session(client_key, + client_secret=client_secret, + resource_owner_key=session['owner_key'], + resource_owner_secret=session['owner_secret']) + + oauth_response = oauth.parse_authorization_response(request.url) + verifier = oauth_response.get('oauth_verifier') + access_token_url = base_url + '?title=Special%3aOAuth%2ftoken' + oauth = OAuth1Session(client_key, + client_secret=client_secret, + resource_owner_key=session['owner_key'], + resource_owner_secret=session['owner_secret'], + verifier=verifier) + + oauth_tokens = oauth.fetch_access_token(access_token_url) + session['owner_key'] = oauth_tokens.get('oauth_token') + session['owner_secret'] = oauth_tokens.get('oauth_token_secret') + + return redirect(url_for("inicio")) + + +# Função para pegar a língua de preferência do usuário +@BABEL.localeselector +def get_locale(): + if request.args.get('lang'): + session['lang'] = request.args.get('lang') + + return session.get('lang', 'pt') + + +# Função para mudar a língua de exibição do conteúdo +@app.route('/set_locale') +def set_locale(): + next_page = request.args.get('return_to') + lang = request.args.get('lang') + + session["lang"] = lang + return redirect(next_page) + +############################################################## +# PÁGINAS +############################################################## +# Página inicial +@app.route('/') +@app.route('/home') +@app.route('/inicio') +def inicio(): + username = get_username() + return render_template('inicio.html', + username=username, + lang=get_locale()) + + +# Página sobre o aplicativo +@app.route('/about') +@app.route('/sobre') +def sobre(): + username = get_username() + return render_template('sobre.html', + username=username, + lang=get_locale()) + + +# Página com tutorial de como contribuir com o jogo +@app.route('/tutorial') +def tutorial(): + username = get_username() + return render_template('tutorial.html', + username=username, + lang=get_locale()) + + +# Página de visualização de coleções definidas por descritores +@app.route('/colecao/') +def colecao(type): + username = get_username() + with open(os.path.join(app.static_folder, 'queries.json')) as category_queries: + all_queries = json.load(category_queries) + + try: + selected_query = all_queries[type]["query"] + selection = query_by_type(selected_query) + return render_template("colecao.html", + collection=selection, + username=username, + lang=get_locale()) + except: + return redirect(url_for('inicio')) + + +# Página de visualização da obra e inserção de qualificadores +@app.route('/item/') +def item(qid): + username = get_username() + with open(os.path.join(app.static_folder, 'queries.json')) as category_queries: + all_queries = json.load(category_queries) + + metadata_query = all_queries["Metadados"]["query"].replace("LANGUAGE", get_locale()).replace("QIDDAOBRA", qid) + depicts_query = all_queries["Retratas"]["query"].replace("LANGUAGE", get_locale()).replace("QIDDAOBRA", qid) + work_metadata = query_metadata_of_work(metadata_query, lang=get_locale) + work_depicts = query_depicts_metadata(depicts_query, qid) + + return render_template('item.html', + metadata=work_metadata, + depicts_metadata=work_depicts, + username=username, + lang=get_locale()) + + +############################################################## +# CONSULTAS E REQUISIÇÕES +############################################################## +# Requisição de adicionar qualificador à item retratado +@app.route('/add_stat', methods=['GET', 'POST']) +def add_statement(): + if request.method == 'POST': + data = request.get_json() + statement = data['statid'].split("#")[-1] # Identificador da declaração + pid = data['tipo'] # Tipo de qualificador + qual = data['qual'] # QID do qualificador a ser adicionado + + token = get_token() + params = { + "action": "wbsetqualifier", + "format": "format", + "claim": statement, + "property": pid, + "value": "{\"entity-type\":\"item\",\"numeric-id\":" + str(qual) + "}", + "snaktype": "value", + "token": token + } + + results = api_post_request(params) + + return jsonify(results=results) + return render_template('item.html') + + +@app.route('/search', methods=['GET', 'POST']) +def search_entity(): + if request.method == "POST": + data = request.get_json() + term = data['term'] + lang = get_locale() + + data = post_search_entity(term, lang) + + items = [] + for item in data["search"]: + items.append(item["id"]) + + query = filter_by_tesauros("wd:"+" wd:".join(items)) + + if query: + return jsonify(query), 200 + else: + return jsonify(query), 204 + + +if __name__ == '__main__': + app.run() diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..02f9d1f --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +encoding = utf-8 +extensions=jinja2.ext.autoescape,jinja2.ext.with_ \ No newline at end of file diff --git a/oauth_wikidata.py b/oauth_wikidata.py new file mode 100644 index 0000000..e079b2b --- /dev/null +++ b/oauth_wikidata.py @@ -0,0 +1,51 @@ +from flask import current_app, session +from requests_oauthlib import OAuth1Session +from urllib.parse import urlencode + + +def raw_request(params): + app = current_app + url = 'https://www.wikidata.org/w/api.php?' + urlencode(params) + client_key = app.config['CONSUMER_KEY'] + client_secret = app.config['CONSUMER_SECRET'] + oauth = OAuth1Session(client_key, + client_secret=client_secret, + resource_owner_key=session['owner_key'], + resource_owner_secret=session['owner_secret']) + return oauth.get(url, timeout=4) + + +def api_request(params): + return raw_request(params).json() + + +def userinfo_call(): + params = {'action': 'query', 'meta': 'userinfo', 'format': 'json'} + return api_request(params) + + +def get_username(): + if 'owner_key' not in session: + return # not authorized + + if 'username' in session: + return session['username'] + + reply = userinfo_call() + if 'query' not in reply: + return + session['username'] = reply['query']['userinfo']['name'] + + return session['username'] + + +def get_token(): + params = { + 'action': 'query', + 'meta': 'tokens', + 'format': 'json', + 'formatversion': 2, + } + reply = api_request(params) + token = reply['query']['tokens']['csrftoken'] + return token \ No newline at end of file diff --git a/pyvenv.cfg b/pyvenv.cfg new file mode 100644 index 0000000..9514adc --- /dev/null +++ b/pyvenv.cfg @@ -0,0 +1,3 @@ +home = C:\Users\ederp\AppData\Local\Programs\Python\Python37-32 +include-system-site-packages = false +version = 3.7.4 diff --git a/static/pagination.css b/static/pagination.css new file mode 100644 index 0000000..bdb4034 --- /dev/null +++ b/static/pagination.css @@ -0,0 +1 @@ +.paginationjs{line-height:1.6;font-family:Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,sans-serif;font-size:14px;box-sizing:initial}.paginationjs:after{display:table;content:" ";clear:both}.paginationjs .paginationjs-pages{float:left}.paginationjs .paginationjs-pages ul{float:left;margin:0;padding:0}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input,.paginationjs .paginationjs-nav{float:left;margin-left:10px;font-size:14px}.paginationjs .paginationjs-pages li{float:left;border:1px solid #aaa;border-right:none;list-style:none}.paginationjs .paginationjs-pages li>a{min-width:30px;height:28px;line-height:28px;display:block;background:#fff;font-size:100%;color:#333;text-decoration:none;text-align:center}.paginationjs .paginationjs-pages li>a:hover{background:#eee}.paginationjs .paginationjs-pages li.active{border:none}.paginationjs .paginationjs-pages li.active>a{height:30px;line-height:30px;background:#aaa;color:#fff}.paginationjs .paginationjs-pages li.disabled>a{opacity:.3}.paginationjs .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs .paginationjs-pages li:first-child,.paginationjs .paginationjs-pages li:first-child>a{border-radius:3px 0 0 3px}.paginationjs .paginationjs-pages li:last-child{border-right:1px solid #aaa;border-radius:0 3px 3px 0}.paginationjs .paginationjs-pages li:last-child>a{border-radius:0 3px 3px 0}.paginationjs .paginationjs-go-input>input[type=text]{width:30px;height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;padding:0;font-size:14px;text-align:center;vertical-align:baseline;outline:0;box-shadow:none;box-sizing:initial}.paginationjs .paginationjs-go-button>input[type=button]{min-width:40px;height:30px;line-height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;text-align:center;padding:0 8px;font-size:14px;vertical-align:baseline;outline:0;box-shadow:none;color:#333;cursor:pointer;vertical-align:middle\9}.paginationjs.paginationjs-theme-blue .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-blue .paginationjs-pages li{border-color:#289de9}.paginationjs .paginationjs-go-button>input[type=button]:hover{background-color:#f8f8f8}.paginationjs .paginationjs-nav{height:30px;line-height:30px}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input{margin-left:5px\9}.paginationjs.paginationjs-small{font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li>a{min-width:26px;height:24px;line-height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li.active>a{height:26px;line-height:26px}.paginationjs.paginationjs-small .paginationjs-go-input{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-input>input[type=text]{width:26px;height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button>input[type=button]{min-width:30px;height:26px;line-height:24px;padding:0 6px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-nav{height:26px;line-height:26px;font-size:12px}.paginationjs.paginationjs-big{font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li>a{min-width:36px;height:34px;line-height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li.active>a{height:36px;line-height:36px}.paginationjs.paginationjs-big .paginationjs-go-input{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{width:36px;height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button>input[type=button]{min-width:50px;height:36px;line-height:34px;padding:0 12px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-nav{height:36px;line-height:36px;font-size:16px}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a{color:#289de9}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a:hover{background:#e9f4fc}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.active>a{background:#289de9;color:#fff}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]{background:#289de9;border-color:#289de9;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-green .paginationjs-pages li{border-color:#449d44}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]:hover{background-color:#3ca5ea}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a{color:#449d44}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a:hover{background:#ebf4eb}.paginationjs.paginationjs-theme-green .paginationjs-pages li.active>a{background:#449d44;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]{background:#449d44;border-color:#449d44;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-yellow .paginationjs-pages li{border-color:#ec971f}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]:hover{background-color:#55a555}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a{color:#ec971f}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a:hover{background:#fdf5e9}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.active>a{background:#ec971f;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]{background:#ec971f;border-color:#ec971f;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-red .paginationjs-pages li{border-color:#c9302c}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]:hover{background-color:#eea135}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a{color:#c9302c}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a:hover{background:#faeaea}.paginationjs.paginationjs-theme-red .paginationjs-pages li.active>a{background:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]{background:#c9302c;border-color:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]:hover{background-color:#ce4541}.paginationjs .paginationjs-pages li.paginationjs-next{border-right:1px solid #aaa\9}.paginationjs .paginationjs-go-input>input[type=text]{line-height:28px\9;vertical-align:middle\9}.paginationjs.paginationjs-big .paginationjs-pages li>a{line-height:36px\9}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{height:36px\9;line-height:36px\9} \ No newline at end of file diff --git a/static/pagination.js b/static/pagination.js new file mode 100644 index 0000000..28e625b --- /dev/null +++ b/static/pagination.js @@ -0,0 +1,1123 @@ +/* + * pagination.js 2.1.5 + * A jQuery plugin to provide simple yet fully customisable pagination. + * https://github.com/superRaytin/paginationjs + * + * Homepage: http://pagination.js.org + * + * Copyright 2014-2100, superRaytin + * Released under the MIT license. + */ + +(function(global, $) { + + if (typeof $ === 'undefined') { + throwError('Pagination requires jQuery.'); + } + + var pluginName = 'pagination'; + + var pluginHookMethod = 'addHook'; + + var eventPrefix = '__pagination-'; + + // Conflict, use backup + if ($.fn.pagination) { + pluginName = 'pagination2'; + } + + $.fn[pluginName] = function(options) { + + if (typeof options === 'undefined') { + return this; + } + + var container = $(this); + + var attributes = $.extend({}, $.fn[pluginName].defaults, options); + + var pagination = { + + initialize: function() { + var self = this; + + // Cache attributes of current instance + if (!container.data('pagination')) { + container.data('pagination', {}); + } + + if (self.callHook('beforeInit') === false) return; + + // Pagination has been initialized, destroy it + if (container.data('pagination').initialized) { + $('.paginationjs', container).remove(); + } + + // Whether to disable Pagination at the initialization + self.disabled = !!attributes.disabled; + + // Model will be passed to the callback function + var model = self.model = { + pageRange: attributes.pageRange, + pageSize: attributes.pageSize + }; + + // dataSource`s type is unknown, parse it to find true data + self.parseDataSource(attributes.dataSource, function(dataSource) { + + // Currently in asynchronous mode + self.isAsync = Helpers.isString(dataSource); + if (Helpers.isArray(dataSource)) { + model.totalNumber = attributes.totalNumber = dataSource.length; + } + + // Currently in asynchronous mode and a totalNumberLocator is specified + self.isDynamicTotalNumber = self.isAsync && attributes.totalNumberLocator; + + var el = self.render(true); + + // Add extra className to the pagination element + if (attributes.className) { + el.addClass(attributes.className); + } + + model.el = el; + + // Append/prepend pagination element to the container + container[attributes.position === 'bottom' ? 'append' : 'prepend'](el); + + // Bind events + self.observer(); + + // Pagination is currently initialized + container.data('pagination').initialized = true; + + // Will be invoked after initialized + self.callHook('afterInit', el); + }); + }, + + render: function(isBoot) { + var self = this; + var model = self.model; + var el = model.el || $('
'); + var isForced = isBoot !== true; + + self.callHook('beforeRender', isForced); + + var currentPage = model.pageNumber || attributes.pageNumber; + var pageRange = attributes.pageRange || 0; + var totalPage = self.getTotalPage(); + + var rangeStart = currentPage - pageRange; + var rangeEnd = currentPage + pageRange; + + if (rangeEnd > totalPage) { + rangeEnd = totalPage; + rangeStart = totalPage - pageRange * 2; + rangeStart = rangeStart < 1 ? 1 : rangeStart; + } + + if (rangeStart <= 1) { + rangeStart = 1; + rangeEnd = Math.min(pageRange * 2 + 1, totalPage); + } + + el.html(self.generateHTML({ + currentPage: currentPage, + pageRange: pageRange, + rangeStart: rangeStart, + rangeEnd: rangeEnd + })); + + // There is only one page + if (attributes.hideWhenLessThanOnePage) { + el[totalPage <= 1 ? 'hide' : 'show'](); + } + + self.callHook('afterRender', isForced); + + return el; + }, + + // Generate HTML of the pages + generatePageNumbersHTML: function(args) { + var self = this; + var currentPage = args.currentPage; + var totalPage = self.getTotalPage(); + var rangeStart = args.rangeStart; + var rangeEnd = args.rangeEnd; + var html = ''; + var i; + + var pageLink = attributes.pageLink; + var ellipsisText = attributes.ellipsisText; + + var classPrefix = attributes.classPrefix; + var activeClassName = attributes.activeClassName; + var disableClassName = attributes.disableClassName; + + // Disable page range, display all the pages + if (attributes.pageRange === null) { + for (i = 1; i <= totalPage; i++) { + if (i == currentPage) { + html += '
  • ' + i + '<\/a><\/li>'; + } else { + html += '
  • ' + i + '<\/a><\/li>'; + } + } + return html; + } + + if (rangeStart <= 3) { + for (i = 1; i < rangeStart; i++) { + if (i == currentPage) { + html += '
  • ' + i + '<\/a><\/li>'; + } else { + html += '
  • ' + i + '<\/a><\/li>'; + } + } + } else { + if (attributes.showFirstOnEllipsisShow) { + html += '
  • 1<\/a><\/li>'; + } + html += '
  • ' + ellipsisText + '<\/a><\/li>'; + } + + for (i = rangeStart; i <= rangeEnd; i++) { + if (i == currentPage) { + html += '
  • ' + i + '<\/a><\/li>'; + } else { + html += '
  • ' + i + '<\/a><\/li>'; + } + } + + if (rangeEnd >= totalPage - 2) { + for (i = rangeEnd + 1; i <= totalPage; i++) { + html += '
  • ' + i + '<\/a><\/li>'; + } + } else { + html += '
  • ' + ellipsisText + '<\/a><\/li>'; + + if (attributes.showLastOnEllipsisShow) { + html += '
  • ' + totalPage + '<\/a><\/li>'; + } + } + + return html; + }, + + // Generate HTML content from the template + generateHTML: function(args) { + var self = this; + var currentPage = args.currentPage; + var totalPage = self.getTotalPage(); + + var totalNumber = self.getTotalNumber(); + + var showPrevious = attributes.showPrevious; + var showNext = attributes.showNext; + var showPageNumbers = attributes.showPageNumbers; + var showNavigator = attributes.showNavigator; + var showGoInput = attributes.showGoInput; + var showGoButton = attributes.showGoButton; + + var pageLink = attributes.pageLink; + var prevText = attributes.prevText; + var nextText = attributes.nextText; + var goButtonText = attributes.goButtonText; + + var classPrefix = attributes.classPrefix; + var disableClassName = attributes.disableClassName; + var ulClassName = attributes.ulClassName; + + var html = ''; + var goInput = ''; + var goButton = ''; + var formattedString; + + var formatNavigator = $.isFunction(attributes.formatNavigator) ? attributes.formatNavigator(currentPage, totalPage, totalNumber) : attributes.formatNavigator; + var formatGoInput = $.isFunction(attributes.formatGoInput) ? attributes.formatGoInput(goInput, currentPage, totalPage, totalNumber) : attributes.formatGoInput; + var formatGoButton = $.isFunction(attributes.formatGoButton) ? attributes.formatGoButton(goButton, currentPage, totalPage, totalNumber) : attributes.formatGoButton; + + var autoHidePrevious = $.isFunction(attributes.autoHidePrevious) ? attributes.autoHidePrevious() : attributes.autoHidePrevious; + var autoHideNext = $.isFunction(attributes.autoHideNext) ? attributes.autoHideNext() : attributes.autoHideNext; + + var header = $.isFunction(attributes.header) ? attributes.header(currentPage, totalPage, totalNumber) : attributes.header; + var footer = $.isFunction(attributes.footer) ? attributes.footer(currentPage, totalPage, totalNumber) : attributes.footer; + + // Whether to display header + if (header) { + formattedString = self.replaceVariables(header, { + currentPage: currentPage, + totalPage: totalPage, + totalNumber: totalNumber + }); + html += formattedString; + } + + if (showPrevious || showPageNumbers || showNext) { + html += '
    '; + + if (ulClassName) { + html += '