added dramatiq and email when you log in

This commit is contained in:
Jakub Kropáček 2025-02-28 15:50:10 +01:00
parent 08d25b42a8
commit e9e4597c96
11 changed files with 219 additions and 26 deletions

View file

@ -5,3 +5,6 @@ DATABASE_HOST=postgres
DATABASE_PORT=5432
DJANGO_ENV=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=

View file

@ -8,5 +8,4 @@
2. `docker compose up`
## Adding new backend packages
`docker compose run --rm backend uv add <package>`

View file

@ -19,8 +19,7 @@ class LoginForm(AuthenticationForm):
class RegisterForm(UserCreationForm):
class Meta:
model = User
fields = UserCreationForm.Meta.fields + \
('first_name', 'last_name', 'email')
fields = ('username', 'first_name', 'last_name', 'email')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View file

@ -21,6 +21,11 @@ def auth_login(req: HttpRequest) -> HttpResponse:
user = form.get_user()
login(req, user)
user.email_user(
subject="You have just logged in!",
message="You have just logged in",
)
redirect_url = getattr(req.GET, 'next', settings.LOGIN_REDIRECT_URL)
return redirect(redirect_url)

View file

@ -1,6 +1,24 @@
volumes:
pg_data:
x-django: &x-django
build:
target: dev
extends:
file: docker-compose.build.yml
service: backend
volumes:
- .:/app
depends_on:
- postgres
environment:
- DJANGO_ENV=${DJANGO_ENV}
- DATABASE_HOST=postgres
- DATABASE_URL=psql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
- EMAIL_HOST_USER=${EMAIL_HOST_USER}
- EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD}
services:
postgres:
image: postgres:17
@ -13,19 +31,18 @@ services:
volumes:
- pg_data:/var/lib/postgresql/data
backend:
build:
target: release
extends:
file: docker-compose.build.yml
service: backend
volumes:
- .:/app
depends_on:
- postgres
<<: *x-django
ports:
- "8000:8000"
environment:
- DJANGO_ENV=${DJANGO_ENV}
- DATABASE_HOST=postgres
- DATABASE_URL=psql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
dramatiq:
<<: *x-django
command: ["/app/scripts/run-dramatiq.sh"]
gotenberg:
image: gotenberg/gotenberg:8
redis:
image: redis:7.4-alpine

View file

