Commit ba57e9ab authored by Romain Loth's avatar Romain Loth

groups in table: more detailed DELETE (subform params) and convert old POST...

groups in table: more detailed DELETE (subform params) and convert old POST method to PUT (for symmetry) (all params are in url)
parent f9e2dbd5
...@@ -4,13 +4,14 @@ API views for advanced operations on ngrams and ngramlists ...@@ -4,13 +4,14 @@ API views for advanced operations on ngrams and ngramlists
- retrieve several lists together ("family") - retrieve several lists together ("family")
- retrieve detailed list infos (ngram_id, term strings, scores...) - retrieve detailed list infos (ngram_id, term strings, scores...)
- modify NodeNgram lists (PUT/DEL an ngram to a MAINLIST OR MAPLIST...) - modify NodeNgram lists (PUT/DEL an ngram to a MAINLIST OR MAPLIST...)
- modify NodeNgramNgram groups (POST a list of groupings like {"767":[209,640],"779":[436,265,385]}") - modify NodeNgramNgram groups (PUT/DEL a list of groupings like {"767[]":[209,640],"779[]":[436,265,385]}")
""" """
from gargantext.util.http import APIView, get_parameters, JsonHttpResponse,\ from gargantext.util.http import APIView, get_parameters, JsonHttpResponse,\
ValidationException, Http404 ValidationException, Http404
from gargantext.util.db import session, aliased, desc, bulk_insert from gargantext.util.db import session, aliased, desc, bulk_insert
from gargantext.util.db_cache import cache, or_ from gargantext.util.db_cache import cache
from sqlalchemy import tuple_
from gargantext.models import Ngram, NodeNgram, NodeNodeNgram, NodeNgramNgram from gargantext.models import Ngram, NodeNgram, NodeNodeNgram, NodeNgramNgram
from gargantext.util.lists import UnweightedList, Translations from gargantext.util.lists import UnweightedList, Translations
...@@ -132,19 +133,11 @@ class GroupChange(APIView): ...@@ -132,19 +133,11 @@ class GroupChange(APIView):
group node group node
to modify to modify
We use POST HTTP method to send group data with structure like: We use PUT HTTP method to send group data to DB and DELETE to remove them.
{
mainform_A: [subform_A1],
mainformB: [subform_B1,subform_B2,subform_B3]
...
}
Chained effect: They both use same data format in the url (see links_to_couples).
any previous group under mainformA or B will be overwritten
No chained effects : simply adds or deletes rows of couples
The DELETE HTTP method also works, with same url
(and simple array in the data)
NB: request.user is also checked for current authentication status NB: request.user is also checked for current authentication status
""" """
...@@ -160,80 +153,120 @@ class GroupChange(APIView): ...@@ -160,80 +153,120 @@ class GroupChange(APIView):
# can't use return in initial() (although 401 maybe better than 404) # can't use return in initial() (although 401 maybe better than 404)
# can't use @requires_auth because of positional 'self' within class # can't use @requires_auth because of positional 'self' within class
def post(self, request): def links_to_couples(self,params):
"""
IN (dict from url params)
---
params = {
"mainform_A": ["subform_A1"]
"mainform_B": ["subform_B1,subform_B2,subform_B3"]
...
}
OUT (for DB rows)
----
couples = [
(mainform_A , subform_A1),
(mainform_B , subform_B1),
(mainform_B , subform_B2),
(mainform_B , subform_B3),
...
]
""" """
Rewrites the group node **selectively** couples = []
for (mainform_id, subforms_ids) in params.items():
for subform_id in subforms_ids[0].split(','):
# append the couple
couples.append((int(mainform_id),int(subform_id)))
return couples
=> removes couples where newly reconnected ngrams where involved def put(self, request):
=> adds new couples from GroupsBuffer of terms view """
Add some group elements to a group node
=> adds new couples from GroupsBuffer._to_add of terms view
TODO see use of util.lists.Translations TODO see use of util.lists.Translations
POST data: Parameters are all in the url (for symmetry with DELETE method)
<QueryDict: {'1228[]': ['891', '1639']}> => creates 1228 - 891 api/ngramlists/groups?node=783&1228[]=891,1639
and 1228 - 1639 => creates 1228 - 891
request.POST.lists() iterator where each elt is like :('1228[]',['891','1639']) and 1228 - 1639
"""
group_node = get_parameters(request)['node']
all_mainforms = []
links = []
for (mainform_key, subforms_ids) in request.POST.lists(): general format is: mainform_id[]=subform_id1,subform_id2 etc
mainform_id = mainform_key[:-2] # remove brackets '543[]' -> '543' => creates mainform_id - subform_id1
all_mainforms.append(mainform_id) and mainform_id - subform_id2
for subform_id in subforms_ids:
links.append((mainform_id,subform_id))
# remove selectively all groupings with these mainforms NB: also checks if the couples exist before because the ngram table
# using IN is correct in this case: list of ids is short and external will send the entire group (old existing links + new links)
# see stackoverflow.com/questions/444475/ """
old_links = (session.query(NodeNgramNgram) # from the url
.filter(NodeNgramNgram.node_id == group_node) params = get_parameters(request)
.filter(NodeNgramNgram.ngram1_id.in_(all_mainforms)) # the node param is unique
group_node = params.pop('node')
# the others params are links to change
couples = self.links_to_couples(params)
# local version of "insert if not exists" -------------------->8--------
# (1) check already existing elements
check_query = (session.query(NodeNgramNgram)
.filter(NodeNgramNgram.node_id == group_node)
.filter(
tuple_(NodeNgramNgram.ngram1_id, NodeNgramNgram.ngram2_id)
.in_(couples)
)
) )
n_removed = old_links.delete(synchronize_session=False)
session.commit()
# add new groupings existing = {}
for synonyms in check_query.all():
existing[(synonyms.ngram1_id,synonyms.ngram2_id)] = True
# (2) compute difference locally
couples_to_add = [(mform,sform) for (mform,sform)
in couples
if (mform,sform) not in existing]
# (3) add new groupings
bulk_insert( bulk_insert(
NodeNgramNgram, NodeNgramNgram,
('node_id', 'ngram1_id', 'ngram2_id', 'weight'), ('node_id', 'ngram1_id', 'ngram2_id', 'weight'),
((group_node, mainform, subform, 1.0) for (mainform,subform) in links) ((group_node, mainform, subform, 1.0) for (mainform,subform)
in couples_to_add)
) )
# ------------------------------------------------------------>8--------
return JsonHttpResponse({ return JsonHttpResponse({
'count_removed': n_removed, 'count_added': len(couples_to_add),
'count_added': len(links),
}, 200) }, 200)
def delete(self, request): def delete(self, request):
""" """
Deletes some groups from the group node Within a groupnode, deletes some group elements from some groups
Send in data format is simply a json { 'keys':'["11492","16438"]' }
==> it means removing any synonym groups having these 2 as mainform
(within the url's groupnode_id)
NB: At reception here it becomes like:
<QueryDict: {'keys[]': ['11492', '16438']}>
Data format just like in POST, everything in the url
""" """
# from the url # from the url
group_node = get_parameters(request)['node'] params = get_parameters(request)
# the node param is unique
print(request.POST) group_node = params.pop('node')
# the others params are links to change
couples_to_remove = self.links_to_couples(params)
# from the data in body # remove selectively group_couples
all_mainforms = request.POST.getlist('keys[]') # using IN is correct in this case: list of ids is short and external
# see stackoverflow.com/questions/444475/
links_to_remove = (session.query(NodeNgramNgram) db_rows = (session.query(NodeNgramNgram)
.filter(NodeNgramNgram.node_id == group_node) .filter(NodeNgramNgram.node_id == group_node)
.filter(NodeNgramNgram.ngram1_id.in_(all_mainforms)) .filter(
tuple_(NodeNgramNgram.ngram1_id, NodeNgramNgram.ngram2_id)
.in_(couples_to_remove)
)
) )
n_removed = links_to_remove.delete(synchronize_session=False)
n_removed = db_rows.delete(synchronize_session=False)
session.commit() session.commit()
return JsonHttpResponse({ return JsonHttpResponse({
...@@ -428,8 +461,6 @@ class MapListGlance(APIView): ...@@ -428,8 +461,6 @@ class MapListGlance(APIView):
class ListFamily(APIView): class ListFamily(APIView):
""" """
Compact combination of *multiple* list info Compact combination of *multiple* list info
......
...@@ -35,8 +35,8 @@ urlpatterns = [ url(r'^nodes$' , nodes.NodeListResource.as_view() ...@@ -35,8 +35,8 @@ urlpatterns = [ url(r'^nodes$' , nodes.NodeListResource.as_view()
, url(r'^ngramlists/groups$', ngramlists.GroupChange.as_view() ) , url(r'^ngramlists/groups$', ngramlists.GroupChange.as_view() )
# modify grouping couples of a group node # modify grouping couples of a group node
# ex: POST ngramlists/groups?node=43 # ex: PUT/DEL ngramlists/groups?node=43
# post data looks like : {"767":[209,640],"779":[436,265,385]}" # & group data also in url: 767[]=209,640 & 779[]=436,265,385
, url(r'^ngramlists/family$' , ngramlists.ListFamily.as_view() ) , url(r'^ngramlists/family$' , ngramlists.ListFamily.as_view() )
# entire combination of lists from a corpus, dedicated to termtable # entire combination of lists from a corpus, dedicated to termtable
......
...@@ -54,9 +54,9 @@ ...@@ -54,9 +54,9 @@
* - new api routes + prefetch maplist terms * - new api routes + prefetch maplist terms
* - simplify UpdateTable * - simplify UpdateTable
* - clarify cruds * - clarify cruds
* - better "created groups" handling * - fine-grained "created groups" handling
* *
* @version 1.2 * @version 1.3
* *
* @requires jquery.dynatable * @requires jquery.dynatable
* @requires d3 * @requires d3
...@@ -144,9 +144,10 @@ var GroupsBuffer = {'_to_add':{}, '_to_del':{}} ...@@ -144,9 +144,10 @@ var GroupsBuffer = {'_to_add':{}, '_to_del':{}}
// to_add will have structure like this { mainformid1 : [array1 of subformids], // to_add will have structure like this { mainformid1 : [array1 of subformids],
// mainformid2 : [array2 of subformids]} // mainformid2 : [array2 of subformids]}
// to_del is simple array of mainforms : [mainform3, mainform4] // to_del has same format
// because deleting "the group of mainform3" is deleting all DB rows of // (will be interpreted as couples to remove from DB rows)
// couples having mainform3 on the left-hand-side ( <=> no need to know the subform) // for instance: _to_del = { A1 : [A2, A5], B1 : [B2] }
// means to remove 3 DB rows A1 -- A2 and A1 -- A5 and B1 -- B2
...@@ -423,7 +424,13 @@ function saveActiveGroup() { ...@@ -423,7 +424,13 @@ function saveActiveGroup() {
for (downgradedNgramId in activeGroup.were_mainforms) { for (downgradedNgramId in activeGroup.were_mainforms) {
// (except the one we just saved) // (except the one we just saved)
if (downgradedNgramId != mainform) { if (downgradedNgramId != mainform) {
GroupsBuffer._to_del[downgradedNgramId] = true // it deletes the current groups with these ex-mainforms
// TODO check
// for DB
GroupsBuffer._to_del[downgradedNgramId] = CurrentGroups["links"][downgradedNgramId]
// locally
deleteInCurrentGroups(downgradedNgramId, false) // "false" <=> no need to change subform states deleteInCurrentGroups(downgradedNgramId, false) // "false" <=> no need to change subform states
} }
} }
...@@ -674,9 +681,12 @@ function removeSubform(ngramId) { ...@@ -674,9 +681,12 @@ function removeSubform(ngramId) {
var oldMainFormId = CurrentGroups["subs"][ngramId] var oldMainFormId = CurrentGroups["subs"][ngramId]
// it must break the old group, mark for deletion // it must break the old group, mark for deletion
GroupsBuffer._to_del[oldMainFormId] = true // TODO make temporary
// for DB
GroupsBuffer._to_del[oldMainFormId] = CurrentGroups["links"][ngramId]
// consequences: // local consequences:
deleteInCurrentGroups(oldMainFormId, true) deleteInCurrentGroups(oldMainFormId, true)
// => they are all removed from CurrentGroups // => they are all removed from CurrentGroups
// => the removed subform and all others from the old group // => the removed subform and all others from the old group
...@@ -1506,7 +1516,7 @@ function SaveLocalChanges() { ...@@ -1506,7 +1516,7 @@ function SaveLocalChanges() {
// (also removes previous groups with same mainforms!) // (also removes previous groups with same mainforms!)
function CRUD_7_AddGroups() { function CRUD_7_AddGroups() {
console.log("===> AJAX CRUD7 AddGroups <===\n") ; console.log("===> AJAX CRUD7 AddGroups <===\n") ;
GROUPCRUDS(groupnode_id, GroupsBuffer._to_add, "POST" , function(success) { GROUPCRUDS(groupnode_id, GroupsBuffer._to_add, "PUT" , function(success) {
if (success) { if (success) {
CRUD_8_RmGroups() // chained AJAX 7 -> 8 CRUD_8_RmGroups() // chained AJAX 7 -> 8
} }
...@@ -1567,35 +1577,46 @@ function CRUD( list_id , ngram_ids , http_method , callback) { ...@@ -1567,35 +1577,46 @@ function CRUD( list_id , ngram_ids , http_method , callback) {
} }
// For group modifications (POST: {mainformA: [subformsA1,A2,A3], mainformB:..}) // For group modifications
// (adds new groups for mainformA and mainformB) // @param groupnode_id: the node with the groupings to change
// (also deletes any old group under A or B) // @param send_data: {mainformA: [subformsA1,A2,A3], mainformB:..})
//
// @param http_method:
// PUT: adds new group rows : groupnode_id -- mainformA -- subformsA1
// groupnode_id -- mainformA -- subformsA2
// groupnode_id -- mainformA -- subformsA3
// groupnode_id -- mainformB -- ....
//
// DEL: idem but removes the group rows
//
// ex: /api/ngramlists/groups?node=783&3409[]=4745,14691,3730
// //
// (DEL: {"keys": [mainformX, mainformY]}) // NB no chained effects
// (just deletes the old groups of X and Y)
function GROUPCRUDS( groupnode_id , send_object, http_method , callback) { function GROUPCRUDS( groupnode_id , send_data, http_method , callback) {
// ngramlists/groups?node=9 // ngramlists/groups?node=9
var the_url = window.location.origin+"/api/ngramlists/groups?node="+groupnode_id; var the_url = window.location.origin+"/api/ngramlists/groups?node="+groupnode_id;
// array of the keys if(Object.keys(send_data).length > 0) {
var mainformIds = Object.keys(send_object) // group details go also in the url as additional params
for (var mainformId in send_data) {
var send_data ; subformIds = send_data[mainformId]
if (typeof subformIds != "undefined" && subformIds.constructor == Array) {
// if DELETE we need only them keys console.log("(ok doing: "+http_method+") for mainform "+mainformId+" with subforms", subformIds)
if (http_method == "DELETE") { the_url = the_url + '&' + mainformId + '[]=' + subformIds.join()
send_data = {"keys": mainformIds} ; }
} else {
else { console.error("(skipping: "+http_method+") for mainform "+mainformId+" with subforms", subformIds)
send_data = send_object ; }
} }
if(mainformIds.length > 0) {
$.ajax({ $.ajax({
method: http_method, method: http_method,
url: the_url, url: the_url,
data: send_data, // data: send_data // all data explicitly in the url (like a GET)
// because DEL can't consistently support form data
beforeSend: function(xhr) { beforeSend: function(xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
}, },
...@@ -2155,6 +2176,11 @@ function HandleAjax(res, sourceUrl) { ...@@ -2155,6 +2176,11 @@ function HandleAjax(res, sourceUrl) {
AfterAjax(sourceUrl) ; AfterAjax(sourceUrl) ;
} }
// throws errors when ngram in list has no infos
// (can't assign state and copy to AjaxRecords)
function AfterAjax(sourceUrl) { function AfterAjax(sourceUrl) {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// console.log(JSON.stringify(OriginalNG)) // console.log(JSON.stringify(OriginalNG))
...@@ -2171,7 +2197,7 @@ function AfterAjax(sourceUrl) { ...@@ -2171,7 +2197,7 @@ function AfterAjax(sourceUrl) {
for(var ngram_id in OriginalNG["map"]) { for(var ngram_id in OriginalNG["map"]) {
myNgramInfo = OriginalNG["records"][ngram_id] myNgramInfo = OriginalNG["records"][ngram_id]
if (typeof myNgramInfo == "undefined") { if (typeof myNgramInfo == "undefined") {
console.error("record of ngram " + ngram_id + " is undefined") console.error("record of ngram " + ngram_id + " was undefined")
} }
else { else {
// initialize state of maplist items // initialize state of maplist items
......
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