diff --git a/ccvpn/__init__.py b/ccvpn/__init__.py index e69de29..d128d39 100644 --- a/ccvpn/__init__.py +++ b/ccvpn/__init__.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, unicode_literals + +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/ccvpn/celery.py b/ccvpn/celery.py new file mode 100644 index 0000000..4f8a65b --- /dev/null +++ b/ccvpn/celery.py @@ -0,0 +1,36 @@ +from __future__ import absolute_import, unicode_literals + +import os + +from celery import Celery +from celery.schedules import crontab + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ccvpn.settings') + +app = Celery('ccvpn') + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings', namespace='CELERY') + +app.conf.beat_schedule = { + 'payments__check_subscriptions': { + 'task': 'payments.tasks.check_subscriptions', + 'schedule': crontab(hour=1), + }, + 'lambdainst__resync': { + 'task': 'lambdainst.tasks.push_all_users', + 'schedule': crontab(day_of_week=1), + }, +} + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() + + +@app.task(bind=True) +def debug_task(self): + print('Request: {0!r}'.format(self.request)) diff --git a/ccvpn/settings.py b/ccvpn/settings.py index 690ee09..264835e 100644 --- a/ccvpn/settings.py +++ b/ccvpn/settings.py @@ -43,6 +43,8 @@ INSTALLED_APPS = ( 'django.contrib.humanize', 'django_countries', 'django_lcore', + 'django_celery_results', + 'django_celery_beat', 'lambdainst', 'payments', 'tickets', @@ -193,6 +195,13 @@ ROOT_URL = '' # Forwarded for header name, if any (None will use remote_addr) REAL_IP_HEADER_NAME = None +# Celery defaults +#CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' +CELERY_RESULT_BACKEND = 'django-db' +CELERY_CACHE_BACKEND = 'django-cache' +CELERY_BROKER_URL = 'redis://localhost/1' +CELERY_TIMEZONE = 'UTC' + # reCAPTCHA API details. If empty, no captcha is displayed. RECAPTCHA_API = 'https://www.google.com/recaptcha/api/siteverify' RECAPTCHA_SITE_KEY = '' diff --git a/lambdainst/models.py b/lambdainst/models.py index d30852a..b0235b7 100644 --- a/lambdainst/models.py +++ b/lambdainst/models.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext as _ from django.db.models.signals import post_save from django.dispatch import receiver from constance import config as site_config -from django_lcore.core import LcoreUserProfileMethods, setup_sync_hooks +from django_lcore.core import LcoreUserProfileMethods, setup_sync_hooks, VPN_AUTH_STORAGE from ccvpn.common import get_trial_period_duration from payments.models import Subscription @@ -84,6 +84,12 @@ class VPNUser(models.Model, LcoreUserProfileMethods): .first() return s + def lcore_sync(self): + if VPN_AUTH_STORAGE == 'inst': + return + from lambdainst.tasks import push_user + push_user.delay(user_id=self.id) + def __str__(self): return self.user.username diff --git a/lambdainst/tasks.py b/lambdainst/tasks.py new file mode 100644 index 0000000..2d2585c --- /dev/null +++ b/lambdainst/tasks.py @@ -0,0 +1,25 @@ +import logging +import django_lcore +from lambdainst.models import User + +logger = logging.getLogger(__name__) +from celery import task + +@task(autoretry_for=(Exception,), default_retry_delay=60*60) +def push_all_users(): + for u in User.objects.all(): + # skip 'empty' accounts + if u.vpnuser.expiration is None: + continue + + logger.debug("pushing user %r", u) + + django_lcore.sync_user(u.vpnuser, fail_silently=False) + +@task(autoretry_for=(Exception,), max_retries=10, retry_backoff=True) +def push_user(user_id): + user = User.objects.get(id=user_id) + logger.debug("pushing user %r", user) + django_lcore.sync_user(user.vpnuser, fail_silently=False) + + diff --git a/lambdainst/views.py b/lambdainst/views.py index 6c3bf5e..d21dbf9 100644 --- a/lambdainst/views.py +++ b/lambdainst/views.py @@ -87,7 +87,7 @@ def signup(request): user.vpnuser.campaign = request.session.get('campaign') user.vpnuser.save() - django_lcore.sync_user(user.vpnuser, True) + user.vpnuser.lcore_sync() user.backend = 'django.contrib.auth.backends.ModelBackend' auth.login(request, user) diff --git a/payments/backends/stripe.py b/payments/backends/stripe.py index fd70caa..d98f0a6 100644 --- a/payments/backends/stripe.py +++ b/payments/backends/stripe.py @@ -125,6 +125,26 @@ class StripeBackend(BackendBase): subscr.save() return True + def refresh_subscription(self, subscr): + if subscr.backend_extid.startswith('cus_'): + customer = self.stripe.Customer.retrieve(subscr.backend_extid) + for s in customer['subscriptions']['data']: + if s['status'] == 'active': + sub = s + break + else: + return + elif subscr.backend_extid.startswith('sub_'): + sub = self.stripe.Subscription.retrieve(subscr.backend_extid) + else: + print("unhandled subscription backend extid: {}".format(subscr.backend_extid)) + return + + if sub['status'] == 'canceled': + subscr.status = 'cancelled' + if sub['status'] == 'past_due': + subscr.status = 'error' + def webhook_session_completed(self, event): session = event['data']['object'] @@ -213,6 +233,20 @@ class StripeBackend(BackendBase): payment.user.vpnuser.save() payment.save() + def webhook_subscr_update(self, event): + from payments.models import Subscription + stripe_sub = event['data']['object'] + sub = Subscription.objects.get(backend_id='stripe', backend_extid=stripe_sub['id']) + + if not sub: + return + + if stripe_sub['status'] == 'canceled': + sub.status = 'cancelled' + if stripe_sub['status'] == 'past_due': + sub.status = 'error' + sub.save() + def webhook(self, request): payload = request.body sig_header = request.META['HTTP_STRIPE_SIGNATURE'] @@ -228,6 +262,8 @@ class StripeBackend(BackendBase): self.webhook_payment_succeeded(event) if event['type'] == 'checkout.session.completed': self.webhook_session_completed(event) + if event['type'] == 'customer.subscription.deleted': + self.webhook_subscr_update(event) return True def get_ext_url(self, payment): diff --git a/payments/tasks.py b/payments/tasks.py new file mode 100644 index 0000000..0010c6e --- /dev/null +++ b/payments/tasks.py @@ -0,0 +1,16 @@ +import logging + +from .models import Subscription, ACTIVE_BACKENDS + +logger = logging.getLogger(__name__) +from celery import task + +@task +def check_subscriptions(): + logger.debug("checking subscriptions") + subs = Subscription.objects.filter(status='active', backend_id='stripe').all() + for sub in subs: + logger.debug("checking subscription #%s on %s", sub.id, sub.backend_id) + sub.refresh_from_db() + ACTIVE_BACKENDS['stripe'].refresh_subscription(sub) + sub.save() diff --git a/poetry.lock b/poetry.lock index 85d07ae..fa52f3a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,14 @@ +[[package]] +category = "main" +description = "Low-level AMQP client for Python (fork of amqplib)." +name = "amqp" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.6.1" + +[package.dependencies] +vine = ">=1.1.3,<5.0.0a1" + [[package]] category = "main" description = "ASGI specs, helper code, and adapters" @@ -26,6 +37,62 @@ wrapt = ">=1.11,<2.0" python = "<3.8" version = ">=1.4.0,<1.5" +[[package]] +category = "main" +description = "Python multiprocessing fork with improvements and bugfixes" +name = "billiard" +optional = false +python-versions = "*" +version = "3.6.3.0" + +[[package]] +category = "main" +description = "Distributed Task Queue." +name = "celery" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "4.4.7" + +[package.dependencies] +billiard = ">=3.6.3.0,<4.0" +kombu = ">=4.6.10,<4.7" +pytz = ">0.0-dev" +vine = "1.3.0" + +[package.extras] +arangodb = ["pyArango (>=1.3.2)"] +auth = ["cryptography"] +azureblockblob = ["azure-storage (0.36.0)", "azure-common (1.1.5)", "azure-storage-common (1.1.0)"] +brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] +cassandra = ["cassandra-driver (<3.21.0)"] +consul = ["python-consul"] +cosmosdbsql = ["pydocumentdb (2.3.2)"] +couchbase = ["couchbase-cffi (<3.0.0)", "couchbase (<3.0.0)"] +couchdb = ["pycouchdb"] +django = ["Django (>=1.11)"] +dynamodb = ["boto3 (>=1.9.178)"] +elasticsearch = ["elasticsearch"] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent"] +librabbitmq = ["librabbitmq (>=1.5.0)"] +lzma = ["backports.lzma"] +memcache = ["pylibmc"] +mongodb = ["pymongo (>=3.3.0)"] +msgpack = ["msgpack"] +pymemcache = ["python-memcached"] +pyro = ["pyro4"] +redis = ["redis (>=3.2.0)"] +riak = ["riak (>=2.0)"] +s3 = ["boto3 (>=1.9.125)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +solar = ["ephem"] +sqlalchemy = ["sqlalchemy"] +sqs = ["boto3 (>=1.9.125)", "pycurl (7.43.0.5)"] +tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] +zstd = ["zstandard"] + [[package]] category = "main" description = "Python package for providing Mozilla's CA Bundle." @@ -76,6 +143,31 @@ optional = false python-versions = "*" version = "1.0.3" +[[package]] +category = "main" +description = "Database-backed Periodic Tasks." +name = "django-celery-beat" +optional = false +python-versions = "*" +version = "2.0.0" + +[package.dependencies] +Django = ">=1.11.17" +celery = "*" +django-timezone-field = ">=4.0,<5.0" +python-crontab = ">=2.3.4" + +[[package]] +category = "main" +description = "Celery result backends for Django." +name = "django-celery-results" +optional = false +python-versions = "*" +version = "1.2.1" + +[package.dependencies] +celery = ">=4.4,<5.0" + [[package]] category = "main" description = "Django live settings with pluggable backends, including Redis." @@ -134,6 +226,7 @@ lcoreapi = "*" reference = "e186aa5719e2e7e09cb1bed13ae729d2623698cf" type = "git" url = "https://git.packetimpact.net/lvpn/django-lcore.git" + [[package]] category = "main" description = "Pickled object field for Django" @@ -148,6 +241,18 @@ Django = ">=2.2" [package.extras] tests = ["tox"] +[[package]] +category = "main" +description = "A Django app providing database and form fields for pytz timezone objects." +name = "django-timezone-field" +optional = false +python-versions = ">=3.5" +version = "4.0" + +[package.dependencies] +django = ">=2.2" +pytz = "*" + [[package]] category = "main" description = "A Django application that provides a fully functional TinyMCE 4 editor widget for models and forms." @@ -160,6 +265,41 @@ version = "1.8.0" Django = ">=1.11" jsmin = "*" +[[package]] +category = "main" +description = "Celery Flower" +name = "flower" +optional = false +python-versions = "*" +version = "0.9.5" + +[package.dependencies] +humanize = "*" +prometheus-client = "0.8.0" +pytz = "*" +[[package.dependencies.celery]] +python = "<3.7" +version = ">=3.1.0" + +[[package.dependencies.celery]] +python = ">=3.7" +version = ">=4.3.0" + +[package.dependencies.tornado] +python = ">=3.5.2" +version = ">=5.0.0,<7.0.0" + +[[package]] +category = "main" +description = "Python humanize utilities" +name = "humanize" +optional = false +python-versions = ">=3.5" +version = "2.6.0" + +[package.extras] +tests = ["freezegun", "pytest", "pytest-cov"] + [[package]] category = "main" description = "Internationalized Domain Names in Applications (IDNA)" @@ -171,7 +311,7 @@ version = "2.10" [[package]] category = "main" description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" +marker = "python_version < \"3.8\" or python_version >= \"3.7\" and python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -206,6 +346,37 @@ optional = false python-versions = "*" version = "2.2.2" +[[package]] +category = "main" +description = "Messaging library for Python." +name = "kombu" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "4.6.11" + +[package.dependencies] +amqp = ">=2.6.0,<2.7" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.18" + +[package.extras] +azureservicebus = ["azure-servicebus (>=0.21.1)"] +azurestoragequeues = ["azure-storage-queue"] +consul = ["python-consul (>=0.6.0)"] +librabbitmq = ["librabbitmq (>=1.5.2)"] +mongodb = ["pymongo (>=3.3.0)"] +msgpack = ["msgpack"] +pyro = ["pyro4"] +qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] +redis = ["redis (>=3.3.11)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +sqlalchemy = ["sqlalchemy"] +sqs = ["boto3 (>=1.4.4)", "pycurl (7.43.0.2)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] + [[package]] category = "dev" description = "A fast and thorough lazy object proxy." @@ -229,6 +400,7 @@ requests = "*" reference = "df771a7dbd1a2166a9a873539f976865f6f6a630" type = "git" url = "https://git.packetimpact.net/lvpn/lcoreapi.git" + [[package]] category = "main" description = "Python implementation of Markdown." @@ -253,6 +425,17 @@ optional = false python-versions = "*" version = "0.6.1" +[[package]] +category = "main" +description = "Python client for the Prometheus monitoring system." +name = "prometheus-client" +optional = false +python-versions = "*" +version = "0.8.0" + +[package.extras] +twisted = ["twisted"] + [[package]] category = "main" description = "psycopg2 - Python-PostgreSQL Database Adapter" @@ -333,6 +516,32 @@ optional = false python-versions = "*" version = "0.11.0" +[[package]] +category = "main" +description = "Python Crontab API" +name = "python-crontab" +optional = false +python-versions = "*" +version = "2.5.1" + +[package.dependencies] +python-dateutil = "*" + +[package.extras] +cron-description = ["cron-descriptor"] +cron-schedule = ["croniter"] + +[[package]] +category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" + +[package.dependencies] +six = ">=1.5" + [[package]] category = "main" description = "Parse and manage posts with YAML (or other) frontmatter" @@ -361,6 +570,17 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "5.3.1" +[[package]] +category = "main" +description = "Python client for Redis key-value store" +name = "redis" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "3.5.3" + +[package.extras] +hiredis = ["hiredis (>=0.1.3)"] + [[package]] category = "main" description = "Python HTTP for Humans." @@ -416,6 +636,15 @@ optional = false python-versions = "*" version = "0.10.1" +[[package]] +category = "main" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +marker = "python_version >= \"3.5.2\"" +name = "tornado" +optional = false +python-versions = ">= 3.5" +version = "6.0.4" + [[package]] category = "dev" description = "a fork of Python 2 and 3 ast modules with type comment support" @@ -438,6 +667,14 @@ 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 = "main" +description = "Promises, promises, promises." +name = "vine" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.3.0" + [[package]] category = "dev" description = "Module for decorators, wrappers and monkey patching." @@ -449,7 +686,7 @@ version = "1.12.1" [[package]] category = "main" description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" +marker = "python_version < \"3.8\" or python_version >= \"3.7\" and python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=3.6" @@ -460,10 +697,14 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "22e6b3c34d0d34295899ba2aca1f916916b922321a021a28a5e519ef05c02aa4" +content-hash = "158e7cac2e0a448cae8dd391c82c452dcd1ff937e14b67bb7d2904e5388d5fd6" python-versions = "^3.6" [metadata.files] +amqp = [ + {file = "amqp-2.6.1-py2.py3-none-any.whl", hash = "sha256:aa7f313fb887c91f15474c1229907a04dac0b8135822d6603437803424c0aa59"}, + {file = "amqp-2.6.1.tar.gz", hash = "sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21"}, +] asgiref = [ {file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"}, {file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"}, @@ -472,6 +713,14 @@ astroid = [ {file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"}, {file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"}, ] +billiard = [ + {file = "billiard-3.6.3.0-py3-none-any.whl", hash = "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede"}, + {file = "billiard-3.6.3.0.tar.gz", hash = "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"}, +] +celery = [ + {file = "celery-4.4.7-py2.py3-none-any.whl", hash = "sha256:a92e1d56e650781fb747032a3997d16236d037c8199eacd5217d1a72893bca45"}, + {file = "celery-4.4.7.tar.gz", hash = "sha256:d220b13a8ed57c78149acf82c006785356071844afe0b27012a4991d44026f9f"}, +] certifi = [ {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, @@ -492,6 +741,14 @@ django-admin-list-filter-dropdown = [ {file = "django-admin-list-filter-dropdown-1.0.3.tar.gz", hash = "sha256:07cd37b6a9be1b08f11d4a92957c69b67bc70b1f87a2a7d4ae886c93ea51eb53"}, {file = "django_admin_list_filter_dropdown-1.0.3-py3-none-any.whl", hash = "sha256:bf1b48bab9772dad79db71efef17e78782d4f2421444d5e49bb10e0da71cd6bb"}, ] +django-celery-beat = [ + {file = "django-celery-beat-2.0.0.tar.gz", hash = "sha256:fdf1255eecfbeb770c6521fe3e69989dfc6373cd5a7f0fe62038d37f80f47e48"}, + {file = "django_celery_beat-2.0.0-py2.py3-none-any.whl", hash = "sha256:fe0b2a1b31d4a6234fea4b31986ddfd4644a48fab216ce1843f3ed0ddd2e9097"}, +] +django-celery-results = [ + {file = "django_celery_results-1.2.1-py2.py3-none-any.whl", hash = "sha256:a29ab580f0e38c66c39f51cc426bbdbb2a391b8cc0867df9dea748db2c961db2"}, + {file = "django_celery_results-1.2.1.tar.gz", hash = "sha256:e390f70cc43bbc2cd7c8e4607dc29ab6211a2ab968f93677583f0160921f670c"}, +] django-constance = [ {file = "django-constance-2.7.0.tar.gz", hash = "sha256:62bdb1a3aef20d80e18d832b30ffcc6626762c538817a5a3571bcefa5a55c849"}, {file = "django_constance-2.7.0-py3-none-any.whl", hash = "sha256:866a7356d3f8ee08374285e97c1edae70edaa3df6eaf7b2e9699f7cde8a88f3b"}, @@ -509,10 +766,22 @@ django-picklefield = [ {file = "django-picklefield-3.0.1.tar.gz", hash = "sha256:15ccba592ca953b9edf9532e64640329cd47b136b7f8f10f2939caa5f9ce4287"}, {file = "django_picklefield-3.0.1-py3-none-any.whl", hash = "sha256:3c702a54fde2d322fe5b2f39b8f78d9f655b8f77944ab26f703be6c0ed335a35"}, ] +django-timezone-field = [ + {file = "django-timezone-field-4.0.tar.gz", hash = "sha256:7e3620fe2211c2d372fad54db8f86ff884098d018d56fda4dca5e64929e05ffc"}, + {file = "django_timezone_field-4.0-py3-none-any.whl", hash = "sha256:758b7d41084e9ea2e89e59eb616e9b6326e6fbbf9d14b6ef062d624fe8cc6246"}, +] django-tinymce4-lite = [ {file = "django-tinymce4-lite-1.8.0.tar.gz", hash = "sha256:eb0ee7eda19970d06484f9e121871de01287b5345c4007ea2582d6f80ec3edb3"}, {file = "django_tinymce4_lite-1.8.0-py3-none-any.whl", hash = "sha256:2d53510ddb5fe20f25e525d4eaf7c8f8a567b85c9cc29f8ab2964419d9352d88"}, ] +flower = [ + {file = "flower-0.9.5-py2.py3-none-any.whl", hash = "sha256:71be02bff7b2f56b0a07bd947fb3c748acba7f44f80ae88125d8954ce1a89697"}, + {file = "flower-0.9.5.tar.gz", hash = "sha256:56916d1d2892e25453d6023437427fc04706a1308e0bd4822321da34e1643f9c"}, +] +humanize = [ + {file = "humanize-2.6.0-py3-none-any.whl", hash = "sha256:fd5b32945687443d5b8bc1e02fad027da1d293a9e963b3450122ad98ef534f21"}, + {file = "humanize-2.6.0.tar.gz", hash = "sha256:8ee358ea6c23de896b9d1925ebe6a8504bb2ba7e98d5ccf4d07ab7f3b28f3819"}, +] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, @@ -528,6 +797,10 @@ isort = [ jsmin = [ {file = "jsmin-2.2.2.tar.gz", hash = "sha256:b6df99b2cd1c75d9d342e4335b535789b8da9107ec748212706ef7bbe5c2553b"}, ] +kombu = [ + {file = "kombu-4.6.11-py2.py3-none-any.whl", hash = "sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a"}, + {file = "kombu-4.6.11.tar.gz", hash = "sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"}, +] lazy-object-proxy = [ {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, @@ -560,6 +833,10 @@ mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +prometheus-client = [ + {file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"}, + {file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"}, +] psycopg2-binary = [ {file = "psycopg2-binary-2.8.5.tar.gz", hash = "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6"}, {file = "psycopg2_binary-2.8.5-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f"}, @@ -616,6 +893,13 @@ python-bitcoinlib = [ {file = "python-bitcoinlib-0.11.0.tar.gz", hash = "sha256:3daafd63cb755f6e2067b7c9c514053856034c9f9363c80c37007744d54a2e06"}, {file = "python_bitcoinlib-0.11.0-py3-none-any.whl", hash = "sha256:6e7982734637135599e2136d3c88d622f147e3b29201636665f799365784cd9e"}, ] +python-crontab = [ + {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] python-frontmatter = [ {file = "python-frontmatter-0.5.0.tar.gz", hash = "sha256:a9c2e90fc38e9f0c68d8b82299040f331ca3b8525ac7fa5f6beffef52b26c426"}, {file = "python_frontmatter-0.5.0-py3-none-any.whl", hash = "sha256:a7dcdfdaf498d488dce98bfa9452f8b70f803a923760ceab1ebd99291d98d28a"}, @@ -637,6 +921,10 @@ pyyaml = [ {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] +redis = [ + {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, + {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, +] requests = [ {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, @@ -657,6 +945,17 @@ toml = [ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, ] +tornado = [ + {file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"}, + {file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"}, + {file = "tornado-6.0.4-cp36-cp36m-win32.whl", hash = "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673"}, + {file = "tornado-6.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a"}, + {file = "tornado-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6"}, + {file = "tornado-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b"}, + {file = "tornado-6.0.4-cp38-cp38-win32.whl", hash = "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52"}, + {file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"}, + {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"}, +] 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"}, @@ -684,6 +983,10 @@ urllib3 = [ {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, ] +vine = [ + {file = "vine-1.3.0-py2.py3-none-any.whl", hash = "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"}, + {file = "vine-1.3.0.tar.gz", hash = "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87"}, +] wrapt = [ {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, ] diff --git a/pyproject.toml b/pyproject.toml index cc26edf..0696426 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,11 @@ python-frontmatter = "^0.5.0" django-tinymce4-lite = "^1.7" django-admin-list-filter-dropdown = "^1.0" django-lcore = {git = "https://git.packetimpact.net/lvpn/django-lcore.git", tag = "v1.4.1"} +celery = "^4.4.7" +django-celery-beat = "^2.0.0" +django-celery-results = "^1.2.1" +redis = "^3.5.3" +flower = "^0.9.5" [tool.poetry.dev-dependencies] pylint = "^2.3"