Allow unsafe async and make more things async

This commit is contained in:
Mark Veidemanis 2022-10-12 07:22:22 +01:00
parent 5bf65e3c90
commit 22df27178b
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
8 changed files with 215 additions and 25 deletions

160
.gitignore vendored Normal file
View File

@ -0,0 +1,160 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
# lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
stack.env
.venv
env/
venv/
env-glibc/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.bash_history
.vscode/
core/static/admin
core/static/debug_toolbar

31
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,31 @@
repos:
- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black
exclude: ^core/migrations
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
hooks:
- id: flake8
args: [--max-line-length=88]
exclude: ^core/migrations
- repo: https://github.com/rtts/djhtml
rev: 'v1.5.2' # replace with the latest tag on GitHub
hooks:
- id: djhtml
args: [-t 2]
- id: djcss
exclude : ^core/static/css # slow
- id: djjs
exclude: ^core/static/js # slow
# - repo: https://github.com/thibaudcolas/curlylint
# rev: v0.13.1
# hooks:
# - id: curlylint
# files: \.(html|sls)$

View File

@ -1,6 +1,9 @@
import os
import stripe import stripe
from django.conf import settings from django.conf import settings
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
# from redis import StrictRedis # from redis import StrictRedis
# r = StrictRedis(unix_socket_path="/var/run/redis/redis.sock", db=0) # r = StrictRedis(unix_socket_path="/var/run/redis/redis.sock", db=0)

View File

@ -1,12 +1,14 @@
from asgiref.sync import sync_to_async
from core.models import Plan from core.models import Plan
def assemble_plan_map(product_id_filter=None): async def assemble_plan_map(product_id_filter=None):
""" """
Get all the plans from the database and create an object Stripe wants. Get all the plans from the database and create an object Stripe wants.
""" """
line_items = [] line_items = []
for plan in Plan.objects.all(): for plan in await sync_to_async(list)(Plan.objects.all()):
if product_id_filter: if product_id_filter:
if plan.product_id != product_id_filter: if plan.product_id != product_id_filter:
continue continue

View File

@ -31,16 +31,6 @@
Subscription management Subscription management
</a> </a>
</article> </article>
<div class="box"> {% include "partials/product-list.html" %}
<h1 class="subtitle">
This product is currently free. You may cancel any plans above.
</h1>
</div>
<div class="box">
<h1 class="subtitle">
You cannot pay for access to the raw data. It is hashed to preserve privacy.
</h1>
</div>
{# {% include "partials/product-list.html" %} #}
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
import logging import logging
from asyncio import sleep
import stripe import stripe
from asgiref.sync import sync_to_async
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse from django.http import JsonResponse
@ -23,34 +23,37 @@ class Home(View):
template_name = "index.html" template_name = "index.html"
async def get(self, request): async def get(self, request):
await sleep(1)
return render(request, self.template_name) return render(request, self.template_name)
class Billing(LoginRequiredMixin, View): class Billing(LoginRequiredMixin, View):
template_name = "billing.html" template_name = "billing.html"
def get(self, request): async def get(self, request):
context = {"plans": Plan.objects.all(), "user_plans": request.user.plans.all()} plans = await sync_to_async(list)(Plan.objects.all())
user_plans = await sync_to_async(list)(request.user.plans.all())
context = {"plans": plans, "user_plans": user_plans}
return render(request, self.template_name, context) return render(request, self.template_name, context)
class Order(LoginRequiredMixin, View): class Order(LoginRequiredMixin, View):
def get(self, request, plan_name): async def get(self, request, plan_name):
plan = Plan.objects.get(name=plan_name) plan = Plan.objects.get(name=plan_name)
try: try:
cast = { cast = {
"payment_method_types": settings.ALLOWED_PAYMENT_METHODS, "payment_method_types": settings.ALLOWED_PAYMENT_METHODS,
"mode": "subscription", "mode": "subscription",
"customer": request.user.stripe_id, "customer": request.user.stripe_id,
"line_items": assemble_plan_map(product_id_filter=plan.product_id), "line_items": await assemble_plan_map(
product_id_filter=plan.product_id
),
"success_url": request.build_absolute_uri(reverse("success")), "success_url": request.build_absolute_uri(reverse("success")),
"cancel_url": request.build_absolute_uri(reverse("cancel")), "cancel_url": request.build_absolute_uri(reverse("cancel")),
} }
if request.user.is_superuser: if request.user.is_superuser:
cast["discounts"] = [{"coupon": settings.STRIPE_ADMIN_COUPON}] cast["discounts"] = [{"coupon": settings.STRIPE_ADMIN_COUPON}]
session = stripe.checkout.Session.create(**cast) session = stripe.checkout.Session.create(**cast)
Session.objects.create(user=request.user, session=session.id) await Session.objects.acreate(user=request.user, session=session.id)
return redirect(session.url) return redirect(session.url)
# return JsonResponse({'id': session.id}) # return JsonResponse({'id': session.id})
except Exception as e: except Exception as e:
@ -59,7 +62,7 @@ class Order(LoginRequiredMixin, View):
class Cancel(LoginRequiredMixin, View): class Cancel(LoginRequiredMixin, View):
def get(self, request, plan_name): async def get(self, request, plan_name):
plan = Plan.objects.get(name=plan_name) plan = Plan.objects.get(name=plan_name)
try: try:
subscriptions = stripe.Subscription.list( subscriptions = stripe.Subscription.list(
@ -84,7 +87,7 @@ class Signup(CreateView):
class Portal(LoginRequiredMixin, View): class Portal(LoginRequiredMixin, View):
def get(self, request): async def get(self, request):
session = stripe.billing_portal.Session.create( session = stripe.billing_portal.Session.create(
customer=request.user.stripe_id, customer=request.user.stripe_id,
return_url=request.build_absolute_uri(reverse("billing")), return_url=request.build_absolute_uri(reverse("billing")),

View File

@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
class Callback(APIView): class Callback(APIView):
parser_classes = [JSONParser] parser_classes = [JSONParser]
# TODO: make async
@csrf_exempt @csrf_exempt
def post(self, request): def post(self, request):
payload = request.body payload = request.body

View File

@ -7,14 +7,14 @@ from django.views import View
class DemoModal(View): class DemoModal(View):
template_name = "modals/modal.html" template_name = "modals/modal.html"
def get(self, request): async def get(self, request):
return render(request, self.template_name) return render(request, self.template_name)
class DemoWidget(View): class DemoWidget(View):
template_name = "widgets/widget.html" template_name = "widgets/widget.html"
def get(self, request): async def get(self, request):
unique = str(uuid.uuid4())[:8] unique = str(uuid.uuid4())[:8]
return render(request, self.template_name, {"unique": unique}) return render(request, self.template_name, {"unique": unique})
@ -22,5 +22,5 @@ class DemoWidget(View):
class DemoWindow(View): class DemoWindow(View):
template_name = "windows/window.html" template_name = "windows/window.html"
def get(self, request): async def get(self, request):
return render(request, self.template_name) return render(request, self.template_name)