Refactor Zasilkovna client, update imports and cleanup

Refactored the Zasilkovna SOAP client to use a singleton pattern with caching and lazy loading, improving reliability and startup performance. Updated invoice PDF generation to import WeasyPrint lazily, preventing startup failures on systems missing dependencies. Cleaned up unused imports and code in several frontend components, removed unused state and variables, and adjusted Docker frontend port mapping. Also updated Django migration files to reflect a new generation timestamp.
This commit is contained in:
2025-12-18 16:23:35 +01:00
parent 1751badb90
commit 72155d4560
21 changed files with 69 additions and 44 deletions

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.9 on 2025-12-14 02:23 # Generated by Django 5.2.7 on 2025-12-18 15:11
import account.models import account.models
import django.contrib.auth.validators import django.contrib.auth.validators

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.9 on 2025-12-14 02:23 # Generated by Django 5.2.7 on 2025-12-18 15:11
import django.core.validators import django.core.validators
import django.db.models.deletion import django.db.models.deletion

View File

@@ -7,9 +7,9 @@ from django.template.loader import render_to_string
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from weasyprint import HTML
import os import os
from configuration.models import SiteConfiguration from configuration.models import SiteConfiguration
from thirdparty.zasilkovna.models import ZasilkovnaPacket from thirdparty.zasilkovna.models import ZasilkovnaPacket
@@ -582,6 +582,16 @@ class Invoice(models.Model):
order = Order.objects.get(invoice=self) order = Order.objects.get(invoice=self)
# Render HTML # Render HTML
html_string = render_to_string("invoice/invoice.html", {"invoice": self}) html_string = render_to_string("invoice/invoice.html", {"invoice": self})
# Import WeasyPrint lazily to avoid startup failures when system
# libraries (Pango/GObject) are not present on Windows.
try:
from weasyprint import HTML
except Exception as e:
raise RuntimeError(
"WeasyPrint is not available. Install its system dependencies (Pango/GTK) or run the backend in Docker."
) from e
pdf_bytes = HTML(string=html_string).write_pdf() pdf_bytes = HTML(string=html_string).write_pdf()
# Save directly to FileField # Save directly to FileField

View File

@@ -5,6 +5,10 @@ from django.apps import apps
from django.utils import timezone from django.utils import timezone
# -- CLEANUP TASKS --
# Delete expired/cancelled orders (older than 24 hours)
@shared_task
def delete_expired_orders(): def delete_expired_orders():
Order = apps.get_model('commerce', 'Order') Order = apps.get_model('commerce', 'Order')

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.9 on 2025-12-14 02:23 # Generated by Django 5.2.7 on 2025-12-18 15:11
from django.db import migrations, models from django.db import migrations, models

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.9 on 2025-12-14 02:23 # Generated by Django 5.2.7 on 2025-12-18 15:11
from django.db import migrations, models from django.db import migrations, models

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.9 on 2025-12-14 02:23 # Generated by Django 5.2.7 on 2025-12-18 15:11
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.9 on 2025-12-14 02:23 # Generated by Django 5.2.7 on 2025-12-18 15:11
from django.db import migrations, models from django.db import migrations, models

View File

