Repos / pytaku / af8f5ac3bc
commit af8f5ac3bc6f61754f569ebe959527c4186aa2d6
Author: Bùi Thành Nhân <hi@imnhan.com>
Date:   Thu Jul 30 16:24:02 2020 +0700

    the big purge

diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 9f1ac39..0000000
--- a/.flake8
+++ /dev/null
@@ -1,6 +0,0 @@
-[flake8]
-ignore =
-    E501 # long line - taken care of by black already
-    E266 # too many leading '#' for block comment - wtf?
-    C901 # mccabe - I'll judge how complex my code is thank you very much
-    W503 # line break before binary operator - Incompatible with black/pep8
diff --git a/.gitignore b/.gitignore
index 6085df7..32201b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
 __pycache__
 *.pyc
+*.json
+*.sqlite3
 *.egg-info
 /dist/
-pytaku.conf.json
-/test_media/
-/pgdata
diff --git a/.isort.cfg b/.isort.cfg
deleted file mode 100644
index cd3bfb3..0000000
--- a/.isort.cfg
+++ /dev/null
@@ -1,3 +0,0 @@
-[settings]
-multi_line_output=3
-include_trailing_comma=True
diff --git a/.lvimrc b/.lvimrc
deleted file mode 100644
index ec1cb36..0000000
--- a/.lvimrc
+++ /dev/null
@@ -1,3 +0,0 @@
-augroup LOCAL_SETUP
-    autocmd BufNewFile,BufRead *.html set filetype=htmldjango
-augroup end
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 706c2eb..0000000
--- a/Makefile
+++ /dev/null
@@ -1,29 +0,0 @@
-dev:
-	pytaku-manage runserver
-
-test: lint djangotest
-
-djangotest:
-	pytaku-manage test --settings=pytaku.test_settings
-
-shell:
-	pytaku-manage shell
-
-lint:
-	flake8
-	black --check .
-	isort --check-only --recursive .
-
-localconfig:
-	pytaku-generate-config > pytaku.conf.json
-
-clean:
-	rm -rf dist
-	rm -rf src/pytaku.egg-info
-
-startdb:
-	docker-compose up -d
-
-destroydb:
-	docker-compose down
-	sudo rm -rf pgdata
diff --git a/README.md b/README.md
deleted file mode 100644
index fbc6196..0000000
--- a/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Pytaku
-
-Goals:
-
-- Simplest backend setup that works, but no simpler.
-  A webmaster's installation runlist should be as simple as:
-  + pip install pytaku
-  + pytaku-generate-config > pytaku.conf.json
-  + [edits pytaku.conf.json: db credentials etc.]
-  + pytaku-run
-
-
-# Dev
-
-On Arch Linux:
-
-```sh
-sudo pacman -S postgresql-libs python-poetry
-
-# spin up postgres container, because manually creating databases for dev
-# purposes is fiddly and annoying.
-docker-compose up -d
-
-# assuming pyenv and pyenv-virtualenv are already installed:
-pyenv virtualenv 3.7.7 pytaku
-pyenv activate pytaku
-poetry install
-
-# need to activate again so pyenv can make shims for installed entrypoints:
-# (see [tool.poetry.scripts] in pyproject.toml)
-pyenv deactivate && pyenv activate
-
-# generate initial config for local dev - should work out of the box with the
-# db provided by docker-compose above.
-make localconfig
-
-make dev
-```
-
-# Env-specific configs
-
-This project uses [goodconf](https://github.com/lincolnloop/goodconf).
-See what options are available at **src/pytaku/conf.py**.
-
-# Installation for actual use
-
-```sh
-pip install pytaku
-```
-
-To be expanded.
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index facf833..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-version: '3.7'
-services:
-  postgres:
-    image: "postgres:12-alpine"
-    environment:
-      - 'POSTGRES_USER=ptklocal'
-      - 'POSTGRES_PASSWORD=ptklocal'
-      - 'POSTGRES_DB=ptklocal'
-    ports:
-      - "127.0.0.1:5432:5432"
-    volumes:
-      - ./pgdata:/var/lib/postgresql/data
diff --git a/poetry.lock b/poetry.lock
deleted file mode 100644
index d391d54..0000000
--- a/poetry.lock
+++ /dev/null
@@ -1,881 +0,0 @@
-[[package]]
-category = "dev"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-name = "appdirs"
-optional = false
-python-versions = "*"
-version = "1.4.3"
-
-[[package]]
-category = "dev"
-description = "Disable App Nap on OS X 10.9"
-marker = "python_version >= \"3.4\" and sys_platform == \"darwin\""
-name = "appnope"
-optional = false
-python-versions = "*"
-version = "0.1.0"
-
-[[package]]
-category = "main"
-description = "ASGI specs, helper code, and adapters"
-name = "asgiref"
-optional = false
-python-versions = ">=3.5"
-version = "3.2.7"
-
-[package.extras]
-tests = ["pytest (>=4.3.0,<4.4.0)", "pytest-asyncio (>=0.10.0,<0.11.0)"]
-
-[[package]]
-category = "dev"
-description = "Classes Without Boilerplate"
-name = "attrs"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "19.3.0"
-
-[package.extras]
-azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
-dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
-docs = ["sphinx", "zope.interface"]
-tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
-
-[[package]]
-category = "dev"
-description = "Specifications for callback functions passed in to an API"
-marker = "python_version >= \"3.4\""
-name = "backcall"
-optional = false
-python-versions = "*"
-version = "0.1.0"
-
-[[package]]
-category = "main"
-description = "Screen-scraping library"
-name = "beautifulsoup4"
-optional = false
-python-versions = "*"
-version = "4.9.0"
-
-[package.dependencies]
-soupsieve = [">1.2", "<2.0"]
-
-[package.extras]
-html5lib = ["html5lib"]
-lxml = ["lxml"]
-
-[[package]]
-category = "dev"
-description = "The uncompromising code formatter."
-name = "black"
-optional = false
-python-versions = ">=3.6"
-version = "19.10b0"
-
-[package.dependencies]
-appdirs = "*"
-attrs = ">=18.1.0"
-click = ">=6.5"
-pathspec = ">=0.6,<1"
-regex = "*"
-toml = ">=0.9.4"
-typed-ast = ">=1.4.0"
-
-[package.extras]
-d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
-
-[[package]]
-category = "main"
-description = "Python package for providing Mozilla's CA Bundle."
-name = "certifi"
-optional = false
-python-versions = "*"
-version = "2020.4.5.1"
-
-[[package]]
-category = "main"
-description = "Universal encoding detector for Python 2 and 3"
-name = "chardet"
-optional = false
-python-versions = "*"
-version = "3.0.4"
-
-[[package]]
-category = "dev"
-description = "Composable command line interface toolkit"
-name = "click"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "7.1.2"
-
-[[package]]
-category = "dev"
-description = "Cross-platform colored terminal text."
-marker = "python_version >= \"3.4\" and sys_platform == \"win32\""
-name = "colorama"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.4.3"
-
-[[package]]
-category = "dev"
-description = "Decorators for Humans"
-marker = "python_version >= \"3.4\""
-name = "decorator"
-optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.4.2"
-
-[[package]]
-category = "main"
-description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
-name = "django"
-optional = false
-python-versions = ">=3.6"
-version = "3.0.5"
-
-[package.dependencies]
-asgiref = ">=3.2,<4.0"
-pytz = "*"
-sqlparse = ">=0.2.2"
-
-[package.extras]
-argon2 = ["argon2-cffi (>=16.1.0)"]
-bcrypt = ["bcrypt"]
-
-[[package]]
-category = "dev"
-description = "Discover and load entry points from installed packages."
-name = "entrypoints"
-optional = false
-python-versions = ">=2.7"
-version = "0.3"
-
-[[package]]
-category = "dev"
-description = "the modular source code checker: pep8, pyflakes and co"
-name = "flake8"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.7.9"
-
-[package.dependencies]
-entrypoints = ">=0.3.0,<0.4.0"
-mccabe = ">=0.6.0,<0.7.0"
-pycodestyle = ">=2.5.0,<2.6.0"
-pyflakes = ">=2.1.0,<2.2.0"
-
-[[package]]
-category = "main"
-description = "Load configuration variables from a file or environment"
-name = "goodconf"
-optional = false
-python-versions = "*"
-version = "1.0.0"
-
-[package.extras]
-maintainer = ["zest.releaser"]
-tests = ["django (<2.1)", "ruamel.yaml", "pytest (3.5.0)", "pytest-cov (2.5.1)", "pytest-mock (1.7.1)"]
-yaml = ["ruamel.yaml"]
-
-[[package]]
-category = "main"
-description = "Internationalized Domain Names in Applications (IDNA)"
-name = "idna"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.9"
-
-[[package]]
-category = "dev"
-description = "Read metadata from Python packages"
-marker = "python_version < \"3.8\""
-name = "importlib-metadata"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "1.6.0"
-
-[package.dependencies]
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["sphinx", "rst.linker"]
-testing = ["packaging", "importlib-resources"]
-
-[[package]]
-category = "dev"
-description = "IPython-enabled pdb"
-name = "ipdb"
-optional = false
-python-versions = ">=2.7"
-version = "0.13.2"
-
-[package.dependencies]
-setuptools = "*"
-
-[package.dependencies.ipython]
-python = ">=3.4"
-version = ">=5.1.0"
-
-[[package]]
-category = "dev"
-description = "IPython: Productive Interactive Computing"
-marker = "python_version >= \"3.4\""
-name = "ipython"
-optional = false
-python-versions = ">=3.6"
-version = "7.14.0"
-
-[package.dependencies]
-appnope = "*"
-backcall = "*"
-colorama = "*"
-decorator = "*"
-jedi = ">=0.10"
-pexpect = "*"
-pickleshare = "*"
-prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
-pygments = "*"
-setuptools = ">=18.5"
-traitlets = ">=4.2"
-
-[package.extras]
-all = ["nose (>=0.10.1)", "Sphinx (>=1.3)", "testpath", "nbformat", "ipywidgets", "qtconsole", "numpy (>=1.14)", "notebook", "ipyparallel", "ipykernel", "pygments", "requests", "nbconvert"]
-doc = ["Sphinx (>=1.3)"]
-kernel = ["ipykernel"]
-nbconvert = ["nbconvert"]
-nbformat = ["nbformat"]
-notebook = ["notebook", "ipywidgets"]
-parallel = ["ipyparallel"]
-qtconsole = ["qtconsole"]
-test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"]
-
-[[package]]
-category = "dev"
-description = "Vestigial utilities from IPython"
-marker = "python_version >= \"3.4\""
-name = "ipython-genutils"
-optional = false
-python-versions = "*"
-version = "0.2.0"
-
-[[package]]
-category = "dev"
-description = "A Python utility / library to sort Python imports."
-name = "isort"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "4.3.21"
-
-[package.extras]
-pipfile = ["pipreqs", "requirementslib"]
-pyproject = ["toml"]
-requirements = ["pipreqs", "pip-api"]
-xdg_home = ["appdirs (>=1.4.0)"]
-
-[[package]]
-category = "dev"
-description = "An autocompletion tool for Python that can be used for text editors."
-name = "jedi"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.15.2"
-
-[package.dependencies]
-parso = ">=0.5.2"
-
-[package.extras]
-testing = ["colorama (0.4.1)", "docopt", "pytest (>=3.9.0,<5.0.0)"]
-
-[[package]]
-category = "main"
-description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
-name = "lxml"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
-version = "4.5.0"
-
-[package.extras]
-cssselect = ["cssselect (>=0.7)"]
-html5 = ["html5lib"]
-htmlsoup = ["beautifulsoup4"]
-source = ["Cython (>=0.29.7)"]
-
-[[package]]
-category = "dev"
-description = "McCabe checker, plugin for flake8"
-name = "mccabe"
-optional = false
-python-versions = "*"
-version = "0.6.1"
-
-[[package]]
-category = "dev"
-description = "A Python Parser"
-name = "parso"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.7.0"
-
-[package.extras]
-testing = ["docopt", "pytest (>=3.0.7)"]
-
-[[package]]
-category = "dev"
-description = "Utility library for gitignore style pattern matching of file paths."
-name = "pathspec"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.8.0"
-
-[[package]]
-category = "dev"
-description = "Pexpect allows easy control of interactive console applications."
-marker = "python_version >= \"3.4\" and sys_platform != \"win32\""
-name = "pexpect"
-optional = false
-python-versions = "*"
-version = "4.8.0"
-
-[package.dependencies]
-ptyprocess = ">=0.5"
-
-[[package]]
-category = "dev"
-description = "Tiny 'shelve'-like database with concurrency support"
-marker = "python_version >= \"3.4\""
-name = "pickleshare"
-optional = false
-python-versions = "*"
-version = "0.7.5"
-
-[[package]]
-category = "dev"
-description = "plugin and hook calling mechanisms for python"
-name = "pluggy"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.13.1"
-
-[package.dependencies]
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
-
-[package.extras]
-dev = ["pre-commit", "tox"]
-
-[[package]]
-category = "dev"
-description = "Library for building powerful interactive command lines in Python"
-marker = "python_version >= \"3.4\""
-name = "prompt-toolkit"
-optional = false
-python-versions = ">=3.6.1"
-version = "3.0.5"
-
-[package.dependencies]
-wcwidth = "*"
-
-[[package]]
-category = "main"
-description = "psycopg2 - Python-PostgreSQL Database Adapter"
-name = "psycopg2"
-optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "2.8.5"
-
-[[package]]
-category = "dev"
-description = "Run a subprocess in a pseudo terminal"
-marker = "python_version >= \"3.4\" and sys_platform != \"win32\""
-name = "ptyprocess"
-optional = false
-python-versions = "*"
-version = "0.6.0"
-
-[[package]]
-category = "dev"
-description = "Python style guide checker"
-name = "pycodestyle"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.5.0"
-
-[[package]]
-category = "dev"
-description = "passive checker of Python programs"
-name = "pyflakes"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.1.1"
-
-[[package]]
-category = "dev"
-description = "Pygments is a syntax highlighting package written in Python."
-marker = "python_version >= \"3.4\""
-name = "pygments"
-optional = false
-python-versions = ">=3.5"
-version = "2.6.1"
-
-[[package]]
-category = "dev"
-description = "JSON RPC 2.0 server library"
-name = "python-jsonrpc-server"
-optional = false
-python-versions = "*"
-version = "0.3.4"
-
-[package.dependencies]
-ujson = "<=1.35"
-
-[package.extras]
-test = ["versioneer", "pylint", "pycodestyle", "pyflakes", "pytest", "mock", "pytest-cov", "coverage"]
-
-[[package]]
-category = "dev"
-description = "Python Language Server for the Language Server Protocol"
-name = "python-language-server"
-optional = false
-python-versions = "*"
-version = "0.31.10"
-
-[package.dependencies]
-jedi = ">=0.14.1,<0.16"
-pluggy = "*"
-python-jsonrpc-server = ">=0.3.2"
-ujson = "<=1.35"
-
-[package.extras]
-all = ["autopep8", "flake8", "mccabe", "pycodestyle", "pydocstyle (>=2.0.0)", "pyflakes (>=1.6.0,<2.2.0)", "pylint", "rope (>=0.10.5)", "yapf"]
-autopep8 = ["autopep8"]
-flake8 = ["flake8"]
-mccabe = ["mccabe"]
-pycodestyle = ["pycodestyle"]
-pydocstyle = ["pydocstyle (>=2.0.0)"]
-pyflakes = ["pyflakes (>=1.6.0,<2.2.0)"]
-pylint = ["pylint"]
-rope = ["rope (>0.10.5)"]
-test = ["versioneer", "pylint", "pytest", "mock", "pytest-cov", "coverage", "numpy", "pandas", "matplotlib", "pyqt5"]
-yapf = ["yapf"]
-
-[[package]]
-category = "main"
-description = "World timezone definitions, modern and historical"
-name = "pytz"
-optional = false
-python-versions = "*"
-version = "2020.1"
-
-[[package]]
-category = "dev"
-description = "Alternative regular expression module, to replace re."
-name = "regex"
-optional = false
-python-versions = "*"
-version = "2020.4.4"
-
-[[package]]
-category = "main"
-description = "Python HTTP for Humans."
-name = "requests"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.23.0"
-
-[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]]
-category = "dev"
-description = "Python 2 and 3 compatibility utilities"
-marker = "python_version >= \"3.4\""
-name = "six"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "1.14.0"
-
-[[package]]
-category = "main"
-description = "A modern CSS selector implementation for Beautiful Soup."
-name = "soupsieve"
-optional = false
-python-versions = "*"
-version = "1.9.5"
-
-[[package]]
-category = "main"
-description = "Non-validating SQL parser"
-name = "sqlparse"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.3.1"
-
-[[package]]
-category = "dev"
-description = "Python Library for Tom's Obvious, Minimal Language"
-name = "toml"
-optional = false
-python-versions = "*"
-version = "0.10.0"
-
-[[package]]
-category = "dev"
-description = "Traitlets Python config system"
-marker = "python_version >= \"3.4\""
-name = "traitlets"
-optional = false
-python-versions = "*"
-version = "4.3.3"
-
-[package.dependencies]
-decorator = "*"
-ipython-genutils = "*"
-six = "*"
-
-[package.extras]
-test = ["pytest", "mock"]
-
-[[package]]
-category = "dev"
-description = "a fork of Python 2 and 3 ast modules with type comment support"
-name = "typed-ast"
-optional = false
-python-versions = "*"
-version = "1.4.1"
-
-[[package]]
-category = "dev"
-description = "Ultra fast JSON encoder and decoder for Python"
-marker = "platform_system != \"Windows\""
-name = "ujson"
-optional = false
-python-versions = "*"
-version = "1.35"
-
-[[package]]
-category = "main"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-name = "urllib3"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "1.25.9"
-
-[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]]
-category = "dev"
-description = "Measures number of Terminal column cells of wide-character codes"
-marker = "python_version >= \"3.4\""
-name = "wcwidth"
-optional = false
-python-versions = "*"
-version = "0.1.9"
-
-[[package]]
-category = "dev"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-marker = "python_version < \"3.8\""
-name = "zipp"
-optional = false
-python-versions = ">=3.6"
-version = "3.1.0"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["jaraco.itertools", "func-timeout"]
-
-[metadata]
-content-hash = "785d3de004285b89514d1f2fb02101901499db037100a2841959771244936f57"
-python-versions = "^3.7"
-
-[metadata.files]
-appdirs = [
-    {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
-    {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
-]
-appnope = [
-    {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
-    {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
-]
-asgiref = [
-    {file = "asgiref-3.2.7-py2.py3-none-any.whl", hash = "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"},
-    {file = "asgiref-3.2.7.tar.gz", hash = "sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5"},
-]
-attrs = [
-    {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
-    {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
-]
-backcall = [
-    {file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"},
-    {file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"},
-]
-beautifulsoup4 = [
-    {file = "beautifulsoup4-4.9.0-py2-none-any.whl", hash = "sha256:a4bbe77fd30670455c5296242967a123ec28c37e9702a8a81bd2f20a4baf0368"},
-    {file = "beautifulsoup4-4.9.0-py3-none-any.whl", hash = "sha256:d4e96ac9b0c3a6d3f0caae2e4124e6055c5dcafde8e2f831ff194c104f0775a0"},
-    {file = "beautifulsoup4-4.9.0.tar.gz", hash = "sha256:594ca51a10d2b3443cbac41214e12dbb2a1cd57e1a7344659849e2e20ba6a8d8"},
-]
-black = [
-    {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
-    {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
-]
-certifi = [
-    {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"},
-    {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"},
-]
-chardet = [
-    {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
-    {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
-]
-click = [
-    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
-    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
-]
-colorama = [
-    {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
-    {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
-]
-decorator = [
-    {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
-    {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
-]
-django = [
-    {file = "Django-3.0.5-py3-none-any.whl", hash = "sha256:642d8eceab321ca743ae71e0f985ff8fdca59f07aab3a9fb362c617d23e33a76"},
-    {file = "Django-3.0.5.tar.gz", hash = "sha256:d4666c2edefa38c5ede0ec1655424c56dc47ceb04b6d8d62a7eac09db89545c1"},
-]
-entrypoints = [
-    {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
-    {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
-]
-flake8 = [
-    {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"},
-    {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"},
-]
-goodconf = [
-    {file = "goodconf-1.0.0-py2.py3-none-any.whl", hash = "sha256:beb2f9ed734015e1becd4338d8b1e363cf51fb52e2f794f4e85e8c59d097442e"},
-    {file = "goodconf-1.0.0.tar.gz", hash = "sha256:2c33460b4d9859ffacff32355b7effb1a922a16c1d54e8edd6452503bd8e809b"},
-]
-idna = [
-    {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
-    {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
-]
-importlib-metadata = [
-    {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"},
-    {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"},
-]
-ipdb = [
-    {file = "ipdb-0.13.2.tar.gz", hash = "sha256:77fb1c2a6fccdfee0136078c9ed6fe547ab00db00bebff181f1e8c9e13418d49"},
-]
-ipython = [
-    {file = "ipython-7.14.0-py3-none-any.whl", hash = "sha256:5b241b84bbf0eb085d43ae9d46adf38a13b45929ca7774a740990c2c242534bb"},
-    {file = "ipython-7.14.0.tar.gz", hash = "sha256:f0126781d0f959da852fb3089e170ed807388e986a8dd4e6ac44855845b0fb1c"},
-]
-ipython-genutils = [
-    {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
-    {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
-]
-isort = [
-    {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
-    {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
-]
-jedi = [
-    {file = "jedi-0.15.2-py2.py3-none-any.whl", hash = "sha256:1349c1e8c107095a55386628bb3b2a79422f3a2cab8381e34ce19909e0cf5064"},
-    {file = "jedi-0.15.2.tar.gz", hash = "sha256:e909527104a903606dd63bea6e8e888833f0ef087057829b89a18364a856f807"},
-]
-lxml = [
-    {file = "lxml-4.5.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0701f7965903a1c3f6f09328c1278ac0eee8f56f244e66af79cb224b7ef3801c"},
-    {file = "lxml-4.5.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:06d4e0bbb1d62e38ae6118406d7cdb4693a3fa34ee3762238bcb96c9e36a93cd"},
-    {file = "lxml-4.5.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5828c7f3e615f3975d48f40d4fe66e8a7b25f16b5e5705ffe1d22e43fb1f6261"},
-    {file = "lxml-4.5.0-cp27-cp27m-win32.whl", hash = "sha256:afdb34b715daf814d1abea0317b6d672476b498472f1e5aacbadc34ebbc26e89"},
-    {file = "lxml-4.5.0-cp27-cp27m-win_amd64.whl", hash = "sha256:585c0869f75577ac7a8ff38d08f7aac9033da2c41c11352ebf86a04652758b7a"},
-    {file = "lxml-4.5.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:8a0ebda56ebca1a83eb2d1ac266649b80af8dd4b4a3502b2c1e09ac2f88fe128"},
-    {file = "lxml-4.5.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:fe976a0f1ef09b3638778024ab9fb8cde3118f203364212c198f71341c0715ca"},
-    {file = "lxml-4.5.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7bc1b221e7867f2e7ff1933165c0cec7153dce93d0cdba6554b42a8beb687bdb"},
-    {file = "lxml-4.5.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d068f55bda3c2c3fcaec24bd083d9e2eede32c583faf084d6e4b9daaea77dde8"},
-    {file = "lxml-4.5.0-cp35-cp35m-win32.whl", hash = "sha256:e4aa948eb15018a657702fee0b9db47e908491c64d36b4a90f59a64741516e77"},
-    {file = "lxml-4.5.0-cp35-cp35m-win_amd64.whl", hash = "sha256:1f2c4ec372bf1c4a2c7e4bb20845e8bcf8050365189d86806bad1e3ae473d081"},
-    {file = "lxml-4.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5d467ce9c5d35b3bcc7172c06320dddb275fea6ac2037f72f0a4d7472035cea9"},
-    {file = "lxml-4.5.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:95e67224815ef86924fbc2b71a9dbd1f7262384bca4bc4793645794ac4200717"},
-    {file = "lxml-4.5.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ebec08091a22c2be870890913bdadd86fcd8e9f0f22bcb398abd3af914690c15"},
-    {file = "lxml-4.5.0-cp36-cp36m-win32.whl", hash = "sha256:deadf4df349d1dcd7b2853a2c8796593cc346600726eff680ed8ed11812382a7"},
-    {file = "lxml-4.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f2b74784ed7e0bc2d02bd53e48ad6ba523c9b36c194260b7a5045071abbb1012"},
-    {file = "lxml-4.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa071559f14bd1e92077b1b5f6c22cf09756c6de7139370249eb372854ce51e6"},
-    {file = "lxml-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:edc15fcfd77395e24543be48871c251f38132bb834d9fdfdad756adb6ea37679"},
-    {file = "lxml-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd52e796fee7171c4361d441796b64df1acfceb51f29e545e812f16d023c4bbc"},
-    {file = "lxml-4.5.0-cp37-cp37m-win32.whl", hash = "sha256:90ed0e36455a81b25b7034038e40880189169c308a3df360861ad74da7b68c1a"},
-    {file = "lxml-4.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:df533af6f88080419c5a604d0d63b2c33b1c0c4409aba7d0cb6de305147ea8c8"},
-    {file = "lxml-4.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4b2c63cc7963aedd08a5f5a454c9f67251b1ac9e22fd9d72836206c42dc2a72"},
-    {file = "lxml-4.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e5d842c73e4ef6ed8c1bd77806bf84a7cb535f9c0cf9b2c74d02ebda310070e1"},
-    {file = "lxml-4.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:63dbc21efd7e822c11d5ddbedbbb08cd11a41e0032e382a0fd59b0b08e405a3a"},
-    {file = "lxml-4.5.0-cp38-cp38-win32.whl", hash = "sha256:4235bc124fdcf611d02047d7034164897ade13046bda967768836629bc62784f"},
-    {file = "lxml-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:d5b3c4b7edd2e770375a01139be11307f04341ec709cf724e0f26ebb1eef12c3"},
-    {file = "lxml-4.5.0.tar.gz", hash = "sha256:8620ce80f50d023d414183bf90cc2576c2837b88e00bea3f33ad2630133bbb60"},
-]
-mccabe = [
-    {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
-    {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
-]
-parso = [
-    {file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"},
-    {file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"},
-]
-pathspec = [
-    {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
-    {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
-]
-pexpect = [
-    {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
-    {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
-]
-pickleshare = [
-    {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
-    {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
-]
-pluggy = [
-    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
-    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
-]
-prompt-toolkit = [
-    {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"},
-    {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"},
-]
-psycopg2 = [
-    {file = "psycopg2-2.8.5-cp27-cp27m-win32.whl", hash = "sha256:a0984ff49e176062fcdc8a5a2a670c9bb1704a2f69548bce8f8a7bad41c661bf"},
-    {file = "psycopg2-2.8.5-cp27-cp27m-win_amd64.whl", hash = "sha256:acf56d564e443e3dea152efe972b1434058244298a94348fc518d6dd6a9fb0bb"},
-    {file = "psycopg2-2.8.5-cp34-cp34m-win32.whl", hash = "sha256:440a3ea2c955e89321a138eb7582aa1d22fe286c7d65e26a2c5411af0a88ae72"},
-    {file = "psycopg2-2.8.5-cp34-cp34m-win_amd64.whl", hash = "sha256:6b306dae53ec7f4f67a10942cf8ac85de930ea90e9903e2df4001f69b7833f7e"},
-    {file = "psycopg2-2.8.5-cp35-cp35m-win32.whl", hash = "sha256:d3b29d717d39d3580efd760a9a46a7418408acebbb784717c90d708c9ed5f055"},
-    {file = "psycopg2-2.8.5-cp35-cp35m-win_amd64.whl", hash = "sha256:6a471d4d2a6f14c97a882e8d3124869bc623f3df6177eefe02994ea41fd45b52"},
-    {file = "psycopg2-2.8.5-cp36-cp36m-win32.whl", hash = "sha256:27c633f2d5db0fc27b51f1b08f410715b59fa3802987aec91aeb8f562724e95c"},
-    {file = "psycopg2-2.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2df2bf1b87305bd95eb3ac666ee1f00a9c83d10927b8144e8e39644218f4cf81"},
-    {file = "psycopg2-2.8.5-cp37-cp37m-win32.whl", hash = "sha256:ac5b23d0199c012ad91ed1bbb971b7666da651c6371529b1be8cbe2a7bf3c3a9"},
-    {file = "psycopg2-2.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2c0afb40cfb4d53487ee2ebe128649028c9a78d2476d14a67781e45dc287f080"},
-    {file = "psycopg2-2.8.5-cp38-cp38-win32.whl", hash = "sha256:2327bf42c1744a434ed8ed0bbaa9168cac7ee5a22a9001f6fc85c33b8a4a14b7"},
-    {file = "psycopg2-2.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535"},
-    {file = "psycopg2-2.8.5.tar.gz", hash = "sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818"},
-]
-ptyprocess = [
-    {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
-    {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
-]
-pycodestyle = [
-    {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"},
-    {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"},
-]
-pyflakes = [
-    {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"},
-    {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"},
-]
-pygments = [
-    {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
-    {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
-]
-python-jsonrpc-server = [
-    {file = "python-jsonrpc-server-0.3.4.tar.gz", hash = "sha256:c73bf5495c9dd4d2f902755bedeb6da5afe778e0beee82f0e195c4655352fe37"},
-    {file = "python_jsonrpc_server-0.3.4-py3-none-any.whl", hash = "sha256:1f85f75f37f923149cc0aa078474b6df55b708e82ed819ca8846a65d7d0ada7f"},
-]
-python-language-server = [
-    {file = "python-language-server-0.31.10.tar.gz", hash = "sha256:6c96567158377a0c725625ef6e24e7b655dcfab95080b463023b6680d1766d4f"},
-    {file = "python_language_server-0.31.10-py3-none-any.whl", hash = "sha256:20a24b4793b804b81c72fe076bd269f2db8cb81f91579c4892602ab02f1a3d62"},
-]
-pytz = [
-    {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
-    {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
-]
-regex = [
-    {file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"},
-    {file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"},
-    {file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"},
-    {file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"},
-    {file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"},
-    {file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"},
-    {file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"},
-    {file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"},
-    {file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"},
-]
-requests = [
-    {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
-    {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
-]
-six = [
-    {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
-    {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
-]
-soupsieve = [
-    {file = "soupsieve-1.9.5-py2.py3-none-any.whl", hash = "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5"},
-    {file = "soupsieve-1.9.5.tar.gz", hash = "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"},
-]
-sqlparse = [
-    {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"},
-    {file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"},
-]
-toml = [
-    {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
-    {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
-    {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
-]
-traitlets = [
-    {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
-    {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
-]
-typed-ast = [
-    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
-    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
-    {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
-    {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
-    {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
-    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
-    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
-    {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
-    {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
-    {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
-    {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
-]
-ujson = [
-    {file = "ujson-1.35.tar.gz", hash = "sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"},
-]
-urllib3 = [
-    {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
-    {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
-]
-wcwidth = [
-    {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"},
-    {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"},
-]
-zipp = [
-    {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
-    {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
-]
diff --git a/pyproject.toml b/pyproject.toml
index fcd37fb..512dbea 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,34 +1,14 @@
 [tool.poetry]
 name = "pytaku"
-version = "0.1.0"
+version = "0.1.1"
 description = ""
-license = "MIT"
 authors = ["Bùi Thành Nhân <hi@imnhan.com>"]
-packages = [
-    { include = "pytaku", from = "src" },
-    { include = "pytaku_web", from = "src" },
-]
-
-[tool.poetry.scripts]
-pytaku-manage = "pytaku:manage"
-pytaku-generate-config = "pytaku:generate_config"
-pytaku-generate-psql-envars = "pytaku:generate_psql_envars"
+license = "AGPL-3.0-only"
 
 [tool.poetry.dependencies]
 python = "^3.7"
-django = "^3.0.5"
-psycopg2 = "^2.8.5"
-goodconf = "^1.0.0"
-requests = "^2.23.0"
-beautifulsoup4 = "^4.9.0"
-lxml = "^4.5.0"
 
 [tool.poetry.dev-dependencies]
-python-language-server = "^0.31.10"
-flake8 = "^3.7.9"
-ipdb = "^0.13.2"
-black = "^19.10b0"
-isort = "^4.3.21"
 
 [build-system]
 requires = ["poetry>=0.12"]
diff --git a/src/__init__.py b/src/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pytaku/__init__.py b/src/pytaku/__init__.py
deleted file mode 100644
index 81de581..0000000
--- a/src/pytaku/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import os
-
-from pytaku.conf import config
-
-
-def manage():
-    """Django's command-line utility for administrative tasks."""
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pytaku.settings")
-    config.django_manage()  # use goodconf to run a monkey-patched "manage.py"
-
-
-def generate_config():
-    print(config.generate_json(DEBUG=True))
-
-
-def generate_psql_envars():
-    """
-    Outputs shell statements to set Postgres connection envars.
-    Usage:
-        pytaku-generate-psql-envars | source
-        psql  # or even better, pgcli
-        # should drop you into the pytaku db shell
-    """
-    print(
-        f"""\
-export PGHOST='{config.DB_HOST}'
-export PGPORT='{config.DB_PORT}'
-export PGDATABASE='{config.DB_NAME}'
-export PGUSER='{config.DB_USER}'
-export PGPASSWORD='{config.DB_PASSWORD}'
-"""
-    )
diff --git a/src/pytaku/conf.py b/src/pytaku/conf.py
deleted file mode 100644
index ebe84b7..0000000
--- a/src/pytaku/conf.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import secrets
-
-from goodconf import GoodConf, Value
-
-
-class Config(GoodConf):
-    DEBUG = Value(default=False)
-    SECRET_KEY = Value(initial=lambda: secrets.token_urlsafe(60))
-    DB_HOST = Value(default="127.0.0.1")
-    DB_PORT = Value(default="5432")
-    DB_NAME = Value(default="ptklocal")
-    DB_USER = Value(default="ptklocal")
-    DB_PASSWORD = Value(default="ptklocal")
-
-    # AWS creds used for the S3 storage backend for uploaded pages
-    AWS_ACCESS_KEY_ID = Value()
-    AWS_SECRET_ACCESS_KEY = Value()
-    AWS_STORAGE_BUCKET_NAME = Value(default="pytaku")
-    AWS_DEFAULT_ACL = Value(default="public-read")
-    AWS_S3_ENDPOINT_URL = Value()
-
-    # Makeshift https "proxy" running on google cloud functions:
-    GCF_PROXY_PROJECT_NAME = Value()
-    GCF_PROXY_FUNCTION_NAME = Value()
-    GCF_PROXY_PASSWORD = Value()
-    GCF_PROXY_REGIONS = Value(
-        default=[
-            "asia-east2",
-            "asia-northeast1",
-            "europe-west1",
-            "europe-west2",
-            "us-central1",
-            "us-east1",
-            "us-east4",
-        ]
-    )
-
-
-config = Config(default_files=["pytaku.conf.json"])
diff --git a/src/pytaku/settings.py b/src/pytaku/settings.py
deleted file mode 100644
index 3d3a2fc..0000000
--- a/src/pytaku/settings.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""
-Django settings for pytaku project.
-
-Generated by 'django-admin startproject' using Django 3.0.5.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/3.0/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/3.0/ref/settings/
-"""
-
-import os
-
-from .conf import config
-
-config.load()
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = config.SECRET_KEY
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = config.DEBUG
-
-ALLOWED_HOSTS = []
-
-
-# Application definition
-
-INSTALLED_APPS = [
-    "django.contrib.admin",
-    "django.contrib.auth",
-    "django.contrib.contenttypes",
-    "django.contrib.sessions",
-    "django.contrib.messages",
-    "django.contrib.staticfiles",
-    "pytaku_web",
-    "pytaku_scraper",
-]
-
-MIDDLEWARE = [
-    "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 = "pytaku.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 = "pytaku.wsgi.application"
-
-
-# Database
-# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
-
-DATABASES = {
-    "default": {
-        "ENGINE": "django.db.backends.postgresql",
-        "NAME": config.DB_NAME,
-        "USER": config.DB_USER,
-        "PASSWORD": config.DB_PASSWORD,
-        "HOST": config.DB_HOST,
-        "PORT": config.DB_PORT,
-    }
-}
-
-
-# Password validation
-# https://docs.djangoproject.com/en/3.0/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.0/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.0/howto/static-files/
-
-STATIC_URL = "/static/"
-
-# TODO: I really should support simple plain filesystem storage backend too.
-# Forcing people to set up S3(-compatible) storage as well ain't nice.
-DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
-AWS_ACCESS_KEY_ID = config.AWS_ACCESS_KEY_ID
-AWS_SECRET_ACCESS_KEY = config.AWS_SECRET_ACCESS_KEY
-AWS_STORAGE_BUCKET_NAME = config.AWS_STORAGE_BUCKET_NAME
-AWS_S3_ENDPOINT_URL = config.AWS_S3_ENDPOINT_URL or None
-AWS_DEFAULT_ACL = None  # use bucket's default policy
-
-
-GCF_PROXY_PROJECT_NAME = config.GCF_PROXY_PROJECT_NAME
-GCF_PROXY_FUNCTION_NAME = config.GCF_PROXY_FUNCTION_NAME
-GCF_PROXY_PASSWORD = config.GCF_PROXY_PASSWORD
-GCF_PROXY_REGIONS = config.GCF_PROXY_REGIONS
diff --git a/src/pytaku/test_settings.py b/src/pytaku/test_settings.py
deleted file mode 100644
index 9f5b010..0000000
--- a/src/pytaku/test_settings.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .settings import *  # noqa
-
-DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
-MEDIA_ROOT = "test_media"
diff --git a/src/pytaku/urls.py b/src/pytaku/urls.py
deleted file mode 100644
index f121eed..0000000
--- a/src/pytaku/urls.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""pytaku URL Configuration
-
-The `urlpatterns` list routes URLs to views. For more information please see:
-    https://docs.djangoproject.com/en/3.0/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'))
-"""
-from django.contrib import admin
-from django.urls import path
-
-urlpatterns = [
-    path("admin/", admin.site.urls),
-]
diff --git a/src/pytaku/wsgi.py b/src/pytaku/wsgi.py
deleted file mode 100644
index 4c689ae..0000000
--- a/src/pytaku/wsgi.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
-WSGI config for pytaku 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.0/howto/deployment/wsgi/
-"""
-
-import os
-
-from django.core.wsgi import get_wsgi_application
-
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pytaku.settings")
-
-application = get_wsgi_application()
diff --git a/src/pytaku_scraper/__init__.py b/src/pytaku_scraper/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pytaku_scraper/commands.py b/src/pytaku_scraper/commands.py
deleted file mode 100644
index 3b4d0fa..0000000
--- a/src/pytaku_scraper/commands.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from django.db import transaction
-from django.utils import timezone
-
-from .httpclient import HttpClient
-from .models import DownloadResult, TaskQueue
-from .sites.mangadex import get_latest_id
-
-
-def put_download_tasks():
-    latest_id = get_latest_id()
-    print("Found latest title id:", latest_id)
-
-    result = TaskQueue.put_bulk(
-        "download",
-        [
-            {"url": f"https://mangadex.org/api/?type=manga&id={i}"}
-            for i in range(1, latest_id + 1)
-        ],
-    )
-    print(f'Successfully put {len(result)} "download" tasks.')
-
-
-def download_worker(proxy_index):
-    http = HttpClient(proxy_index)
-    while True:
-        with transaction.atomic():
-            task = TaskQueue.pop("download")
-            task_id = task.id
-            print(f"Processing task {task_id}: {task.payload}")
-            resp = http.proxied_get(task.payload["url"])
-            assert resp.status_code in (200, 404), f"Unexpected DL error: {resp.text}"
-
-            DownloadResult.objects.update_or_create(
-                url=task.payload["url"],
-                method="get",
-                defaults={
-                    "downloaded_at": timezone.now(),
-                    "resp_body": resp.text,
-                    "resp_status": resp.status_code,
-                },
-            )
-
-            task.finish()
-            print("Done task", task_id)
-
-
-def purge_queue(task_name):
-    count, _ = TaskQueue.objects.filter(name=task_name).delete()
-    print(f'Deleted {count} "{task_name}" tasks.')
diff --git a/src/pytaku_scraper/httpclient.py b/src/pytaku_scraper/httpclient.py
deleted file mode 100644
index bf9f2d7..0000000
--- a/src/pytaku_scraper/httpclient.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import requests
-from django.conf import settings
-
-HEADERS = {
-    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
-}
-
-
-class HttpClient:
-    def __init__(self, proxy_index=0):
-        proxy_region = settings.GCF_PROXY_REGIONS[proxy_index]
-        self.proxy_url = "https://{}-{}.cloudfunctions.net/{}".format(
-            proxy_region,
-            settings.GCF_PROXY_PROJECT_NAME,
-            settings.GCF_PROXY_FUNCTION_NAME,
-        )
-        print("HttpClient proxy region:", proxy_region)
-
-    def proxied_get(self, url, timeout=10):
-        return requests.post(
-            self.proxy_url,
-            json={
-                "url": url,
-                "method": "get",
-                "body": None,
-                "headers": HEADERS,
-                "password": settings.GCF_PROXY_PASSWORD,
-            },
-            timeout=timeout,
-        )
diff --git a/src/pytaku_scraper/management/commands/download_worker.py b/src/pytaku_scraper/management/commands/download_worker.py
deleted file mode 100644
index bf357b0..0000000
--- a/src/pytaku_scraper/management/commands/download_worker.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from pytaku_scraper.commands import download_worker
-
-
-class Command(BaseCommand):
-    help = "Download worker. Run as many as needed."
-
-    def add_arguments(self, parser):
-        parser.add_argument("proxy_index", type=int)
-
-    def handle(self, *args, **options):
-        download_worker(options["proxy_index"])
diff --git a/src/pytaku_scraper/management/commands/purge_queue.py b/src/pytaku_scraper/management/commands/purge_queue.py
deleted file mode 100644
index e21f547..0000000
--- a/src/pytaku_scraper/management/commands/purge_queue.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from pytaku_scraper.commands import purge_queue
-
-
-class Command(BaseCommand):
-    help = "Delete all tasks in a queue."
-
-    def add_arguments(self, parser):
-        parser.add_argument("task")
-
-    def handle(self, *args, **options):
-        task = options["task"]
-        purge_queue(task)
diff --git a/src/pytaku_scraper/management/commands/put_download_tasks.py b/src/pytaku_scraper/management/commands/put_download_tasks.py
deleted file mode 100644
index 689477f..0000000
--- a/src/pytaku_scraper/management/commands/put_download_tasks.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from pytaku_scraper.commands import put_download_tasks
-
-
-class Command(BaseCommand):
-    help = "Puts download tasks for mangadex titles."
-
-    def handle(self, *args, **options):
-        put_download_tasks()
diff --git a/src/pytaku_scraper/migrations/0001_initial.py b/src/pytaku_scraper/migrations/0001_initial.py
deleted file mode 100644
index 75291d0..0000000
--- a/src/pytaku_scraper/migrations/0001_initial.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Generated by Django 3.0.5 on 2020-05-24 11:32
-
-import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='ScrapeAttempt',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('scraped_at', models.DateTimeField(auto_now_add=True)),
-                ('url', models.CharField(max_length=1024)),
-                ('method', models.CharField(max_length=7)),
-                ('headers', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
-                ('body', models.TextField()),
-                ('resp_body', models.TextField()),
-                ('resp_status', models.IntegerField()),
-            ],
-            options={
-                'db_table': 'scrape_attempt',
-            },
-        ),
-        migrations.CreateModel(
-            name='TaskQueue',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('created_at', models.DateTimeField(auto_now_add=True)),
-                ('name', models.CharField(choices=[('Scrape', 'scrape')], max_length=100)),
-                ('payload', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
-            ],
-            options={
-                'db_table': 'task_queue',
-            },
-        ),
-    ]
diff --git a/src/pytaku_scraper/migrations/0002_auto_20200524_1413.py b/src/pytaku_scraper/migrations/0002_auto_20200524_1413.py
deleted file mode 100644
index 181b284..0000000
--- a/src/pytaku_scraper/migrations/0002_auto_20200524_1413.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Generated by Django 3.0.5 on 2020-05-24 14:13
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('pytaku_scraper', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='DownloadResult',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('created_at', models.DateTimeField(auto_now_add=True)),
-                ('url', models.CharField(max_length=1024)),
-                ('method', models.CharField(default='get', max_length=7)),
-                ('resp_body', models.TextField()),
-                ('resp_status', models.IntegerField()),
-            ],
-            options={
-                'db_table': 'download_result',
-            },
-        ),
-        migrations.DeleteModel(
-            name='ScrapeAttempt',
-        ),
-        migrations.AddConstraint(
-            model_name='downloadresult',
-            constraint=models.UniqueConstraint(fields=('url', 'method'), name='unique_url_method'),
-        ),
-    ]
diff --git a/src/pytaku_scraper/migrations/0003_auto_20200524_1508.py b/src/pytaku_scraper/migrations/0003_auto_20200524_1508.py
deleted file mode 100644
index 66b7737..0000000
--- a/src/pytaku_scraper/migrations/0003_auto_20200524_1508.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Generated by Django 3.0.5 on 2020-05-24 15:08
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('pytaku_scraper', '0002_auto_20200524_1413'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='taskqueue',
-            name='name',
-            field=models.CharField(choices=[('Download', 'download')], max_length=100),
-        ),
-        migrations.AddConstraint(
-            model_name='taskqueue',
-            constraint=models.UniqueConstraint(fields=('name', 'payload'), name='unique_url_payload'),
-        ),
-    ]
diff --git a/src/pytaku_scraper/migrations/0004_auto_20200524_1538.py b/src/pytaku_scraper/migrations/0004_auto_20200524_1538.py
deleted file mode 100644
index 1a70a30..0000000
--- a/src/pytaku_scraper/migrations/0004_auto_20200524_1538.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 3.0.5 on 2020-05-24 15:38
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('pytaku_scraper', '0003_auto_20200524_1508'),
-    ]
-
-    operations = [
-        migrations.RenameField(
-            model_name='downloadresult',
-            old_name='created_at',
-            new_name='downloaded_at',
-        ),
-    ]
diff --git a/src/pytaku_scraper/migrations/__init__.py b/src/pytaku_scraper/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pytaku_scraper/models.py b/src/pytaku_scraper/models.py
deleted file mode 100644
index 4ac7710..0000000
--- a/src/pytaku_scraper/models.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from django.contrib.postgres.fields import JSONField
-from django.db import models
-
-QUEUE_NAMES = [("Download", "download")]
-
-
-class TaskQueue(models.Model):
-    """
-    Simple postgres-backed task queue.
-    Supports concurrent consumers thanks to SELECT FOR UPDATE SKIP LOCKED.
-
-    Usage:
-        - Create task: TaskQueue.put() or TaskQueue.put_bulk()
-        - Consume task:
-            with transaction.atomic():
-                task = TaskQueue.pop()
-                # do work with task
-                task.finish()
-        - If anything goes wrong between pop() and finish(),
-          the task is automatically put back in the queue.
-    """
-
-    class Meta:
-        db_table = "task_queue"
-        constraints = [
-            models.UniqueConstraint(
-                fields=["name", "payload"], name="unique_url_payload"
-            )
-        ]
-
-    created_at = models.DateTimeField(auto_now_add=True)
-    name = models.CharField(max_length=100, choices=QUEUE_NAMES)
-    payload = JSONField(default=dict)
-
-    @classmethod
-    def put(cls, name, payload):
-        return cls.objects.create(name=name, payload=payload)
-
-    @classmethod
-    def put_bulk(cls, name, payloads):
-        return cls.objects.bulk_create(
-            [cls(name=name, payload=payload) for payload in payloads],
-            ignore_conflicts=True,
-        )
-
-    @classmethod
-    def pop(cls, name):
-        """
-        SELECT FOR UPDATE SKIP LOCKED.
-        Must be run inside a transaction.
-
-        Remember to call instance.finish() once you're done.
-        """
-        return (
-            TaskQueue.objects.select_for_update(skip_locked=True)
-            .filter(name=name)
-            .order_by("id")
-            .first()
-        )
-
-    def finish(self):
-        return self.delete()
-
-
-class DownloadResult(models.Model):
-    class Meta:
-        db_table = "download_result"
-        constraints = [
-            models.UniqueConstraint(fields=["url", "method"], name="unique_url_method")
-        ]
-
-    downloaded_at = models.DateTimeField(auto_now_add=True)
-
-    url = models.CharField(max_length=1024)
-    method = models.CharField(max_length=7, default="get")
-
-    resp_body = models.TextField()
-    resp_status = models.IntegerField()
diff --git a/src/pytaku_scraper/sites/__init__.py b/src/pytaku_scraper/sites/__init__.py
deleted file mode 100644
index 8f796a6..0000000
--- a/src/pytaku_scraper/sites/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from importlib import import_module
-from urllib.parse import urlparse
-
-available_sites = {"mangadex.org": ".mangadex"}
-
-
-# return suitable module from url
-def get_site(url):
-    netloc = urlparse(url).netloc
-    module_name = available_sites.get(netloc)
-    return (
-        None
-        if module_name is None
-        else import_module(module_name, "pytaku_web.scraper")
-    )
diff --git a/src/pytaku_scraper/sites/mangadex.py b/src/pytaku_scraper/sites/mangadex.py
deleted file mode 100644
index 8ce7558..0000000
--- a/src/pytaku_scraper/sites/mangadex.py
+++ /dev/null
@@ -1,190 +0,0 @@
-import itertools
-import re
-
-import requests
-from attr import attrib, attrs
-from bs4 import BeautifulSoup
-
-DOMAIN = "https://mangadex.org"
-API_URL = "https://mangadex.org/api/"
-
-session = requests.Session()
-session.headers = {
-    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"
-}
-
-
-@attrs(slots=True, kw_only=True)
-class Title(object):
-    url = attrib(type=str)
-    name = attrib(type=str)
-    alt_names = attrib(type=list)
-    authors = attrib(type=list)
-    tags = attrib(type=list)
-    publication_status = attrib(type=str)
-    descriptions = attrib(type=list)
-    chapters = attrib(type=list)
-
-
-@attrs(slots=True, kw_only=True)
-class Chapter(object):
-    name = attrib(type=str)
-    pages = attrib(type=list)
-
-
-def title_url_from_id(original_id):
-    return f"{DOMAIN}/title/{original_id}"
-
-
-def chapter_url_from_id(original_id):
-    return f"{DOMAIN}/chapter/{original_id}"
-
-
-_chapter_url_regex = re.compile("^" + DOMAIN + r"/chapter/(\d+)/?$")
-
-
-def chapter_id_from_url(url):
-    match = _chapter_url_regex.match(url)
-    return match.group(1) if match else None
-
-
-_title_url_regex = re.compile("^" + DOMAIN + r"/title/(\d+)(/.*)?$")
-
-
-def title_id_from_url(url):
-    match = _title_url_regex.match(url)
-    return match.group(1) if match else None
-
-
-def scrape_title(original_id):
-    source_url = title_url_from_id(original_id)
-    html = session.get(source_url).text
-    soup = BeautifulSoup(html, "lxml")
-
-    url = soup.select('link[rel="canonical"]')[0].attrs["href"]
-    name = soup.select(".card-header span.mx-1")[0].text
-
-    alt_names = _get_next_column_of(soup, "Alt name(s)", "li")
-    authors = _get_next_column_of(soup, "Author", "a")
-    artists = _get_next_column_of(soup, "Artist", "a")
-    genres = _get_next_column_of(soup, "Genre", "a")
-    themes = _get_next_column_of(soup, "Theme", "a")
-    pub_status = _get_next_column_of(soup, "Pub. status")
-
-    raw_descs = _get_next_column_of(soup, "Description")
-    descriptions = [desc.strip() for desc in raw_descs.split("\n\n") if desc.strip()]
-
-    chapters = _get_chapters(soup)
-    return {
-        "url": url,
-        "name": name,
-        "alt_names": alt_names,
-        "authors": sorted(set(authors + artists)),
-        "tags": sorted(set(genres + themes)),
-        "publication_status": pub_status,
-        "descriptions": descriptions,
-        "chapters": chapters,
-    }
-
-
-def _get_next_column_of(soup, query, subtag=None):
-    label = soup.find("div", string=f"{query}:")
-    if label is None:
-        return None if subtag is None else []
-
-    # newlines also count as sibling, so we have to filter them out:
-    siblings = [sibl for sibl in label.next_siblings if sibl.name is not None]
-    if len(siblings) != 1:
-        raise Exception(f'Unexpected siblings found for "{query}": {siblings}')
-    next_column = siblings[0]
-    return _get_column_content(next_column, subtag)
-
-
-def _get_column_content(column, subtag):
-    if subtag is None:
-        return column.text.strip()
-    else:
-        return [
-            child.text.strip()
-            for child in column.find_all(subtag)
-            if child.text.strip()
-        ]
-
-
-def _get_chapters(soup):
-    chapter_page_urls = _chapter_page_urls(soup)
-    chapter_page_soups = [
-        BeautifulSoup(session.get(f"{DOMAIN}{url}").text, "lxml")
-        for url in chapter_page_urls
-    ]
-    chapter_page_soups.insert(0, soup)  # saves us 1 http request :)
-    chapters = [_chapters_data(soup) for soup in chapter_page_soups]
-    return list(itertools.chain(*chapters))  # flatten list of list
-
-
-def _chapter_page_urls(soup):
-    """
-    Excluding first page because we already have it
-    """
-    last_chapter_link_tag = soup.find(title="Jump to last page")
-    if not last_chapter_link_tag:
-        return []
-
-    last_chapter_link = last_chapter_link_tag.parent.attrs["href"]
-    if last_chapter_link[-1] == "/":
-        last_chapter_link = last_chapter_link[:-1]
-
-    parts = last_chapter_link.split("/")
-    max_page = int(parts.pop())
-
-    template = "/".join(parts + ["%d/"])
-
-    return [template % page_num for page_num in range(2, max_page + 1)]
-
-
-def _chapters_data(soup):
-    chapter_container = soup.find(class_="chapter-container")
-
-    def is_chapter_link(href):
-        return href.startswith("/chapter/") and not href.endswith("comments")
-
-    chapters = chapter_container.find_all("a", href=is_chapter_link)
-
-    eng_chapters = [
-        {
-            "id": chapter_id_from_url(f'{DOMAIN}{chapter.attrs["href"]}'),
-            "name": chapter.text,
-        }
-        for chapter in chapters
-        if chapter.parent.parent.find(class_="flag", title="English")
-    ]
-    return eng_chapters
-
-
-def scrape_chapter(original_id):
-    data = session.get(API_URL, params={"id": original_id, "type": "chapter"}).json()
-
-    if data["status"] == "deleted":
-        return None
-
-    # data["server"] can be either of:
-    # - "/data/..." - meaning same origin as web server: https://mangadex.org/data/...
-    # - "https://sX.mangadex.org/data/..." where X is any digit.
-    page_base_url = data["server"] + data["hash"]
-    if page_base_url.startswith("/"):
-        page_base_url = f"https://mangadex.org{page_base_url}"
-    pages = [f"{page_base_url}/{page}" for page in data["page_array"]]
-
-    return {
-        "name": data["title"],
-        "pages": pages,
-    }
-
-
-def get_latest_id():
-    resp = session.get("https://mangadex.org/")
-    assert resp.status_code == 200, resp.text
-    soup = BeautifulSoup(resp.text, "lxml")
-    latest_href = soup.select_one("#new_titles_owl_carousel a").attrs["href"]
-    latest_id = re.search(r"/(\d+)/", latest_href).group(1)
-    return int(latest_id)
diff --git a/src/pytaku_web/__init__.py b/src/pytaku_web/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pytaku_web/admin.py b/src/pytaku_web/admin.py
deleted file mode 100644
index 6a4dbf2..0000000
--- a/src/pytaku_web/admin.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.contrib import admin
-
-from .models import Chapter, Title
-
-admin.site.register(Title)
-admin.site.register(Chapter)
diff --git a/src/pytaku_web/apps.py b/src/pytaku_web/apps.py
deleted file mode 100644
index a38ace0..0000000
--- a/src/pytaku_web/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class PytakuWebConfig(AppConfig):
-    name = 'pytaku_web'
diff --git a/src/pytaku_web/migrations/0001_initial.py b/src/pytaku_web/migrations/0001_initial.py
deleted file mode 100644
index 0bdf305..0000000
--- a/src/pytaku_web/migrations/0001_initial.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Generated by Django 3.0.5 on 2020-05-05 05:41
-
-import django.contrib.postgres.fields
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='Title',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('site', models.CharField(max_length=50)),
-                ('original_id', models.CharField(max_length=255)),
-                ('name', models.CharField(max_length=255)),
-                ('alt_names', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), default=list, size=None)),
-                ('cover', models.CharField(max_length=255)),
-                ('descriptions', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None)),
-                ('publication_status', models.CharField(max_length=100)),
-                ('authors', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), default=list, size=None)),
-                ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), default=list, size=None)),
-            ],
-            options={
-                'db_table': 'title',
-            },
-        ),
-        migrations.CreateModel(
-            name='Chapter',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('original_id', models.CharField(max_length=255)),
-                ('name', models.CharField(max_length=255)),
-                ('num_major', models.PositiveIntegerField()),
-                ('num_minor', models.PositiveIntegerField(blank=True, null=True)),
-                ('pages', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None)),
-                ('title', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chapters', to='pytaku_web.Title')),
-            ],
-            options={
-                'db_table': 'chapter',
-                'ordering': ['num_major', 'num_minor'],
-            },
-        ),
-        migrations.AddConstraint(
-            model_name='chapter',
-            constraint=models.UniqueConstraint(fields=('title', 'num_major', 'num_minor'), name='unique_number_within_title'),
-        ),
-    ]
diff --git a/src/pytaku_web/migrations/__init__.py b/src/pytaku_web/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pytaku_web/models.py b/src/pytaku_web/models.py
deleted file mode 100644
index 3e94a2a..0000000
--- a/src/pytaku_web/models.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from django.contrib.postgres.fields import ArrayField
-from django.db import models
-
-
-class Title(models.Model):
-    class Meta:
-        db_table = "title"
-
-    site = models.CharField(max_length=50)
-    original_id = models.CharField(max_length=255)
-    name = models.CharField(max_length=255)
-    alt_names = ArrayField(models.CharField(max_length=255), default=list)
-    cover = models.CharField(max_length=255)
-    descriptions = ArrayField(models.TextField(), default=list)  # paragraphs
-    publication_status = models.CharField(max_length=100)
-    authors = ArrayField(models.CharField(max_length=100), default=list)
-    tags = ArrayField(models.CharField(max_length=50), default=list)
-
-    def __str__(self):
-        return f"{self.name} ({self.id})"
-
-
-class Chapter(models.Model):
-    class Meta:
-        db_table = "chapter"
-        constraints = [
-            models.UniqueConstraint(
-                fields=["title", "num_major", "num_minor"],
-                name="unique_number_within_title",
-            )
-        ]
-        ordering = ["num_major", "num_minor"]
-
-    original_id = models.CharField(max_length=255)
-    name = models.CharField(max_length=255)
-    title = models.ForeignKey(Title, on_delete=models.CASCADE, related_name="chapters")
-    num_major = models.PositiveIntegerField()
-    num_minor = models.PositiveIntegerField(null=True, blank=True)
-    pages = ArrayField(models.TextField(), default=list)
-
-    def __str__(self):
-        return f"{self.name} ({self.id})"
-
-    @property
-    def next_chapter(self):
-        try:
-            return Title.chapters.get(ordering=self.ordering + 1)
-        except Chapter.DoesNotExist:
-            return None
-
-    @property
-    def prev_chapter(self):
-        # premature optimzation: avoid unnecessary db query
-        if self.ordering == 0:
-            return None
-
-        try:
-            return Title.chapters.get(ordering=self.ordering - 1)
-        except Chapter.DoesNotExist:
-            return None
diff --git a/src/pytaku_web/tests.py b/src/pytaku_web/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/src/pytaku_web/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/src/pytaku_web/views.py b/src/pytaku_web/views.py
deleted file mode 100644
index 91ea44a..0000000
--- a/src/pytaku_web/views.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.shortcuts import render
-
-# Create your views here.