Updated form, docker changes.

This commit is contained in:
2026-01-09 14:27:55 +01:00
parent 45dc3703d7
commit d2651c2be3
13 changed files with 404 additions and 147 deletions

View File

@@ -1 +1,4 @@
SECRET_KEY=django-insecure-change-me-in-production-!@#$%^&*()
# Comma separated list of trusted origins (include protocol)
CSRF_TRUSTED_ORIGINS=https://servis.ferdzo.xyz,https://www.servis.ferdzo.xyz

View File

@@ -14,6 +14,8 @@ COPY manage.py ./
RUN uv sync --frozen --no-dev
# Collect static files
RUN SECRET_KEY=dummy-key-for-build DEBUG=False uv run manage.py collectstatic --noinput
FROM python:3.13-alpine
@@ -24,6 +26,7 @@ RUN apk add --no-cache postgresql-client
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/serviceCRM/ /app/serviceCRM/
COPY --from=builder /app/manage.py /app/
COPY --from=builder /app/staticfiles/ /app/staticfiles/
RUN adduser -D -u 1000 appuser && \
chown -R appuser:appuser /app
@@ -35,4 +38,4 @@ ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "serviceCRM.asgi:application", "--host", "0.0.0.0", "--port", "8000"]
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "-k", "uvicorn.workers.UvicornWorker", "serviceCRM.asgi:application"]

View File

@@ -1,12 +1,10 @@
version: '3.8'
services:
web:
build: .
volumes:
- .:/app
ports:
- "8000:8000"
volumes:
- ./db.sqlite3:/app/db.sqlite3
env_file:
- .env
environment:

View File

