mirror of
https://github.com/Findus23/PaperLibrary.git
synced 2024-09-11 06:43:47 +02:00
initial version
This commit is contained in:
commit
f9300e7753
40 changed files with 1230 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
oldcode/
|
||||
.idea
|
||||
__pycache__/
|
||||
media/
|
||||
paperlibrary/secrets.py
|
0
library/__init__.py
Normal file
0
library/__init__.py
Normal file
35
library/admin.py
Normal file
35
library/admin.py
Normal 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
5
library/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class LibraryConfig(AppConfig):
|
||||
name = 'library'
|
14
library/management/commands/create_previews.py
Normal file
14
library/management/commands/create_previews.py
Normal 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)
|
13
library/management/commands/extract_fulltext.py
Normal file
13
library/management/commands/extract_fulltext.py
Normal 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)
|
13
library/management/commands/fetch_pdf.py
Normal file
13
library/management/commands/fetch_pdf.py
Normal 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)
|
52
library/migrations/0001_initial.py
Normal file
52
library/migrations/0001_initial.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
39
library/migrations/0002_auto_20200924_1611.py
Normal file
39
library/migrations/0002_auto_20200924_1611.py
Normal 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'),
|
||||
),
|
||||
]
|
23
library/migrations/0003_auto_20200924_1612.py
Normal file
23
library/migrations/0003_auto_20200924_1612.py
Normal 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),
|
||||
),
|
||||
]
|
18
library/migrations/0004_auto_20200924_1617.py
Normal file
18
library/migrations/0004_auto_20200924_1617.py
Normal 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',
|
||||
),
|
||||
]
|
18
library/migrations/0005_auto_20200924_1625.py
Normal file
18
library/migrations/0005_auto_20200924_1625.py
Normal 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',
|
||||
),
|
||||
]
|
29
library/migrations/0006_auto_20201009_1633.py
Normal file
29
library/migrations/0006_auto_20201009_1633.py
Normal 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'),
|
||||
),
|
||||
]
|
25
library/migrations/0007_auto_20201009_1635.py
Normal file
25
library/migrations/0007_auto_20201009_1635.py
Normal 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'),
|
||||
),
|
||||
]
|
18
library/migrations/0008_auto_20201009_1635.py
Normal file
18
library/migrations/0008_auto_20201009_1635.py
Normal 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),
|
||||
),
|
||||
]
|
23
library/migrations/0009_auto_20201009_1636.py
Normal file
23
library/migrations/0009_auto_20201009_1636.py
Normal 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(),
|
||||
),
|
||||
]
|
38
library/migrations/0010_auto_20201012_1734.py
Normal file
38
library/migrations/0010_auto_20201012_1734.py
Normal 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'),
|
||||
),
|
||||
]
|
18
library/migrations/0011_pdf_updated_at.py
Normal file
18
library/migrations/0011_pdf_updated_at.py
Normal 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),
|
||||
),
|
||||
]
|
0
library/migrations/__init__.py
Normal file
0
library/migrations/__init__.py
Normal file
10
library/models/Author.py
Normal file
10
library/models/Author.py
Normal 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
|
8
library/models/DocType.py
Normal file
8
library/models/DocType.py
Normal 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
|
9
library/models/Keyword.py
Normal file
9
library/models/Keyword.py
Normal 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
16
library/models/PDF.py
Normal 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
83
library/models/Paper.py
Normal 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)
|
8
library/models/Publication.py
Normal file
8
library/models/Publication.py
Normal 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
|
6
library/models/__init__.py
Normal file
6
library/models/__init__.py
Normal 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
44
library/serializers.py
Normal 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
3
library/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
19
library/urls.py
Normal file
19
library/urls.py
Normal 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
3
library/utils/http.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from requests import Session
|
||||
|
||||
requests_session = Session()
|
47
library/utils/pdf.py
Normal file
47
library/utils/pdf.py
Normal 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
31
library/views.py
Normal 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
22
manage.py
Executable 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
0
paperlibrary/__init__.py
Normal file
16
paperlibrary/asgi.py
Normal file
16
paperlibrary/asgi.py
Normal 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
125
paperlibrary/settings.py
Normal 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
32
paperlibrary/urls.py
Normal 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
16
paperlibrary/wsgi.py
Normal 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
324
poetry.lock
generated
Normal 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
22
pyproject.toml
Normal 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"
|
Loading…
Reference in a new issue