it is actually working
This commit is contained in:
parent
e9e4597c96
commit
246ef996c1
11 changed files with 179 additions and 8 deletions
|
@ -8,3 +8,5 @@
|
|||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
media/
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -161,3 +161,6 @@ cython_debug/
|
|||
|
||||
# Drawio
|
||||
.*.drawio.bkp
|
||||
|
||||
# Django media
|
||||
media/
|
||||
|
|
|
@ -19,7 +19,6 @@ 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/
|
||||
|
@ -32,6 +31,8 @@ DEBUG = env.bool('DEBUG', default=True)
|
|||
|
||||
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', str, default=['*'])
|
||||
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
# Application definition
|
||||
DJANGO_APPS = [
|
||||
'django.contrib.admin',
|
||||
|
@ -152,7 +153,8 @@ CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
|
|||
CRISPY_TEMPLATE_PACK = 'bootstrap5'
|
||||
|
||||
# Template to PDF rendering
|
||||
GOTENBERG_API_URL = env.url('GOTENBERG_API_URL', 'http://gotenberg:3000')
|
||||
GOTENBERG_API_URL = env.str('GOTENBERG_API_URL', 'http://gotenberg:3000')
|
||||
INTERNAL_API_URL = env.str('INTERNAL_API_URL', "http://backend:8000")
|
||||
|
||||
# Dramatiq
|
||||
DRAMATIQ_BROKER = {
|
||||
|
|
|
@ -2,6 +2,14 @@ from django.contrib import admin
|
|||
|
||||
from . import models
|
||||
|
||||
class InvoiceFileInline(admin.TabularInline):
|
||||
model = models.InvoiceFile
|
||||
extra = 0
|
||||
readonly_fields = [
|
||||
'created_date',
|
||||
'file',
|
||||
]
|
||||
can_delete = False
|
||||
|
||||
class InvoiceItemInline(admin.TabularInline):
|
||||
model = models.InvoiceItem
|
||||
|
@ -18,4 +26,7 @@ class InvoiceItemInline(admin.TabularInline):
|
|||
@admin.register(models.Invoice)
|
||||
class InvoiceAdmin(admin.ModelAdmin):
|
||||
list_display = ['__str__', 'invoice_date']
|
||||
inlines = [InvoiceItemInline]
|
||||
inlines = [
|
||||
InvoiceItemInline,
|
||||
InvoiceFileInline,
|
||||
]
|
||||
|
|
27
invoices/migrations/0004_invoicefile.py
Normal file
27
invoices/migrations/0004_invoicefile.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-03 13:39
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invoices', '0003_alter_invoice_customer_alter_invoice_customer_data_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InvoiceFile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('file', models.FileField(blank=True, null=True, upload_to='invoice-files', verbose_name='File')),
|
||||
('created_date', models.DateTimeField(auto_now=True, verbose_name='Created Date')),
|
||||
('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='invoices.invoice')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Invoice File',
|
||||
'verbose_name_plural': 'Invoice Files',
|
||||
},
|
||||
),
|
||||
]
|
18
invoices/migrations/0005_alter_invoicefile_file.py
Normal file
18
invoices/migrations/0005_alter_invoicefile_file.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-03 13:50
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('invoices', '0004_invoicefile'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='invoicefile',
|
||||
name='file',
|
||||
field=models.FileField(blank=True, null=True, upload_to='invoice-files/%Y/%m/%d/', verbose_name='File'),
|
||||
),
|
||||
]
|
|
@ -65,3 +65,19 @@ class InvoiceItem(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return f'{self.id} -> {self.invoice.id}'
|
||||
|
||||
|
||||
class InvoiceFile(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _('Invoice File')
|
||||
verbose_name_plural = _('Invoice Files')
|
||||
|
||||
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='files')
|
||||
file = models.FileField(_('File'), upload_to='invoice-files/%Y/%m/%d/', null=True, blank=True)
|
||||
created_date = models.DateTimeField(_('Created Date'), auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.file.name
|
||||
|
||||
def get_file_name(self, ext: str) -> str:
|
||||
return f"{self.invoice.id}-{self.created_date.strftime('%H%M%S')}.{ext}"
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
import dramatiq
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.urls import reverse
|
||||
from gotenberg_client import GotenbergClient
|
||||
|
||||
from invoices.models import Invoice
|
||||
|
||||
|
||||
@dramatiq.actor()
|
||||
def generate_pdf(url: str):
|
||||
print(url)
|
||||
pass
|
||||
def generate_pdf(invoice_id: int):
|
||||
invoice = Invoice.objects.get(pk=invoice_id)
|
||||
print_url = settings.INTERNAL_API_URL + reverse('invoices:print_invoice', kwargs=dict(invoice_id=invoice.id))
|
||||
|
||||
invoice_file = invoice.files.create()
|
||||
|
||||
with GotenbergClient(settings.GOTENBERG_API_URL) as client:
|
||||
with client.chromium.url_to_pdf() as task:
|
||||
res = task.url(print_url).run()
|
||||
invoice_file.file.save(invoice_file.get_file_name('pdf'), ContentFile(res.content))
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db import transaction
|
||||
from django.http import HttpRequest
|
||||
|
@ -27,7 +26,7 @@ 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)),
|
||||
invoice.id,
|
||||
)
|
||||
return redirect(reverse('invoices:invoice', kwargs=dict(invoice_id=invoice.id)))
|
||||
else:
|
||||
|
|
|
@ -13,6 +13,7 @@ dependencies = [
|
|||
"django-environ>=0.12.0",
|
||||
"django-post-office>=3.9.1",
|
||||
"dramatiq[redis]>=1.17.1",
|
||||
"gotenberg-client>=0.9.0",
|
||||
"psycopg[c]>=3.2.5",
|
||||
"uvicorn[standard]>=0.34.0",
|
||||
"wait-for-it>=2.3.0",
|
||||
|
|
78
uv.lock
78
uv.lock
|
@ -241,6 +241,7 @@ dependencies = [
|
|||
{ name = "django-environ" },
|
||||
{ name = "django-post-office" },
|
||||
{ name = "dramatiq", extra = ["redis"] },
|
||||
{ name = "gotenberg-client" },
|
||||
{ name = "psycopg", extra = ["c"] },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
{ name = "wait-for-it" },
|
||||
|
@ -261,6 +262,7 @@ requires-dist = [
|
|||
{ name = "django-environ", specifier = ">=0.12.0" },
|
||||
{ name = "django-post-office", specifier = ">=3.9.1" },
|
||||
{ name = "dramatiq", extras = ["redis"], specifier = ">=1.17.1" },
|
||||
{ name = "gotenberg-client", specifier = ">=0.9.0" },
|
||||
{ name = "psycopg", extras = ["c"], specifier = ">=3.2.5" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" },
|
||||
{ name = "wait-for-it", specifier = ">=2.3.0" },
|
||||
|
@ -278,6 +280,18 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gotenberg-client"
|
||||
version = "0.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx", extra = ["http2"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/7f/2bb2ab55a3f00d859649094327e7e3a71776957f40fdf79a53ad68960afe/gotenberg_client-0.9.0.tar.gz", hash = "sha256:bd3c1ed42b74d470a7e192118276f3d91b558b90aa7c0035afbcac04f42179bb", size = 419242 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/74/536bf5f66571971e9a7b5babece1c008386c16ee5b7ad6c4a3689f43a999/gotenberg_client-0.9.0-py3-none-any.whl", hash = "sha256:18955453e6b2a29a5015e95d645efff3ef6d000a663bd1550b2c92e9055bdd5b", size = 32696 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
|
@ -287,6 +301,41 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "4.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "hpack" },
|
||||
{ name = "hyperframe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpack"
|
||||
version = "4.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.6.4"
|
||||
|
@ -309,6 +358,35 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
http2 = [
|
||||
{ name = "h2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperframe"
|
||||
version = "6.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.7"
|
||||
|
|
Reference in a new issue