Commit c55ca0c1 authored by sim's avatar sim

Remove annotations module

parent ce761204
{
"directory": "static/bower_components"
}
{
"globalstrict": true,
"globals": {
"angular": false,
"describe": false,
"it": false,
"expect": false,
"beforeEach": false,
"afterEach": false,
"module": false,
"inject": false
}
}
\ No newline at end of file
set tabstop=4
set shiftwidth=4
set expandtab
set softtabstop=4
# Gargantext Annotations web application
## Install dependencies
(2016-05-11) Instead of the previously "npm-installable" structure, all the libraries are now permanently installed outside of the app 'annotations', directly in the 'static' dir of the entire gargantext project.
Initial version specifications of the dependencies are:
- "angular": "~1.2.x",
- "angular-cookies": "~1.2.x",
- "angular-loader": "~1.2.x",
- "angular-resource": "~1.2.x",
- "bootstrap": "~3.x",
- "angular-cookies": "1.2",
- "bootstrap-select": "silviomoreto/bootstrap-select#~1.7.3"
- "underscore": "1.5.2"
These dependencies are imported via annotations/main.js (js) and main.html (css).
## Directory Layout
The layout is :
```
├── __init__.py
├── README.md
├── static
│   └── annotations
│   ├── activelists.js
│   ├── app.css
│   ├── app.js
│   ├── document.js
│   ├── highlight.js
│   ├── http.js
│   ├── keyword_tpl.html
│   ├── main.js
│   ├── ngramlist.js
│   └── utils.js
├── templates
│   └── annotations
│   └── main.html
├── urls.py
└── views.py
```
# Conception and workflow documentation
## TODO : à traduire en anglais
Cette API permet d'éditer les mots-clés miamlistés ou stoplistés associé à un document affiché dans un cadre d'une page web permettant de naviguer à travers un ensemble de document d'un corpus.
### Architecture
- Templates : Django et Angular.js ?
- Communication entre les modules : évènements Angular ($emit et $broadcast)
- Pas de routage entre différentes URL, car ici une seule vue principale basée sur le template django corpus.html
- Modèle d'abstraction de données : côté client (Angular Scopes) et côté serveur (Django Model et SQLAlchemy)
- Composants : TODO lister et décrire les composants client et serveur
- Structure de l'application : organisation du client et du serveur
- Style : Bootstrap et un thème spécifique choisi pour Gargantext
- Gestion des dépendances :
- bower, npm pour le développement web et les tests côté client
- pip requirements pour le côté serveur
## Quelles actions execute l'API ?
- afficher le titre, les auteurs, le résumé, la date de publication et le corps d'un document.
- lecture des mots-clés miamlistés associés à un document (dans le texte et hors du texte).
- lecture des mots-clés stoplistés associés à un document (dans le texte et hors du texte).
- lecture des documents ayant le plus de mots-clés miamlistés associés identiques pour afficher une liste de liens vers de nouveaux documents
- lecture du groupe de mots-clés auquel appartient un mot-clé (synonymes, différentes formes)
- modification du groupe de mots-clés auquel appartient un mot-clé donné
On désigne par mot-clé un NGram.
## Schéma de l'API
Liste des endpoints
### Lecture des données
- POST '^api/nodes/(\d+)/children/queries$' : liste des NGrams d'un document avec la possibilité de filtrer par NGrams
- GET '^api/nodes$' : liste des identifiants de mots-clés filtrés par type (NGram ou autre) pour un identifiant de parent (Document ou autre)
- GET '^api/nodes/(\d+)/ngrams$': liste des termes des mots-clés associés à un Document parent, filtrés par termes
- GET ^api/nodes/(\d+)/children/metadata$ : liste des metadata d'un Node, c'est-à-dire :
- pour un document : titre, auteur, etc
- pour un NGram : stoplisté ou miamlisté ?
### Écriture des données
TODO
## Workflow
Nous nous fixons sur cette documentation et spécification de l'API
- en parallèle : développement de l'API et prototypage de l'interface
- le prototypage de l'interface peut modifier l'API si besoin
### Spécifications des fondations de l'interface
- résolutions d'écran
- browsers
- langue: english only
- SEO: aucun ?
- collaboratif : oui, les modifications d'un autre utilisateurs seront notifiées à tous les utilisateurs présent sur le même corpus de documents
- fonctionne offline ?
### Working process
- follow board is updated regularly (https://trello.com/b/96ItkDBS/gargantext-miamlists-and-stoplists)[on Trello]
- calendrier prévisionnel: TODO
- interactions entre les acteurs: emails
- git, branches : branche "elias", `git pull --rebase origin master` réguliers
- prévision des revues de code et de l'interface : TODO
### Plateforme
- Python 3.4
- Django 1.6
- Postgresql 9.3 + HSTORE
- SQLAlchemy
- Bootstrap CSS
- Angular.js
### Outils de qualité de code
- pylint
- jshint (voir .jshintrc)
- indentations : 4 espaces (voir .lvimrc)
- nettoyage automatique des espaces en fin de ligne
## Tests
There are two kinds of tests possible : Unit tests and End to End tests.
- côté client : étudier karma.js et protractor
- définir la stratégie de tests : TODO
## Déploiement
- définir le processus de déploiement
- prévoir un système de monitoring des erreurs du serveur une fois en ligne
- Sentry ?
ANNOTATIONS
===========
2016-01
## Routines de manipulation de ngrams dans les listes
#### Trajectoire globale des actions choisies
1. angular: ngramlist.js (user input) or highlight.js (user menu controller)
2. angular: http.js configuration object
{ 'action': 'post', 'listId': miamlist_id, ..}
3. AJAX POST/DELETE
4. "API locale" (=> annotations.views)
5. DB insert/delete
Remarque:
Dans le code annotations d'Elias, il y a une "API locale" qui transmet les actions client vers le serveur.
=> l'interconnexion est configurée pour angular dans annotations/static/annotations/app.js qui lance son propre main sur la fenêtre en prenant les paramètres depuis l'url et en s'isolant de django
=> les routes amont sont définies pour django dans annotations.urls et reprises pour angular dans http.js
#### Par ex: l'étape AJAX pour suppression
`curl -XDELETE http://localhost:8000/annotations/lists/7129/ngrams/4497`
via annotations.views.NgramEdit.as_view())
#### ajout d'un ngram
```
curl -XPOST http://localhost:8000/annotations/lists/1866/ngrams/create \
-H "Content-Type: application/json" \
-d '{"text":"yooooooooo"}' > response_to_ngrams_create.html
```
## Points d'interaction côté client (GUI)
Add Ngram via dialog box :
- controller:
ngramlist.annotationsAppNgramList.controller('NgramInputController')
- effect:
1. NgramHttpService.post()
Add Ngram via select new + menu
- controller:
highlight.annotationsAppHighlight.controller('TextSelectionMenuController')
1. toggleMenu (sets action = X)
2. onMenuClick
- effect:
1. NgramHttpService[action]
(function () {
'use strict';
var annotationsAppActiveLists = angular.module('annotationsAppActiveLists', []);
annotationsAppActiveLists.controller('ActiveListsController',
['$scope', '$rootScope', '$timeout',
function ($scope, $rootScope, $timeout) {
$scope.activeListsChange = function() {
var selected = $('.selectpicker option:selected').val();
var newActive = {};
$('.selectpicker option:selected').each(function(item, opt) {
// ex opt:
// <option id="list---748" value="MAINLIST">MAINLIST</option>
var id = opt.id.split("---", 2)[1];
newActive[id] = opt.value;
});
// ex: {745: "MAINLIST", 748: "MAPLIST"}
$rootScope.activeLists = newActive;
};
$rootScope.$watchCollection('activeLists', function (newValue, oldValue) {
if (newValue === undefined) return;
$timeout(function() {
$('.selectpicker').selectpicker('refresh');
});
});
// FIXME: est-ce qu'on ne pourrait pas directement utiliser lists
// au lieu de recopier dans allListsSelect ?
$rootScope.$watchCollection('lists', function (newValue, oldValue) {
if (newValue === undefined) return;
// reformat lists to allListsSelect
var allListsSelect = [];
// console.log($rootScope.lists)
angular.forEach($rootScope.lists, function(value, key) {
this.push({
'id': key,
'label': value
});
// initialize activeLists with the MAPLIST by default
if (value == 'MAPLIST') {
$rootScope.activeLists = {};
$rootScope.activeLists[key] = value;
}
}, allListsSelect);
$rootScope.allListsSelect = allListsSelect;
$timeout(function() {
$('.selectpicker').selectpicker();
$('.selectpicker').selectpicker('val', ['MAPLIST']);
});
});
}]);
})(window);
/* app css stylesheet */
/*
* Class names corresponding to server-side list names
* To display another list name, add a new class under this
*/
.MAPLIST {
color: black;
/* green */
background-color: rgba(23, 255, 189, .7);
/* background-color: rgba(60, 118, 61, .7); */
cursor: pointer;
}
.MAINLIST {
color: black;
/* orange */
background-color: orange;
/* background-color: rgba(60, 118, 61, 0.5); */
cursor: pointer;
}
.STOPLIST {
color: black;
/* grey */
background-color: rgba(169, 68, 66, 0.2);
cursor: pointer;
}
.FOCUS {
color: black;
font-weight: bold;
/* yellow */
background-color: rgba(255, 224, 101, 1);
cursor: pointer;
}
.inlay {
border-radius: .8em;
padding: 0 .5em;
margin: 0 .2em;
}
.words-panel > .tab-content {
font-size: 80%;
border-left: 1px solid #aaa;
border-right: 1px solid #aaa;
border-bottom: 1px solid #aaa;
padding: 1em ;
margin-right: 1px;
min-height: 22em ; /* needs to be bigger than .words-list */
}
#addfreengram {
padding: 3em .5em .2em .5em ;
}
.nav-pills.activelists >li>a {
padding:10px 1px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-top: 1px solid #ddd;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
border-bottom: 1px solid #aaa;
}
.nav-pills.activelists > li.active > a,
.nav-pills.activelists > li.active > a:focus,
.nav-pills.activelists > li.active > a:hover {
background-color: #337ab7;
border-top: 1px solid #aaa;
border-left: 1px solid #aaa;
border-right: 1px solid #aaa;
border-bottom: none ;
}
.delete-keyword, .occurrences {
vertical-align: super;
font-size: 70%;
}
.delete-keyword {
cursor: pointer;
}
.center-block {
display: block;
margin-left: auto;
margin-right: auto;
}
.keyword-inline {
display: inline;
}
.keyword-inline:hover {
text-decoration: none;
}
.nav-tabs {
border-bottom: none;
}
.main-panel, .text-panel, .words-panel {
margin: 10px 0;
}
#annotationsApp {
min-width: 780px;
}
.words-panel {
min-width: 220px;
}
.text-panel {
overflow-y: auto;
min-width: 400px;
}
.words-list {
margin-bottom: 5px;
min-height: 17em ;
}
.keyword-text {
word-break: break-all;
}
.keyword-group-item {
display: inline-block;
float: left;
padding: 5px;
margin: .25em;
box-shadow: .2em .2em .1em rgba(0, 0, 0, .125);
}
.words-pagination {
margin: 5px 0;
}
.text-panel p, .text-panel h3 {
-webkit-transition: all 0.25s linear;
-moz-transition: all 0.25s linear;
-ms-transition: all 0.25s linear;
-o-transition: all 0.25s linear;
transition: all 0.25s linear;
}
/* this was used for the p or div that *contained* a selection */
/*.selection {
color: #aaa;
}*/
/* this is used for the selected text itself */
::selection {
color: black;
background-color: #aaa;
}
.noselection {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.selection-menu {
display: none;
position: absolute;
color: #394141;
background: white;
font-size: 0.8em;
font-weight: 600;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
/*.selection-menu:before {
content: '';
position: absolute;
left: -10px;
top: 0px;
border-right: solid white 10px;
border-top: solid transparent 8px;
border-bottom: solid transparent 8px;
}*/
.selection-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
.selection-menu li {
border-bottom: solid thin #CCC;
padding: 10px;
white-space: nowrap;
}
.selection-menu [class*="glyphicon"] {
min-width: 25px;
display: inline-block;
text-align: center;
border-right: solid thin #CCC;
margin-right: 5px;
}
.float-right {
float: right;
}
.spacer {
margin-top:40px;
}
.favactive {
/* yellow */
color: #FFF50D;
text-shadow: -1px 0 #777777, 0 1px #777777, 1px 0 #777777, 0 -1px #777777;
}
(function () {
'use strict';
/*
* Django STATIC_URL given to angular to load async resources
*/
var S = window.STATIC_URL;
window.annotationsApp = angular.module('annotationsApp', ['annotationsAppHttp',
'annotationsAppNgramList', 'annotationsAppHighlight', 'annotationsAppDocument',
'annotationsAppActiveLists', 'annotationsAppUtils']);
/*
* Angular Templates must not conflict with Django's
*/
window.annotationsApp.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('{[{');
$interpolateProvider.endSymbol('}]}');
});
/*
* Main function
* GET the document node and all its ngrams
*/
window.annotationsApp.run(function ($rootScope, NgramListHttpService) {
// ex: projects/1/corpora/2/documents/9/
// ex: projects/1/corpora/2/documents/9/focus=2677 (to highlight ngram 2677 more)
var path = window.location.pathname.match(/\/projects\/(.*)\/corpora\/(.*)\/documents\/(.*)\/(?:focus=([0-9,]+))?/);
// shared vars -------------------
$rootScope.projectId = path[1];
$rootScope.corpusId = path[2];
$rootScope.docId = path[3];
// ex: ["483", "3561", "9754", "35183"]
// (passed from graphExplorer selections)
if (path[4])
$rootScope.focusNgrams = path[4].split(",");
else
$rootScope.focusNgrams = []
// -------------------------------
// shared toolbox (functions useful for several modules) -------------------
$rootScope.refresh = function() {
// Refresh the annotations
NgramListHttpService.get(
{'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to refresh the list of ngrams");
}
);
}
// chained recursion to do several AJAX actions and then a callback (eg refresh)
$rootScope.makeChainedCalls =
function (i, listOfCalls, finalCallback, lastCache) {
var callDetails = listOfCalls[i]
console.log(">> calling ajax call ("+(i+1)+"/"+listOfCalls.length+")")
// each callDetails object describes the Ajax call
// and passes the required functions and arguments
// via 3 properties: service, action, params
// ex: callDetails = {
// 'service' : MainApiChangeNgramHttpService,
// 'action' : 'delete'
// 'params' : { 'listId': ..., 'ngramIdList':...}
// there is an optional 4th slot: the dataPropertiesToCache directive
//
// 'dataPropertiesToCache' : ['id'] <== means that on call success
// we will store data.id into
// cache.id for next calls
// }
var service = callDetails['service']
var params = callDetails['params']
var action = callDetails['action']
// cache if we need to store properties of data response for next calls
var cache = {}
if (lastCache) cache = lastCache
// and interpolation of params with this current cache
for (var key in params) {
var val = params[key]
if (typeof val == "object" && val["fromCache"]) {
var propToRead = val["fromCache"]
// console.log("reading from cache: response data property "
// +propToRead+" ("+cache[propToRead]+")")
params[key] = cache[propToRead]
}
else if (typeof val == "object" && val["fromCacheIfElse"]) {
var propToReadIf = val["fromCacheIfElse"][0]
var propToReadElse = val["fromCacheIfElse"][1]
// console.log("reading from cache: response data property " +
// "if:"+propToReadIf+" ("+cache[propToReadIf]+")"+
// " else:"+propToReadElse+" ("+cache[propToReadElse]+")")
var valueIf = cache[propToReadIf]
var valueElse = cache[propToReadElse]
if (valueIf && valueIf != 'null' && valueIf != '') {
params[key] = valueIf
}
else {
params[key] = valueElse
}
}
}
// Now we run the call
// ex:
// service action
// vvvvv vvvv
// MainApiChangeNgramHttpService["delete"](
// params >>> {'listId': listId, 'ngramIdList': ngramId},
// onsuccess(), onfailure() )
service[action](
params,
// on success
function(data) {
// console.log("SUCCESS:" + action)
// console.log("listOfCalls.length:" + listOfCalls.length)
// console.log("i+1:" + i+1)
// case NEXT
// ----
// when chained actions
if (listOfCalls.length > i+1) {
// if we need to store anything it's the right moment
for (var k in callDetails['dataPropertiesToCache']) {
var prop = callDetails['dataPropertiesToCache'][k]
// console.log("storing in cache: response data property "
// +prop+" ("+data[prop]+")")
cache[prop] = data[prop]
}
// ======= recursive call for next action in list ================
$rootScope.makeChainedCalls(i+1, listOfCalls, finalCallback, cache)
// ================================================================
}
// case LAST
// ------
// when last action
else {
console.log(">> calling refresh")
finalCallback()
}
},
// on error
function(data) {
console.error("unable to call ajax no "+i+" with service "+service.name+
" (http "+action+" with args "+JSON.stringify(params)+")");
}
);
}
// -------------------------------------------------------------------------
// debug
console.log("==> $rootScope <==")
console.log($rootScope)
});
})(window);
(function () {
'use strict';
var annotationsAppDocument = angular.module('annotationsAppDocument', ['annotationsAppHttp']);
annotationsAppDocument.controller('DocController',
['$scope', '$rootScope', '$timeout', 'NgramListHttpService', 'DocumentHttpService',
function ($scope, $rootScope, $timeout, NgramListHttpService, DocumentHttpService) {
// dataLoading = signal pour afficher wait
$scope.dataLoading = true ;
console.log("annotations.document.DocController.DocumentHttpService.get():before")
$rootScope.documentResource = DocumentHttpService.get(
{'docId': $rootScope.docId},
function(data, responseHeaders) {
$scope.authors = data.authors;
$scope.source = data.source;
$scope.publication_date = data.publication_date;
//$scope.current_page_number = data.current_page_number;
//$scope.last_page_number = data.last_page_number;
$rootScope.title = data.title;
$rootScope.docId = data.id;
$rootScope.full_text = data.full_text;
$rootScope.abstract_text = data.abstract_text;
$rootScope.workflow_finished = data.corpus_status['complete'] ;
console.log("workflow status", $rootScope.workflow_finished)
if ($scope.workflow_finished) {
console.log("annotations.document.DocController.getannotations")
// GET the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
// eg id => 'MAPLIST'
$rootScope.lists = data[$rootScope.corpusId.toString()].lists;
// inverted 'MAPLIST' => id
$rootScope.listIds = _.invert($rootScope.lists)
$scope.dataLoading = false ;
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
}
else {
$scope.dataLoading = false ;
}
});
// TODO setup article pagination
$scope.onPreviousClick = function () {
DocumentHttpService.get($scope.docId - 1);
};
$scope.onNextClick = function () {
DocumentHttpService.get($scope.docId + 1);
};
}]);
annotationsAppDocument.controller('DocFavoriteController',
['$scope', '$rootScope', 'MainApiFavoritesHttpService',
function ($scope, $rootScope, MainApiFavoritesHttpService) {
$scope.isFavorite = false;
MainApiFavoritesHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
if (data['favdocs'].length > 0
&& data['favdocs'][0] == $scope.docId) {
$scope.isFavorite = true ;
}
else {
$scope.isFavorite = false ;
}
},
function(data) {
console.error("unable to check if document belongs to favorites");
$scope.isFavorite = false ;
}
) ;
$scope.onStarClick = function($event) {
console.log($scope.isFavorite)
// console.log($scope)
console.log("TODO");
var myAction ;
if (! $scope.isFavorite) {
// PUT api/nodes/574/favorites?docs=576
myAction = MainApiFavoritesHttpService.put
}
else {
// DELETE api/nodes/574/favorites?docs=576
myAction = MainApiFavoritesHttpService.delete
}
// (1) do the action
myAction(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
// success
function(data) {
// (2) toggle status and refresh
$scope.isFavorite = ! $scope.isFavorite
$rootScope.refreshDisplay();
},
// failure
function(data) {
console.error("unable to change favorite status");
}
);
};
}]);
})(window);
This diff is collapsed.
(function () {
'use strict';
var http = angular.module('annotationsAppHttp', ['ngResource', 'ngCookies']);
http.config(['$httpProvider', function($httpProvider){
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
}]);
function url(path) {
// adding explicit "http[s]://" -- for cross origin requests
return location.protocol + '//' + window.GARG_ROOT_URL + path;
}
/*
* DocumentHttpService: Read Document
* ===================
*
* route: annotations/documents/@d_id
* ------
* TODO use external: api/nodes/@d_id?fields[]=hyperdata
* exemple:
* --------
* {
* "id": 556,
* "publication_date": "01/01/66",
* "title": "Megalithic astronomy: Indications in standing stones",
* "abstract_text": "An account is given of a number of surveys of
* stone circles, alignments, etc., found in Britain.
* The geometry of the rings is discussed in so far
* as it affects the determination of the azimuths
* to outliers and other circles.",
* "full_text": null,
* "journal": "Vistas in Astronomy",
* "authors": "A. Thom"
* }
*
*/
http.factory('DocumentHttpService', function($resource) {
return $resource(
window.ANNOTATION_API_URL + "documents/:docId/",
{
docId: '@docId'
},
{
get: {
method: 'GET',
params: {docId: '@docId'}
}
}
);
});
/*
* NgramListHttpService: Read all Ngrams
* =====================
*
* route: annotations/corpora/@c_id/documents/@d_id
* ------
*
* json return format:
* -------------------
* corpus_id : {
* lists: {(list_id:name)+}
* doc_id : [ngrams_objects]+,
* }
*
* exemple:
* --------
* "554": {
* "lists": { "558": "StopList", "564": "MiamList", "565": "MapList" }
* "556": [{ "uuid": 2368, "occs": 1.0, "text": "idea", "list_id": 564 },
* { "uuid": 5031, "occs": 1.0, "text": "indications", "list_id": 564},
* { "uuid": 5015, "occs": 3.0, "text": "star", "list_id": 565 },
* ... ],
* }
*/
http.factory('NgramListHttpService', function ($resource) {
return $resource(
window.ANNOTATION_API_URL + 'corpora/:corpusId/documents/:docId',
{
corpusId: '@corpusId',
docId: '@docId'
},
{
get: {
method: 'GET',
params: {}
}
}
);
});
/*
* MainApiAddNgramHttpService: Create and index a new ngram
* ===========================
* route: PUT api/ngrams?text=mynewngramstring&corpus=corpus_id
*
* NB it also checks if ngram exists (returns the preexisting id)
* and if it has a mainform/group (via 'testgroup' option)
* (useful if we add it to a list afterwards)
*
*/
http.factory('MainApiAddNgramHttpService', function($resource) {
return $resource(
url("/api/ngrams?text=:ngramStr&corpus=:corpusId&testgroup"),
{
ngramStr: '@ngramStr',
corpusId: '@corpusId',
},
{
put: {
method: 'PUT',
params: {listId: '@listId', ngramIdList: '@ngramIdList'}
}
}
);
});
/*
* MainApiChangeNgramHttpService: Add/remove ngrams from lists
* =============================
* route: api/ngramlists/change?list=LISTID&ngrams=ID1,ID2...
*
* (same route used in ngrams table)
*
* /!\ for this route we reach out of this annotation module
* and send directly to the gargantext api route for list change
* (cross origin request with http protocol scheme)
* ------
*
*/
http.factory('MainApiChangeNgramHttpService', function($resource) {
return $resource(
url("/api/ngramlists/change?list=:listId&ngrams=:ngramIdList"),
{
listId: '@listId',
ngramIdList: '@ngramIdList' // list in str form (sep=","): "12,25,30"
// (usually in this app just 1 id): "12"
},
{
put: {
method: 'PUT',
params: {listId: '@listId', ngramIdList: '@ngramIdList'}
},
delete: {
method: 'DELETE',
params: {listId: '@listId', ngramIdList: '@ngramIdList'}
}
}
);
});
/*
* MainApiFavoritesHttpService: Check/Add/Del Document in favorites
* ============================
* route: api/nodes/574/favorites?docs=576
* /!\ for this route we reach out of this annotation module
* and send directly to the gargantext api route for favs
* (cross origin request with http protocol scheme)
* ------
*
* exemple:
* --------
* {
* "favdocs": [576] // <== if doc is among favs
* "missing": [] // <== if doc is not in favs
* }
*
*/
http.factory('MainApiFavoritesHttpService', function($resource) {
return $resource(
url("/api/nodes/:corpusId/favorites?docs=:docId"),
{
corpusId: '@corpusId',
docId: '@docId'
},
{
get: {
method: 'GET',
params: {corpusId: '@corpusId', docId: '@docId'}
},
put: {
method: 'PUT',
params: {corpusId: '@corpusId', docId: '@docId'}
},
delete: {
method: 'DELETE',
params: {corpusId: '@corpusId', docId: '@docId'}
}
}
);
});
})(window);
<span ng-click='onDeleteClick()' class="delete-keyword">×</span>
<span data-toggle="tooltip" class="keyword-text {[{keyword.listName}]}">{[{keyword.text}]}</span>
<span class="occurrences" data-keyword-id="{[{keyword.uuid}]}">{[{keyword.occs}]}</span>
// include angular loader, which allows the files to load in any order
//@@NG_LOADER_START@@
// You need to run `npm run update-index-async` to inject the angular async code here
//@@NG_LOADER_END@@
// include a third-party async loader library
/*!
* $script.js v1.3
* https://github.com/ded/script.js
* Copyright: @ded & @fat - Dustin Diaz, Jacob Thornton 2011
* Follow our software http://twitter.com/dedfat
* License: MIT
*/
!function(a,b,c){function t(a,c){var e=b.createElement("script"),f=j;e.onload=e.onerror=e[o]=function(){e[m]&&!/^c|loade/.test(e[m])||f||(e.onload=e[o]=null,f=1,c())},e.async=1,e.src=a,d.insertBefore(e,d.firstChild)}function q(a,b){p(a,function(a){return!b(a)})}var d=b.getElementsByTagName("head")[0],e={},f={},g={},h={},i="string",j=!1,k="push",l="DOMContentLoaded",m="readyState",n="addEventListener",o="onreadystatechange",p=function(a,b){for(var c=0,d=a.length;c<d;++c)if(!b(a[c]))return j;return 1};!b[m]&&b[n]&&(b[n](l,function r(){b.removeEventListener(l,r,j),b[m]="complete"},j),b[m]="loading");var s=function(a,b,d){function o(){if(!--m){e[l]=1,j&&j();for(var a in g)p(a.split("|"),n)&&!q(g[a],n)&&(g[a]=[])}}function n(a){return a.call?a():e[a]}a=a[k]?a:[a];var i=b&&b.call,j=i?b:d,l=i?a.join(""):b,m=a.length;c(function(){q(a,function(a){h[a]?(l&&(f[l]=1),o()):(h[a]=1,l&&(f[l]=1),t(s.path?s.path+a+".js":a,o))})},0);return s};s.get=t,s.ready=function(a,b,c){a=a[k]?a:[a];var d=[];!q(a,function(a){e[a]||d[k](a)})&&p(a,function(a){return e[a]})?b():!function(a){g[a]=g[a]||[],g[a][k](b),c&&c(d)}(a.join("|"));return s};var u=a.$script;s.noConflict=function(){a.$script=u;return this},typeof module!="undefined"&&module.exports?module.exports=s:a.$script=s}(this,document,setTimeout)
// load all of the dependencies asynchronously.
var S = window.STATIC_URL;
$script([
S + 'lib/angular/1.2.29/angular.min.js',
S + 'lib/bootstrap/3.3.6/js/bootstrap.min.js',
S + 'lib/bootstrap-select/1.7.7/js/bootstrap-select.min.js',
S + 'lib/angular-loader/1.2.29/angular-loader.min.js',
S + 'lib/underscore/underscore-1.5.2.js',
//'bower_components/angular-route/angular-route.js',
], function() {
$script([
S + 'lib/angular-cookies/1.2.29/angular-cookies.min.js',
S + 'lib/angular-resource/1.2.29/angular-resource.min.js'], function() {
$script([S + 'annotations/http.js', S + 'annotations/highlight.js',
S + 'annotations/document.js', S + 'annotations/ngramlist.js',
S + 'annotations/activelists.js', S + 'annotations/ngramlist.js',
S + 'annotations/utils.js', S + 'annotations/app.js'], function() {
// when all is done, execute bootstrap angular application (replace ng-app directive)
angular.bootstrap(document.getElementById("annotationsApp"), ['annotationsApp']);
});
});
});
(function () {
'use strict';
var annotationsAppNgramList = angular.module('annotationsAppNgramList', ['annotationsAppHttp']);
/*
* Controls one Ngram displayed in the flat lists (called "extra-text")
*/
annotationsAppNgramList.controller('NgramController',
['$scope', '$rootScope', 'MainApiChangeNgramHttpService', 'NgramListHttpService',
function ($scope, $rootScope, MainApiChangeNgramHttpService, NgramListHttpService) {
/*
* Click on the 'delete' cross button
* (NB: we have different delete consequences depending on list)
*/
$scope.onDeleteClick = function () {
var listName = $scope.keyword.listName
var thisListId = $scope.keyword.list_id
var thisNgramId = $scope.keyword.uuid
var crudActions = [] ;
if (listName == 'MAPLIST') {
crudActions = [
// only 'remove' is needed here
{'service': MainApiChangeNgramHttpService, 'action': 'delete',
'params' : {'listId':thisListId, 'ngramIdList': thisNgramId} }
]
}
else if (listName == 'MAINLIST') {
crudActions = [
// remove
{'service': MainApiChangeNgramHttpService, 'action': 'delete',
'params' : {'listId':thisListId, 'ngramIdList': thisNgramId} },
// consequence: add to opposite list
{'service': MainApiChangeNgramHttpService, 'action': 'put',
'params' : {'listId':$rootScope.listIds.STOPLIST, 'ngramIdList': thisNgramId} }
]
}
else if (listName == 'STOPLIST') {
crudActions = [
// remove
{'service': MainApiChangeNgramHttpService, 'action': 'delete',
'params' : {'listId':thisListId, 'ngramIdList': thisNgramId} },
// consequence: add to opposite list
{'service': MainApiChangeNgramHttpService, 'action': 'put',
'params' : {'listId':$rootScope.listIds.MAINLIST, 'ngramIdList': thisNgramId} }
]
}
// console.log(crudActions)
// using recursion to make chained calls,
// run the loop by calling the initial recursion step
$rootScope.makeChainedCalls(0, crudActions, $rootScope.refresh)
};
}]);
/*
* Controller for the list panel displaying extra-text ngram
*/
annotationsAppNgramList.controller('NgramListPaginationController',
['$scope', '$rootScope', function ($scope, $rootScope) {
$rootScope.$watchCollection('ngramsInPanel', function (newValue, oldValue) {
$scope.currentListPage = 0;
$scope.pageSize = 15;
$scope.nextListPage = function() {
$scope.currentListPage = $scope.currentListPage + 1;
};
$scope.previousListPage = function() {
$scope.currentListPage = $scope.currentListPage - 1;
};
$scope.totalListPages = function(listId) {
if ($rootScope.ngramsInPanel[listId] === undefined) return 0;
return Math.ceil($rootScope.ngramsInPanel[listId].length / $scope.pageSize);
};
});
}]);
/*
* Template of the ngram element displayed in the flat lists
*/
annotationsAppNgramList.directive('keywordTemplate', function () {
return {
templateUrl: function ($element, $attributes) {
return S + 'annotations/keyword_tpl.html';
}
};
});
/*
* new NGram from the user input
*/
annotationsAppNgramList.controller('NgramInputController',
['$scope', '$rootScope', '$element', 'NgramListHttpService',
'MainApiChangeNgramHttpService', 'MainApiAddNgramHttpService',
function ($scope, $rootScope, $element, NgramListHttpService,
MainApiChangeNgramHttpService, MainApiAddNgramHttpService) {
/*
* Add a new NGram from the user input in the extra-text list
*/
$scope.onListSubmit = function ($event, tgtListId) {
var inputEltId = "#"+ tgtListId +"-input";
if ($event.keyCode !== undefined && $event.keyCode != 13) return;
var value = angular.element(inputEltId).val().trim();
if (value === "") return;
// locally check if already in annotations NodeNgrams ------------
// $rootScope.annotations = array of ngram objects like:
// {"list_id":805,"occs":2,"uuid":9386,"text":"petit échantillon"}
// TODO £NEW : lookup obj[list_id][term_text] = {terminfo}
// // $rootScope.lookup =
console.log('looking for "' + value + '" in list:' + tgtListId)
var already_in_list = false ;
angular.forEach($rootScope.annotations, function(annot,i) {
// console.log(i + ' => ' + annot.text + ',' + annot.list_id) ;
if (value == annot.text && tgtListId == annot.list_id) {
console.log('the term "' + value + '" was already present in list')
// no creation
already_in_list = true ;
}
}
);
if (already_in_list) { return ; }
// ---------------------------------------------------------------
var tgtListName = $rootScope.lists[tgtListId]
// alert("ADDING TO listId: " + tgtListId +"\n listName: "+ tgtListName)
var crudCallsToMake = []
switch (tgtListName) {
case "STOPLIST":
crudCallsToMake = [
{'service': MainApiAddNgramHttpService, 'action': 'put',
'params' : {'ngramStr':value, corpusId: $rootScope.corpusId},
'dataPropertiesToCache': ['id', 'group'] },
{'service': MainApiChangeNgramHttpService, 'action': 'put',
'params' : {'listId':tgtListId, 'ngramIdList': {'fromCacheIfElse': ['group','id']} } }
];
break;
case "MAINLIST":
crudCallsToMake = [
{'service': MainApiAddNgramHttpService, 'action': 'put',
'params' : {'ngramStr':value, corpusId: $rootScope.corpusId},
'dataPropertiesToCache': ['id', 'group'] },
{'service': MainApiChangeNgramHttpService, 'action': 'put',
'params' : {'listId':tgtListId, 'ngramIdList': {'fromCacheIfElse': ['group','id']} } }
];
break;
case "MAPLIST":
crudCallsToMake = [
{'service': MainApiAddNgramHttpService, 'action': 'put',
'params' : {'ngramStr':value, corpusId: $rootScope.corpusId},
'dataPropertiesToCache': ['id', 'group'] },
{'service': MainApiChangeNgramHttpService, 'action': 'put',
'params' : {'listId':$rootScope.listIds.MAINLIST, 'ngramIdList': {'fromCacheIfElse': ['group','id']} } },
{'service': MainApiChangeNgramHttpService, 'action': 'put',
'params' : {'listId':tgtListId, 'ngramIdList': {'fromCacheIfElse': ['group','id']} } }
];
break;
}
// run the ajax calls in a recursive loop by calling the step n° 0
$rootScope.makeChainedCalls(0, crudCallsToMake, $rootScope.refresh)
}; // onListSubmit
}]);
})(window);
(function () {
'use strict';
var annotationsAppUtils = angular.module('annotationsAppUtils', []);
/*
* Filter used in lists pagination (extra-text panel)
*/
annotationsAppUtils.filter('startFrom', function () {
return function (input, start) {
if (input === undefined) return;
start = +start; //parse to int
return input.slice(start);
};
});
})(window);
{% load staticfiles %}
<!DOCTYPE html>
<!--[if lt IE 7]> <html lang="en" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html lang="en" class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html lang="en" class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html lang="en" class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Gargantext article editor</title>
<meta name="description" content="Gargantext">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% static 'lib/bootstrap/3.3.6/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'lib/bootstrap-select/1.7.7/css/bootstrap-select.min.css' %}">
<link rel="stylesheet" href="{% static 'lib/angular/1.2.29/angular-csp.css' %}">
<link rel="stylesheet" href="{% static 'annotations/app.css' %}"> <!-- todo -->
<script src="{% static 'lib/jquery/2.2.0/jquery.min.js' %}"></script>
</head>
<body>
<!-- TODO integrate this later into the any other django template -->
<div id="annotationsApp" ng-cloak>
<div class="container-fluid">
<div class="row-fluid main-panel" ng-controller="NGramHighlightController">
<div ng-if="workflow_finished" class="col-md-4 col-xs-4 tabbable words-panel">
<div class="list-selector">
<h5>Select highlighted list(s)
<select class="selectpicker" multiple ng-change="activeListsChange()" ng-model="lists" ng-controller="ActiveListsController">
<option ng-repeat="item in allListsSelect" id="list---{[{item.id}]}" ng-disabled="{[{ item.label == 'MAPLIST' }]}" >{[{item.label}]}</option>
<!-- to disallow unchecking MapList add this into <option> element: ng-disabled="{[{ item.label == 'MAPLIST' }]}" -->
</select>
</h5>
<div class="row spacer"></div>
<ul class="nav nav-pills nav-justified activelists">
<li ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}">
<a href="#tab-{[{listId}]}" data-toggle="tab">
<span class="{[{listName}]} inlay">{[{listName}]}</span>
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div ng-controller="NgramListPaginationController" ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}" class="tab-pane" id="tab-{[{listId}]}">
<div ng-if="ngramsInPanel[listId].length == 0" class="alert alert-info" role="alert">
Input any keyword you want to link to this article and the list named '{[{listName}]}'
</div>
<ul class="list-group words-list clearfix">
<li ng-repeat="keyword in ngramsInPanel[listId] | startFrom:currentListPage * pageSize | limitTo:pageSize" class="keyword-group-item">
<div ng-controller="NgramController" keyword-template class="keyword-container"></div>
</li>
</ul>
<nav ng-class="{invisible: totalListPages(listId) - 1 == 0}" class="clearfix">
<ul class="pagination pagination-s pull-right words-pagination">
<li ng-class="{'disabled': currentListPage == 0}"><a ng-click="previousListPage()" class="glyphicon glyphicon-backward"></a></li>
<li ng-class="{'disabled': currentListPage >= totalListPages(listId) - 1}"><a ng-click="nextListPage()" class="glyphicon glyphicon-forward"></a></li>
</ul>
</nav>
<div id="addfreengram" class="form-group" ng-controller="NgramInputController">
<label for="{[{listId}]}-input">Add a free term to <span class="{[{listName}]} inlay">{[{listName}]}</span>:</label>
<input autosave="search" maxlength="240" placeholder="Any text" type="text" class="form-control" id="{[{listId}]}-input" ng-keypress="onListSubmit($event, listId)">
<button type="submit" class="form-control btn btn-default" ng-click="onListSubmit($event, listId)">Create &amp; add</button>
</div>
</div>
</div>
</div>
<div class="col-md-8 col-xs-8 text-panel" ng-controller="DocController" id="document">
<div class="row-fluid clearfix">
<div class="col-md-10 col-xs-10">
<h3 class="text-container" id="title">{[{title}]}</h3>
</div>
<div class="col-md-2 col-xs-2 clearfix">
<button ng-controller="DocFavoriteController" type="button" class="btn btn-default float-right" ng-click="onStarClick($event)">
<span class="glyphicon" ng-class="{'glyphicon-star-empty': isFavorite == false, 'glyphicon-star favactive': isFavorite == true}"></span>
</button>
<!--<nav>
<ul class="pager">
<li ng-if="current_page_number > 1"><a ng-click="onPreviousClick()" href="#">Previous</a></li>
<li ng-if="current_page_number < last_page_number"><a ng-click="onNextClick()" href="#">Next</a></li>
</ul>
</nav>-->
</div>
</div>
<div class="row-fluid">
<ul class="list-group clearfix">
<li class="list-group-item small"><span class="badge">source</span>{[{source || '&nbsp;'}]}</li>
<li class="list-group-item small"><span class="badge">authors</span>{[{authors || '&nbsp;'}]}</li>
<li class="list-group-item small"><span class="badge">date</span>{[{publication_date || '&nbsp;'}]}</li>
</ul>
</div>
<div ng-if="dataLoading">
Loading text...
<br>
<center>
<img width="10%" src="{% static 'img/ajax-loader.gif'%}"></img>
</center>
<br>
</div>
<div ng-if="abstract_text != null">
<span class="badge">abstract</span>
</div>
<p id="abstract-text" class="text-container">
{[{abstract_text}]}
<div ng-if="abstract_text == null" class="alert alert-info small" role="alert">Empty abstract text</div>
</p>
<div ng-if="full_text != null">
<span class="badge">full article</span>
</div>
<p id="full-text" class="text-container">
{[{full_text}]}
<div ng-if="full_text == null" class="alert alert-info small" role="alert">Empty full text</div>
</p>
</div>
</div> <!-- end of the main row -->
</div>
<!-- this menu is over the text on mouse selection -->
<div ng-controller="TextSelectionMenuController" id="selection" class="selection-menu">
<ul class="noselection">
<li ng-repeat="item in menuItems"
class="{[{item.tgtListName}]}"
ng-click="onMenuClick($event, item.crudCalls)"
>{[{item.comment ? item.comment : 'Move to ' + item.tgtListName}]}</li>
</ul>
</div>
</div>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<script type="application/javascript">
/* Constants required for annotations app JS to work */
window.STATIC_URL = "{% static '' %}";
window.ANNOTATION_API_URL = "{{ api_url }}";
window.GARG_ROOT_URL = "{{ garg_url }}";
window.NODES_API_URL = "{{ nodes_api_url }}";
</script>
<script src="{% static 'annotations/main.js' %}"></script>
</body>
</html>
from django.conf.urls import url
from gargantext.annotations import views
# /!\ urls patterns here are *without* the trailing slash
urlpatterns = [
# GET [DocumentHttpService]
# json:title,id,authors,journal,
# publication_date
# abstract_text,full_text
url(r'^documents/(?P<doc_id>[0-9]+)$', views.Document.as_view()), # document view
# GET [NgramListHttpService]
# ngrams from {lists ∩ document}
url(r'^corpora/(?P<corpus_id>[0-9]+)/documents/(?P<doc_id>[0-9]+)$', views.NgramList.as_view()), # the list associated with an ngram
# 2016-03-24: refactoring, deactivated NgramEdit and NgramCreate
# 2016-05-27: removed NgramEdit: replaced the local httpservice by api/ngramlists
# 2016-07-21: removed NgramCreate: replaced the local httpservice by api/ngrams (put)
]
from urllib.parse import urljoin
import json
import datetime
from django.shortcuts import render
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
from rest_framework.exceptions import APIException
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from gargantext.models.ngrams import Node, NodeNgram, Ngram, NodeNgramNgram
from gargantext.util.db import session, aliased
from gargantext.util.db_cache import cache
from gargantext.util.http import requires_auth
from sqlalchemy.sql.expression import case
@requires_auth
def main(request, project_id, corpus_id, document_id, optional_focus_ngram):
"""
Full page view
NB: url params are NOT used here (angular has its own url regex in app.js)
"""
context = { 'api_url' : urljoin(request.get_host(), '/annotations/')
, 'garg_url' : request.get_host()
, 'nodes_api_url': urljoin(request.get_host(), '/api/')
}
return render(request, 'annotations/main.html', context)
class NgramList(APIView):
"""Read the lists of ngrams (terms) that will become annotations"""
renderer_classes = (JSONRenderer,)
def get(self, request, corpus_id, doc_id):
"""
Get all ngrams for a doc id, sorted by list
usual route: /annotations/documents/<docid>
NB1 : we are within a doc only
NB2 : MAINLIST items are actually MAINLIST without MAP items
NB3 : mostly the mainforms are in lists, but doc can have subform
=> if we simply join on ngram_id, we'll filter out the subforms
=> join on value filled by case switch:
(the ngram itself or a mainform if exists)
"""
corpus_id = int(corpus_id)
doc_id = int(doc_id)
# our results: ngrams within a doc and a list + weights in the doc
doc_ngram_list = []
doc_ngram_list_add = doc_ngram_list.append
lists = {}
corpus_nod = cache.Node[corpus_id]
doc_nod = cache.Node[doc_id]
# scores_nod = corpus_nod.children(typename="OCCURRENCES").first()
groups_nod = corpus_nod.children(typename="GROUPLIST").first()
# synonyms sub table for outerjoins
Syno = (session.query(NodeNgramNgram.ngram1_id,
NodeNgramNgram.ngram2_id)
.filter(NodeNgramNgram.node_id == groups_nod.id)
.subquery()
)
# maplist_ids to filter map ngrams from mainlist
maplist_ids = {}
# NB must do mainlist after map for filtering map items out of main
for list_type in ['MAPLIST', 'STOPLIST', 'MAINLIST']:
list_nod = corpus_nod.children(typename=list_type).first()
list_id = list_nod.id
lists["%s" % list_id] = list_type
ListsTable = aliased(NodeNgram)
mainform_id = case([
(Syno.c.ngram1_id != None, Syno.c.ngram1_id),
(Syno.c.ngram1_id == None, Ngram.id)
])
q = (session
# ngrams from the doc_id
.query(NodeNgram.weight, Ngram, mainform_id)
# debug
#.query(NodeNgram.weight, Ngram.terms, Ngram.id, Syno.c.ngram1_id, mainform_id)
.select_from(NodeNgram)
.join(Ngram)
.filter(NodeNgram.node_id == doc_id)
# add mainforms next to their subforms
.outerjoin(Syno,
Syno.c.ngram2_id == Ngram.id)
# filter mainforms on the list we want
.join(ListsTable,
# possible that mainform is in list
# and not the subform
ListsTable.ngram_id == mainform_id
)
.filter(ListsTable.node_id == list_id)
)
# add to results (and optional filtering)
for (w,obj, mainform_id) in q.all():
ngram_id = obj.id
# boolean if needed
# is_subform = (ngram_id == mainform_id)
# special filtering case
# when MAINLIST requested we actually want MAIN without MAP
if list_type == "MAPLIST":
maplist_ids[ngram_id] = True
if list_type == "MAINLIST":
if ngram_id in maplist_ids:
# skip object
continue
if mainform_id == ngram_id:
group = None
else:
group = mainform_id
# normal case
doc_ngram_list_add((ngram_id, obj.terms, group, w, list_id))
# debug
# print("annotations.views.NgramList.doc_ngram_list: ", doc_ngram_list)
data = { '%s' % corpus_id : {
'%s' % doc_id :
[
{'uuid': ngram_id,
'group': group, # the mainform if there is a group
'text': ngram_text,
'occs': ngram_occs,
'list_id': list_id,}
for (ngram_id,ngram_text,group,ngram_occs,list_id) in doc_ngram_list
],
'lists': lists
}}
# format alternatif de transmission des "annotations", classé par listes puis ngram_id
# { 'corpus_id' : {
# list_id_stop: {term_stop1: {term_data}, term_stop2: {term_data}..},
# list_id_miam: {term_miam1: {term_data}, term_miam2: {term_data}..},
# list_id_map: {term_map1: {term_data}, term_map2: {term_data}..},
# }
# 'lists' : {"list_id" : "list_type" ... }
# }
# NB 3rd possibility: unicity of ngram_text could also allow us to use it
# as key and could enhance lookup later (frequent checks if term exists)
return Response(data)
# 2016-03-24: refactoring, deactivated NgramEdit and NgramCreate
# 2016-05-27: removed NgramEdit: replaced the local httpservice by api/ngramlists
# 2016-07-21: removed NgramCreate: replaced the local httpservice by api/ngrams (put)
class Document(APIView):
"""
Read-only Document view, similar to /api/nodes/
"""
renderer_classes = (JSONRenderer,)
def get(self, request, doc_id):
"""Document by ID"""
node = session.query(Node).filter(Node.id == doc_id).first()
corpus = session.query(Node).filter(Node.id == node.parent_id).first()
corpus_workflow_status = corpus.hyperdata['statuses'][0]
if node is None:
raise APIException('This node does not exist', 404)
try:
pub_date = datetime.datetime.strptime(node.hyperdata.get('publication_date'),
"%Y-%m-%d %H:%M:%S")
pub_date = pub_date.strftime("%x")
except ValueError:
pub_date = node.hyperdata.get('publication_date')
data = {
'corpus_status': corpus_workflow_status,
'title': node.hyperdata.get('title'),
'authors': node.hyperdata.get('authors'),
'source': node.hyperdata.get('source'),
'publication_date': pub_date,
'full_text': node.hyperdata.get('full_text'),
'abstract_text': node.hyperdata.get('abstract'),
'id': node.id
}
return Response(data)
......@@ -63,7 +63,6 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'rest_framework',
'djcelery',
'gargantext.annotations',
'gargantext.moissonneurs',
'gargantext',
]
......
......@@ -4,7 +4,6 @@ Views are shared between these modules:
- `api`, for JSON and CSV interaction with data
- `pages`, to present HTML views to the user
- `contents`, for Python-generated contents
- `annotations`, to annotate local context of a corpus (as global context)
"""
from django.conf.urls import include, url
......@@ -15,11 +14,6 @@ from django.contrib.staticfiles.storage import staticfiles_storage as static
import gargantext.views.api.urls
import gargantext.views.pages.urls
# Module Annotation
## tempo: unchanged doc-annotations --
from gargantext.annotations import urls as annotations_urls
from gargantext.annotations.views import main as annotations_main_view
# Module Scrapers
import gargantext.moissonneurs.urls
......@@ -30,12 +24,6 @@ urlpatterns = [ url(r'^admin/' , admin.site.urls
, url(r'^favicon.ico$', Redirect.as_view( url=static.url('favicon.ico')
, permanent=False), name="favicon" )
# Module Annotation
# tempo: unchanged doc-annotations routes --
, url(r'^annotations/', include( annotations_urls ) )
, url(r'^projects/(\d+)/corpora/(\d+)/documents/(\d+)/(focus=[0-9,]+)?$'
, annotations_main_view)
# Module Scrapers (Moissonneurs in French)
, url(r'^moissonneurs/' , include( gargantext.moissonneurs.urls ) )
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment