Commit 08ae2331 authored by Alexandre Delanoë's avatar Alexandre Delanoë

Merge branch 'unstable-notebook' into testing-notebook

parents aa325e73 e7ac6426
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## Community ## Community
* [http://gargantext.org/about](http://gargantext.org/about) * [http://gargantext.org/about](http://gargantext.org/about)
* IRC Chat: (OFTC/FreeNode) #gargantex * IRC Chat: (OFTC/FreeNode) #gargantext
##Tools ##Tools
* gogs * gogs
......
from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint, Index from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint, Index
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship, validates
from sqlalchemy.types import TypeDecorator, \ from sqlalchemy.types import TypeDecorator, \
Integer, Float, Boolean, DateTime, String, Text Integer, Float, Boolean, DateTime, String, Text
from sqlalchemy.dialects.postgresql import JSONB, DOUBLE_PRECISION as Double from sqlalchemy.dialects.postgresql import JSONB, DOUBLE_PRECISION as Double
...@@ -7,6 +7,7 @@ from sqlalchemy.ext.mutable import MutableDict, MutableList ...@@ -7,6 +7,7 @@ from sqlalchemy.ext.mutable import MutableDict, MutableList
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
__all__ = ["Column", "ForeignKey", "UniqueConstraint", "relationship", __all__ = ["Column", "ForeignKey", "UniqueConstraint", "relationship",
"validates", "ValidatorMixin",
"Integer", "Float", "Boolean", "DateTime", "String", "Text", "Integer", "Float", "Boolean", "DateTime", "String", "Text",
"TypeDecorator", "TypeDecorator",
"JSONB", "Double", "JSONB", "Double",
...@@ -18,6 +19,25 @@ __all__ = ["Column", "ForeignKey", "UniqueConstraint", "relationship", ...@@ -18,6 +19,25 @@ __all__ = ["Column", "ForeignKey", "UniqueConstraint", "relationship",
# all tables handled by Alembic migration scripts. # all tables handled by Alembic migration scripts.
Base = declarative_base() Base = declarative_base()
# To be used by tables already handled by Django ORM, such as User model. We # To be used by tables already handled by Django ORM, such as User model. We
# separate them in order to keep those out of Alembic sight. # separate them in order to keep those out of Alembic sight.
DjangoBase = declarative_base() DjangoBase = declarative_base()
class ValidatorMixin(object):
def enforce_length(self, key, value):
"""Truncate a string according to its column length
Usage example:
.. code-block:: python
@validates('some_column')
def validate_some_column(self, key, value):
self.enforce_length(key, value)
"""
max_len = getattr(self.__class__, key).prop.columns[0].type.length
if value and len(value) > max_len:
return value[:max_len]
return value
...@@ -9,7 +9,7 @@ from datetime import datetime ...@@ -9,7 +9,7 @@ from datetime import datetime
from .base import Base, Column, ForeignKey, relationship, TypeDecorator, Index, \ from .base import Base, Column, ForeignKey, relationship, TypeDecorator, Index, \
Integer, Float, String, DateTime, JSONB, \ Integer, Float, String, DateTime, JSONB, \
MutableList, MutableDict MutableList, MutableDict, validates, ValidatorMixin
from .users import User from .users import User
__all__ = ['Node', 'NodeNode', 'CorpusNode'] __all__ = ['Node', 'NodeNode', 'CorpusNode']
...@@ -26,7 +26,7 @@ class NodeType(TypeDecorator): ...@@ -26,7 +26,7 @@ class NodeType(TypeDecorator):
return NODETYPES[typeindex] return NODETYPES[typeindex]
class Node(Base): class Node(ValidatorMixin, Base):
"""This model can fit many purposes: """This model can fit many purposes:
myFirstCorpus = session.query(CorpusNode).first() myFirstCorpus = session.query(CorpusNode).first()
...@@ -112,6 +112,10 @@ class Node(Base): ...@@ -112,6 +112,10 @@ class Node(Base):
'user_id={0.user_id}, parent_id={0.parent_id}, ' \ 'user_id={0.user_id}, parent_id={0.parent_id}, ' \
'name={0.name!r}, date={0.date})>'.format(self) 'name={0.name!r}, date={0.date})>'.format(self)
@validates('name')
def validate_name(self, key, value):
return self.enforce_length(key, value)
@property @property
def ngrams(self): def ngrams(self):
"""Pseudo-attribute allowing to retrieve a node's ngrams. """Pseudo-attribute allowing to retrieve a node's ngrams.
......
...@@ -113,7 +113,7 @@ class HalCrawler(Crawler): ...@@ -113,7 +113,7 @@ class HalCrawler(Crawler):
msg = "Invalid sample size N = %i (max = %i)" % ( self.query_max msg = "Invalid sample size N = %i (max = %i)" % ( self.query_max
, QUERY_SIZE_N_MAX , QUERY_SIZE_N_MAX
) )
print("ERROR (scrap: Multivac d/l ): " , msg) print("ERROR (scrap: HAL d/l ): " , msg)
self.query_max = QUERY_SIZE_N_MAX self.query_max = QUERY_SIZE_N_MAX
#for page in range(1, trunc(self.query_max / 100) + 2): #for page in range(1, trunc(self.query_max / 100) + 2):
......
...@@ -73,7 +73,8 @@ from rest_framework.views import APIView ...@@ -73,7 +73,8 @@ from rest_framework.views import APIView
from gargantext.util.json import json_encoder from gargantext.util.json import json_encoder
def JsonHttpResponse(data, status=200): def JsonHttpResponse(data, status=200):
return HttpResponse( return HttpResponse(
content = json_encoder.encode(data), content = data.encode('utf-8') if isinstance(data, str) else \
json_encoder.encode(data),
content_type = 'application/json; charset=utf-8', content_type = 'application/json; charset=utf-8',
status = status status = status
) )
......
...@@ -11,17 +11,8 @@ from datetime import datetime ...@@ -11,17 +11,8 @@ from datetime import datetime
import json import json
class HalParser(Parser): class HalParser(Parser):
def _parse(self, json_docs):
def parse(self, filebuf):
'''
parse :: FileBuff -> [Hyperdata]
'''
contents = filebuf.read().decode("UTF-8")
data = json.loads(contents)
filebuf.close()
json_docs = data
hyperdata_list = [] hyperdata_list = []
hyperdata_path = { "id" : "isbn_s" hyperdata_path = { "id" : "isbn_s"
...@@ -73,3 +64,13 @@ class HalParser(Parser): ...@@ -73,3 +64,13 @@ class HalParser(Parser):
hyperdata_list.append(hyperdata) hyperdata_list.append(hyperdata)
return hyperdata_list return hyperdata_list
def parse(self, filebuf):
'''
parse :: FileBuff -> [Hyperdata]
'''
contents = filebuf.read().decode("UTF-8")
data = json.loads(contents)
return self._parse(data)
...@@ -16,7 +16,7 @@ sudo docker run \ ...@@ -16,7 +16,7 @@ sudo docker run \
--env POSTGRES_HOST=localhost \ --env POSTGRES_HOST=localhost \
-v /srv/gargantext:/srv/gargantext \ -v /srv/gargantext:/srv/gargantext \
-it garg-notebook:latest \ -it garg-notebook:latest \
/bin/bash -c "/bin/su notebooks -c 'source /env_3-5/bin/activate && cd /srv/gargantext/ && jupyter notebook --port=8899 --ip=0.0.0.0 --no-browser'" /bin/bash -c "/bin/su notebooks -c 'source /env_3-5/bin/activate && cd /home/notebooks && jupyter notebook --port=8899 --ip=0.0.0.0 --no-browser'"
# #&& jupyter nbextension enable --py widgetsnbextension --sys-prefix # #&& jupyter nbextension enable --py widgetsnbextension --sys-prefix
#/bin/bash -c "/bin/su notebooks -c 'source /env_3-5/bin/activate && cd /srv/gargantext/ && jupyter notebook --port=8899 --ip=0.0.0.0 --no-browser --notebook-dir=/home/notebooks/'" #/bin/bash -c "/bin/su notebooks -c 'source /env_3-5/bin/activate && cd /srv/gargantext/ && jupyter notebook --port=8899 --ip=0.0.0.0 --no-browser --notebook-dir=/home/notebooks/'"
......
...@@ -78,32 +78,8 @@ RUN . /env_3-5/bin/activate && pip3 install -r requirements.txt ...@@ -78,32 +78,8 @@ RUN . /env_3-5/bin/activate && pip3 install -r requirements.txt
#RUN ./psql_configure.sh #RUN ./psql_configure.sh
#RUN ./django_configure.sh #RUN ./django_configure.sh
RUN chown notebooks:notebooks -R /env_3-5 RUN chown notebooks:notebooks -R /env_3-5
########################################################################
### Notebook IHaskell and IPYTHON ENVIRONNEMENT
########################################################################
#RUN apt-get update && apt-get install -y \
# libtinfo-dev \
# libzmq3-dev \
# libcairo2-dev \
# libpango1.0-dev \
# libmagic-dev \
# libblas-dev \
# liblapack-dev
#RUN curl -sSL https://get.haskellstack.org/ | sh
#RUN stack setup
#RUN git clone https://github.com/gibiansky/IHaskell
#RUN . /env_3-5/bin/activate \
# && cd IHaskell \
# && stack install gtk2hs-buildtools \
# && stack install --fast \
# && /root/.local/bin/ihaskell install --stack
#
#
######################################################################## ########################################################################
### POSTGRESQL DATA (as ROOT) ### POSTGRESQL DATA (as ROOT)
######################################################################## ########################################################################
...@@ -115,3 +91,32 @@ RUN chown notebooks:notebooks -R /env_3-5 ...@@ -115,3 +91,32 @@ RUN chown notebooks:notebooks -R /env_3-5
EXPOSE 5432 8899 EXPOSE 5432 8899
VOLUME ["/srv/","/home/notebooks/"] VOLUME ["/srv/","/home/notebooks/"]
########################################################################
### Notebook IHaskell and IPYTHON ENVIRONNEMENT
########################################################################
RUN apt-get update && apt-get install -y \
libtinfo-dev \
libzmq3-dev \
libcairo2-dev \
libpango1.0-dev \
libmagic-dev \
libblas-dev \
liblapack-dev
USER notebooks
RUN cd /home/notebooks \
&& curl -sSL https://get.haskellstack.org/ | sh \
&& stack setup \
&& git clone https://github.com/gibiansky/IHaskell \
&& . /env_3-5/bin/activate \
&& cd IHaskell \
&& stack install gtk2hs-buildtools \
&& stack install --fast \
&& /root/.local/bin/ihaskell install --stack
#!/usr/bin/env python
""" """
Gargantext Software Copyright (c) 2016-2017 CNRS ISC-PIF - Gargantext Software Copyright (c) 2016-2017 CNRS ISC-PIF -
http://iscpif.fr http://iscpif.fr
...@@ -6,45 +7,29 @@ http://gitlab.iscpif.fr/humanities/gargantext/blob/stable/LICENSE ) ...@@ -6,45 +7,29 @@ http://gitlab.iscpif.fr/humanities/gargantext/blob/stable/LICENSE )
- In France : a CECILL variant affero compliant - In France : a CECILL variant affero compliant
- GNU aGPLV3 for all other countries - GNU aGPLV3 for all other countries
""" """
#!/usr/bin/env python
import sys
import os
import os
import django
# Django settings os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gargantext.settings')
dirname = os.path.dirname(os.path.realpath(__file__)) django.setup()
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gargantext.settings")
# initialize Django application
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from gargantext.util.toolchain.main import parse_extract_indexhyperdata from gargantext.constants import QUERY_SIZE_N_MAX, get_resource, get_resource_by_name
from gargantext.util.db import * from gargantext.models import ProjectNode, DocumentNode, UserNode, User
from gargantext.models import Node from gargantext.util.db import session, get_engine
from collections import Counter
import importlib
from django.http import Http404
from nltk.tokenize import wordpunct_tokenize
from gargantext.models import * class NotebookError(Exception):
from nltk.tokenize import word_tokenize pass
import nltk as nltk
from statistics import mean
from math import log
from collections import defaultdict
import matplotlib.pyplot as plt
import numpy as np
import datetime
from collections import Counter
from langdetect import detect as detect_lang
def documents(corpus_id): def documents(corpus_id):
return (session.query(Node).filter( Node.parent_id==corpus_id return (session.query(DocumentNode).filter_by(parent_id=corpus_id)
, Node.typename=="DOCUMENT" #.order_by(Node.hyperdata['publication_date'])
) .all())
# .order_by(Node.hyperdata['publication_date'])
.all()
)
#import seaborn as sns #import seaborn as sns
...@@ -63,6 +48,7 @@ def scan_hal(request): ...@@ -63,6 +48,7 @@ def scan_hal(request):
hal = HalCrawler() hal = HalCrawler()
return hal.scan_results(request) return hal.scan_results(request)
def scan_gargantext(corpus_id, lang, request): def scan_gargantext(corpus_id, lang, request):
connection = get_engine().connect() connection = get_engine().connect()
# TODO add some sugar the request (ideally request should be the same for hal and garg) # TODO add some sugar the request (ideally request should be the same for hal and garg)
...@@ -73,3 +59,123 @@ def scan_gargantext(corpus_id, lang, request): ...@@ -73,3 +59,123 @@ def scan_gargantext(corpus_id, lang, request):
return [i for i in connection.execute(query)][0][0] return [i for i in connection.execute(query)][0][0]
connection.close() connection.close()
def myProject_fromUrl(url):
"""
myProject :: String -> Project
"""
project_id = url.split("/")[4]
project = session.query(ProjectNode).get(project_id)
return project
def newCorpus(project, source, name=None, query=None):
error = False
if name is None:
name = query
if not isinstance(project, ProjectNode):
error = "a valid project"
if not isinstance(source, int) and not isinstance(source, str):
error = "a valid source identifier: id or name"
elif not isinstance(query, str):
error = "a valid query"
elif not isinstance(name, str):
error = "a valid name"
if error:
raise NotebookError("Please provide %s." % error)
resource = get_resource(source) if isinstance(source, int) else \
get_resource_by_name(source)
moissonneur_name = get_moissonneur_name(resource) if resource else \
source.lower()
try:
moissonneur = get_moissonneur(moissonneur_name)
except ImportError:
raise NotebookError("Invalid source identifier: %r" % source)
return run_moissonneur(moissonneur, project, name, query)
def get_moissonneur_name(ident):
""" Return moissonneur module name from RESOURCETYPE or crawler name """
# Does it quacks like a RESOURCETYPE ?
if hasattr(ident, 'get'):
ident = ident.get('crawler')
# Extract name from crawler class name, otherwise assume ident is already
# a moissonneur name.
if isinstance(ident, str) and ident.endswith('Crawler'):
return ident[:-len('Crawler')].lower()
def get_moissonneur(name):
""" Return moissonneur module from its name """
if not isinstance(name, str) or not name.islower():
raise NotebookError("Invalid moissonneur name: %r" % name)
module = importlib.import_module('moissonneurs.%s' % name)
module.name = name
return module
def run_moissonneur(moissonneur, project, name, query):
""" Run moissonneur and return resulting corpus """
# XXX Uber-kludge with gory details. Spaghetti rulezzzzz!
class Dummy(object):
pass
request = Dummy()
request.method = 'POST'
request.path = 'nowhere'
request.META = {}
# XXX 'string' only have effect on moissonneurs.pubmed; its value is added
# when processing request client-side, take a deep breath and see
# templates/projects/project.html for more details.
request.POST = {'string': name,
'query': query,
'N': QUERY_SIZE_N_MAX}
request.user = Dummy()
request.user.id = project.user_id
request.user.is_authenticated = lambda: True
if moissonneur.name == 'istex':
# Replace ALL spaces by plus signs
request.POST['query'] = '+'.join(filter(None, query.split(' ')))
try:
import json
r = moissonneur.query(request)
raw_json = r.content.decode('utf-8')
data = json.loads(raw_json)
if moissonneur.name == 'pubmed':
count = sum(x['count'] for x in data)
request.POST['query'] = raw_json
elif moissonneur.name == 'istex':
count = data.get('total', 0)
else:
count = data.get('results_nb', 0)
if count > 0:
corpus = moissonneur.save(request, project.id, return_corpus=True)
else:
return None
except (ValueError, Http404) as e:
raise e
# Sometimes strange things happens...
if corpus.name != name:
corpus.name = name
session.commit()
return corpus
...@@ -30,7 +30,7 @@ def query( request): ...@@ -30,7 +30,7 @@ def query( request):
#ids = crawlerbot.get_ids(query) #ids = crawlerbot.get_ids(query)
return JsonHttpResponse({"results_nb":crawlerbot.results_nb}) return JsonHttpResponse({"results_nb":crawlerbot.results_nb})
def save(request, project_id): def save(request, project_id, return_corpus=False):
'''save''' '''save'''
if request.method == "POST": if request.method == "POST":
...@@ -101,6 +101,9 @@ def save(request, project_id): ...@@ -101,6 +101,9 @@ def save(request, project_id):
session.rollback() session.rollback()
# -------------------------------------------- # --------------------------------------------
if return_corpus:
return corpus
return render( return render(
template_name = 'pages/projects/wait.html', template_name = 'pages/projects/wait.html',
request = request, request = request,
......
...@@ -33,7 +33,7 @@ def query( request): ...@@ -33,7 +33,7 @@ def query( request):
print(results) print(results)
return JsonHttpResponse({"results_nb":crawlerbot.results_nb}) return JsonHttpResponse({"results_nb":crawlerbot.results_nb})
def save(request, project_id): def save(request, project_id, return_corpus=False):
'''save''' '''save'''
if request.method == "POST": if request.method == "POST":
...@@ -103,6 +103,9 @@ def save(request, project_id): ...@@ -103,6 +103,9 @@ def save(request, project_id):
session.rollback() session.rollback()
# -------------------------------------------- # --------------------------------------------
if return_corpus:
return corpus
return render( return render(
template_name = 'pages/projects/wait.html', template_name = 'pages/projects/wait.html',
request = request, request = request,
......
...@@ -29,7 +29,7 @@ def query( request): ...@@ -29,7 +29,7 @@ def query( request):
#ids = crawlerbot.get_ids(query) #ids = crawlerbot.get_ids(query)
return JsonHttpResponse({"results_nb":crawlerbot.results_nb}) return JsonHttpResponse({"results_nb":crawlerbot.results_nb})
def save(request, project_id): def save(request, project_id, return_corpus=False):
'''save''' '''save'''
if request.method == "POST": if request.method == "POST":
...@@ -100,6 +100,9 @@ def save(request, project_id): ...@@ -100,6 +100,9 @@ def save(request, project_id):
session.rollback() session.rollback()
# -------------------------------------------- # --------------------------------------------
if return_corpus:
return corpus
return render( return render(
template_name = 'pages/projects/wait.html', template_name = 'pages/projects/wait.html',
request = request, request = request,
......
...@@ -52,7 +52,7 @@ def query( request ): ...@@ -52,7 +52,7 @@ def query( request ):
def save(request , project_id): def save(request , project_id, return_corpus=False):
print("testISTEX:") print("testISTEX:")
print(request.method) print(request.method)
alist = ["bar","foo"] alist = ["bar","foo"]
...@@ -171,6 +171,9 @@ def save(request , project_id): ...@@ -171,6 +171,9 @@ def save(request , project_id):
session.rollback() session.rollback()
# -------------------------------------------- # --------------------------------------------
if return_corpus:
return corpus
return render( return render(
template_name = 'pages/projects/wait.html', template_name = 'pages/projects/wait.html',
request = request, request = request,
......
...@@ -33,7 +33,7 @@ def query( request): ...@@ -33,7 +33,7 @@ def query( request):
print(results) print(results)
return JsonHttpResponse({"results_nb":crawlerbot.results_nb}) return JsonHttpResponse({"results_nb":crawlerbot.results_nb})
def save(request, project_id): def save(request, project_id, return_corpus=False):
'''save''' '''save'''
if request.method == "POST": if request.method == "POST":
...@@ -104,6 +104,9 @@ def save(request, project_id): ...@@ -104,6 +104,9 @@ def save(request, project_id):
session.rollback() session.rollback()
# -------------------------------------------- # --------------------------------------------
if return_corpus:
return corpus
return render( return render(
template_name = 'pages/projects/wait.html', template_name = 'pages/projects/wait.html',
request = request, request = request,
......
...@@ -69,7 +69,7 @@ def query( request ): ...@@ -69,7 +69,7 @@ def query( request ):
return JsonHttpResponse(data) return JsonHttpResponse(data)
def save( request , project_id ) : def save( request , project_id, return_corpus=False ) :
# implicit global session # implicit global session
# do we have a valid project id? # do we have a valid project id?
try: try:
...@@ -164,6 +164,10 @@ def save( request , project_id ) : ...@@ -164,6 +164,10 @@ def save( request , project_id ) :
session.rollback() session.rollback()
# -------------------------------------------- # --------------------------------------------
sleep(1) sleep(1)
if return_corpus:
return corpus
return HttpResponseRedirect('/projects/' + str(project_id)) return HttpResponseRedirect('/projects/' + str(project_id))
data = alist data = alist
......
...@@ -2,11 +2,38 @@ ...@@ -2,11 +2,38 @@
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {
"deletable": true,
"editable": true
},
"source": [ "source": [
"# Advanced Gargantext Tutorial (Python)" "# Advanced Gargantext Tutorial (Python)"
] ]
}, },
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"ename": "TypeError",
"evalue": "'list' object is not callable",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-3-a8e3501c9a54>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'/srv/gargantext'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: 'list' object is not callable"
]
}
],
"source": [
"import sys\n",
"sys.pa"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 1,
...@@ -28,7 +55,9 @@ ...@@ -28,7 +55,9 @@
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 8,
"metadata": { "metadata": {
"collapsed": false "collapsed": false,
"deletable": true,
"editable": true
}, },
"outputs": [ "outputs": [
{ {
......
This diff is collapsed.
This diff is collapsed.
...@@ -532,7 +532,7 @@ ...@@ -532,7 +532,7 @@
$("#submit_thing").html("Process a {{ query_size }} sample!") $("#submit_thing").html("Process a {{ query_size }} sample!")
thequeries = data thequeries = data
var N=0,k=0; var N=0;
for(var i in thequeries) N += thequeries[i].count for(var i in thequeries) N += thequeries[i].count
if( N>0) { if( N>0) {
...@@ -571,12 +571,11 @@ ...@@ -571,12 +571,11 @@
$("#submit_thing").html("Process a {{ query_size }} sample!") $("#submit_thing").html("Process a {{ query_size }} sample!")
thequeries = data thequeries = data
var N=data.length,k=0; var N = data.total;
// for(var i in thequeries) N += thequeries[i].count
if( N>1) { if (N > 0) {
var total = JSON.parse(data).total console.log("N: "+N)
console.log("N: "+total) $("#theresults").html("<i> <b>"+pubmedquery+"</b>: "+N+" publications.</i><br>")
$("#theresults").html("<i> <b>"+pubmedquery+"</b>: "+total+" publications.</i><br>")
$('#submit_thing').prop('disabled', false); $('#submit_thing').prop('disabled', false);
} else { } else {
$("#theresults").html("<i> <b>"+data[0]+"</b></i><br>") $("#theresults").html("<i> <b>"+data[0]+"</b></i><br>")
......
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