@ -19,35 +19,48 @@ env = Env()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent
INTERNAL_BASE_URL = "http://backend:8000"
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env(
'SECRET_KEY', default='django-insecure-+l+g3)q1zz2bz7=mz4ys6lhu5uj+=ucj34flm^clo4vb3(wmdp',
)
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool('DEBUG', default=True)
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', str, default=['*'])
# Application definition
INSTALLED_APPS = [
DJANGO_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'crispy_forms',
'crispy_bootstrap5',
]
MY_APPS = [
'accounts.apps.AccountConfig',
'subjects.apps.SubjectsConfig',
'invoices.apps.InvoicesConfig',
]
THIRD_PARTY_APPS = [
'django_dramatiq',
'crispy_forms',
'crispy_bootstrap5',
'post_office',
]
INSTALLED_APPS = [
*DJANGO_APPS,
*MY_APPS,
*THIRD_PARTY_APPS,
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@ -109,13 +122,10 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'cs'
LANGUAGES = [('en', _('English')), ('cs', _('Czech'))]
LOCALE_PATHS = [BASE_DIR / 'locale']
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
@ -140,3 +150,34 @@ AUTH_USER_MODEL = 'accounts.User'
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
CRISPY_TEMPLATE_PACK = 'bootstrap5'
# Template to PDF rendering
GOTENBERG_API_URL = env.url('GOTENBERG_API_URL', 'http://gotenberg:3000')
# Dramatiq
DRAMATIQ_BROKER = {
"BROKER": "dramatiq.brokers.redis.RedisBroker",
"OPTIONS": {
"url": "redis://redis:6379",
},
"MIDDLEWARE": [
"dramatiq.middleware.AgeLimit",
"dramatiq.middleware.TimeLimit",
"dramatiq.middleware.Callbacks",
"dramatiq.middleware.Retries",
"django_dramatiq.middleware.DbConnectionsMiddleware",
"django_dramatiq.middleware.AdminMiddleware",
],
}
# Email config
EMAIL_BACKEND = 'post_office.EmailBackend'
EMAIL_HOST = env.str("EMAIL_HOST", "smtp.seznam.cz")
EMAIL_PORT = env.int("EMAIL_PORT", 465)
EMAIL_HOST_USER = env.str("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD")
EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", True)
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", False)
DEFAULT_FROM_EMAIL = env.str("EMAIL_DEFAULT_FROM", "Facturio <no-reply@kropcloud.net>")

6
invoices/tasks.py Normal file
View file

@ -0,0 +1,6 @@
import dramatiq
@dramatiq.actor()
def generate_pdf(url: str):
print(url)
pass

View file

@ -1,3 +1,4 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.http import HttpRequest
@ -8,6 +9,7 @@ from django.urls import reverse
from . import forms
from . import models
from . import tasks
@login_required
@ -24,6 +26,9 @@ def home(req: HttpRequest) -> HttpResponse:
)
if formset.is_valid():
formset.save()
tasks.generate_pdf.send(
settings.INTERNAL_BASE_URL + reverse('invoices:print_invoice', kwargs=dict(invoice_id=invoice.id)),
)
return redirect(reverse('invoices:invoice', kwargs=dict(invoice_id=invoice.id)))
else:
transaction.set_rollback(True)
@ -46,7 +51,7 @@ def view_invoice(req: HttpRequest, invoice_id: int) -> HttpResponse:
return render(req, 'invoices/view.html', dict(invoice=invoice))
@login_required
# @login_required
def print_invoice(req: HttpRequest, invoice_id: int):
invoice = models.Invoice.objects.get(pk=invoice_id)
return render(req, 'invoices/invoice.html', dict(invoice=invoice))

View file

