Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc8a19ff83 | ||
|
|
0bc652f4d7 | ||
|
|
c57a52e075 | ||
|
|
3fff0bcf0d | ||
|
|
fa4981693c | ||
|
|
3bbcc454c7 | ||
|
|
66a1054472 |
No files matched your search
@@ -96,7 +96,8 @@ class PublicationResult:
|
|||||||
year: int
|
year: int
|
||||||
pages: Optional[Tuple[int, int]]
|
pages: Optional[Tuple[int, int]]
|
||||||
dblp_doi: Optional[str] = None
|
dblp_doi: Optional[str] = None
|
||||||
authors: List[str] = field(default_factory=list)
|
access: Optional[str] = None
|
||||||
|
authors: List[Tuple[str,str]] = field(default_factory=list)
|
||||||
urls: List[str] = field(default_factory=list)
|
urls: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -166,7 +167,7 @@ class PublicationResult:
|
|||||||
|
|
||||||
url = f"https://dblp.uni-trier.de/rec/{key}.xml"
|
url = f"https://dblp.uni-trier.de/rec/{key}.xml"
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
response.raise_for_status
|
response.raise_for_status()
|
||||||
|
|
||||||
parser = xml.sax.make_parser()
|
parser = xml.sax.make_parser()
|
||||||
handler = DBLPHandler({key})
|
handler = DBLPHandler({key})
|
||||||
@@ -195,6 +196,7 @@ class PublicationResult:
|
|||||||
|
|
||||||
# TODO Parse URLs ('ee')
|
# TODO Parse URLs ('ee')
|
||||||
doi = None if not 'doi' in info else info['doi']
|
doi = None if not 'doi' in info else info['doi']
|
||||||
|
access = None if not 'access' in info else info['access']
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
key=info['key'],
|
key=info['key'],
|
||||||
@@ -202,7 +204,8 @@ class PublicationResult:
|
|||||||
year=int(info['year']),
|
year=int(info['year']),
|
||||||
pages=pages,
|
pages=pages,
|
||||||
dblp_doi=doi,
|
dblp_doi=doi,
|
||||||
authors=[html.unescape(author['text']) for author in authors],
|
access = access,
|
||||||
|
authors=[(author['@pid'], html.unescape(author['text'])) for author in authors],
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -223,10 +226,10 @@ class PublicationResult:
|
|||||||
'format': 'json',
|
'format': 'json',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
response.raise_for_status
|
response.raise_for_status()
|
||||||
search_result = response.json()['result']
|
search_result = response.json()['result']
|
||||||
hits = search_result['hits']
|
hits = search_result['hits']
|
||||||
if hits['@total'] == '0': return (None, None, 0)
|
if hits['@total'] == '0' or hits['@sent'] == '0': return (None, None, 0)
|
||||||
results = [cls.from_search_hit(hit) for hit in hits['hit']]
|
results = [cls.from_search_hit(hit) for hit in hits['hit']]
|
||||||
|
|
||||||
total = hits['@total']
|
total = hits['@total']
|
||||||
@@ -453,6 +456,7 @@ class Command(BaseCommand):
|
|||||||
# Add authors to database
|
# Add authors to database
|
||||||
authors: List[Author] = []
|
authors: List[Author] = []
|
||||||
for name in result.authors:
|
for name in result.authors:
|
||||||
|
# TODO? find author id's -> not in xml!
|
||||||
author, created = Author.objects.get_or_create(name=name)
|
author, created = Author.objects.get_or_create(name=name)
|
||||||
if created:
|
if created:
|
||||||
self.log_success(f"Added author: {author}")
|
self.log_success(f"Added author: {author}")
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write("")
|
self.stdout.write("")
|
||||||
self.log_info(result.cite_key)
|
self.log_info(result.cite_key)
|
||||||
if 0 < len(result.authors):
|
if 0 < len(result.authors):
|
||||||
self.stdout.write(" " + ", ".join([name for name in result.authors]))
|
self.stdout.write(" " + ", ".join([author.name for author in result.authors]))
|
||||||
self.log_info(" " + result.title, nl=False)
|
self.log_info(" " + result.title, nl=False)
|
||||||
self.stdout.write(f" ({result.year})")
|
self.stdout.write(f" ({result.year})")
|
||||||
|
|
||||||
@@ -66,8 +66,16 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# Store Authors
|
# Store Authors
|
||||||
authors: List[Author] = []
|
authors: List[Author] = []
|
||||||
for name in result.authors:
|
for (pid, name) in result.authors:
|
||||||
author, created = Author.objects.get_or_create(name=name)
|
author, created = Author.objects.get_or_create(name=name)
|
||||||
|
if not author.dblp_id and pid != None:
|
||||||
|
try:
|
||||||
|
author.dblp_id = pid
|
||||||
|
author.full_clean()
|
||||||
|
author.save()
|
||||||
|
except ValidationError as e:
|
||||||
|
raise CommandError(f"{author}: {e}")
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
self.log_success(f"Added author: {author}")
|
self.log_success(f"Added author: {author}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ from pprint import pprint
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
import re, requests, html
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError, CommandParser
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
import sok.management.commands.dblpimport as dblp
|
import sok.management.commands.dblpimport as dblp
|
||||||
|
|
||||||
from sok.management.commands.snowball import semanticscholar
|
from sok.management.commands.snowball import semanticscholar
|
||||||
from sok.models import Publication, PublicationReference, SemanticScholar
|
from sok.models import Publication, PublicationReference, SemanticScholar, Author, PublicationAuthor
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@@ -17,6 +22,9 @@ class Command(BaseCommand):
|
|||||||
def log_success(self, msg: str):
|
def log_success(self, msg: str):
|
||||||
self.stdout.write(self.style.SUCCESS(msg))
|
self.stdout.write(self.style.SUCCESS(msg))
|
||||||
|
|
||||||
|
def log_warn(self, msg: str):
|
||||||
|
self.stdout.write(self.style.WARNING(msg))
|
||||||
|
|
||||||
def log_info(self, msg: str, nl: bool = True):
|
def log_info(self, msg: str, nl: bool = True):
|
||||||
self.stdout.write(self.style.HTTP_INFO(msg), ending='\n' if nl else '')
|
self.stdout.write(self.style.HTTP_INFO(msg), ending='\n' if nl else '')
|
||||||
self.stdout.flush()
|
self.stdout.flush()
|
||||||
@@ -71,6 +79,7 @@ class Command(BaseCommand):
|
|||||||
data = semanticscholar(semantic.paper_id)
|
data = semanticscholar(semantic.paper_id)
|
||||||
if abstract := data['abstract']:
|
if abstract := data['abstract']:
|
||||||
publication.abstract = abstract
|
publication.abstract = abstract
|
||||||
|
if publication.peer_reviewed == None: publication.peer_reviewed = False
|
||||||
try:
|
try:
|
||||||
publication.full_clean()
|
publication.full_clean()
|
||||||
publication.save()
|
publication.save()
|
||||||
@@ -83,9 +92,22 @@ class Command(BaseCommand):
|
|||||||
def search_on_dblp(self, publication: Publication):
|
def search_on_dblp(self, publication: Publication):
|
||||||
query, results, total = dblp.PublicationResult.from_search(publication.title, 100)
|
query, results, total = dblp.PublicationResult.from_search(publication.title, 100)
|
||||||
if total == 0: return
|
if total == 0: return
|
||||||
|
plausible = []
|
||||||
for result in results:
|
for result in results:
|
||||||
if publication.doi and result.doi:
|
if publication.doi and result.doi:
|
||||||
if publication.doi.lower() == result.doi.lower():
|
if publication.doi.lower() == result.doi.lower():
|
||||||
|
plausible = [result]
|
||||||
|
break
|
||||||
|
else: continue # quite definitely not the same publication
|
||||||
|
elif publication.title == result.title and publication.year == result.year:
|
||||||
|
plausible.append(result)
|
||||||
|
self.log_warn(f"Not quite certain about {result.cite_key} for publication {publication.title}")
|
||||||
|
else: continue # I'd rather look at that in detail for now
|
||||||
|
if len(plausible) == 1:
|
||||||
|
result = plausible[0]
|
||||||
|
if result.access.lower() == 'withdrawn':
|
||||||
|
self.log_warn(f"Publication {publication.title} was WITHDRAWN!")
|
||||||
|
return
|
||||||
publication.cite_key = result.cite_key
|
publication.cite_key = result.cite_key
|
||||||
publication.year = result.year
|
publication.year = result.year
|
||||||
if not result.is_peer_reviewed == None:
|
if not result.is_peer_reviewed == None:
|
||||||
@@ -93,20 +115,56 @@ class Command(BaseCommand):
|
|||||||
publication.first_page = result.first_page
|
publication.first_page = result.first_page
|
||||||
publication.last_page = result.last_page
|
publication.last_page = result.last_page
|
||||||
print(publication.peer_reviewed, result)
|
print(publication.peer_reviewed, result)
|
||||||
# keep authors from semantic scholar for now, even though they might be a little broken?
|
|
||||||
try:
|
try:
|
||||||
publication.full_clean()
|
publication.full_clean()
|
||||||
publication.save()
|
publication.save()
|
||||||
self.log_success(f"Added DBLP info for: {publication}")
|
self.log_success(f"Added DBLP info for: {publication}")
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise CommandError(f"{publication}: {e}")
|
raise CommandError(f"{publication}: {e}")
|
||||||
else: continue # quite definitely not the same publication
|
|
||||||
elif publication.title == result.title and publication.year == result.year:
|
# Store Authors
|
||||||
print(f"Not quite certain about {result.cite_key} for publication {publication.title}")
|
authors: List[Author] = []
|
||||||
else: continue # I'd rather look at that in detail for now
|
for (pid, name) in result.authors:
|
||||||
|
author, created = Author.objects.get_or_create(name=name)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Added author: {author}")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Author '{author}' already known")
|
||||||
|
if not author.dblp_id and pid != None:
|
||||||
|
try:
|
||||||
|
author.dblp_id = pid
|
||||||
|
author.full_clean()
|
||||||
|
author.save()
|
||||||
|
self.log_success(f"Added pid to author: {author}")
|
||||||
|
except ValidationError as e:
|
||||||
|
raise CommandError(f"{author}: {e}")
|
||||||
|
|
||||||
|
authors.append(author)
|
||||||
|
|
||||||
|
# Replace authors for publication
|
||||||
|
if len(authors) != len(PublicationAuthor.objects.filter(publication=publication)): print(authors, [pa.author for pa in PublicationAuthor.objects.filter(publication=publication)])
|
||||||
|
assert len(authors) >= len(PublicationAuthor.objects.filter(publication=publication))
|
||||||
|
for publication_author in PublicationAuthor.objects.filter(publication=publication):
|
||||||
|
self.log_warn(f"Will now try to delete {publication_author.author} from {publication}")
|
||||||
|
try:
|
||||||
|
publication_author.delete()
|
||||||
|
except e:
|
||||||
|
raise CommandError(f"{publication} - {author}: {e}")
|
||||||
|
|
||||||
|
for position, author in enumerate(authors):
|
||||||
|
publication_author, created = PublicationAuthor.objects.get_or_create(
|
||||||
|
author=author,
|
||||||
|
publication=publication,
|
||||||
|
position=position,
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Assigned author '{author}' to publication '{publication}' at position {position}")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Author '{author}' already assigned to publication '{publication}' at position '{position}'")
|
||||||
|
|
||||||
def find_secondary_on_dblp(self):
|
def find_secondary_on_dblp(self):
|
||||||
self.log_info("--- Searching for secondary and tertiary sources in the default DBLP dump ---")
|
self.log_info("--- Searching for snowballed sources on DBLP ---")
|
||||||
for publication in tqdm(Publication.objects.exclude(cite_key__startswith=dblp.CITE_KEY_PREFIX), unit="publication"):
|
for publication in tqdm(Publication.objects.exclude(cite_key__startswith=dblp.CITE_KEY_PREFIX), unit="publication"):
|
||||||
if publication.stage == 'excluded': continue
|
if publication.stage == 'excluded': continue
|
||||||
self.search_on_dblp(publication)
|
self.search_on_dblp(publication)
|
||||||
@@ -117,7 +175,7 @@ class Command(BaseCommand):
|
|||||||
).values_list('cite_key', flat=True).distinct()
|
).values_list('cite_key', flat=True).distinct()
|
||||||
)
|
)
|
||||||
n = len(keys)
|
n = len(keys)
|
||||||
self.log_info(f"Found {n} publications that still need to be checked")
|
self.log_info(f"Found {n} publications that still need to be verified")
|
||||||
|
|
||||||
def fix_dblp(self):
|
def fix_dblp(self):
|
||||||
self.log_info("--- Searching for entries not in the default DBLP dump ---")
|
self.log_info("--- Searching for entries not in the default DBLP dump ---")
|
||||||
@@ -165,16 +223,156 @@ class Command(BaseCommand):
|
|||||||
obj.full_clean()
|
obj.full_clean()
|
||||||
obj.save()
|
obj.save()
|
||||||
self.log_success(f"Set semanticscholar ID for publication '{publication}': {paper_id}")
|
self.log_success(f"Set semanticscholar ID for publication '{publication}': {paper_id}")
|
||||||
else: print(publication)
|
else:
|
||||||
|
self.log_warn(f"Could not find semanticscholar ID for publication '{publication.title}' with DOI '{publication.doi}'")
|
||||||
|
|
||||||
sleep(2) # Throttle to avoid rate-limiting
|
sleep(1) # Throttle to avoid rate-limiting (1/s with API Key)
|
||||||
|
|
||||||
|
def find_author_on_dblp(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
limit: int = 100,
|
||||||
|
) -> List[Tuple[str, str]]:
|
||||||
|
url = 'http://dblp.org/search/author/api'
|
||||||
|
response = requests.get(
|
||||||
|
url,
|
||||||
|
params={
|
||||||
|
'q': name,
|
||||||
|
'f': 0,
|
||||||
|
'h': limit,
|
||||||
|
'c': 0,
|
||||||
|
'format': 'json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
search_result = response.json()['result']
|
||||||
|
hits = search_result['hits']
|
||||||
|
if hits['@total'] == '0': return None
|
||||||
|
results = [hit['info'] for hit in hits['hit']]
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def verify_author(self, pid, keys):
|
||||||
|
url = f"https://dblp.org/pid/{pid}.xml"
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
pubs = re.findall('key="([^"]*)"', str(response.content))
|
||||||
|
sleep(1) # Throttle to avoid rate-limiting
|
||||||
|
return keys.intersection(pubs) == keys
|
||||||
|
|
||||||
|
def find_pid_for_authors(self):
|
||||||
|
self.log_info("--- Adding ID's to authors ---")
|
||||||
|
authors = Author.objects.filter(dblp_id__exact="")
|
||||||
|
for author in tqdm(authors, unit='author'):
|
||||||
|
pid = ""
|
||||||
|
keys = set({
|
||||||
|
dblp.strip_cite_key_prefix(cite_key)
|
||||||
|
for cite_key in PublicationAuthor.objects.filter(
|
||||||
|
author__exact=author,
|
||||||
|
publication__cite_key__startswith=dblp.CITE_KEY_PREFIX
|
||||||
|
).values_list('publication__cite_key', flat=True).distinct()}
|
||||||
|
)
|
||||||
|
if len(keys) == 0: continue
|
||||||
|
options = self.find_author_on_dblp(author.name)
|
||||||
|
if not options:
|
||||||
|
self.log_warn(f"Could not find ID for author {author}")
|
||||||
|
elif len(options) == 1:
|
||||||
|
pid = re.match('https://dblp.org/pid/(.*)', options[0]['url']).groups()[0]
|
||||||
|
if self.verify_author(pid, keys):
|
||||||
|
self.log_success(f"Found {pid} for author {author}")
|
||||||
|
author.dblp_id = pid
|
||||||
|
author.full_clean()
|
||||||
|
author.save()
|
||||||
|
self.log_success(f"Added pid for author {author}")
|
||||||
|
else:
|
||||||
|
for opt in options:
|
||||||
|
if not 'url' in opt: continue
|
||||||
|
pid = re.match('https://dblp.org/pid/(.*)', opt['url']).groups()[0]
|
||||||
|
if self.verify_author(pid, keys):
|
||||||
|
self.log_success(f"Found {pid} for author {author}")
|
||||||
|
author.dblp_id = pid
|
||||||
|
author.full_clean()
|
||||||
|
author.save()
|
||||||
|
self.log_success(f"Added pid for author {author}")
|
||||||
|
break
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def merge(self, author, variant):
|
||||||
|
for pub_author in PublicationAuthor.objects.filter(author=variant):
|
||||||
|
publication = pub_author.publication
|
||||||
|
if PublicationAuthor.objects.filter(author=author, publication=publication).exists():
|
||||||
|
self.log_warn(f"{author} is already author of {publication}")
|
||||||
|
break
|
||||||
|
fixed = PublicationAuthor(
|
||||||
|
author=author,
|
||||||
|
publication=publication,
|
||||||
|
position=pub_author.position
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
pub_author.delete()
|
||||||
|
fixed.full_clean()
|
||||||
|
fixed.save()
|
||||||
|
self.log_success(f"Changed author: {publication}: {variant} -> {fixed.author}")
|
||||||
|
except ValidationError as e:
|
||||||
|
raise CommandError(f"{publication}: {variant} -> {fixed.author}: {e}")
|
||||||
|
assert not PublicationAuthor.objects.filter(author=variant).exists()
|
||||||
|
variant.delete()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def merge_authors(self) -> None:
|
||||||
|
self.log_info("--- Searching for potential duplicate authors and merging their publications ---")
|
||||||
|
|
||||||
|
dup_ids = Author.objects.exclude(dblp_id__exact="").values_list('dblp_id').annotate(count=Count('id')).filter(count__gt=1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for (pid, counter) in tqdm(dup_ids, unit="duplicate"):
|
||||||
|
|
||||||
|
response = requests.get(f"https://dblp.org/pid/{pid}.xml")
|
||||||
|
response.raise_for_status()
|
||||||
|
dblp_person = re.search('dblpperson name="([^"]*)"', response.content.decode('ascii')).groups()
|
||||||
|
assert len(dblp_person) == 1
|
||||||
|
dblp_name = html.unescape(dblp_person[0])
|
||||||
|
|
||||||
|
variants = Author.objects.filter(dblp_id__exact=pid).exclude(name__exact=dblp_name)
|
||||||
|
# for v in variants:
|
||||||
|
# print(v.name, v.dblp_id)
|
||||||
|
# break
|
||||||
|
orig = Author.objects.filter(dblp_id__exact=pid, name__exact=dblp_name)
|
||||||
|
print(variants, dblp_name, orig)
|
||||||
|
assert len(orig) == 1
|
||||||
|
|
||||||
|
self.log_info(f"Suggestion to merge {', '.join([v.name for v in variants])} into {orig[0]}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.log_warn("Merge? [Y/n]")
|
||||||
|
choice = input().lower()
|
||||||
|
if choice in {'', 'y', 'yes'}:
|
||||||
|
for variant in variants: self.merge(orig[0], variant)
|
||||||
|
break
|
||||||
|
elif choice in {'', 'n', 'no'}:
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise CommandError("Aborted.")
|
||||||
|
|
||||||
# BaseCommand
|
# BaseCommand
|
||||||
|
def add_arguments(self, parser: CommandParser):
|
||||||
|
parser.add_argument('-b', '--abstract', action='store_true')
|
||||||
|
parser.add_argument('-d', '--dblp', action='store_true')
|
||||||
|
parser.add_argument('-a', '--authors', action='store_true')
|
||||||
|
parser.add_argument('-s', '--secondary', action='store_true')
|
||||||
|
parser.add_argument('-i', '--scholarid', action='store_true')
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
self.fix_references()
|
self.fix_references()
|
||||||
self.fix_abstracts()
|
if options['secondary']: self.find_secondary_on_dblp()
|
||||||
self.find_secondary_on_dblp()
|
if options['dblp']:
|
||||||
self.fix_dblp()
|
self.fix_dblp()
|
||||||
self.find_missing_dois()
|
self.find_missing_dois()
|
||||||
|
if options['scholarid'] or options['dblp']:
|
||||||
self.find_semanticscholar_ids()
|
self.find_semanticscholar_ids()
|
||||||
|
if options['authors']:
|
||||||
|
self.find_pid_for_authors()
|
||||||
|
self.merge_authors()
|
||||||
|
if options['abstract']: self.fix_abstracts()
|
||||||
|
|
||||||
@@ -6,6 +6,8 @@ from pathlib import Path
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any, Dict, List, Set
|
from typing import Any, Dict, List, Set
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandParser, CommandError
|
from django.core.management.base import BaseCommand, CommandParser, CommandError
|
||||||
@@ -24,7 +26,7 @@ from sok.models import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def semanticscholar(identifier: str, include_unknown_references: bool = False) -> Dict[str, Any]:
|
def semanticscholar(identifier: str, fields: str = None, type: str = None, offset: int = 0, include_unknown_references: bool = False) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Retrieve information from the Semantic Scholar API.
|
Retrieve information from the Semantic Scholar API.
|
||||||
|
|
||||||
@@ -33,11 +35,24 @@ def semanticscholar(identifier: str, include_unknown_references: bool = False) -
|
|||||||
See: https://api.semanticscholar.org
|
See: https://api.semanticscholar.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = f'https://api.semanticscholar.org/v1/paper/{identifier}'
|
url = f'https://api.semanticscholar.org/graph/v1/paper/{identifier}'
|
||||||
params: Dict[str, Any] = dict()
|
params: Dict[str, Any] = dict()
|
||||||
|
|
||||||
|
if type in ['citations', 'references']:
|
||||||
|
url += ('/' + type)
|
||||||
|
params['limit'] = '200'
|
||||||
|
params['offset'] = str(offset)
|
||||||
|
|
||||||
if include_unknown_references:
|
if include_unknown_references:
|
||||||
params['include_unknown_references'] = 'true'
|
params['include_unknown_references'] = 'true'
|
||||||
response = requests.get(url, params=params)
|
if fields:
|
||||||
|
params['fields'] = fields
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'x-api-key': settings.SCHOLAR_API_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, params=params, headers=headers)
|
||||||
response.raise_for_status
|
response.raise_for_status
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
@@ -48,7 +63,6 @@ class Command(BaseCommand):
|
|||||||
if bold:
|
if bold:
|
||||||
msg = self.style.HTTP_INFO(msg)
|
msg = self.style.HTTP_INFO(msg)
|
||||||
tqdm.write(msg, end='\n' if nl else '')
|
tqdm.write(msg, end='\n' if nl else '')
|
||||||
#self.stdout.write(msg, ending='\n' if nl else '')
|
|
||||||
|
|
||||||
def warn(self, msg: str):
|
def warn(self, msg: str):
|
||||||
self.echo(self.style.WARNING(msg))
|
self.echo(self.style.WARNING(msg))
|
||||||
@@ -113,8 +127,9 @@ class Command(BaseCommand):
|
|||||||
base: Publication,
|
base: Publication,
|
||||||
is_reference: bool,
|
is_reference: bool,
|
||||||
) -> Publication:
|
) -> Publication:
|
||||||
data = semanticscholar(paper_id)
|
data = semanticscholar(paper_id, fields='title,authors,year,abstract,externalIds')
|
||||||
# Add authors to database
|
# Add authors to database
|
||||||
|
## TODO: semantic scholar author ids nutzen?
|
||||||
authors: List[Author] = []
|
authors: List[Author] = []
|
||||||
first = True
|
first = True
|
||||||
cite_key = ''
|
cite_key = ''
|
||||||
@@ -145,7 +160,7 @@ class Command(BaseCommand):
|
|||||||
publication = None
|
publication = None
|
||||||
|
|
||||||
# Add publication to database
|
# Add publication to database
|
||||||
doi = data.get('doi', None)
|
doi = data.get('externalIds', None).get('DOI', None)
|
||||||
if not publication:
|
if not publication:
|
||||||
publication = Publication.objects.create(
|
publication = Publication.objects.create(
|
||||||
cite_key=cite_key,
|
cite_key=cite_key,
|
||||||
@@ -192,9 +207,11 @@ class Command(BaseCommand):
|
|||||||
title = "Reference" if is_reference else "Citation"
|
title = "Reference" if is_reference else "Citation"
|
||||||
if 0 < len(objs):
|
if 0 < len(objs):
|
||||||
self.echo(f"--- {title}s ---")
|
self.echo(f"--- {title}s ---")
|
||||||
for obj in tqdm(objs, unit=title.lower()):
|
progress_iterator = tqdm(objs, unit=title.lower(), position=2, leave=False, desc=title + 's')
|
||||||
|
for obj in progress_iterator:
|
||||||
if paper_id := obj.get('paperId', None):
|
if paper_id := obj.get('paperId', None):
|
||||||
try:
|
try:
|
||||||
|
# This publication already exists and has a semantic scholar entry
|
||||||
existing = SemanticScholar.objects.get(paper_id=paper_id)
|
existing = SemanticScholar.objects.get(paper_id=paper_id)
|
||||||
if is_reference:
|
if is_reference:
|
||||||
self.add_reference(base, existing.publication)
|
self.add_reference(base, existing.publication)
|
||||||
@@ -202,6 +219,7 @@ class Command(BaseCommand):
|
|||||||
self.add_reference(existing.publication, base, is_reference)
|
self.add_reference(existing.publication, base, is_reference)
|
||||||
continue
|
continue
|
||||||
except SemanticScholar.DoesNotExist:
|
except SemanticScholar.DoesNotExist:
|
||||||
|
# This publication already exists but does not have a semantic scholar entry
|
||||||
if doi := obj.get('doi', None):
|
if doi := obj.get('doi', None):
|
||||||
try:
|
try:
|
||||||
publication = Publication.objects.get(doi=doi)
|
publication = Publication.objects.get(doi=doi)
|
||||||
@@ -215,6 +233,7 @@ class Command(BaseCommand):
|
|||||||
self.add_reference(new.publication, base, is_reference)
|
self.add_reference(new.publication, base, is_reference)
|
||||||
continue
|
continue
|
||||||
except Publication.DoesNotExist:
|
except Publication.DoesNotExist:
|
||||||
|
# This publication does not exist so we need to create it
|
||||||
pass
|
pass
|
||||||
|
|
||||||
identifier = self.get_identifier(obj)
|
identifier = self.get_identifier(obj)
|
||||||
@@ -225,11 +244,12 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
paper_id = obj.get('paperId', None)
|
paper_id = obj.get('paperId', None)
|
||||||
while True:
|
while True:
|
||||||
self.echo("Ignore? [Y/n]", nl=True)
|
self.echo("Ignore? [Y/n]", nl=False)
|
||||||
if paper_id is not None:
|
if paper_id:
|
||||||
self.echo(", Show abstract [a]", nl=False)
|
self.echo(", Show abstract [a]", nl=False)
|
||||||
self.echo(": ")
|
self.echo(": ")
|
||||||
choice = input().lower()
|
choice = input().lower()
|
||||||
|
|
||||||
if choice in {'', 'y', 'yes'}:
|
if choice in {'', 'y', 'yes'}:
|
||||||
# Store choice
|
# Store choice
|
||||||
self.cache.add(identifier)
|
self.cache.add(identifier)
|
||||||
@@ -238,9 +258,10 @@ class Command(BaseCommand):
|
|||||||
break
|
break
|
||||||
elif choice in {'a'}:
|
elif choice in {'a'}:
|
||||||
assert paper_id is not None
|
assert paper_id is not None
|
||||||
data = semanticscholar(paper_id)
|
if abstract := obj.get('abstract', None):
|
||||||
if abstract := data.get('abstract', None):
|
|
||||||
self.echo(abstract)
|
self.echo(abstract)
|
||||||
|
else:
|
||||||
|
self.echo('Sorry, there is no abstract for this publication on Semantic Scholar')
|
||||||
elif choice in {'', 'n', 'no'}:
|
elif choice in {'', 'n', 'no'}:
|
||||||
# DONE Import? copied and adapted from PR
|
# DONE Import? copied and adapted from PR
|
||||||
if paper_id is not None:
|
if paper_id is not None:
|
||||||
@@ -277,23 +298,44 @@ class Command(BaseCommand):
|
|||||||
semanticscholar__isnull=False,
|
semanticscholar__isnull=False,
|
||||||
exclusion_criteria__isnull=True,
|
exclusion_criteria__isnull=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if stage < 10000:
|
if stage < 10000:
|
||||||
publications = [p for p in publications if p.stage_added() == stage]
|
publications = [p for p in publications if p.stage_added() == stage]
|
||||||
self.echo(f"==== {len(publications)} publications from stage {stage} will be shown ====")
|
self.echo(f"==== {len(publications)} publications from stage {stage} will be shown ====")
|
||||||
try:
|
try:
|
||||||
for publication in tqdm(publications, unit="publication"):
|
progress_iterator = tqdm(publications, unit="publication", position=1, desc='Publications')
|
||||||
|
for publication in progress_iterator:
|
||||||
self.echo(f"=== Publication {publication}: {publication.title} ===")
|
self.echo(f"=== Publication {publication}: {publication.title} ===")
|
||||||
for semantic in publication.semanticscholar_set.all():
|
for semantic in publication.semanticscholar_set.all():
|
||||||
data = semanticscholar(semantic.paper_id)
|
|
||||||
|
|
||||||
if not no_references:
|
if not no_references:
|
||||||
references: List[Dict[str, Any]] = data['references']
|
offset = 0
|
||||||
|
while True:
|
||||||
|
data = semanticscholar(semantic.paper_id, type='references', fields='title,abstract', offset=offset)
|
||||||
|
if not data.get('data', None):
|
||||||
|
self.echo(self.style.WARNING("API did not return any references, verify manually!"))
|
||||||
|
break
|
||||||
|
references: List[Dict[str, Any]] = [d['citedPaper'] for d in data['data']]
|
||||||
self.handle_objs(publication, references, is_reference=True)
|
self.handle_objs(publication, references, is_reference=True)
|
||||||
|
# Handle limitation if there are more than 200 paper references
|
||||||
|
if 'next' in data: offset = data['next']
|
||||||
|
else: break
|
||||||
|
|
||||||
if not no_citations:
|
if not no_citations:
|
||||||
citations: List[Dict[str, Any]] = data['citations']
|
offset = 0
|
||||||
|
while True:
|
||||||
|
data = semanticscholar(semantic.paper_id, type='citations', fields='title,abstract', offset=offset)
|
||||||
|
if not data.get('data', None):
|
||||||
|
self.echo(self.style.WARNING("API did not return any citations, verify manually!"))
|
||||||
|
break
|
||||||
|
citations: List[Dict[str, Any]] = [d['citingPaper'] for d in data['data']]
|
||||||
self.handle_objs(publication, citations, is_reference=False)
|
self.handle_objs(publication, citations, is_reference=False)
|
||||||
|
# Handle limitation if there are more than 200 paper references
|
||||||
|
if 'next' in data: offset = data['next']
|
||||||
|
else: break
|
||||||
|
|
||||||
|
sleep(1) # Throttle
|
||||||
|
|
||||||
sleep(2) # Throttle
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise CommandError("Aborted.")
|
raise CommandError("Aborted.")
|
||||||
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
|
import pickle
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
@@ -43,7 +47,22 @@ class Command(BaseCommand):
|
|||||||
).distinct():
|
).distinct():
|
||||||
publications_relevant.add(publication.cite_key)
|
publications_relevant.add(publication.cite_key)
|
||||||
|
|
||||||
|
self.cache_path = Path('.choices.semanticscholar.pickle')
|
||||||
|
if self.cache_path.exists():
|
||||||
|
self.echo("Loading choices from snowballing...")
|
||||||
|
with self.cache_path.open('rb') as f:
|
||||||
|
self.cache = pickle.load(f)
|
||||||
|
self.echo(f"{len(self.cache)} publications excluded during snowballing")
|
||||||
|
self.echo(f"{len(Publication.objects.filter(variant_of__isnull=True))} publications included in total so far")
|
||||||
|
|
||||||
# Output
|
# Output
|
||||||
self.echo(f"Total publications: {len(publications_found):4d}", bold=True)
|
self.echo(f"Total publications: {len(publications_found):4d}", bold=True)
|
||||||
self.echo(f"- peer reviewed: {len(publications_peer_reviewed):4d}", bold=True)
|
self.echo(f"- peer reviewed: {len(publications_peer_reviewed):4d}", bold=True)
|
||||||
self.echo(f"- relevant: {len(publications_relevant):4d}", bold=True)
|
self.echo(f"- relevant: {len(publications_relevant):4d}", bold=True)
|
||||||
|
|
||||||
|
pubs = Publication.objects.values('title').annotate(count=Count('referenced_by')).values('title', 'year', 'count').order_by('count').reverse()
|
||||||
|
for p in pubs[:50]: print(f"{p['title']} ({p['year']}): {p['count']} citations")
|
||||||
|
|
||||||
|
years = Publication.objects.values('year').annotate(count=Count('year')).values('year', 'count')
|
||||||
|
for y in years: print(f"{y['year']}, {y['count']}")
|
||||||
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import html
|
||||||
|
import io
|
||||||
|
import string
|
||||||
|
import csv
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
from django.core.management.base import BaseCommand, CommandParser, CommandError
|
||||||
|
|
||||||
|
from sok.models import (
|
||||||
|
Author,
|
||||||
|
Publication,
|
||||||
|
PublicationAuthor,
|
||||||
|
PublicationSource,
|
||||||
|
PublicationTag,
|
||||||
|
SearchTerm,
|
||||||
|
Source,
|
||||||
|
Tag,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: anpassen für WoS?
|
||||||
|
PUBLICATIONS = {
|
||||||
|
'article',
|
||||||
|
'inproceedings',
|
||||||
|
'proceedings',
|
||||||
|
'book',
|
||||||
|
'incollection',
|
||||||
|
'phdthesis',
|
||||||
|
'mastersthesis',
|
||||||
|
'www',
|
||||||
|
'person',
|
||||||
|
'data',
|
||||||
|
}
|
||||||
|
|
||||||
|
CITE_KEY_PREFIX = 'Z:'
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def log_success(self, msg: str):
|
||||||
|
self.stdout.write(self.style.SUCCESS(msg))
|
||||||
|
|
||||||
|
def log_info(self, msg: str, nl: bool = True):
|
||||||
|
self.stdout.write(self.style.HTTP_INFO(msg), ending='\n' if nl else '')
|
||||||
|
self.stdout.flush()
|
||||||
|
|
||||||
|
# BaseCommand
|
||||||
|
|
||||||
|
def add_arguments(self, parser: CommandParser):
|
||||||
|
parser.add_argument('--search-term', default='Not specified')
|
||||||
|
parser.add_argument('--source', default='Zotero')
|
||||||
|
parser.add_argument('zfile')
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
source, created = Source.objects.get_or_create(name=options['source'])
|
||||||
|
|
||||||
|
search_term: Optional[SearchTerm] = None
|
||||||
|
if name := options['search_term']:
|
||||||
|
search_term, created = SearchTerm.objects.get_or_create(name=name)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Created search term: {search_term}")
|
||||||
|
|
||||||
|
publications: List[Publication] = []
|
||||||
|
|
||||||
|
zotero_file = options['zfile']
|
||||||
|
with open(zotero_file, 'r') as csvfile:
|
||||||
|
reader = csv.DictReader(csvfile)
|
||||||
|
for publ in reader:
|
||||||
|
|
||||||
|
authors: List[Author] = []
|
||||||
|
for name in set(publ['Author'].split('; ')):
|
||||||
|
author, created = Author.objects.get_or_create(name=name)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Added author: {author}")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Author '{author}' already known")
|
||||||
|
authors.append(author)
|
||||||
|
|
||||||
|
tags: List[Tag] = []
|
||||||
|
for t in set(publ['Manual Tags'].split('; ')).union(publ['Automatic Tags'].split('; ')):
|
||||||
|
if t == '': continue
|
||||||
|
tag, created = Tag.objects.get_or_create(name=t)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Added tag: {tag}")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Tag '{tag}' already exists")
|
||||||
|
tags.append(tag)
|
||||||
|
|
||||||
|
pages = (None, None)
|
||||||
|
if '-' in publ['Pages']:
|
||||||
|
pages = publ['Pages'].split('-')
|
||||||
|
|
||||||
|
# Add publication to database
|
||||||
|
publication, created = Publication.objects.get_or_create(
|
||||||
|
cite_key=publ['Key'],
|
||||||
|
title=publ['Title'],
|
||||||
|
year=publ['Publication Year'],
|
||||||
|
# TODO: peer_reviewed=result.is_peer_reviewed,
|
||||||
|
first_page=pages[0],
|
||||||
|
last_page=pages[1],
|
||||||
|
doi=publ['DOI'] or None,
|
||||||
|
abstract=publ['Abstract Note'] or None,
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Added publication: {publication}")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Publication '{publication}' already known")
|
||||||
|
publications.append(publication)
|
||||||
|
|
||||||
|
# Assign authors
|
||||||
|
for position, author in enumerate(authors):
|
||||||
|
publication_author, created = PublicationAuthor.objects.get_or_create(
|
||||||
|
author=author,
|
||||||
|
publication=publication,
|
||||||
|
position=position,
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Assigned author '{author}' to publication '{publication}' at position {position}")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Author '{author}' already assigned to publication '{publication}' at position '{position}'")
|
||||||
|
|
||||||
|
# Assign tags
|
||||||
|
for position, tag in enumerate(tags):
|
||||||
|
publication_tag, created = PublicationTag.objects.get_or_create(
|
||||||
|
tag=tag,
|
||||||
|
publication=publication,
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Assigned tag '{tag}' to publication '{publication}'")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Tag '{tag}' already assigned to publication '{publication}'")
|
||||||
|
|
||||||
|
# Assign sources
|
||||||
|
if search_term is not None:
|
||||||
|
for publication in publications:
|
||||||
|
print(publication, search_term, source)
|
||||||
|
publication_source, created = PublicationSource.objects.get_or_create(
|
||||||
|
source=source,
|
||||||
|
publication=publication,
|
||||||
|
search_term=search_term,
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.log_success(f"Assigned source '{source}' to publication '{publication}' with search term '{search_term}'")
|
||||||
|
else:
|
||||||
|
self.log_info(f"Source '{source}' already assigned to publication '{publication}' with search term '{search_term}'")
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.1 on 2025-05-16 08:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sok', '0006_alter_publication_abstract'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='author',
|
||||||
|
name='dblp_id',
|
||||||
|
field=models.CharField(blank=True, max_length=127),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -7,6 +7,7 @@ from django.db.models.query import QuerySet
|
|||||||
|
|
||||||
class Author(models.Model):
|
class Author(models.Model):
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
dblp_id = models.CharField(max_length=127, blank=True)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
li a {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Tagging publications</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if publication %}
|
||||||
|
{% for author in publication.authors.all %}
|
||||||
|
<a>{{ author.name }}, </a>
|
||||||
|
{% endfor %}
|
||||||
|
<h2>{{ publication.title }} ({{ publication.year }})</h2>
|
||||||
|
{% if publication.doi %}
|
||||||
|
<!-- atm this is a link under /tag as well! -->
|
||||||
|
<a href="https://doi.org/{{ publication.doi }}">{{ publication.doi }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p>No publications are available.</p>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{% static 'tag/style.css' %}">
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>List for tagging publications</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if newest_publications %}
|
||||||
|
<ul>
|
||||||
|
{% for publication in newest_publications %}
|
||||||
|
<li><a href="{% url 'detail' publication.cite_key %}/">{{ publication.title }}, {{publication.year}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>No publications are available.</p>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", views.IndexView.as_view(), name="index"),
|
||||||
|
path("<path:cite_key>", views.detail, name="detail"),
|
||||||
|
]
|
||||||
+30
-2
@@ -1,3 +1,31 @@
|
|||||||
from django.shortcuts import render
|
from django.db.models import F
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views import generic
|
||||||
|
|
||||||
|
from sok.models import Publication, PublicationReference
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(generic.ListView):
|
||||||
|
template_name = "tag/index.html"
|
||||||
|
context_object_name = "newest_publications"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Publication.objects.order_by("-year")[:10]
|
||||||
|
|
||||||
|
def detail(request, cite_key):
|
||||||
|
try:
|
||||||
|
publication = get_object_or_404(Publication, cite_key=cite_key.strip('/'))
|
||||||
|
except Publication.DoesNotExist:
|
||||||
|
raise Http404("Publication does not exist")
|
||||||
|
return render(request, "tag/detail.html", {"publication": publication})
|
||||||
|
|
||||||
|
|
||||||
|
# def overview(request):
|
||||||
|
# try:
|
||||||
|
# ps = Publication.objects.get(...)
|
||||||
|
# except Publication.DoesNotExist:
|
||||||
|
# raise Http404("Publication does not exist")
|
||||||
|
# return render(request, "publications/overview.html", {"to_tag": ps})
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
+13
-1
@@ -32,9 +32,17 @@ def get_or_generate_key() -> str:
|
|||||||
assert path.exists()
|
assert path.exists()
|
||||||
return path.read_text()
|
return path.read_text()
|
||||||
|
|
||||||
|
def get_api_key() -> str:
|
||||||
|
path = Path(Path(__file__).parent,'api.secret')
|
||||||
|
|
||||||
|
assert path.exists()
|
||||||
|
return path.read_text()
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = get_or_generate_key()
|
SECRET_KEY = get_or_generate_key()
|
||||||
|
|
||||||
|
SCHOLAR_API_KEY = get_api_key().strip('\n')
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
@@ -89,9 +97,13 @@ WSGI_APPLICATION = 'sokman.wsgi.application'
|
|||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'jens.sqlite3',
|
||||||
|
},
|
||||||
|
'uc_sok': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -14,8 +14,9 @@ Including another URLconf
|
|||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('tag/', include('sok.urls')),
|
||||||
]
|
]
|
||||||
Reference in new issue
Block a user