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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
49
backend/thirdparty/zasilkovna/client.py
vendored
49
backend/thirdparty/zasilkovna/client.py
vendored
@@ -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}")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
6
frontend/src/components/hero/HeroCarousel.tsx
Normal file
6
frontend/src/components/hero/HeroCarousel.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default function HeroCarousel() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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"}}>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
|
||||||
|
|
||||||
export default function Downloader() {
|
export default function Downloader() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user