@ -9,7 +9,10 @@ dependencies = [
"crispy-bootstrap5>=2024.10",
"django>=5.1.6",
"django-crispy-forms>=2.3",
"django-dramatiq>=0.13.0",
"django-environ>=0.12.0",
"django-post-office>=3.9.1",
"dramatiq[redis]>=1.17.1",
"psycopg[c]>=3.2.5",
"uvicorn[standard]>=0.34.0",
"wait-for-it>=2.3.0",

10
scripts/run-dramatiq.sh Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -o nounset
set -o xtrace
set -o errexit
set -o pipefail
echo "Running dramatiq..."
./manage.py rundramatiq -p 2 -t 2

105
uv.lock
View file

@ -37,6 +37,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 },
]
[[package]]
name = "bleach"
version = "6.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "webencodings" },
]
sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 },
]
[package.optional-dependencies]
css = [
{ name = "tinycss2" },
]
[[package]]
name = "certifi"
version = "2025.1.31"
@ -159,6 +176,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4c/3b/5dc3faf8739d1ce7a73cedaff508b4af8f6aa1684120ded6185ca0c92734/django_crispy_forms-2.3-py3-none-any.whl", hash = "sha256:efc4c31e5202bbec6af70d383a35e12fc80ea769d464fb0e7fe21768bb138a20", size = 31411 },
]
[[package]]
name = "django-dramatiq"
version = "0.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "dramatiq" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a2/89/bb40b3287acea4e7451ee66a82a75bdeb3814252759d2e39c08c4a02a5f8/django_dramatiq-0.13.0.tar.gz", hash = "sha256:1cdf723ec6a223761985c0855b92fe40cca85d364914df6d99d3034f0f6cee15", size = 15535 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/09/b19725f405621e41c119c6f4b43359c281ddd471794c215b754b8903f52c/django_dramatiq-0.13.0-py3-none-any.whl", hash = "sha256:855c590606c74bd478ad0a3a588667e3e73419e47e43ef2ae047dbebe19dff0b", size = 12376 },
]
[[package]]
name = "django-environ"
version = "0.12.0"
@ -168,6 +198,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957 },
]
[[package]]
name = "django-post-office"
version = "3.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bleach", extra = ["css"] },
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/de/e9/7369fb453269ded8d2fff58299fe653033702bd15a427bf35353d4606df3/django-post_office-3.9.1.tar.gz", hash = "sha256:3d8f502f7829e4cf83e830d9efd6909bb44690af6bc41c7e4fc5a85d7b04df10", size = 76624 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/12/19fdf17fb1a0d5e4418798cf9ed66b8aee39da564e875ec025627e08abdf/django_post_office-3.9.1-py3-none-any.whl", hash = "sha256:0082c4dd17854f66077ef457cf868c9bc5b6247de99d5755c45ddea1c518ed6f", size = 85671 },
]
[[package]]
name = "dramatiq"
version = "1.17.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "prometheus-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/7a/6792ddc64a77d22bfd97261b751a7a76cf2f9d62edc59aafb679ac48b77d/dramatiq-1.17.1.tar.gz", hash = "sha256:2675d2f57e0d82db3a7d2a60f1f9c536365349db78c7f8d80a63e4c54697647a", size = 99071 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ee/36/925c7afd5db4f1a3f00676b9c3c58f31ff7ae29a347282d86c8d429280a5/dramatiq-1.17.1-py3-none-any.whl", hash = "sha256:951cdc334478dff8e5150bb02a6f7a947d215ee24b5aedaf738eff20e17913df", size = 120382 },
]
[package.optional-dependencies]
redis = [
{ name = "redis" },
]
[[package]]
name = "facturio"
version = "0.1.0"
@ -177,7 +237,10 @@ dependencies = [
{ name = "crispy-bootstrap5" },
{ name = "django" },
{ name = "django-crispy-forms" },
{ name = "django-dramatiq" },
{ name = "django-environ" },
{ name = "django-post-office" },
{ name = "dramatiq", extra = ["redis"] },
{ name = "psycopg", extra = ["c"] },
{ name = "uvicorn", extra = ["standard"] },
{ name = "wait-for-it" },
@ -194,7 +257,10 @@ requires-dist = [
{ name = "crispy-bootstrap5", specifier = ">=2024.10" },
{ name = "django", specifier = ">=5.1.6" },
{ name = "django-crispy-forms", specifier = ">=2.3" },
{ name = "django-dramatiq", specifier = ">=0.13.0" },
{ name = "django-environ", specifier = ">=0.12.0" },
{ name = "django-post-office", specifier = ">=3.9.1" },
{ name = "dramatiq", extras = ["redis"], specifier = ">=1.17.1" },
{ name = "psycopg", extras = ["c"], specifier = ">=3.2.5" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" },
{ name = "wait-for-it", specifier = ">=2.3.0" },
@ -295,6 +361,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 },
]
[[package]]
name = "prometheus-client"
version = "0.21.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682 },
]
[[package]]
name = "psycopg"
version = "3.2.5"
@ -354,6 +429,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
]
[[package]]
name = "redis"
version = "5.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/da/d283a37303a995cd36f8b92db85135153dc4f7a8e4441aa827721b442cfb/redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f", size = 4608355 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/5f/fa26b9b2672cbe30e07d9a5bdf39cf16e3b80b42916757c5f92bca88e4ba/redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4", size = 261502 },
]
[[package]]
name = "requests"
version = "2.32.3"
@ -387,6 +471,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 },
]
[[package]]
name = "tinycss2"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "webencodings" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
@ -520,6 +616,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 },
]
[[package]]
name = "webencodings"
version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 },
]
[[package]]
name = "websockets"
version = "15.0"