Commit 364475de authored by Kenzo-Hugo Hillion's avatar Kenzo-Hugo Hillion
Browse files

Merge branch '45-permissions-auth' into 'dev'

Use permissions and auth

Closes #45

See merge request !24
parents 4643e950 3ef29baf
Pipeline #19422 passed with stages
in 2 minutes and 52 seconds
[flake8] [flake8]
exclude = exclude =
*/migrations/* */migrations/*
*/src/bioapi/*
...@@ -56,9 +56,9 @@ metagenedb = {editable = true,path = "."} ...@@ -56,9 +56,9 @@ metagenedb = {editable = true,path = "."}
drf-yasg = "*" drf-yasg = "*"
packaging = "*" packaging = "*"
python-slugify = "*" python-slugify = "*"
master = {git = "https://github.com/khillion/bioapi.git"}
marshmallow = "*" marshmallow = "*"
django-pandas = "*" django-pandas = "*"
bioapi = {git = "https://github.com/khillion/bioapi.git"}
[requires] [requires]
python_version = "3.7" python_version = "3.7"
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "4be3394e3c4abe5fc7b75328ef912eaba09e15365322b7493e256f2def2ff013" "sha256": "c448dca73004a15b2c0d65f4ff1e2b2da1aac4d1439a0be2adf97531783af899"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
...@@ -16,13 +16,24 @@ ...@@ -16,13 +16,24 @@
] ]
}, },
"default": { "default": {
"asgiref": {
"hashes": [
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
"sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"
],
"version": "==3.2.3"
},
"bioapi": {
"git": "https://github.com/khillion/bioapi.git",
"ref": "8aa8a5e446887a91ada4c6cc63ea06ef2e52123d"
},
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
], ],
"index": "pypi", "index": "pypi",
"version": "==2019.9.11" "version": "==2019.11.28"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
...@@ -48,11 +59,11 @@ ...@@ -48,11 +59,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:16040e1288c6c9f68c6da2fe75ebde83c0a158f6f5d54f4c5177b0c1478c5b86", "sha256:6f857bd4e574442ba35a7172f1397b303167dae964cf18e53db5e85fe248d000",
"sha256:89c2007ca4fa5b351a51a279eccff298520783b713bf28efb89dfb81c80ea49b" "sha256:d98c9b6e5eed147bc51f47c014ff6826bd1ab50b166956776ee13db5a58804ae"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.2.7" "version": "==3.0"
}, },
"django-cors-headers": { "django-cors-headers": {
"hashes": [ "hashes": [
...@@ -180,15 +191,11 @@ ...@@ -180,15 +191,11 @@
}, },
"marshmallow": { "marshmallow": {
"hashes": [ "hashes": [
"sha256:1a358beb89c2b4d5555272065a9533591a3eb02f1b854f3c4002d88d8f2a1ddb", "sha256:0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e",
"sha256:eb97c42c5928b5720812c9268865fe863d4807bc1a8b48ddd7d5c9e1779a6af0" "sha256:3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.2.2" "version": "==3.3.0"
},
"master": {
"git": "https://github.com/khillion/bioapi.git",
"ref": "c01af30f9b60428c0d8d2aeae04e5adf1fadba36"
}, },
"metagenedb": { "metagenedb": {
"editable": true, "editable": true,
...@@ -514,10 +521,10 @@ ...@@ -514,10 +521,10 @@
}, },
"faker": { "faker": {
"hashes": [ "hashes": [
"sha256:48c03580720e0b46538d528b1296e4e5b24a809dcaf33a7dddec719489a9edb8", "sha256:202ad3b2ec16ae7c51c02904fb838831f8d2899e61bf18db1e91a5a582feab11",
"sha256:6327c665c0d8721280b3036d9c9e851c60092bc1f30c8394cc433f8723e2bda5" "sha256:92c84a10bec81217d9cb554ee12b3838c8986ce0b5d45f72f769da22e4bb5432"
], ],
"version": "==2.0.4" "version": "==3.0.0"
}, },
"flake8": { "flake8": {
"hashes": [ "hashes": [
...@@ -529,11 +536,11 @@ ...@@ -529,11 +536,11 @@
}, },
"importlib-metadata": { "importlib-metadata": {
"hashes": [ "hashes": [
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "sha256:3a8b2dfd0a2c6a3636e7c016a7e54ae04b997d30e69d5eacdca7a6c2221a1402",
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" "sha256:41e688146d000891f32b1669e8573c57e39e5060e7f5f647aa617cd9a9568278"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.23" "version": "==1.2.0"
}, },
"inflection": { "inflection": {
"hashes": [ "hashes": [
...@@ -550,11 +557,11 @@ ...@@ -550,11 +557,11 @@
}, },
"ipython": { "ipython": {
"hashes": [ "hashes": [
"sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280", "sha256:c66c7e27239855828a764b1e8fc72c24a6f4498a2637572094a78c5551fb9d51",
"sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995" "sha256:f186b01b36609e0c5d0de27c7ef8e80c990c70478f8c880863004b3489a9030e"
], ],
"markers": "python_version >= '3.3'", "markers": "python_version >= '3.3'",
"version": "==7.9.0" "version": "==7.10.1"
}, },
"ipython-genutils": { "ipython-genutils": {
"hashes": [ "hashes": [
...@@ -755,11 +762,11 @@ ...@@ -755,11 +762,11 @@
}, },
"more-itertools": { "more-itertools": {
"hashes": [ "hashes": [
"sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d",
"sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"
], ],
"index": "pypi", "index": "pypi",
"version": "==7.2.0" "version": "==8.0.2"
}, },
"nbconvert": { "nbconvert": {
"hashes": [ "hashes": [
...@@ -874,10 +881,10 @@ ...@@ -874,10 +881,10 @@
}, },
"pygments": { "pygments": {
"hashes": [ "hashes": [
"sha256:83ec6c6133ca6b529b7ff5aa826328fd14b5bb02a58c37f4f06384e96a0f94ab", "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
"sha256:b7949de3d396836085fea596998b135a22610bbcc4f2abfe9e448e44cbc58388" "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
], ],
"version": "==2.5.1" "version": "==2.5.2"
}, },
"pylint": { "pylint": {
"hashes": [ "hashes": [
......
...@@ -3,11 +3,13 @@ from rest_framework import status ...@@ -3,11 +3,13 @@ from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from metagenedb.common.django_default.permissions import ListAndRetrieveAll
from metagenedb.common.django_default.qparams_validators import PaginatedQueryParams from metagenedb.common.django_default.qparams_validators import PaginatedQueryParams
class BulkViewSet(ModelViewSet): class BulkViewSet(ModelViewSet):
query_params_parser = PaginatedQueryParams query_params_parser = PaginatedQueryParams
permission_classes = [ListAndRetrieveAll]
def get_objects(self, instance_ids): def get_objects(self, instance_ids):
return self.queryset.in_bulk(instance_ids, field_name=self.lookup_field) return self.queryset.in_bulk(instance_ids, field_name=self.lookup_field)
......
...@@ -5,6 +5,7 @@ from drf_yasg import openapi ...@@ -5,6 +5,7 @@ from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from marshmallow.exceptions import ValidationError from marshmallow.exceptions import ValidationError
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_422_UNPROCESSABLE_ENTITY from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_422_UNPROCESSABLE_ENTITY
...@@ -60,6 +61,8 @@ class GeneViewSet(BulkViewSet): ...@@ -60,6 +61,8 @@ class GeneViewSet(BulkViewSet):
DEFAULT_LEVEL = 'phylum' DEFAULT_LEVEL = 'phylum'
def get_permissions(self): def get_permissions(self):
if self.action in ['gene_length', 'taxonomy_counts']:
return [AllowAny()]
return super(self.__class__, self).get_permissions() return super(self.__class__, self).get_permissions()
def _count_windows(self, queryset, window_size=DEFAULT_WINDOW_SIZE, window_col=GENE_LENGTH_COL, def _count_windows(self, queryset, window_size=DEFAULT_WINDOW_SIZE, window_col=GENE_LENGTH_COL,
......
...@@ -3,9 +3,35 @@ from rest_framework.test import APITestCase ...@@ -3,9 +3,35 @@ from rest_framework.test import APITestCase
from metagenedb.apps.catalog.factory import FunctionFactory from metagenedb.apps.catalog.factory import FunctionFactory
from metagenedb.common.utils.mocks.metagenedb import MetageneDBCatalogFunctionAPIMock from metagenedb.common.utils.mocks.metagenedb import MetageneDBCatalogFunctionAPIMock
from metagenedb.common.utils.tests.apitestbase import AdminUserBasedTest
class TestOperationsBulkViewSet(APITestCase): class TestOperationsBulkViewSetNoCredentials(APITestCase):
def setUp(self):
self.function_api = MetageneDBCatalogFunctionAPIMock(self.client)
def test_create_function(self):
data = {
"function_id": 'k_test1',
"source": "kegg",
"name": "Kegg Test 1"
}
with self.assertRaises(HTTPError):
self.function_api.post(data)
def test_update_existing_function(self):
function = FunctionFactory()
data = {
"function_id": function.function_id,
"source": function.source,
"name": "Kegg Test 1"
}
with self.assertRaises(HTTPError):
self.function_api.put(data, function.function_id)
class TestOperationsBulkViewSet(AdminUserBasedTest):
""" """
We are testing the different functions through the API directly through the mock redirecting We are testing the different functions through the API directly through the mock redirecting
requests to the test database. requests to the test database.
...@@ -14,7 +40,8 @@ class TestOperationsBulkViewSet(APITestCase): ...@@ -14,7 +40,8 @@ class TestOperationsBulkViewSet(APITestCase):
""" """
def setUp(self): def setUp(self):
self.function_api = MetageneDBCatalogFunctionAPIMock(self.client) super().setUp()
self.function_api = MetageneDBCatalogFunctionAPIMock(self.client, jwt_token=self.jwt_token)
def test_create_function(self): def test_create_function(self):
data = { data = {
......
from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from rest_framework_jwt.settings import api_settings
from metagenedb.apps.catalog.factory import GeneFactory, TaxonomyFactory from metagenedb.apps.catalog.factory import GeneFactory, TaxonomyFactory
from metagenedb.common.utils.mocks.metagenedb import MetageneDBCatalogGeneAPIMock from metagenedb.common.utils.mocks.metagenedb import MetageneDBCatalogGeneAPIMock
...@@ -20,24 +18,6 @@ class TestGenes(TestCase): ...@@ -20,24 +18,6 @@ class TestGenes(TestCase):
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp.status_code, status.HTTP_200_OK)
def test_get_genes_auth(self):
"""
Authenticated users should be able to access genes
"""
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
user = User.objects.create_user(username='user', email='user@foo.com', password='pass')
user.is_active = True
user.save()
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
url = reverse('api:catalog:v1:genes-list')
resp = self.client.get(url, format='json', HTTP_AUTHORIZATION=f"JWT {token}")
self.assertEqual(resp.status_code, status.HTTP_200_OK)
class TestCountWindowsAPI(APITestCase): class TestCountWindowsAPI(APITestCase):
......
from rest_framework.permissions import BasePermission
class ListAndRetrieveAll(BasePermission):
"""
Custom permission to only allow access to lists for admins
"""
def has_permission(self, request, view):
return view.action in ['list', 'retrieve'] or request.user.is_staff
...@@ -14,33 +14,43 @@ class MetageneDBAPIMock(MetageneDBCatalogGeneAPI): ...@@ -14,33 +14,43 @@ class MetageneDBAPIMock(MetageneDBCatalogGeneAPI):
REVERSE_PATH = '' REVERSE_PATH = ''
BAD_REQUESTS = range(400, 452) BAD_REQUESTS = range(400, 452)
def __init__(self, client): def __init__(self, client, jwt_token=None):
self.client = client self.client = client
self.reverse_path = ':'.join([self.BASE_REVERSE, self.REVERSE_PATH]) self.reverse_path = ':'.join([self.BASE_REVERSE, self.REVERSE_PATH])
self.headers = {}
if jwt_token is not None:
self.headers.update({
'HTTP_AUTHORIZATION': f"JWT {jwt_token}",
})
def get_all(self, params=None): def get_all(self, params=None):
response = self.client.get(reverse(f'{self.reverse_path}-list'), params) response = self.client.get(reverse(f'{self.reverse_path}-list'), params, **self.headers)
if response.status_code in self.BAD_REQUESTS: if response.status_code in self.BAD_REQUESTS:
raise HTTPError raise HTTPError
return response.json() return response.json()
def get(self, entry_id, params=None): def get(self, entry_id, params=None):
response = self.client.get(reverse(f'{self.reverse_path}-detail', kwargs={self.KEY_ID: entry_id}), params) response = self.client.get(reverse(f'{self.reverse_path}-detail', kwargs={self.KEY_ID: entry_id}),
params, **self.headers)
if response.status_code in self.BAD_REQUESTS: if response.status_code in self.BAD_REQUESTS:
raise HTTPError raise HTTPError
return response.json() return response.json()
def post(self, data): def post(self, data):
response = self.client.post(reverse(f'{self.reverse_path}-list'), data, format='json') response = self.client.post(reverse(f'{self.reverse_path}-list'), data, format='json', **self.headers)
if response.status_code in self.BAD_REQUESTS: if response.status_code in self.BAD_REQUESTS:
raise HTTPError raise HTTPError
return response.json() return response.json()
def put(self, data, entry_id=None): def put(self, data, entry_id=None):
if entry_id: if entry_id:
return self.client.put(reverse(f'{self.reverse_path}-detail', kwargs={self.KEY_ID: entry_id}), response = self.client.put(reverse(f'{self.reverse_path}-detail', kwargs={self.KEY_ID: entry_id}),
data, format='json').json() data, format='json', **self.headers)
return self.client.put(reverse(f'{self.reverse_path}-list'), data, format='json').json() else:
response = self.client.put(reverse(f'{self.reverse_path}-list'), data, format='json', **self.headers)
if response.status_code in self.BAD_REQUESTS:
raise HTTPError
return response.json()
class MetageneDBCatalogGeneAPIMock(MetageneDBAPIMock): class MetageneDBCatalogGeneAPIMock(MetageneDBAPIMock):
......
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
from rest_framework_jwt.settings import api_settings
class AdminUserBasedTest(APITestCase):
"""
We are testing the different functions through the API directly through the mock redirecting
requests to the test database.
The extent is a bit more than a unittest since it is not just involving concerned methods.
"""
def setUp(self):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
user = User.objects.create_user(username='user_admin', email='user@admin.com', password='pass')
user.is_active = True
user.is_staff = True
user.save()
payload = jwt_payload_handler(user)
self.jwt_token = jwt_encode_handler(payload)
...@@ -116,7 +116,7 @@ REST_FRAMEWORK = { ...@@ -116,7 +116,7 @@ REST_FRAMEWORK = {
# JWT Authorization settings # JWT Authorization settings
JWT_AUTH = { JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3600) 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7)
} }
......
...@@ -24,12 +24,12 @@ class ImportIGCGenes(object): ...@@ -24,12 +24,12 @@ class ImportIGCGenes(object):
GENUS_COL = 'taxo_genus' GENUS_COL = 'taxo_genus'
SELECTED_KEYS = ['gene_id', 'length', 'kegg_ko', PHYLUM_COL, GENUS_COL] SELECTED_KEYS = ['gene_id', 'length', 'kegg_ko', PHYLUM_COL, GENUS_COL]
def __init__(self, annotation_file, url, skip_tax=False, skip_functions=False): def __init__(self, annotation_file, url, jwt_token, skip_tax=False, skip_functions=False):
self.annotation_file = annotation_file self.annotation_file = annotation_file
self.url = url self.url = url
self.metagenedb_gene_api = self.METAGENEDB_GENE_API(base_url=self.url) self.metagenedb_gene_api = self.METAGENEDB_GENE_API(base_url=self.url, jwt_token=jwt_token)
self.metagenedb_taxonomy_api = self.METAGENEDB_TAXONOMY_API(base_url=self.url) self.metagenedb_taxonomy_api = self.METAGENEDB_TAXONOMY_API(base_url=self.url, jwt_token=jwt_token)
self.metagenedb_function_api = self.METAGENEDB_FUNCTION_API(base_url=self.url) self.metagenedb_function_api = self.METAGENEDB_FUNCTION_API(base_url=self.url, jwt_token=jwt_token)
self.total_genes = self._get_number_genes() self.total_genes = self._get_number_genes()
self._reset_counters() self._reset_counters()
# Skip some insertion if specified in script options # Skip some insertion if specified in script options
...@@ -177,6 +177,7 @@ def parse_arguments(): ...@@ -177,6 +177,7 @@ def parse_arguments():
# Common arguments for analysis and annotations # Common arguments for analysis and annotations
parser.add_argument('annotation', help='IGC annotation file') parser.add_argument('annotation', help='IGC annotation file')
parser.add_argument('--url', help='base URL of the instance.', default='http://localhost/') parser.add_argument('--url', help='base URL of the instance.', default='http://localhost/')
parser.add_argument('-t', '--jwt_token', help='your JWT token obtain from web app', required=True)
parser.add_argument('--chunk_size', type=int, default=1000, parser.add_argument('--chunk_size', type=int, default=1000,
help='How many genes to handle and create in the same time.') help='How many genes to handle and create in the same time.')
parser.add_argument('--skip_taxonomy', action='store_true', help='Skip taxonomy information from genes.') parser.add_argument('--skip_taxonomy', action='store_true', help='Skip taxonomy information from genes.')
...@@ -194,7 +195,7 @@ def run(): ...@@ -194,7 +195,7 @@ def run():
args = parse_arguments() args = parse_arguments()
if args.verbose: if args.verbose:
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
import_igc_genes = ImportIGCGenes(args.annotation, args.url, import_igc_genes = ImportIGCGenes(args.annotation, args.url, args.jwt_token,
skip_tax=args.skip_taxonomy, skip_functions=args.skip_functions) skip_tax=args.skip_taxonomy, skip_functions=args.skip_functions)
import_igc_genes.load_annotation_file_to_db_in_chunks(chunk_size=args.chunk_size, test=args.test) import_igc_genes.load_annotation_file_to_db_in_chunks(chunk_size=args.chunk_size, test=args.test)
......
...@@ -18,8 +18,8 @@ class ImportNCBITaxonomy(object): ...@@ -18,8 +18,8 @@ class ImportNCBITaxonomy(object):
METAGENEDB_TAX_API = MetageneDBCatalogTaxonomyAPI METAGENEDB_TAX_API = MetageneDBCatalogTaxonomyAPI
FOREIGN_KEY_FIELDS = ['parent_tax_id'] FOREIGN_KEY_FIELDS = ['parent_tax_id']
def __init__(self, url, tax_names_file, tax_nodes_file): def __init__(self, url, jwt_token, tax_names_file, tax_nodes_file):
self.metagenedb_tax_api = self.METAGENEDB_TAX_API(base_url=url) self.metagenedb_tax_api = self.METAGENEDB_TAX_API(base_url=url, jwt_token=jwt_token)
self.tax_names_file = tax_names_file self.tax_names_file = tax_names_file
self.tax_nodes_file = tax_nodes_file self.tax_nodes_file = tax_nodes_file
self.total_tax = self._get_number_nodes() self.total_tax = self._get_number_nodes()
...@@ -104,6 +104,7 @@ def parse_arguments(): ...@@ -104,6 +104,7 @@ def parse_arguments():
parser.add_argument('--names', help='names.dmp file from ncbi_taxonomy', required=True) parser.add_argument('--names', help='names.dmp file from ncbi_taxonomy', required=True)
parser.add_argument('--skip_creation', action='store_true', help='Skip taxonomy creation.') parser.add_argument('--skip_creation', action='store_true', help='Skip taxonomy creation.')
parser.add_argument('--url', help='base URL of the instance.', default='http://localhost/') parser.add_argument('--url', help='base URL of the instance.', default='http://localhost/')
parser.add_argument('-t', '--jwt_token', help='your JWT token obtain from web app', required=True)
parser.add_argument('-v', '--verbose', action='store_true') parser.add_argument('-v', '--verbose', action='store_true')
try: try:
...@@ -116,7 +117,7 @@ def run(): ...@@ -116,7 +117,7 @@ def run():
args = parse_arguments() args = parse_arguments()
if args.verbose: if args.verbose:
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
import_ncbi_tax = ImportNCBITaxonomy(args.url, args.names, args.nodes) import_ncbi_tax = ImportNCBITaxonomy(args.url, args.jwt_token, args.names, args.nodes)
taxonomy_names = import_ncbi_tax.import_names() taxonomy_names = import_ncbi_tax.import_names()
if not args.skip_creation: if not args.skip_creation:
import_ncbi_tax.create_taxo_nodes(taxonomy_names) import_ncbi_tax.create_taxo_nodes(taxonomy_names)
......
...@@ -21,9 +21,9 @@ class ImportKEGGKO(object): ...@@ -21,9 +21,9 @@ class ImportKEGGKO(object):
ORM_SOURCE_KEY = 'source'