@@ -3,7 +3,7 @@ name = "servicecrm"
version = "0.1.0"
description = "A CRM application built with Django."
readme = "README.md"
requires-python = ">=3.14"
requires-python = ">=3.13"
dependencies = [
"asgiref>=3.11.0",
"cffi>=2.0.0",
@@ -19,6 +19,7 @@ dependencies = [
"django-tables2>=2.8.0",
"dnspython>=2.8.0",
"feedparser>=6.0.12",
"gunicorn>=23.0.0",
"nanoid>=2.0.0",
"pillow>=12.1.0",
"publicsuffix>=1.1.1",
@@ -32,4 +33,5 @@ dependencies = [
"typing-extensions>=4.15.0",
"tzdata>=2025.3",
"uvicorn>=0.40.0",
"whitenoise>=6.11.0",
]

View File

@@ -29,6 +29,12 @@ DEBUG = env.bool('DEBUG', default=False)
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['*'])
CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS', default=[])
# Trust the X-Forwarded-Proto header for SSL
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True
# Application definition
@@ -39,7 +45,7 @@ INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'whitenoise.runserver_nostatic', # Add whitenoise before staticfiles
# 'whitenoise.runserver_nostatic', # Removed to allow default Django static serving in dev
'django.contrib.staticfiles',
'crispy_forms',
'crispy_tailwind',
@@ -57,6 +63,8 @@ DJANGO_TABLES2_TABLE_ATTRS = {
'class': 'bg-white divide-y divide-gray-200',
},
}
DJANGO_TABLES2_TEMPLATE = "table_tailwind.html"
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # Add whitenoise middleware
@@ -146,8 +154,11 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [
BASE_DIR / "serviceCRM" / "static",
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Default primary key field type

View File

@@ -51,7 +51,5 @@
</div>
</div>
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
{% render_table table %}
</div>
{% endblock%}

View File

@@ -4,17 +4,17 @@
<div class="max-w-2xl mx-auto bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6 flex justify-between items-center">
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">Сервисен Налог #{{ data.ticket_id }}</h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500">Креиран на {{ data.date }} (Интерен ID: {{ data.id }})</p>
<h3 class="text-lg leading-6 font-bold text-black">Сервисен Налог #{{ data.ticket_id }}</h3>
<p class="mt-1 max-w-2xl text-sm font-medium text-black">Креиран на {{ data.date }} (Интерен ID: {{ data.id }})</p>
</div>
<div class="flex space-x-3">
<a href="{% url 'print_label' data.ticket_id %}" target="_blank" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<a href="{% url 'print_label' data.ticket_id %}" target="_blank" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-black bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5 text-black" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
Печати Налепница
</a>
<a href="{% url 'print_receipt' data.ticket_id %}" target="_blank" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-gray-900 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<a href="{% url 'print_receipt' data.ticket_id %}" target="_blank" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-black hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="-ml-1 mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
@@ -24,26 +24,26 @@
</div>
<div class="border-t border-gray-200">
<dl>
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">Име на клиент</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">{{ data.name }}</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">Телефонски број</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">{{ data.phone }}</dd>
<dt class="text-sm font-bold text-black">Име на клиент</dt>
<dd class="mt-1 text-sm font-bold text-black sm:mt-0 sm:col-span-2">{{ data.name }}</dd>
</div>
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">Опис на дефект</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">{{ data.description }}</dd>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 border-t border-gray-200">
<dt class="text-sm font-bold text-black">Телефонски број</dt>
<dd class="mt-1 text-sm font-bold text-black sm:mt-0 sm:col-span-2">{{ data.phone }}</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">Статус</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">{{ data.get_status_display }}</dd>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 border-t border-gray-200">
<dt class="text-sm font-bold text-black">Опис на дефект</dt>
<dd class="mt-1 text-sm font-bold text-black sm:mt-0 sm:col-span-2">{{ data.description }}</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 border-t border-gray-200">
<dt class="text-sm font-bold text-black">Статус</dt>
<dd class="mt-1 text-sm font-bold text-black sm:mt-0 sm:col-span-2">{{ data.get_status_display }}</dd>
</div>
{% if data.note %}
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">Интерна забелешка</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">{{ data.note }}</dd>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 border-t border-gray-200">
<dt class="text-sm font-bold text-black">Интерна забелешка</dt>
<dd class="mt-1 text-sm font-bold text-black sm:mt-0 sm:col-span-2">{{ data.note }}</dd>
</div>
{% endif %}
{% if data.done %}

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8">
<title>Потврда #{{ ticket.id }}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<style>
@media print {
@page {
@@ -69,51 +70,54 @@
<!-- Copy 1: Customer -->
<div class="a5-copy">
<!-- Header -->
<div class="flex justify-between items-start border-b-2 border-gray-800 pb-2 mb-4">
<div class="flex justify-between items-start border-b-2 border-black pb-2 mb-4">
<div>
<img src="{% static 'fer-logo.png' %}" alt="Logo" class="h-10 w-auto mb-1">
<h1 class="text-2xl font-bold text-gray-900">СЕРВИСНА ПОТВРДА</h1>
<span class="text-xs font-bold text-gray-500 uppercase tracking-widest">Примерок за Клиент</span>
<h1 class="text-2xl font-bold text-black">СЕРВИСНА ПОТВРДА</h1>
<span class="text-xs font-bold text-black uppercase tracking-widest">Примерок за Клиент</span>
</div>
<div class="flex gap-4 items-center">
<div id="qr-client" class="bg-white"></div>
<div class="text-right">
<div class="text-xl font-bold">Налог #{{ ticket.id }}</div>
<div class="text-xs font-mono text-gray-500 mb-1">Реф: {{ ticket.ticket_id }}</div>
<div class="text-gray-600 text-sm">{{ ticket.date }}</div>
<div class="mt-1 inline-block px-2 py-0.5 bg-gray-200 rounded text-xs font-bold">{{ ticket.get_status_display }}</div>
<div class="text-xl font-bold text-black">Налог #{{ ticket.id }}</div>
<div class="text-xs font-mono font-bold text-black mb-1">Реф: {{ ticket.ticket_id }}</div>
<div class="text-black font-bold text-sm">{{ ticket.date }}</div>
<div class="mt-1 inline-block px-2 py-0.5 border border-black rounded text-xs font-bold text-black">{{ ticket.get_status_display }}</div>
</div>
</div>
</div>
<!-- Content Grid -->
<div class="flex gap-6 mb-4">
<div class="w-1/3">
<h3 class="text-gray-500 text-xs uppercase tracking-wide border-b border-gray-200 mb-1">Клиент</h3>
<p class="font-bold text-gray-900">{{ ticket.name }}</p>
<p class="text-sm text-gray-800">{{ ticket.phone }}</p>
<h3 class="text-black font-bold text-xs uppercase tracking-wide border-b border-black mb-1">Клиент</h3>
<p class="font-bold text-black">{{ ticket.name }}</p>
<p class="text-sm font-bold text-black">{{ ticket.phone }}</p>
</div>
<div class="w-2/3">
<h3 class="text-gray-500 text-xs uppercase tracking-wide border-b border-gray-200 mb-1">Детали за сервис</h3>
<div class="bg-gray-50 p-2 rounded text-sm">
<p class="font-bold text-xs text-gray-500">Пријавен дефект:</p>
<p class="mb-2">{{ ticket.description }}</p>
<h3 class="text-black font-bold text-xs uppercase tracking-wide border-b border-black mb-1">Детали за сервис</h3>
<div class="p-2 rounded text-sm border border-black">
<p class="font-bold text-xs text-black">Пријавен дефект:</p>
<p class="mb-2 font-bold text-black">{{ ticket.description }}</p>
{% if ticket.plateno %}
<p class="font-bold text-xs text-gray-500">Проценка/Наплатено:</p>
<p class="font-mono font-bold">{{ ticket.plateno }}</p>
<p class="font-bold text-xs text-black">Проценка/Наплатено:</p>
<p class="font-mono font-bold text-black">{{ ticket.plateno }}</p>
{% endif %}
</div>
</div>
</div>
<!-- Terms (Condensed) -->
<div class="text-[10px] leading-tight text-justify text-gray-500 mb-8 border-t border-gray-100 pt-2 absolute bottom-[12mm] left-[10mm] right-[10mm]">
<div class="text-[10px] leading-tight text-justify text-black font-bold mb-8 border-t border-black pt-2 absolute bottom-[12mm] left-[10mm] right-[10mm]">
<p><strong>УСЛОВИ:</strong> Со потпис потврдувате дека сте согласни со условите. Сервисот не одговара за загуба на податоци. Дијагностика се наплаќа ако поправката е одбиена. Уреди неподигнати 30 дена по известувањето се отстрануваат.</p>
<!-- Signatures inline with terms to save space -->
<div class="grid grid-cols-2 gap-8 mt-2 pt-2">
<div class="border-t border-gray-400 pt-1">
<p class="text-xs text-gray-600">Потпис на клиент</p>
<div class="border-t border-black pt-1">
<p class="text-xs font-bold text-black">Потпис на клиент</p>
</div>
<div class="border-t border-gray-400 pt-1 text-right">
<p class="text-xs text-gray-600">Сервисер</p>
<div class="border-t border-black pt-1 text-right">
<p class="text-xs font-bold text-black">Сервисер</p>
</div>
</div>
</div>
@@ -123,51 +127,54 @@
<!-- Copy 2: Service -->
<div class="a5-copy">
<!-- Header -->
<div class="flex justify-between items-start border-b-2 border-gray-800 pb-2 mb-4">
<div class="flex justify-between items-start border-b-2 border-black pb-2 mb-4">
<div>
<img src="{% static 'fer-logo.png' %}" alt="Logo" class="h-10 w-auto mb-1">
<h1 class="text-2xl font-bold text-gray-900">СЕРВИСНА ПОТВРДА</h1>
<span class="text-xs font-bold text-gray-500 uppercase tracking-widest">Примерок за Сервис</span>
<h1 class="text-2xl font-bold text-black">СЕРВИСНА ПОТВРДА</h1>
<span class="text-xs font-bold text-black uppercase tracking-widest">Примерок за Сервис</span>
</div>
<div class="flex gap-4 items-center">
<div id="qr-service" class="bg-white"></div>
<div class="text-right">
<div class="text-xl font-bold">Налог #{{ ticket.id }}</div>
<div class="text-xs font-mono text-gray-500 mb-1">Реф: {{ ticket.ticket_id }}</div>
<div class="text-gray-600 text-sm">{{ ticket.date }}</div>
<div class="mt-1 inline-block px-2 py-0.5 bg-gray-200 rounded text-xs font-bold">{{ ticket.get_status_display }}</div>
<div class="text-xl font-bold text-black">Налог #{{ ticket.id }}</div>
<div class="text-xs font-mono font-bold text-black mb-1">Реф: {{ ticket.ticket_id }}</div>
<div class="text-black font-bold text-sm">{{ ticket.date }}</div>
<div class="mt-1 inline-block px-2 py-0.5 border border-black rounded text-xs font-bold text-black">{{ ticket.get_status_display }}</div>
</div>
</div>
</div>
<!-- Content Grid -->
<div class="flex gap-6 mb-4">
<div class="w-1/3">
<h3 class="text-gray-500 text-xs uppercase tracking-wide border-b border-gray-200 mb-1">Клиент</h3>
<p class="font-bold text-gray-900">{{ ticket.name }}</p>
<p class="text-sm text-gray-800">{{ ticket.phone }}</p>
<h3 class="text-black font-bold text-xs uppercase tracking-wide border-b border-black mb-1">Клиент</h3>
<p class="font-bold text-black">{{ ticket.name }}</p>
<p class="text-sm font-bold text-black">{{ ticket.phone }}</p>
</div>
<div class="w-2/3">
<h3 class="text-gray-500 text-xs uppercase tracking-wide border-b border-gray-200 mb-1">Детали за сервис</h3>
<div class="bg-gray-50 p-2 rounded text-sm">
<p class="font-bold text-xs text-gray-500">Пријавен дефект:</p>
<p class="mb-2">{{ ticket.description }}</p>
<h3 class="text-black font-bold text-xs uppercase tracking-wide border-b border-black mb-1">Детали за сервис</h3>
<div class="p-2 rounded text-sm border border-black">
<p class="font-bold text-xs text-black">Пријавен дефект:</p>
<p class="mb-2 font-bold text-black">{{ ticket.description }}</p>
{% if ticket.plateno %}
<p class="font-bold text-xs text-gray-500">Проценка/Наплатено:</p>
<p class="font-mono font-bold">{{ ticket.plateno }}</p>
<p class="font-bold text-xs text-black">Проценка/Наплатено:</p>
<p class="font-mono font-bold text-black">{{ ticket.plateno }}</p>
{% endif %}
</div>
</div>
</div>
<!-- Terms (Condensed) -->
<div class="text-[10px] leading-tight text-justify text-gray-500 mb-8 border-t border-gray-100 pt-2 absolute bottom-[12mm] left-[10mm] right-[10mm]">
<div class="text-[10px] leading-tight text-justify text-black font-bold mb-8 border-t border-black pt-2 absolute bottom-[12mm] left-[10mm] right-[10mm]">
<p><strong>УСЛОВИ:</strong> Со потпис потврдувате дека сте согласни со условите. Сервисот не одговара за загуба на податоци. Дијагностика се наплаќа ако поправката е одбиена. Уреди неподигнати 30 дена по известувањето се отстрануваат.</p>
<!-- Signatures -->
<div class="grid grid-cols-2 gap-8 mt-2 pt-2">
<div class="border-t border-gray-400 pt-1">
<p class="text-xs text-gray-600">Потпис на клиент</p>
<div class="border-t border-black pt-1">
<p class="text-xs font-bold text-black">Потпис на клиент</p>
</div>
<div class="border-t border-gray-400 pt-1 text-right">
<p class="text-xs text-gray-600">Сервисер</p>
<div class="border-t border-black pt-1 text-right">
<p class="text-xs font-bold text-black">Сервисер</p>
</div>
</div>
</div>
@@ -180,5 +187,23 @@
<button onclick="window.print()" class="bg-gray-900 text-white px-4 py-2 rounded shadow hover:bg-gray-800">Печати</button>
<button onclick="window.close()" class="bg-gray-500 text-white px-4 py-2 rounded shadow hover:bg-gray-600">Затвори</button>
</div>
<script type="text/javascript">
// Generate tracking URL dynamically based on current host
const trackingUrl = "{{ request.scheme }}://{{ request.get_host }}{% url 'track_ticket' %}?ticket_id={{ ticket.ticket_id }}";
// Common config
const qrConfig = {
text: trackingUrl,
width: 80,
height: 80,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.L
};
new QRCode(document.getElementById("qr-client"), qrConfig);
new QRCode(document.getElementById("qr-service"), qrConfig);
</script>
</body>
</html>

View File

@@ -1,35 +1,40 @@
{% extends 'base.html' %}
{% block content %}
<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
<div class="min-h-full flex flex-col justify-center py-6 sm:py-12 px-4 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md bg-white rounded-lg shadow px-4 py-8">
<div class="text-center">
<h2 class="text-2xl font-bold tracking-tight text-gray-900 sm:text-3xl">
Проверка на статус
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
Внесете го бројот на вашиот налог
<p class="mt-2 text-base text-gray-600">
Скенирајте го QR кодот од вашата потврда или внесете го рачно бројот на налогот.
</p>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<form class="space-y-6" action="{% url 'track_ticket' %}" method="get">
<form class="mt-8 space-y-6" action="{% url 'track_ticket' %}" method="get">
<div>
<label for="ticket_id" class="block text-sm font-medium text-gray-700">
Број на налог
</label>
<div class="mt-1">
<input id="ticket_id" name="ticket_id" type="text" placeholder="на пр. 9aotIewrNmPl" required class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-gray-500 focus:border-gray-500 sm:text-sm">
<div class="mt-2 relative rounded-md shadow-sm">
<input type="text"
name="ticket_id"
id="ticket_id"
class="block w-full rounded-md border-0 py-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-gray-600 sm:text-lg sm:leading-6 text-center tracking-wider"
placeholder="XXXX-XXXX"
required
autofocus
autocapitalize="characters">
</div>
</div>
<div>
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-900 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<button type="submit" class="w-full flex justify-center rounded-md bg-gray-900 px-3 py-3 text-base font-semibold leading-6 text-white shadow-sm hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600">
Провери статус
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,76 +1,83 @@
{% extends 'base.html' %}
{% block content %}
<div class="max-w-3xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6 flex justify-between items-center bg-gray-100 border-b border-gray-200">
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">
Статус на поправка
</h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500">
Рефернца: <span class="font-mono font-bold text-gray-900">{{ ticket.ticket_id }}</span>
</p>
</div>
<div>
<span class="inline-flex items-center px-4 py-2 rounded-full text-base font-medium
<div class="max-w-xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<div class="bg-white shadow overflow-hidden rounded-xl">
<!-- Status Header (Mobile Friendly) -->
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<div class="flex flex-col items-center justify-center space-y-3">
<span class="inline-flex items-center px-6 py-2 rounded-full text-lg font-bold shadow-sm
{% if ticket.done %}
bg-green-100 text-green-800
bg-green-100 text-green-800 ring-1 ring-inset ring-green-600/20
{% else %}
bg-yellow-100 text-yellow-800
bg-yellow-100 text-yellow-800 ring-1 ring-inset ring-yellow-600/20
{% endif %}">
{% if ticket.done %}
ЗАВРШЕНО
ЗАВРШЕНО
{% else %}
ВО ИЗРАБОТКА
ВО ИЗРАБОТКА
{% endif %}
</span>
<div class="text-center">
<p class="text-xs text-gray-500 uppercase tracking-widest font-semibold">Број на налог</p>
<p class="font-mono text-xl font-bold text-gray-900 tracking-wider">{{ ticket.ticket_id }}</p>
</div>
</div>
</div>
<div class="border-t border-gray-200">
<dl>
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dl class="divide-y divide-gray-100">
<!-- Device -->
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 hover:bg-gray-50 bg-white">
<dt class="text-sm font-medium text-gray-500">
Уред
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
<dd class="mt-1 text-base text-gray-900 font-semibold sm:mt-0 sm:col-span-2">
{{ ticket.name }}
</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<!-- Defect -->
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 hover:bg-gray-50 bg-white">
<dt class="text-sm font-medium text-gray-500">
Дефект / Проблем
Опис на дефект
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2 leading-relaxed">
{{ ticket.description }}
</dd>
</div>
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<!-- Detailed Status Message -->
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 hover:bg-gray-50 bg-white">
<dt class="text-sm font-medium text-gray-500">
Моментален статус
Статус на обработка
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2 font-semibold">
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2 font-medium">
{{ ticket.get_status_display }}
</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<!-- Dates -->
<div class="px-4 py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 hover:bg-gray-50 bg-white">
<dt class="text-sm font-medium text-gray-500">
Примен на
Датум на прием
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{{ ticket.date }}
</dd>
</div>
{% if ticket.done %}
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 border-t border-gray-200">
<dt class="text-sm font-medium text-gray-900">
Забелешки за сервисот
<!-- Resolution -->
<div class="px-4 py-4 bg-green-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 border-t border-green-100">
<dt class="text-sm font-bold text-green-900">
Опис на поправка
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{{ ticket.repair|default:"Нема забелешки." }}
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2 font-medium">
{{ ticket.repair|default:"Уредот е поправен според спецификација." }}
</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<div class="px-4 py-4 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
Завршено на
</dt>
@@ -81,8 +88,11 @@
{% endif %}
</dl>
</div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
<a href="{% url 'track_ticket' %}" class="font-medium text-gray-900 hover:text-gray-700">Провери друг налог &rarr;</a>
<div class="px-4 py-4 bg-gray-50 border-t border-gray-200">
<a href="{% url 'track_ticket' %}" class="w-full flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
&larr; Назад
</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,125 @@
{% load django_tables2 %}
{% load i18n %}
{% block table-wrapper %}
<div class="overflow-x-auto shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
{% block table %}
<table {% render_attrs table.attrs %}>
{% block table.thead %}
{% if table.show_header %}
<thead {{ table.attrs.thead.as_html }}>
<tr>
{% for column in table.columns %}
<th {{ column.attrs.th.as_html }} class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
{% if column.orderable %}
<a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}" class="group inline-flex">
{{ column.header }}
<span class="ml-2 flex-none rounded bg-gray-200 text-gray-900 group-hover:bg-gray-300">
<!-- Sort icon placeholder -->
</span>
</a>
{% else %}
{{ column.header }}
{% endif %}
</th>
{% endfor %}
</tr>
</thead>
{% endif %}
{% endblock table.thead %}
{% block table.tbody %}
<tbody {{ table.attrs.tbody.as_html }}>
{% for row in table.paginated_rows %}
{% block table.tbody.row %}
<tr {{ row.attrs.as_html }}>
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }} class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}
</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% empty %}
{% if table.empty_text %}
{% block table.tbody.empty_text %}
<tr><td colspan="{{ table.columns|length }}" class="px-3 py-4 text-sm text-gray-500 text-center">{{ table.empty_text }}</td></tr>
{% endblock table.tbody.empty_text %}
{% endif %}
{% endfor %}
</tbody>
{% endblock table.tbody %}
{% block table.tfoot %}
{% if table.has_footer %}
<tfoot {{ table.attrs.tfoot.as_html }}>
<tr>
{% for column in table.columns %}
<td {{ column.attrs.tf.as_html }} class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{{ column.footer }}</td>
{% endfor %}
</tr>
</tfoot>
{% endif %}
{% endblock table.tfoot %}
</table>
{% endblock table %}
{% block pagination %}
{% if table.page and table.paginated_rows|length > 0 %}
<div class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6">
<div class="flex flex-1 justify-between sm:hidden">
{% if table.page.has_previous %}
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}" class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Previous</a>
{% endif %}
{% if table.page.has_next %}
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}" class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Next</a>
{% endif %}
</div>
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700">
Showing
<span class="font-medium">{{ table.page.start_index }}</span>
to
<span class="font-medium">{{ table.page.end_index }}</span>
of
<span class="font-medium">{{ table.paginated_rows.total_count }}</span>
results
</p>
</div>
<div>
<nav class="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
{% if table.page.has_previous %}
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}" class="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0">
<span class="sr-only">Previous</span>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" />
</svg>
</a>
{% endif %}
{% for p in table.page|table_page_range:table.paginator %}
{% if p == '...' %}
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-300 focus:outline-offset-0">...</span>
{% else %}
<a href="{% querystring table.prefixed_page_field=p %}" class="relative inline-flex items-center px-4 py-2 text-sm font-semibold {% if table.page.number == p %} z-10 bg-gray-900 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 {% else %} text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-offset-0 {% endif %}">
{{ p }}
</a>
{% endif %}
{% endfor %}
{% if table.page.has_next %}
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}" class="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0">
<span class="sr-only">Next</span>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
</a>
{% endif %}
</nav>
</div>
</div>
</div>
{% endif %}
{% endblock pagination %}
</div>
{% endblock table-wrapper %}