@@ -1,28 +1,49 @@
from zeep import Client from zeep import Client
from zeep.exceptions import Fault from zeep.exceptions import Fault
from zeep.transports import Transport
from zeep.cache import SqliteCache
import tempfile
import base64 import base64
import logging import logging
import os import os
from functools import lru_cache
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
WSDL_URL = os.getenv("PACKETA_WSDL_URL", "https://www.zasilkovna.cz/api/soap.wsdl") WSDL_URL = os.getenv("PACKETA_WSDL_URL", "https://www.zasilkovna.cz/api/soap.wsdl")
PACKETA_API_PASSWORD = os.getenv("PACKETA_API_PASSWORD") PACKETA_API_PASSWORD = os.getenv("PACKETA_API_PASSWORD")
zeepZasClient = Client(wsdl=WSDL_URL)
# --- 1. Singleton pro klienta (aby se WSDL stáhlo jen jednou pro celý proces) ---
@lru_cache(maxsize=1)
def get_shared_client():
"""
Tato funkce vrátí instanci klienta a drží ji v tempfile na jednu hodinu.
"""
if not PACKETA_API_PASSWORD:
raise ValueError("Packeta API password is not set.")
tempFpath = os.path.join(tempfile.gettempdir(), 'zeep_packeta_cache.db')
transport = Transport(cache=SqliteCache(path=tempFpath, timeout=3600))
return Client(wsdl=WSDL_URL, transport=transport)
class PacketaAPI: class PacketaAPI:
#TODO: zeptat se jestli nepřidat další checkovací parametry ohledně zásilkovny např: blokování podle configurace webu # --- 2. Property pro lazy loading ---
# popřemýšlet, jestli api klíče nenastavit přes configurator webu @property
def __getattribute__(self): def client(self):
if PACKETA_API_PASSWORD in [None, ""]: """
raise Exception("Packeta API password is not set in environment variables.") Při zavolání self.client se zavolá tento kód.
Vrátí již existujícího klienta z cache.
"""
return get_shared_client()
elif zeepZasClient is None:
raise Exception("Packeta SOAP client is not initialized.")
# ---------- CREATE PACKET METHODS ---------- # ---------- CREATE PACKET METHODS ----------
@@ -68,7 +89,7 @@ class PacketaAPI:
try: try:
# Použijeme createPacketClaimWithPassword, protože umožňuje ukládat email a telefon # Použijeme createPacketClaimWithPassword, protože umožňuje ukládat email a telefon
result = zeepZasClient.service.createPacketClaimWithPassword(PACKETA_API_PASSWORD, attributes) result = self.client.service.createPacketClaimWithPassword(PACKETA_API_PASSWORD, attributes)
return result return result
except Fault as e: except Fault as e:
@@ -83,7 +104,7 @@ class PacketaAPI:
packet_id = 1234567890 packet_id = 1234567890
""" """
try: try:
zeepZasClient.service.cancelPacket(PACKETA_API_PASSWORD, packet_id) self.client.service.cancelPacket(PACKETA_API_PASSWORD, packet_id)
return {"status": "ok", "message": f"Zásilka {packet_id} byla zrušena."} return {"status": "ok", "message": f"Zásilka {packet_id} byla zrušena."}
@@ -99,7 +120,7 @@ class PacketaAPI:
Vrací datum do kdy je uložená zásilka. Vrací datum do kdy je uložená zásilka.
""" """
try: try:
request = zeepZasClient.service.packetGetStoredUntil(PACKETA_API_PASSWORD, packet_id) request = self.client.service.packetGetStoredUntil(PACKETA_API_PASSWORD, packet_id)
if request is None: if request is None:
raise Exception(f"Zásilka {packet_id} nebyla ještě doručena.") raise Exception(f"Zásilka {packet_id} nebyla ještě doručena.")
@@ -128,7 +149,7 @@ class PacketaAPI:
bytes: PDF soubor (base64 dekódovaný) bytes: PDF soubor (base64 dekódovaný)
""" """
try: try:
pdf_base64 = zeepZasClient.service.packetLabelPdf( pdf_base64 = self.client.service.packetLabelPdf(
PACKETA_API_PASSWORD, packet_id, format, offset PACKETA_API_PASSWORD, packet_id, format, offset
) )
@@ -163,7 +184,7 @@ class PacketaAPI:
packet_ids = ["1234567890", "1234567891", "1234567892"] packet_ids = ["1234567890", "1234567891", "1234567892"]
""" """
try: try:
result = zeepZasClient.service.createShipment(PACKETA_API_PASSWORD, packet_ids) result = self.client.service.createShipment(PACKETA_API_PASSWORD, packet_ids)
return result return result
@@ -176,7 +197,7 @@ class PacketaAPI:
Získá seznam balíků ve shipmentu podle jeho shipmentId. Získá seznam balíků ve shipmentu podle jeho shipmentId.
""" """
try: try:
result = zeepZasClient.service.shipmentPackets(PACKETA_API_PASSWORD, shipment_id) result = self.client.service.shipmentPackets(PACKETA_API_PASSWORD, shipment_id)
return result return result
except Fault as e: except Fault as e:
logger.error(f"Packeta shipmentPackets error: {e}") logger.error(f"Packeta shipmentPackets error: {e}")

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.9 on 2025-12-14 02:23 # Generated by Django 5.2.7 on 2025-12-18 15:11
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models

View File

@@ -93,8 +93,8 @@ services:
env_file: env_file:
- ./frontend/.env - ./frontend/.env
ports: ports:
- 80:80 # - 80:80
# - 9000:80 - 9000:80
depends_on: depends_on:
- backend - backend
networks: networks:

View File

