1
0
Fork 0
mirror of https://github.com/Findus23/PaperLibrary.git synced 2024-09-11 06:43:47 +02:00

initial version

This commit is contained in:
Lukas Winkler 2020-10-14 21:35:55 +02:00
commit f9300e7753
Signed by: lukas
GPG key ID: 54DE4D798D244853
40 changed files with 1230 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
oldcode/
.idea
__pycache__/
media/
paperlibrary/secrets.py

0
library/__init__.py Normal file
View file

35
library/admin.py Normal file
View file

@ -0,0 +1,35 @@
from django.contrib import admin
# Register your models here.
from django.forms import ModelForm
from djangoql.admin import DjangoQLSearchMixin
from library.models import Paper, Author, Keyword, PDF
class AddPaperForm(ModelForm):
class Meta:
model = Paper
fields = ['doi']
class PaperAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
form = AddPaperForm
readonly_fields = ["title", "first_author", "authors", "pubdate", "entry_date", "keywords", "publication",
"doctype", "arxiv_id", "bibtex", ]
date_hierarchy = "entry_date"
list_filter = ["authors", "publication", "year", "keywords", "doctype"]
list_display = ["title", "first_author"]
search_fields = ["@abstract"]
save_on_top = True
class PDFAdmin(admin.ModelAdmin):
search_fields = ["@full_text"]
admin.site.register(Paper, PaperAdmin)
admin.site.register(Author)
admin.site.register(Keyword)
admin.site.register(PDF,PDFAdmin)
# Paper.objects.filter(pubdate__month=)

5
library/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class LibraryConfig(AppConfig):
name = 'library'

View file

@ -0,0 +1,14 @@
from django.core.management.base import BaseCommand
from library.models import PDF
from library.utils.pdf import create_preview
class Command(BaseCommand):
help = 'create previews for all PDFs'
def handle(self, *args, **options):
for pdf in PDF.objects.all():
print(pdf)
pdf.preview.delete()
create_preview(pdf)

View file

@ -0,0 +1,13 @@
from django.core.management.base import BaseCommand
from library.models import PDF
from library.utils.pdf import pdf_to_text
class Command(BaseCommand):
help = 'Fetches text for all PDFs'
def handle(self, *args, **options):
for pdf in PDF.objects.filter(full_text__exact=""):
print(pdf)
pdf_to_text(pdf)

View file

@ -0,0 +1,13 @@
from django.core.management.base import BaseCommand
from library.models import Paper
from library.utils.pdf import fetch_arxiv_pdf
class Command(BaseCommand):
help = 'Fetches PDFs for all papers'
def handle(self, *args, **options):
for paper in Paper.objects.filter(pdfs=None):
print(paper)
fetch_arxiv_pdf(paper)

View file

@ -0,0 +1,52 @@
# Generated by Django 3.1.1 on 2020-09-24 14:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Author',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1000, unique=True)),
('affiliation', models.CharField(max_length=1000)),
('orcid_id', models.CharField(max_length=100, unique=True)),
],
),
migrations.CreateModel(
name='PDF',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(upload_to='pdfs')),
('sha265', models.CharField(editable=False, max_length=64)),
('full_text', models.TextField(blank=True)),
],
),
migrations.CreateModel(
name='Paper',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=1000)),
('abstract', models.TextField()),
('doi', models.CharField(max_length=50, unique=True)),
('bibtext', models.TextField()),
('publication', models.TextField()),
('doctype', models.TextField()),
('arxiv_identifier', models.CharField(max_length=10, unique=True)),
('bibcode', models.CharField(max_length=20, unique=True)),
('year', models.IntegerField()),
('pubdate', models.DateField()),
('authors', models.ManyToManyField(related_name='papers', to='library.Author')),
('first_author', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='library.author')),
('pdfs', models.ManyToManyField(related_name='papers', to='library.PDF')),
],
),
]

View file