View File

@@ -82,12 +82,6 @@ class CloseTicketView(LoginRequiredMixin, generic.UpdateView):
@login_required
def Nalog(request, ticket_id):
data = get_object_or_404(Insert, ticket_id=ticket_id)
template = "serviceCRM/nalog.html"
context = {"data": data}
return render(request, template, context)
@login_required
def print_label(request, ticket_id):
ticket = get_object_or_404(Insert, ticket_id=ticket_id)
@@ -98,6 +92,18 @@ def print_receipt(request, ticket_id):
ticket = get_object_or_404(Insert, ticket_id=ticket_id)
return render(request, 'serviceCRM/print_receipt.html', {'ticket': ticket})
def Nalog(request, ticket_id):
data = get_object_or_404(Insert, ticket_id=ticket_id)
# If user is staff/logged in, show the internal Nalog view (with print buttons, notes etc)
if request.user.is_authenticated:
template = "serviceCRM/nalog.html"
context = {"data": data}
return render(request, template, context)
# If public user, show the public tracking result
else:
return render(request, 'serviceCRM/public_track_result.html', {'ticket': data})
class Done(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Insert
table_class = DoneInsertTable

73
uv.lock generated
View File

@@ -1,6 +1,6 @@
version = 1
revision = 3
requires-python = ">=3.14"
requires-python = ">=3.13"
[[package]]
name = "asgiref"
@@ -20,6 +20,18 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
@@ -253,6 +265,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/eb/c96d64137e29ae17d83ad2552470bafe3a7a915e85434d9942077d7fd011/feedparser-6.0.12-py3-none-any.whl", hash = "sha256:6bbff10f5a52662c00a2e3f86a38928c37c48f77b3c511aedcd51de933549324", size = 81480, upload-time = "2025-09-10T13:33:58.022Z" },
]
[[package]]
name = "gunicorn"
version = "23.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
@@ -271,12 +295,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844, upload-time = "2018-11-20T14:45:50.165Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pillow"
version = "12.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" },
{ url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" },
{ url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" },
{ url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" },
{ url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" },
{ url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" },
{ url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" },
{ url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" },
{ url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" },
{ url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" },
{ url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" },
{ url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" },
{ url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" },
{ url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" },
{ url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" },
{ url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" },
{ url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" },
{ url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" },
{ url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" },
{ url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" },
{ url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" },
{ url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" },
{ url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" },
{ url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" },
{ url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" },
{ url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" },
{ url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" },
{ url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" },
@@ -359,6 +417,7 @@ dependencies = [
{ name = "django-tables2" },
{ name = "dnspython" },
{ name = "feedparser" },
{ name = "gunicorn" },
{ name = "nanoid" },
{ name = "pillow" },
{ name = "publicsuffix" },
@@ -372,6 +431,7 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "tzdata" },
{ name = "uvicorn" },
{ name = "whitenoise" },
]
[package.metadata]
@@ -390,6 +450,7 @@ requires-dist = [
{ name = "django-tables2", specifier = ">=2.8.0" },
{ name = "dnspython", specifier = ">=2.8.0" },
{ name = "feedparser", specifier = ">=6.0.12" },
{ name = "gunicorn", specifier = ">=23.0.0" },
{ name = "nanoid", specifier = ">=2.0.0" },
{ name = "pillow", specifier = ">=12.1.0" },
{ name = "publicsuffix", specifier = ">=1.1.1" },
@@ -403,6 +464,7 @@ requires-dist = [
{ name = "typing-extensions", specifier = ">=4.15.0" },
{ name = "tzdata", specifier = ">=2025.3" },
{ name = "uvicorn", specifier = ">=0.40.0" },
{ name = "whitenoise", specifier = ">=6.11.0" },
]
[[package]]
@@ -486,3 +548,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" },
]
[[package]]
name = "whitenoise"
version = "6.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/95/8c81ec6b6ebcbf8aca2de7603070ccf37dbb873b03f20708e0f7c1664bc6/whitenoise-6.11.0.tar.gz", hash = "sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f", size = 26432, upload-time = "2025-09-18T09:16:10.995Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/e9/4366332f9295fe0647d7d3251ce18f5615fbcb12d02c79a26f8dba9221b3/whitenoise-6.11.0-py3-none-any.whl", hash = "sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258", size = 20197, upload-time = "2025-09-18T09:16:09.754Z" },
]