@@ -0,0 +1,6 @@
export default function HeroCarousel() {
return (
<>
</>
);
}

View File

@@ -12,7 +12,6 @@ import {
FaPlayCircle, FaPlayCircle,
FaUsers, FaUsers,
FaHandsHelping, FaHandsHelping,
FaProjectDiagram,
} from "react-icons/fa"; } from "react-icons/fa";
import {FaClapperboard, FaCubes} from "react-icons/fa6"; import {FaClapperboard, FaCubes} from "react-icons/fa6";
import styles from "./navbar.module.css"; import styles from "./navbar.module.css";

View File

@@ -1,4 +1,5 @@
import { createContext, useContext, useState, useEffect, ReactNode } from "react"; // import type { ReactNode } from "react";
// import { createContext, useContext, useState, useEffect } from "react";
//TODO: připraveno pro použití jenom linknout funkce z vygenerovaného api klientan a logout() a currentUser() //TODO: připraveno pro použití jenom linknout funkce z vygenerovaného api klientan a logout() a currentUser()

View File

@@ -1,17 +1,17 @@
import Footer from "../components/Footer/footer"; import Footer from "../components/Footer/footer";
import ContactMeForm from "../components/Forms/ContactMe/ContactMeForm"; import ContactMeForm from "../components/Forms/ContactMe/ContactMeForm";
import HomeNav from "../components/navbar/HomeNav";
import Drone from "../components/ads/Drone/Drone"; import Drone from "../components/ads/Drone/Drone";
import Portfolio from "../components/ads/Portfolio/Portfolio"; import Portfolio from "../components/ads/Portfolio/Portfolio";
import Home from "../pages/home/home"; import Home from "../pages/home/home";
import { Outlet } from "react-router"; import { Outlet } from "react-router";
import Navbar from "../components/navbar/SiteNav";
export default function HomeLayout(){ export default function HomeLayout(){
return( return(
<> <>
{/* Example usage of imported components, adjust as needed */} {/* Example usage of imported components, adjust as needed */}
<HomeNav /> <Navbar user={null} onLogin={() => {}} onLogout={() => {}} />
<Home /> {/*page*/} <Home /> {/*page*/}
<div style={{margin: "10em 0"}}> <div style={{margin: "10em 0"}}>

View File

@@ -1,5 +1,3 @@
import { useEffect, useMemo, useState } from "react";
export default function Downloader() { export default function Downloader() {
return ( return (

View File

@@ -1,13 +1,8 @@
import { useState } from "react";
import KinematografieSection from "./kinematografie"; import KinematografieSection from "./kinematografie";
import DroneServisSection from "./droneServis"; import DroneServisSection from "./droneServis";
import WebsiteServiceSection from "./webs"; import WebsiteServiceSection from "./webs";
import styles from "./Services.module.css";
export default function Services() { export default function Services() {
const [active, setActive] = useState<Project | null>(null);
return ( return (
<article id="services" className="section"> <article id="services" className="section">
<WebsiteServiceSection /> <WebsiteServiceSection />

View File

@@ -37,7 +37,6 @@ export default function TradingGraph() {
const colorOther = styles.getPropertyValue("--c-other").trim(); const colorOther = styles.getPropertyValue("--c-other").trim();
const colorLines = styles.getPropertyValue("--c-lines").trim(); const colorLines = styles.getPropertyValue("--c-lines").trim();
const colorBoxes = styles.getPropertyValue("--c-boxes").trim(); const colorBoxes = styles.getPropertyValue("--c-boxes").trim();
const colorBg = styles.getPropertyValue("--c-background").trim();
// Set canvas size // Set canvas size
const rect = canvas.getBoundingClientRect(); const rect = canvas.getBoundingClientRect();

View File

@@ -1,6 +1,3 @@
import styles from "./Services.module.css";
export default function DroneServisSection() { export default function DroneServisSection() {
return ( return (
<article id="drone-servis" className="section"> <article id="drone-servis" className="section">

View File

@@ -1,5 +1,3 @@
import styles from "./Services.module.css";
export default function KinematografieSection() { export default function KinematografieSection() {
return ( return (
<article id="kinematografie" className="section"> <article id="kinematografie" className="section">

View File

@@ -1,6 +1,3 @@
import styles from "./Services.module.css";
import TradingGraph from "./TradingGraph";
export default function WebsiteServiceSection() { export default function WebsiteServiceSection() {
return ( return (
<article id="web-services" className="section"> <article id="web-services" className="section">