@ -0,0 +1,39 @@
# Generated by Django 3.1.1 on 2020-09-24 16:11
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('library', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Keyword',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1000, unique=True)),
('schema', models.CharField(max_length=1000)),
],
),
migrations.AddField(
model_name='paper',
name='citation_count',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='paper',
name='entry_date',
field=models.DateField(default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='paper',
name='keywords',
field=models.ManyToManyField(related_name='papers', to='library.Keyword'),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 3.1.1 on 2020-09-24 16:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0002_auto_20200924_1611'),
]
operations = [
migrations.AlterField(
model_name='author',
name='affiliation',
field=models.CharField(blank=True, max_length=1000, null=True),
),
migrations.AlterField(
model_name='author',
name='orcid_id',
field=models.CharField(blank=True, max_length=100, null=True, unique=True),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2020-09-24 16:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('library', '0003_auto_20200924_1612'),
]
operations = [
migrations.RenameField(
model_name='paper',
old_name='bibtext',
new_name='bibtex',
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2020-09-24 16:25
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('library', '0004_auto_20200924_1617'),
]
operations = [
migrations.RenameField(
model_name='paper',
old_name='arxiv_identifier',
new_name='arxiv_id',
),
]

View file

@ -0,0 +1,29 @@
# Generated by Django 3.1.1 on 2020-10-09 16:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('library', '0005_auto_20200924_1625'),
]
operations = [
migrations.RemoveField(
model_name='paper',
name='pdfs',
),
migrations.AddField(
model_name='pdf',
name='paper',
field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.PROTECT, to='library.paper'),
preserve_default=False,
),
migrations.AddField(
model_name='pdf',
name='preview',
field=models.FileField(null=True, upload_to='previews'),
),
]

View file

@ -0,0 +1,25 @@
# Generated by Django 3.1.1 on 2020-10-09 16:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('library', '0006_auto_20201009_1633'),
]
operations = [
migrations.AddField(
model_name='pdf',
name='type',
field=models.CharField(default='other', max_length=100),
preserve_default=False,
),
migrations.AlterField(
model_name='pdf',
name='paper',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='pdfs', to='library.paper'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2020-10-09 16:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0007_auto_20201009_1635'),
]
operations = [
migrations.AlterField(
model_name='pdf',
name='type',
field=models.CharField(default='other', max_length=100),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 3.1.1 on 2020-10-09 16:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0008_auto_20201009_1635'),
]
operations = [
migrations.AlterField(
model_name='paper',
name='citation_count',
field=models.IntegerField(),
),
migrations.AlterField(
model_name='paper',
name='entry_date',
field=models.DateField(),
),
]

View file

@ -0,0 +1,38 @@
# Generated by Django 3.1.1 on 2020-10-12 17:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('library', '0009_auto_20201009_1636'),
]
operations = [
migrations.CreateModel(
name='DocType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1000, unique=True)),
],
),
migrations.CreateModel(
name='Publication',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=1000, unique=True)),
],
),
migrations.AlterField(
model_name='paper',
name='doctype',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.doctype'),
),
migrations.AlterField(
model_name='paper',
name='publication',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='library.publication'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2020-10-14 19:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0010_auto_20201012_1734'),
]
operations = [
migrations.AddField(
model_name='pdf',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
]

View file

10
library/models/Author.py Normal file
View file

@ -0,0 +1,10 @@
from django.db import models
class Author(models.Model):
name = models.CharField(unique=True, max_length=1000)
affiliation = models.CharField(max_length=1000, null=True, blank=True)
orcid_id = models.CharField(unique=True, max_length=100, null=True, blank=True)
def __str__(self):
return self.name

View file

@ -0,0 +1,8 @@
from django.db import models
class DocType(models.Model):
name = models.CharField(unique=True, max_length=1000)
def __str__(self):
return self.name

View file

@ -0,0 +1,9 @@
from django.db import models
class Keyword(models.Model):
name = models.CharField(unique=True, max_length=1000)
schema = models.CharField(max_length=1000)
def __str__(self):
return self.name

16
library/models/PDF.py Normal file
View file

@ -0,0 +1,16 @@
from django.db import models
from library.models import Paper
class PDF(models.Model):
file = models.FileField(upload_to="pdfs")
sha265 = models.CharField(max_length=64, editable=False)
full_text = models.TextField(blank=True)
paper = models.ForeignKey(Paper, on_delete=models.PROTECT, related_name="pdfs")
type = models.CharField(max_length=100, default="other")
preview = models.FileField(upload_to="previews", null=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.file.name

83
library/models/Paper.py Normal file
View file

@ -0,0 +1,83 @@
import ads
from ads.search import Article
from django.db import models
from library.models import Author, Keyword, Publication, DocType
from paperlibrary.settings import ADS_AUTH_TOKEN
class Paper(models.Model):
title = models.CharField(max_length=1000)
abstract = models.TextField()
doi = models.CharField(unique=True, max_length=50)
bibtex = models.TextField()
first_author = models.ForeignKey(Author, on_delete=models.RESTRICT)
authors = models.ManyToManyField(Author, related_name="papers")
publication = models.ForeignKey(Publication, on_delete=models.CASCADE)
doctype = models.ForeignKey(DocType, on_delete=models.CASCADE)
arxiv_id = models.CharField(unique=True, max_length=10)
bibcode = models.CharField(unique=True, max_length=20)
year = models.IntegerField()
pubdate = models.DateField()
entry_date = models.DateField()
citation_count = models.IntegerField()
keywords = models.ManyToManyField(Keyword, related_name="papers")
def __str__(self):
return self.title
@property
def arxiv_url(self):
if not self.arxiv_id:
return None
return f"https://arxiv.org/abs/{self.arxiv_id}"
@property
def arxiv_pdf_url(self):
if not self.arxiv_id:
return None
return f"https://export.arxiv.org/pdf/{self.arxiv_id}.pdf"
def save(self, *args, **kwargs):
if self.id:
return super(Paper, self).save(*args, **kwargs)
ads.config.token = ADS_AUTH_TOKEN
cols = [
"title", "author", "first_author", "year", "bibcode", "id", "pubdate", "doi",
"identifier", "pub", "citation_count", "abstract", "bibtex", "doctype", "keyword"
]
# ads.ExportQuery
papers = ads.SearchQuery(doi=self.doi, fl=cols)
paper: Article = next(papers)
self.title = paper.title[0]
self.publication, _ = Publication.objects.get_or_create(name=paper.pub)
self.abstract = paper.abstract
self.bibtex = paper.bibtex
self.year = int(paper.year)
self.entry_date = paper._get_field("entry_date").replace("T00:00:00Z", "")
self.citation_count = int(paper.citation_count)
self.doctype, _ = DocType.objects.get_or_create(name=paper.doctype)
self.first_author, _ = Author.objects.get_or_create(name=paper.first_author)
self.year = paper.year
self.pubdate = paper.pubdate.replace("-00", "-01").replace("T", "")
if paper.doi and len(paper.doi) > 0:
self.doi = paper.doi[0]
else:
self.doi = None
arxiv_papers = [ident for ident in paper.identifier if "arXiv:" in ident]
if len(arxiv_papers) > 0:
self.arxiv_id = arxiv_papers[0].split("arXiv:")[-1]
else:
self.arxiv_id = None
super(Paper, self).save(*args, **kwargs)
for author_name in paper.author:
author, created = Author.objects.get_or_create(name=author_name)
self.authors.add(author)
for kw in zip(paper.keyword, paper._get_field("keyword_schema")):
keyword_name, keyword_schema = kw
keyword, created = Keyword.objects.get_or_create(name=keyword_name, schema=keyword_schema)
self.keywords.add(keyword)

View file

@ -0,0 +1,8 @@
from django.db import models
class Publication(models.Model):
name = models.CharField(unique=True, max_length=1000)
def __str__(self):
return self.name

View file

@ -0,0 +1,6 @@
from .Publication import Publication
from .DocType import DocType
from .Author import Author
from .Keyword import Keyword
from .Paper import Paper
from .PDF import PDF

44
library/serializers.py Normal file
View file

@ -0,0 +1,44 @@
from rest_framework import serializers
from library.models import Paper, Author, Keyword, PDF, DocType, Publication
class PDFSerializer(serializers.ModelSerializer):
class Meta:
model = PDF
exclude = ["full_text", "paper"]
class SimplePaperSerializer(serializers.HyperlinkedModelSerializer):
pdfs = PDFSerializer(many=True)
class Meta:
model = Paper
fields = ["id", "url", "title", "pdfs", "doi"]
class AuthorSerializer(serializers.HyperlinkedModelSerializer):
papers = SimplePaperSerializer(many=True)
class Meta:
model = Author
fields = "__all__"
class KeywordSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Keyword
fields = "__all__"
class PaperSerializer(serializers.HyperlinkedModelSerializer):
keywords = serializers.SlugRelatedField("name", many=True, queryset=Keyword.objects.all())
authors = serializers.SlugRelatedField("name", many=True, queryset=Author.objects.all())
first_author = serializers.SlugRelatedField("name", queryset=Author.objects.all())
publication = serializers.SlugRelatedField("name", queryset=Publication.objects.all())
doctype = serializers.SlugRelatedField("name", queryset=DocType.objects.all())
pdfs = PDFSerializer(many=True)
class Meta:
model = Paper
exclude = ["abstract", "bibtex"]

3
library/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

19
library/urls.py Normal file
View file

@ -0,0 +1,19 @@
from django.urls import include, path
from rest_framework import routers
from rest_framework.authtoken.views import obtain_auth_token
from library import views
router = routers.DefaultRouter()
router.register('papers', views.PaperViewSet)
router.register('authors', views.AuthorViewSet)
router.register('keywords', views.KeywordViewSet)
router.register('pdfs', views.PDFViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api-token-auth/', obtain_auth_token)
]

3
library/utils/http.py Normal file
View file

@ -0,0 +1,3 @@
from requests import Session
requests_session = Session()

47
library/utils/pdf.py Normal file
View file

@ -0,0 +1,47 @@
import hashlib
from subprocess import run
from tempfile import TemporaryFile
from alive_progress import alive_bar
from django.core.files import File
from wand.image import Image
from library.models import Paper, PDF
from library.utils.http import requests_session
def fetch_arxiv_pdf(paper: Paper) -> None:
with TemporaryFile("rb+") as fd:
print(paper.arxiv_pdf_url)
r = requests_session.get(paper.arxiv_pdf_url)
sha256 = hashlib.sha256()
with alive_bar(int(r.headers["Content-Length"])) as bar:
for chunk in r.iter_content(chunk_size=1):
fd.write(chunk)
sha256.update(chunk)
bar()
pdf_file = File(fd)
pdf_obj = PDF()
pdf_obj.file.save(f"{paper.arxiv_id}.pdf", pdf_file, save=False)
pdf_obj.sha265 = sha256.hexdigest()
pdf_obj.type = "arxiv"
pdf_obj.paper = paper
pdf_obj.save()
def create_preview(pdf: PDF, width=1000) -> None:
with Image(filename=f"pdf:{pdf.file.path}[0]") as img:
img.trim(fuzz=0.2)
img.format = "png"
new_height = width / img.width * img.height
img.thumbnail(width=width, height=int(new_height))
with TemporaryFile("rb+") as tf:
img.save(tf)
preview = File(tf)
pdf.preview.save(f"{pdf.paper.arxiv_id}.png", preview)
def pdf_to_text(pdf: PDF) -> None:
output = run(["pdftotext", pdf.file.path, "-"], capture_output=True)
pdf.full_text = output.stdout.decode().replace("\n", " ")
pdf.save()

31
library/views.py Normal file
View file

@ -0,0 +1,31 @@
# Create your views here.
from rest_framework import viewsets, permissions
from library.models import Paper, Author, Keyword, PDF
from library.serializers import PaperSerializer, AuthorSerializer, PDFSerializer, KeywordSerializer
class PaperViewSet(viewsets.ModelViewSet):
queryset = Paper.objects.all().select_related("first_author")\
.select_related("publication").select_related("doctype") \
.prefetch_related("pdfs").prefetch_related("authors")\
.prefetch_related("keywords")
serializer_class = PaperSerializer
permission_classes = [permissions.IsAuthenticated]
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
permission_classes = [permissions.IsAuthenticated]
class KeywordViewSet(viewsets.ModelViewSet):
queryset = Keyword.objects.all()
serializer_class = KeywordSerializer
permission_classes = [permissions.IsAuthenticated]
class PDFViewSet(viewsets.ModelViewSet):
queryset = PDF.objects.all()
serializer_class = PDFSerializer
permission_classes = [permissions.IsAuthenticated]

22
manage.py Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'paperlibrary.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
paperlibrary/__init__.py Normal file
View file

16
paperlibrary/asgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
ASGI config for paperlibrary project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'paperlibrary.settings')
application = get_asgi_application()

125
paperlibrary/settings.py Normal file
View file

@ -0,0 +1,125 @@
"""
Django settings for paperlibrary project.
Generated by 'django-admin startproject' using Django 3.1.1.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
from pathlib import Path
from .secrets import *
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
# SECURITY WARNING: don't run with debug turned on in production!
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'library.apps.LibraryConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.postgres',
'djangoql',
'debug_toolbar',
'rest_framework',
"rest_framework.authtoken"
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'paperlibrary.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'paperlibrary.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/'
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
INTERNAL_IPS = [
"127.0.0.1"
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}

32
paperlibrary/urls.py Normal file
View file

@ -0,0 +1,32 @@
"""paperlibrary URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
import debug_toolbar
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from paperlibrary import settings
from paperlibrary.settings import DEBUG
urlpatterns = [
path('', include('library.urls')),
path('admin/', admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if DEBUG:
urlpatterns += [
path('__debug__/', include(debug_toolbar.urls)),
]

16
paperlibrary/wsgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
WSGI config for paperlibrary project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'paperlibrary.settings')
application = get_wsgi_application()

324
poetry.lock generated Normal file
View file

@ -0,0 +1,324 @@
[[package]]
name = "ads"
version = "0.12.3"
description = "A Python module for NASA's ADS that doesn't suck."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
httpretty = "0.8.10"
mock = "*"
requests = "*"
six = "*"
werkzeug = "*"
[[package]]
name = "alive-progress"
version = "1.6.1"
description = "A new kind of Progress Bar, with real-time throughput, eta and very cool animations!"
category = "main"
optional = false
python-versions = ">=2.7, <4"
[[package]]
name = "asgiref"
version = "3.2.10"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
python-versions = ">=3.5"
[package.extras]
tests = ["pytest", "pytest-asyncio"]
[[package]]
name = "certifi"
version = "2020.6.20"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "chardet"
version = "3.0.4"
description = "Universal encoding detector for Python 2 and 3"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "django"
version = "3.1.1"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
asgiref = ">=3.2.10,<3.3.0"
pytz = "*"
sqlparse = ">=0.2.2"
[package.extras]
argon2 = ["argon2-cffi (>=16.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "django-debug-toolbar"
version = "3.1.1"
description = "A configurable set of panels that display various debug information about the current request/response."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
Django = ">=2.2"
sqlparse = ">=0.2.0"
[[package]]
name = "djangoql"
version = "0.14.0"
description = "DjangoQL: Advanced search language for Django"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
ply = ">=3.8"
[[package]]
name = "djangorestframework"
version = "3.12.1"
description = "Web APIs for Django, made easy."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
django = ">=2.2"
[[package]]
name = "httpretty"
version = "0.8.10"
description = "HTTP client mock for Python"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "idna"
version = "2.10"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "mock"
version = "4.0.2"
description = "Rolling backport of unittest.mock for all Pythons"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
build = ["twine", "wheel", "blurb"]
docs = ["sphinx"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "ply"
version = "3.11"
description = "Python Lex & Yacc"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "psycopg2"
version = "2.8.6"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
[[package]]
name = "pytz"
version = "2020.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "requests"
version = "2.24.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<4"
idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "sqlparse"
version = "0.3.1"
description = "Non-validating SQL parser"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "urllib3"
version = "1.25.10"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
[[package]]
name = "wand"
version = "0.6.3"
description = "Ctypes-based simple MagickWand API binding for Python"
category = "main"
optional = false
python-versions = "*"
[package.extras]
doc = ["Sphinx (>=2.4.1)"]
test = ["pytest (>=5.3.5)"]
[[package]]
name = "werkzeug"
version = "1.0.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "7d0913be4faf7c68a468021465c2fb273d039113c097143d4ecf9645d88fdb53"
[metadata.files]
ads = [
{file = "ads-0.12.3.tar.gz", hash = "sha256:ce523f266f6b815bf1d6371b6e7791b4d10989ebe743681b27bd54301f7cdcc9"},
]
alive-progress = [
{file = "alive-progress-1.6.1.tar.gz", hash = "sha256:2a0d7516ec0f596d5ce53755c0913a909eb1c91854e1d782e511ef5e1dd53218"},
{file = "alive_progress-1.6.1-py3-none-any.whl", hash = "sha256:9a0fae6b94fb4e4bcd9fb51760506d29a33358ebbfef2c6516dce3e359a661b5"},
]
asgiref = [
{file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"},
{file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"},
]
certifi = [
{file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
{file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
]
chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
]
django = [
{file = "Django-3.1.1-py3-none-any.whl", hash = "sha256:b5fbb818e751f660fa2d576d9f40c34a4c615c8b48dd383f5216e609f383371f"},
{file = "Django-3.1.1.tar.gz", hash = "sha256:59c8125ca873ed3bdae9c12b146fbbd6ed8d0f743e4cf5f5817af50c51f1fc2f"},
]
django-debug-toolbar = [
{file = "django-debug-toolbar-3.1.1.tar.gz", hash = "sha256:c97921a9cd421d392e7860dc4b464db8e06c8628df4dc58fedab012888c293c6"},
{file = "django_debug_toolbar-3.1.1-py3-none-any.whl", hash = "sha256:a1ce0665f7ef47d27b8df4b5d1058748e1f08500a01421a30d35164f38aaaf4c"},
]
djangoql = [
{file = "djangoql-0.14.0.tar.gz", hash = "sha256:1b1f80940bb15982e06208b97d7b3d08e1d5f8fb62aa07e5e33599c7ce4c0334"},
]
djangorestframework = [
{file = "djangorestframework-3.12.1-py3-none-any.whl", hash = "sha256:5c5071fcbad6dce16f566d492015c829ddb0df42965d488b878594aabc3aed21"},
{file = "djangorestframework-3.12.1.tar.gz", hash = "sha256:d54452aedebb4b650254ca092f9f4f5df947cb1de6ab245d817b08b4f4156249"},
]
httpretty = [
{file = "httpretty-0.8.10-py2-none-any.whl", hash = "sha256:77cbd62e20cb47f1c66125531aedcb80c3141dc10c5b24779389693cfc8bc3a2"},
{file = "httpretty-0.8.10.tar.gz", hash = "sha256:474a72722d66841f0e59cee285d837e1c6263be5be7bf2f8e824fc849a99adda"},
]
idna = [
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
]
mock = [
{file = "mock-4.0.2-py3-none-any.whl", hash = "sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0"},
{file = "mock-4.0.2.tar.gz", hash = "sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72"},
]
ply = [
{file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"},
{file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"},
]
psycopg2 = [
{file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"},
{file = "psycopg2-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:d160744652e81c80627a909a0e808f3c6653a40af435744de037e3172cf277f5"},
{file = "psycopg2-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:b8cae8b2f022efa1f011cc753adb9cbadfa5a184431d09b273fb49b4167561ad"},
{file = "psycopg2-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:f22ea9b67aea4f4a1718300908a2fb62b3e4276cf00bd829a97ab5894af42ea3"},
{file = "psycopg2-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:26e7fd115a6db75267b325de0fba089b911a4a12ebd3d0b5e7acb7028bc46821"},
{file = "psycopg2-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301"},
{file = "psycopg2-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a49833abfdede8985ba3f3ec641f771cca215479f41523e99dace96d5b8cce2a"},
{file = "psycopg2-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:f974c96fca34ae9e4f49839ba6b78addf0346777b46c4da27a7bf54f48d3057d"},
{file = "psycopg2-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:6a3d9efb6f36f1fe6aa8dbb5af55e067db802502c55a9defa47c5a1dad41df84"},
{file = "psycopg2-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5"},
{file = "psycopg2-2.8.6-cp38-cp38-win32.whl", hash = "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e"},
{file = "psycopg2-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051"},
{file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"},
]
pytz = [
{file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
{file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
]
requests = [
{file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
{file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
]
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
sqlparse = [
{file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"},
{file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"},
]
urllib3 = [
{file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
{file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
]
wand = [
{file = "Wand-0.6.3-py2.py3-none-any.whl", hash = "sha256:566b3d049858efa879096a7ab2e0516d67a240e6c3ffd7a267298c41e81c14b7"},
{file = "Wand-0.6.3.tar.gz", hash = "sha256:d21429288fe0de63d829dbbfb26736ebaed9fd0792c2a0dc5943c5cab803a708"},
]
werkzeug = [
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
]

22
pyproject.toml Normal file
View file

@ -0,0 +1,22 @@
[tool.poetry]
name = "paperlibrary"
version = "0.1.0"
description = ""
authors = ["Lukas Winkler <git@lw1.at>"]
[tool.poetry.dependencies]
python = "^3.8"
django = "^3.1.1"
ads = "^0.12.3"
djangoql = "^0.14.0"
alive-progress = "^1.6.1"
Wand = "^0.6.3"
psycopg2 = "^2.8.6"
django-debug-toolbar = "^3.1.1"
djangorestframework = "^3.12.1"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0a5"]
build-backend = "poetry.core.masonry.api"