Working prototype with tailwind, added uv, updated models, added docker.

This commit is contained in:
2026-01-09 14:27:34 +01:00
parent a95f7d44c8
commit 45dc3703d7
35 changed files with 1747 additions and 223 deletions

1
.env.example Normal file
View File

@@ -0,0 +1 @@
SECRET_KEY=django-insecure-change-me-in-production-!@#$%^&*()

58
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,58 @@
# ServiceCRM Copilot Instructions
## Project Overview
ServiceCRM is a simple Service Ticket Management System built with Django. It manages repair tickets ("Inserts") for service customers, tracking details like customer info, defect description, repair status, and notes.
## Architecture
- **Framework**: Django 4.x (compatible with 5.x/6.x in dependencies).
- **Structure**: Single-app project where the `serviceCRM` package contains both project settings and application logic (views, models, urls).
- **Patterns**: Model-View-Template (MVT).
- **Frontend**: Server-side rendered templates using Bootstrap 5 and `django-crispy-forms`.
## Key Components
### Data Model
- **Primary Model**: `Insert` (defined in `serviceCRM/models.py`).
- Represents a service ticket.
- Key fields: `name`, `phone`, `description` (defect), `repair` (resolution), `done` (status), `date`.
### Views & Controllers
- **Location**: `serviceCRM/views.py`.
- **Pattern**: Mix of Class-Based Views (CBV) and Function-Based Views (FBV).
- List views use `django_tables2.SingleTableView`.
- Create/Update use `generic.View` and `generic.UpdateView`.
- **Note**: Some views use a non-standard static method pattern (e.g., `InsertNew.insert` referenced in URLs).
### UI/UX
- **Tables**: `django-tables2` is used for listing records (`serviceCRM/tables.py`).
- **Forms**: `django-crispy-forms` with `crispy-bootstrap5` pack (`serviceCRM/forms.py`).
- **Templates**: Located in `templates/` and `templates/serviceCRM/`.
## Configuration & Environment
- **Settings**: `serviceCRM/settings.py` uses `django-environ`.
- **Environment**: Configuration reads from `.env` file (see `.env.example`).
- **Database**:
- Local/Dev: SQLite (configured via `settings.py` overriding env vars if needed).
- Production: Configurable via `DATABASE_URL` or individual env vars (PostgreSQL supported).
## Development Workflow
### Setup
1. Create `.env` from `.env.example`.
2. Install dependencies: `pip install -r requirements.txt`.
3. Run migrations: `python manage.py migrate`.
### Common Commands
- **Run Server**: `python manage.py runserver`
- **Make Migrations**: `python manage.py makemigrations` (Required when changing `models.py`)
- **Migrate**: `python manage.py migrate`
## Coding Conventions
- **Imports**: standard library -> third party -> django -> local apps.
- **Urls**: Defined in `serviceCRM/urls.py` (which is the root URLconf).
- **Routing**: Routes are mixed between CBV (`.as_view()`) and function references. Maintain consistency with existing patterns when adding new routes.
## Critical Patterns to Respect
1. **Forms**: Always use `InputForm` in `serviceCRM/forms.py` for ticket creation/editing to ensure consistent widget rendering with Bootstrap classes.
2. **Tables**: When adding lists, subclass `tables.Table` in `serviceCRM/tables.py` and use `SingleTableView` for display/sorting features.
3. **Template Inheritance**: All templates should extend `base.html`.

6
.gitignore vendored
View File

@@ -2,7 +2,7 @@
.idea/
# Ignore __pycache__ folders and .pyc files
__pycache__/
*__pycache__/
*.pyc
# Ignore .env files
@@ -14,7 +14,9 @@ __pycache__/
# Ignore .DS_Store files
.DS_Store
.vscode\
.vscode
db.sqlite3
pyvenv.cfg

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

19
.vscode/launch.json vendored
View File

@@ -1,19 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Django",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}\\manage.py",
"args": [
"runserver"
],
"django": true,
"autoStartBrowser": false
}
]
}

View File

@@ -1,4 +0,0 @@
{
"cmake.configureOnOpen": false,
"python.pythonPath": ".venv\\Scripts\\python.exe"
}

38
Dockerfile Normal file
View File

@@ -0,0 +1,38 @@
# Django Backend Dockerfile
FROM ghcr.io/astral-sh/uv:python3.13-alpine AS builder
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY serviceCRM/ ./serviceCRM/
COPY manage.py ./
RUN uv sync --frozen --no-dev
FROM python:3.13-alpine
WORKDIR /app
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/
RUN adduser -D -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "serviceCRM.asgi:application", "--host", "0.0.0.0", "--port", "8000"]

14
docker-compose.yml Normal file
View File

@@ -0,0 +1,14 @@
version: '3.8'
services:
web:
build: .
volumes:
- .:/app
ports:
- "8000:8000"
env_file:
- .env
environment:
- DEBUG=False
- ALLOWED_HOSTS=*

35
pyproject.toml Normal file
View File

@@ -0,0 +1,35 @@
[project]
name = "servicecrm"
version = "0.1.0"
description = "A CRM application built with Django."
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"asgiref>=3.11.0",
"cffi>=2.0.0",
"crispy-bootstrap5>=2025.6",
"crispy-tailwind>=1.0.3",
"cryptography>=46.0.3",
"django>=6.0.1",
"django-crispy-forms>=2.5",
"django-datatable-view>=2.1.6",
"django-environ>=0.12.0",
"django-filter>=25.2",
"django-pipeline>=4.1.0",
"django-tables2>=2.8.0",
"dnspython>=2.8.0",
"feedparser>=6.0.12",
"nanoid>=2.0.0",
"pillow>=12.1.0",
"publicsuffix>=1.1.1",
"pycparser>=2.23",
"python-dateutil>=2.9.0.post0",
"pytz>=2025.2",
"sgmllib3k>=1.0.0",
"six>=1.17.0",
"sqlparse>=0.5.5",
"tablib>=3.9.0",
"typing-extensions>=4.15.0",
"tzdata>=2025.3",
"uvicorn>=0.40.0",
]

Binary file not shown.

View File

@@ -1,6 +1,27 @@
from .models import Insert
import django_filters as filters
from django_filters import FilterSet
from django.db.models import Q
from django import forms
class InsertFilter(FilterSet):
start_date = filters.DateFilter(field_name="date", lookup_expr='gte', label='Од датум', widget=forms.TextInput(attrs={'type': 'date'}))
end_date = filters.DateFilter(field_name="date", lookup_expr='lte', label='До датум', widget=forms.TextInput(attrs={'type': 'date'}))
search = filters.CharFilter(method='filter_search', label='Пребарај (Име, Тел, ID, Опис)')
class Meta:
model = Insert
fields = ['search', 'done']
def filter_search(self, queryset, name, value):
return queryset.filter(
Q(name__icontains=value) |
Q(phone__icontains=value) |
Q(description__icontains=value) |
Q(plateno__icontains=value) |
Q(ticket_id__icontains=value) |
Q(id__icontains=value)
)
class DoneTable(FilterSet):
class Meta:
@@ -11,4 +32,22 @@ class DoneTable(FilterSet):
if value:
return queryset.filter(done=True)
else:
return queryset.filter(done=False)
return queryset.filter(done=False)
class DoneFilter(FilterSet):
start_date = filters.DateFilter(field_name="date_close", lookup_expr='gte', label='Од датум (Затворено)', widget=forms.TextInput(attrs={'type': 'date'}))
end_date = filters.DateFilter(field_name="date_close", lookup_expr='lte', label='До датум (Затворено)', widget=forms.TextInput(attrs={'type': 'date'}))
search = filters.CharFilter(method='filter_search', label='Пребарај (Име, Тел, ID)')
class Meta:
model = Insert
fields = ['status']
def filter_search(self, queryset, name, value):
return queryset.filter(
Q(name__icontains=value) |
Q(phone__icontains=value) |
Q(description__icontains=value) |
Q(plateno__icontains=value) |
Q(ticket_id__icontains=value) |
Q(id__icontains=value)
)

View File

@@ -1,4 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from .models import Insert
@@ -9,17 +10,31 @@ class DateInput(forms.DateInput):
class InputForm(forms.ModelForm):
class Meta:
model = Insert
fields = {"name", "phone", "description", "date", "note"}
labels = {'name': "Name", 'phone': "Phone", 'date': "Date", 'description': "Description", 'note': "Note"}
fields = ["name", "phone", "date", "description", "note"]
labels = {
'name': "Име",
'phone': "Телефон",
'date': "Датум на прием",
'description': "Опис на проблем",
'note': "Забелешка"
}
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}),
'date': DateInput(),
'description': forms.Textarea(attrs={'class': 'form-control'}),
'note': forms.TextInput(attrs={'class': 'form-control'})
'description': forms.Textarea(attrs={'rows': 3}),
}
field_order = ["name", "phone", "date", "description", "done"]
class CloseForm(forms.ModelForm):
class Meta:
model = Insert
fields = ["repair", "plateno"]
labels = {
'repair': "Детали за поправка",
'plateno': "Наплатено"
}
widgets = {
'repair': forms.Textarea(attrs={'rows': 4}),
'plateno': forms.TextInput(attrs={'placeholder': 'пр. 1500 МКД'})
}
# class EditForm(forms.ModelForm):
# class Meta:

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-01-08 11:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('serviceCRM', '0005_alter_insert_done'),
]
operations = [
migrations.AddField(
model_name='insert',
name='date_close',
field=models.DateField(blank=True, null=True, verbose_name='date closed'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-01-08 11:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('serviceCRM', '0006_insert_date_close'),
]
operations = [
migrations.AddField(
model_name='insert',
name='status',
field=models.CharField(choices=[('RECEIVED', 'Received'), ('DIAGNOSING', 'Diagnosing'), ('WAITING_PARTS', 'Waiting for Parts'), ('READY', 'Ready for Pickup'), ('COMPLETED', 'Completed')], default='RECEIVED', max_length=20),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 6.0.1 on 2026-01-08 11:30
import serviceCRM.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('serviceCRM', '0007_insert_status'),
]
operations = [
migrations.AddField(
model_name='insert',
name='ticket_id',
field=models.CharField(default=serviceCRM.models.generate_nanoid, max_length=12, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 6.0.1 on 2026-01-08 11:32
import serviceCRM.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('serviceCRM', '0008_insert_ticket_id'),
]
operations = [
migrations.AlterField(
model_name='insert',
name='ticket_id',
field=models.CharField(default=serviceCRM.models.generate_nanoid, editable=False, max_length=12, unique=True),
),
]

View File

@@ -0,0 +1,27 @@
# Generated by Django 6.0.1 on 2026-01-08 13:04
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('serviceCRM', '0009_alter_insert_ticket_id'),
]
operations = [
migrations.CreateModel(
name='TicketLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(auto_now_add=True)),
('action', models.CharField(max_length=50)),
('details', models.TextField(blank=True, null=True)),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='serviceCRM.insert')),
],
options={
'ordering': ['-timestamp'],
},
),
]

View File

@@ -0,0 +1,63 @@
# Generated by Django 6.0.1 on 2026-01-08 14:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('serviceCRM', '0010_ticketlog'),
]
operations = [
migrations.AlterField(
model_name='insert',
name='date',
field=models.DateField(verbose_name='Датум'),
),
migrations.AlterField(
model_name='insert',
name='date_close',
field=models.DateField(blank=True, null=True, verbose_name='Датум затворање'),
),
migrations.AlterField(
model_name='insert',
name='description',
field=models.CharField(max_length=300, verbose_name='Опис'),
),
migrations.AlterField(
model_name='insert',
name='done',
field=models.BooleanField(default=False, verbose_name='Завршено'),
),
migrations.AlterField(
model_name='insert',
name='name',
field=models.CharField(max_length=50, verbose_name='Име'),
),
migrations.AlterField(
model_name='insert',
name='note',
field=models.CharField(blank=True, default=None, max_length=100, null=True, verbose_name='Забелешка'),
),
migrations.AlterField(
model_name='insert',
name='phone',
field=models.CharField(max_length=20, verbose_name='Телефон'),
),
migrations.AlterField(
model_name='insert',
name='plateno',
field=models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='Плаќање/Рег.'),
),
migrations.AlterField(
model_name='insert',
name='repair',
field=models.CharField(blank=True, default=None, max_length=300, null=True, verbose_name='Поправка'),
),
migrations.AlterField(
model_name='insert',
name='status',
field=models.CharField(choices=[('RECEIVED', 'Примено'), ('DIAGNOSING', 'Дијагностика'), ('WAITING_PARTS', 'Чека делови'), ('READY', 'Готово за подигање'), ('COMPLETED', 'Завршено')], default='RECEIVED', max_length=20, verbose_name='Статус'),
),
]

View File

@@ -1,19 +1,63 @@
from django.db import models
from django.contrib.auth.models import UserManager
from nanoid import generate
def generate_nanoid():
return generate(size=12)
class Insert(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20)
description = models.CharField(max_length=300)
note= models.CharField(max_length=100, default=None, blank=True, null=True)
date = models.DateField("date submitted")
done = models.BooleanField(default=False)
repair = models.CharField(default=None, blank=True, null=True,max_length=300)
plateno = models.CharField(max_length=10, default=None, blank=True, null=True)
ticket_id = models.CharField(max_length=12, default=generate_nanoid, unique=True, editable=False)
name = models.CharField(max_length=50, verbose_name="Име")
phone = models.CharField(max_length=20, verbose_name="Телефон")
description = models.CharField(max_length=300, verbose_name="Опис")
note= models.CharField(max_length=100, default=None, blank=True, null=True, verbose_name="Забелешка")
date = models.DateField("Датум")
done = models.BooleanField(default=False, verbose_name="Завршено")
STATUS_CHOICES = [
('RECEIVED', 'Примено'),
('DIAGNOSING', 'Дијагностика'),
('WAITING_PARTS', 'Чека делови'),
('READY', 'Готово за подигање'),
('COMPLETED', 'Завршено'),
]
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='RECEIVED', verbose_name="Статус")
date_close = models.DateField("Датум затворање", null=True, blank=True)
repair = models.CharField(default=None, blank=True, null=True,max_length=300, verbose_name="Поправка")
plateno = models.CharField(max_length=10, default=None, blank=True, null=True, verbose_name="Плаќање/Рег.")
def save(self, *args, **kwargs):
from django.utils import timezone
# Sync done and status fields
if self.status == 'COMPLETED':
self.done = True
if not self.date_close:
self.date_close = timezone.now().date()
elif self.done:
self.status = 'COMPLETED'
if not self.date_close:
self.date_close = timezone.now().date()
else:
self.done = False
super(Insert, self).save(*args, **kwargs)
def __str__(self):
return "Ime: " + self.name + " Telefonski broj: " + self.phone + "\nDefekt: " + self.description + "\nDatum: \n"
def isDone(self):
return self.done
class TicketLog(models.Model):
ticket = models.ForeignKey(Insert, on_delete=models.CASCADE, related_name='logs')
timestamp = models.DateTimeField(auto_now_add=True)
action = models.CharField(max_length=50)
details = models.TextField(blank=True, null=True)
class Meta:
ordering = ['-timestamp']
def __str__(self):
return f"{self.ticket.ticket_id} - {self.action} at {self.timestamp}"

View File

@@ -25,9 +25,9 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = env('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = env.bool('DEBUG', default=False)
ALLOWED_HOSTS = ['*']
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['*'])
# Application definition
@@ -39,22 +39,29 @@ INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'whitenoise.runserver_nostatic', # Add whitenoise before staticfiles
'django.contrib.staticfiles',
'crispy_forms',
'crispy_bootstrap5',
'crispy_tailwind',
'django_tables2',
'django_filters',
]
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind"
CRISPY_TEMPLATE_PACK = "tailwind"
DJANGO_TABLES2_TABLE_ATTRS = {
'class': 'table table-hover',
'class': 'min-w-full divide-y divide-gray-200 border',
'thead': {
'class': 'table-light',
'class': 'bg-gray-50',
},
'tbody': {
'class': 'bg-white divide-y divide-gray-200',
},
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # Add whitenoise middleware
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -88,12 +95,8 @@ WSGI_APPLICATION = 'serviceCRM.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DATABASE_NAME'),
'USER': env('DATABASE_USER'),
'PASSWORD': env('DATABASE_PASS'),
'HOST': env('DATABASE_HOST'),
'PORT': env('DATABASE_PORT'),
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
@@ -120,9 +123,20 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'mk'
TIME_ZONE = 'UTC'
from django.utils.translation import gettext_lazy as _
LANGUAGES = [
('mk', _('Macedonian')),
('en', _('English')),
]
LOCALE_PATHS = [
BASE_DIR / 'locale',
]
TIME_ZONE = 'Europe/Skopje'
USE_I18N = True
@@ -133,8 +147,13 @@ USE_TZ = True
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/admin/login/'

View File

@@ -6,18 +6,24 @@ from .models import Insert
class InsertTable(tables.Table):
actions = TemplateColumn(template_code='<a class="btn btn-secondary" href="{% url \'update\' record.id %}">Edit</a> <a class="btn btn-secondary" href="{% url \'done\' record.id %}">Details</a>')
actions = TemplateColumn(template_code='''
<a class="text-indigo-600 hover:text-indigo-900 mr-2" href="{% url 'nalog' ticket_id=record.ticket_id %}">Види</a>
<a class="text-gray-600 hover:text-gray-900 mr-2" href="{% url 'update' ticket_id=record.ticket_id %}">Уреди</a>
{% if not record.done %}
<a class="text-green-600 hover:text-green-900 font-medium" href="{% url 'close_ticket' ticket_id=record.ticket_id %}">Затвори</a>
{% endif %}
''')
class Meta:
model = Insert
fields = ("id","name","phone","description","date","done")
fields = ("ticket_id","name","phone","description","date","status","done")
per_page = 5
class DoneInsertTable(InsertTable):
class Meta:
model = Insert
fields = ("id","name","phone","description","date","done")
fields = ("ticket_id","name","phone","description","date", "date_close", "done")
per_page = 5
def __init__(self, *args, **kwargs):

View File

@@ -1,43 +1,92 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"/>
<title>Service </title>
</head>
<body>
<script src="http://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.full.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/">Service CRM</a>
{% load static i18n %}
<html lang="mk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Service CRM</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
</style>
</head>
<body class="bg-gray-50 text-gray-900">
<nav class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="flex-shrink-0 flex items-center">
{% if user.is_authenticated %}
<a href="{% url 'dashboard' %}">
<img src="{% static 'fer-logo.png' %}" alt="Logo" class="h-8 w-auto">
</a>
{% else %}
<a href="{% url 'home' %}">
<img src="{% static 'fer-logo.png' %}" alt="Logo" class="h-8 w-auto">
</a>
{% endif %}
</div>
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
{% if user.is_authenticated %}
<a href="{% url 'dashboard' %}" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
Активни налози
</a>
<a href="{% url 'done' %}" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
</svg>
Архива
</a>
{% endif %}
<a href="{% url 'track_ticket' %}" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
<svg class="-ml-1 mr-2 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
Проверка на статус
</a>
</div>
</div>
<div class="flex items-center space-x-4">
{% if user.is_authenticated %}
<a href="{% url 'insert' %}" class="bg-gray-900 text-white hover:bg-gray-800 px-4 py-2 rounded-md text-sm font-medium transition duration-150 ease-in-out shadow-sm flex items-center">
<svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Нов Налог
</a>
<div class="ml-3 relative flex items-center space-x-3">
<span class="text-sm text-gray-500">Здраво, <strong>{{ user.username }}</strong></span>
<a href="{% url 'logout' %}" class="text-gray-400 hover:text-gray-500">
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</a>
</div>
{% endif %}
</div>
</div>
</div>
<ul class="nav navbar-nav navbar-right">
<li><a href="/insert"><span class="glyphicon glyphicon-user"></span>Insert</a></li>
<li><a href="/admin"><span class="glyphicon glyphicon-log-in"></span> Admin</a></li>
<li><a href="/done"><span class="glyphicon glyphicon-log-in"></span> Done</a></li>
</ul>
</div>
</nav>
<div class="container">
{% comment %} <h1 class="mt-2">Service CRM</h1>
<a href='/insert'><button type="button" class="btn btn-dark">Insert</button></a>
<hr class="mt-0 mb-4"> {% endcomment %}
<div class="row justify-content-center">
<div class="col-8">
{% block content %}
{% endblock %}
<main class="max-w-7xl mx-auto py-8 sm:px-6 lg:px-8">
<div class="px-4 py-4 sm:px-0">
{% if messages %}
<div class="mb-6 space-y-2">
{% for message in messages %}
<div class="p-4 rounded-md shadow-sm {% if message.tags == 'success' %}bg-green-50 text-green-800 border-l-4 border-green-400{% elif message.tags == 'error' %}bg-red-50 text-red-800 border-l-4 border-red-400{% else %}bg-blue-50 text-blue-800 border-l-4 border-blue-400{% endif %}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</main>
</body>
</html>

View File

@@ -0,0 +1,34 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Затвори налог #{{ object.id }}{% endblock %}
{% block content %}
<div class="max-w-3xl mx-auto">
<div class="bg-white shadow sm:rounded-lg mb-6">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-2">Затвори налог #{{ object.id }}</h3>
<p class="text-sm text-gray-500 mb-6">
Завршување на поправка за <strong>{{ object.name }}</strong> ({{ object.description|truncatechars:50 }})
</p>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="mt-5 flex space-x-3">
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-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="M5 13l4 4L19 7" />
</svg>
Заврши и Затвори
</button>
<a href="{% url 'home' %}" class="inline-flex items-center px-4 py-2 border border-gray-300 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">
Откажи
</a>
</div>
</form>
</div>
</div>
</div>
{% endblock%}

View File

@@ -1,6 +1,31 @@
{% extends 'base.html' %}
{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}
{% block content %}
{% render_table table %}
<div class="mb-6 bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Пребарување Архива</h3>
<form method="get" class="space-y-4 md:space-y-0 md:grid md:grid-cols-12 md:gap-6 items-end">
<div class="md:col-span-3">
{{ filter.form.start_date|as_crispy_field }}
</div>
<div class="md:col-span-3">
{{ filter.form.end_date|as_crispy_field }}
</div>
<div class="md:col-span-4">
{{ filter.form.search|as_crispy_field }}
</div>
<div class="md:col-span-2">
<button type="submit" class="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-gray-900 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
Филтрирај
</button>
</div>
</form>
</div>
</div>
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
{% render_table table %}
</div>
{%endblock%}

View File

@@ -1,13 +1,17 @@
{% extends 'base.html' %}
{% block title %}Edit{% endblock %}
{% load crispy_forms_tags %}
{% block title %}Уреди{% endblock %}
{% block content %}
{% csrf_token %}
<div class="bg-white shadow sm:rounded-lg mb-6">
<div class="px-4 py-5 sm:p-6">
<form method="post">
<div class="form-group">
{% csrf_token %}
{{form.as_p}}
<input type="submit" class="btn btn-success" value="Update">
<input type="submit" class="btn btn-danger" value="Delete" formaction="{% url 'delete' id=object.id %}">
{{ form|crispy }}
<div class="mt-5 flex space-x-3">
<input type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-gray-900 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 cursor-pointer" value="Ажурирај">
<input type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 cursor-pointer" value="Избриши" formaction="{% url 'delete' ticket_id=object.ticket_id %}">
</div>
</form>
</div>
</div>
{% endblock%}

View File

@@ -1,11 +1,16 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
{% csrf_token %}
<div class="bg-white shadow sm:rounded-lg mb-6">
<div class="px-4 py-5 sm:p-6">
<form action="/insert/" method="post">
<div class="form-group">
{% csrf_token %}
{{form.as_p}}
<input type="submit" class="btn btn-success" value="Submit">
{{ form|crispy }}
<div class="mt-5">
<input type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-gray-900 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 cursor-pointer" value="Внеси">
</div>
</form>
</div>
</div>
{% endblock%}

View File

@@ -1,5 +1,57 @@
{% extends 'base.html' %}
{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}
{% block content %}
{% render_table table %}
<!-- Dashboard Stats -->
<div class="grid grid-cols-1 gap-5 sm:grid-cols-3 mb-6">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<dt class="text-sm font-medium text-gray-500 truncate">Активни налози</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900">{{ stats_active }}</dd>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<dt class="text-sm font-medium text-gray-500 truncate">Завршени денес</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-900">{{ stats_today_closed }}</dd>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<dt class="text-sm font-medium text-gray-500 truncate">Спремни за подигање</dt>
<dd class="mt-1 text-3xl font-semibold text-yellow-600">{{ stats_ready }}</dd>
</div>
</div>
</div>
<div class="bg-white shadow sm:rounded-lg mb-6">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Пребарај налози</h3>
<form method="get" class="space-y-4 md:space-y-0 md:grid md:grid-cols-12 md:gap-6 items-end">
<div class="md:col-span-3">
{{ filter.form.start_date|as_crispy_field }}
</div>
<div class="md:col-span-3">
{{ filter.form.end_date|as_crispy_field }}
</div>
<div class="md:col-span-4">
{{ filter.form.search|as_crispy_field }}
</div>
<!-- Hidden done field if needed, or included in search/other filters -->
<div class="md:col-span-2">
<button type="submit" class="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-gray-900 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
Пребарај
</button>
</div>
</form>
</div>
</div>
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
{% render_table table %}
</div>
{% endblock%}

View File

@@ -1,83 +1,107 @@
{% block content %}
{% extends 'base.html' %}
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Servicing Ticket</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
}
.header img {
max-width: 150px;
}
.ticket {
border: 2px solid #000;
padding: 20px;
margin-bottom: 20px;
}
.ticket-info {
margin-bottom: 20px;
}
.ticket-info label {
font-weight: bold;
}
.ticket-description {
border-top: 2px solid #000;
padding-top: 10px;
}
.center {
display: block;
margin-left: auto;
margin-right: auto;
max-width: 100%;
max-height: 100%;
margin-bottom: 5 0px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="{% static 'fer-logo.png' %}" class="center" alt="Company Logo">
{% block content %}
<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>
</div>
<div class="ticket">
<div class="ticket-info">
<label>Име и презиме:</label> {{ name }}
</div>
<div class="ticket-info">
<label>Телефонски број:</label> {{phone}}
</div>
<div class="ticket-info">
<label>Датум:</label> {{date}}
</div>
<div class="ticket-description">
<label><b>Опис на дефект:</b></label> {{ desc }}
</div>
</div>
<div style="height:150px;"></div>
<div class="ticket">
<div class="ticket-info">
<label>Име и презиме:</label> {{ name }}
</div>
<div class="ticket-info">
<label>Телефонски број:</label> {{ phone }}
</div>
<div class="ticket-info">
<label>Датум:</label> {{ date }}
</div>
<div class="ticket-description">
<label><b>Опис на дефект:</b></label> {{ desc }}
</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">
<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">
<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>
Печати Потврда
</a>
</div>
</div>
</body>
</html>
<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>
</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>
<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>
{% 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>
{% endif %}
{% if data.done %}
<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.repair }}</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.plateno }}</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.date_close }}</dd>
</div>
{% endif %}
</dl>
</div>
<!-- Ticket History -->
<div class="bg-gray-50 px-4 py-5 sm:px-6 border-t border-gray-200">
<h4 class="text-md leading-6 font-medium text-gray-900 mb-4">Историја на активности</h4>
<div class="flow-root">
<ul role="list" class="-mb-8">
{% for log in data.logs.all %}
<li>
<div class="relative pb-8">
{% if not forloop.last %}
<span class="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200" aria-hidden="true"></span>
{% endif %}
<div class="relative flex space-x-3">
<div>
<span class="h-8 w-8 rounded-full bg-gray-500 flex items-center justify-center ring-8 ring-white">
<svg class="h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" />
</svg>
</span>
</div>
<div class="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
<div>
<p class="text-sm text-gray-500">{{ log.details }} <span class="font-medium text-gray-900">({{ log.action }})</span></p>
</div>
<div class="text-right text-sm whitespace-nowrap text-gray-500">
<time datetime="{{ log.timestamp|date:'Y-m-d H:i' }}">{{ log.timestamp|date:"M d, H:i" }}</time>
</div>
</div>
</div>
</div>
</li>
{% empty %}
<li class="text-sm text-gray-500">Нема историја.</li>
{% endfor %}
</ul>
</div>
</div>
<div class="px-4 py-3 bg-white border-t border-gray-200 text-right sm:px-6">
<a href="{% url 'dashboard' %}" class="text-indigo-600 hover:text-indigo-900">Назад кон листа</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% load static %}
<!DOCTYPE html>
<html lang="mk">
<head>
<meta charset="UTF-8">
<title>Налепница #{{ ticket.id }}</title>
<style>
body {
font-family: sans-serif;
margin: 0;
padding: 10px;
width: 300px; /* Adjust for label printer width (e.g., BROTHER QL) */
text-align: center;
}
.header { font-size: 1.2em; font-weight: bold; margin-bottom: 5px; }
.id { font-size: 2em; font-weight: bold; margin: 10px 0; border: 2px solid black; display: inline-block; padding: 5px 20px; }
.details { font-size: 0.9em; text-align: left; margin-top: 10px; }
@media print {
body { width: 100%; margin: 0; padding: 0; }
button { display: none; }
}
</style>
</head>
<body onload="window.print()">
<div class="header">
<img src="{% static 'fer-logo.png' %}" style="max-width: 150px;" alt="Logo">
</div>
<div>{{ ticket.date }}</div>
<div class="id">#{{ ticket.id }}</div>
<div style="font-size: 10px; font-family: monospace;">{{ ticket.ticket_id }}</div>
<div class="header">{{ ticket.name }}</div>
<div>{{ ticket.phone }}</div>
<div class="details">
<strong>Дефект:</strong><br>
{{ ticket.description|truncatechars:50 }}
</div>
</body>
</html>

View File

@@ -0,0 +1,184 @@
{% load static %}
<!DOCTYPE html>
<html lang="mk">
<head>
<meta charset="UTF-8">
<title>Потврда #{{ ticket.id }}</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@media print {
@page {
size: A4 portrait;
margin: 0;
}
html, body {
width: 210mm;
height: 296mm; /* Slightly less than 297 to prevent overflow */
margin: 0;
padding: 0;
overflow: hidden;
}
.no-print { display: none !important; }
.page-container {
width: 100%;
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
}
.a5-copy {
flex: 1;
height: 50%;
padding: 10mm; /* Reduced padding to fit better */
box-sizing: border-box;
border-bottom: 1px dashed #999;
overflow: hidden;
position: relative;
}
.a5-copy:last-child {
border-bottom: none;
}
.cut-line { display: none; }
}
/* Screen preview styles */
body { background: #f3f4f6; }
.page-container {
background: white;
width: 210mm;
min-height: 297mm;
margin: 20px auto;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.a5-copy {
height: 148mm;
padding: 15mm;
position: relative;
background: white;
border-bottom: 1px dashed #eee;
}
</style>
</head>
<body onload="window.print()">
<div class="page-container">
<!-- 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>
<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>
</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>
</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>
</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>
{% if ticket.plateno %}
<p class="font-bold text-xs text-gray-500">Проценка/Наплатено:</p>
<p class="font-mono font-bold">{{ 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]">
<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>
<div class="border-t border-gray-400 pt-1 text-right">
<p class="text-xs text-gray-600">Сервисер</p>
</div>
</div>
</div>
</div>
<!-- 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>
<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>
</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>
</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>
</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>
{% if ticket.plateno %}
<p class="font-bold text-xs text-gray-500">Проценка/Наплатено:</p>
<p class="font-mono font-bold">{{ 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]">
<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>
<div class="border-t border-gray-400 pt-1 text-right">
<p class="text-xs text-gray-600">Сервисер</p>
</div>
</div>
</div>
</div>
</div>
<!-- Print Control -->
<div class="fixed bottom-4 right-4 no-print flex gap-2">
<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>
</body>
</html>

View File

@@ -0,0 +1,35 @@
{% 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">
Проверка на статус
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
Внесете го бројот на вашиот налог
</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">
<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>
</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>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,89 @@
{% 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
{% if ticket.done %}
bg-green-100 text-green-800
{% else %}
bg-yellow-100 text-yellow-800
{% endif %}">
{% if ticket.done %}
ЗАВРШЕНО
{% else %}
ВО ИЗРАБОТКА
{% endif %}
</span>
</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">
<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.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">
{{ 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">
<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">
{{ 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">
<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">
Забелешки за сервисот
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{{ 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">
<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_close }}
</dd>
</div>
{% 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>
</div>
</div>
{% endblock %}

View File

@@ -19,13 +19,24 @@ from django.urls import path
import serviceCRM.views as view
urlpatterns = [
path("", view.InsertListView.as_view(), name="index"),
path("", view.home, name="home"),
path("dashboard/", view.InsertListView.as_view(), name="dashboard"),
path('admin/', admin.site.urls),
path("insert/", view.InsertNew.insert, name="insert"),
path("edit/<int:pk>/", view.Update.as_view(), name="update"),
path("nalog/<int:id>/", view.Nalog, name="nalog"),
path("delete/<int:id>/", view.Delete.delete, name="delete"),
path("logout/", view.logout_view, name="logout"),
path("track/", view.track_ticket, name="track_ticket"),
path("ticket/<str:ticket_id>/", view.Nalog, name="nalog"),
# Secure public links
path("print/label/<str:ticket_id>/", view.print_label, name="print_label"),
path("print/receipt/<str:ticket_id>/", view.print_receipt, name="print_receipt"),
# Secure staff actions
path("edit/<str:ticket_id>/", view.Update.as_view(), name="update"),
path("close/<str:ticket_id>/", view.CloseTicketView.as_view(), name="close_ticket"),
path("delete/<str:ticket_id>/", view.Delete.delete, name="delete"),
path("done/", view.Done.as_view(), name="done"),
path("done/<int:id>/", view.Done.done_by_id, name="done"),
path("done/<str:ticket_id>/", view.Done.done_by_id, name="done_detail"),
path("datatable/", view.DatatableView.as_view(), name="datatable"),
]

View File

@@ -1,74 +1,144 @@
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.shortcuts import get_object_or_404, render, redirect
from django.views import generic
from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.contrib.auth import logout
from .forms import InputForm
from .models import Insert
from .forms import InputForm, CloseForm
from .models import Insert, TicketLog
from .tables import DoneInsertTable, InsertTable
from .filter import InsertFilter, DoneFilter
from django_tables2 import SingleTableView
from django_tables2.views import SingleTableMixin,SingleTableView
from django_filters.views import FilterView
from datatableview.views import DatatableView
class InsertListView(SingleTableView):
class InsertListView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Insert
table_class = InsertTable
template_name = 'serviceCRM/list.html'
filterset_class = InsertFilter
def get_queryset(self):
return Insert.objects.filter(done=False).order_by('-date', '-id')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Dashboard Widgets Data
context['stats_active'] = Insert.objects.filter(done=False).count()
context['stats_today_closed'] = Insert.objects.filter(done=True, date_close=timezone.now().date()).count()
context['stats_ready'] = Insert.objects.filter(status='READY', done=False).count()
return context
class InsertNew(generic.View):
model = Insert
template_name = "serviceCRM/form.html"
@login_required
def insert(request):
if request.method == 'POST':
form = InputForm(request.POST)
if form.is_valid():
ticket = form.save()
print("Raboti")
return HttpResponseRedirect(f"/nalog/{ticket.id}/")
TicketLog.objects.create(ticket=ticket, action="Креирано", details="Налогот е креиран во системот.")
return HttpResponseRedirect(f"/ticket/{ticket.ticket_id}/")
else:
form = InputForm()
return render(request, InsertNew.template_name, {'form': form})
class Update(generic.UpdateView):
class Update(LoginRequiredMixin, generic.UpdateView):
model = Insert
template_name = "serviceCRM/edit.html"
fields = ["name", "phone", "description","note", "done", "repair", "plateno"]
fields = ["status", "name", "phone", "description","note", "repair", "plateno", "done"]
success_url = '/'
slug_field = 'ticket_id'
slug_url_kwarg = 'ticket_id'
def form_valid(self, form):
response = super().form_valid(form)
if 'status' in form.changed_data:
TicketLog.objects.create(ticket=self.object, action="Промена на статус", details=f"Статусот е променет во {self.object.get_status_display()}")
else:
TicketLog.objects.create(ticket=self.object, action="Ажурирано", details="Деталите за налогот се ажурирани.")
return response
def Nalog(request, id):
try:
data = Insert.objects.get(id=id)
except:
return HttpResponseRedirect("/")
template = "serviceCRM/nalog.html"
context = {"name": data.name, "phone": data.phone, "desc": data.description, "date": data.date}
return render(request, template, context)
class Done(SingleTableView):
class CloseTicketView(LoginRequiredMixin, generic.UpdateView):
model = Insert
form_class = CloseForm
template_name = "serviceCRM/close_ticket.html"
success_url = '/done/'
slug_field = 'ticket_id'
slug_url_kwarg = 'ticket_id'
def form_valid(self, form):
form.instance.done = True
form.instance.date_close = timezone.now().date()
response = super().form_valid(form)
TicketLog.objects.create(ticket=self.object, action="Затворено", details="Налогот и поправката се завршени.")
return response
@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)
return render(request, 'serviceCRM/print_label.html', {'ticket': ticket})
@login_required
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})
class Done(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Insert
table_data = Insert.objects.filter(done=True)
table_class = DoneInsertTable
template_name = 'serviceCRM/done.html'
filterset_class = DoneFilter
def done_by_id(request, id):
try:
req = get_object_or_404(Insert, id=id)
except:
return HttpResponseRedirect("/done/")
context = {"name": req.name, "phone": req.phone, "desc": req.description, "date": req.date}
return HttpResponse(f"Report ID: {id} \nName: {req.name} \nPhone: {req.phone} \nDescription: {req.description} \n Note:{req.note} \nDate: {req.date} \nDone: {req.done} \nRepair: {req.repair} \n Plateno: {req.plateno} \n")
def get_queryset(self):
return Insert.objects.filter(done=True)
@login_required
def done_by_id(request, ticket_id):
req = get_object_or_404(Insert, ticket_id=ticket_id)
return HttpResponse(f"Report ID: {ticket_id} \nName: {req.name} \nPhone: {req.phone} \nDescription: {req.description} \n Note:{req.note} \nDate: {req.date} \nDone: {req.done} \nRepair: {req.repair} \n Plateno: {req.plateno} \n")
class Delete(generic.View):
model = Insert
def delete(request, id):
req = get_object_or_404(Insert, id=id)
@login_required
def delete(request, ticket_id):
req = get_object_or_404(Insert, ticket_id=ticket_id)
req.delete()
return HttpResponseRedirect("/")
class DatatableView(DatatableView):
class DatatableView(LoginRequiredMixin, DatatableView):
model = Insert
template_name = 'serviceCRM/Insert_list.html'
def track_ticket(request):
if request.method == 'GET' and 'ticket_id' in request.GET:
ticket_id = request.GET.get('ticket_id').strip()
try:
ticket = Insert.objects.get(ticket_id=ticket_id)
return render(request, 'serviceCRM/public_track_result.html', {'ticket': ticket})
except Insert.DoesNotExist:
return render(request, 'serviceCRM/public_track_form.html', {'error': 'Налогот не е пронајден. Проверете го бројот на налогот.'})
return render(request, 'serviceCRM/public_track_form.html')
def logout_view(request):
logout(request)
return redirect('/')
def home(request):
if request.user.is_authenticated:
return redirect('dashboard')
return track_ticket(request)

488
uv.lock generated Normal file
View File

@@ -0,0 +1,488 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "asgiref"
version = "3.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" },
]
[[package]]
name = "cffi"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
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/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" },
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
]
[[package]]
name = "click"
version = "8.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "crispy-bootstrap5"
version = "2025.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "django-crispy-forms" },
]
sdist = { url = "https://files.pythonhosted.org/packages/97/30/36cc4144b6dff91bb54490a3b474897b7469bcda9517bf9f54681ea91011/crispy_bootstrap5-2025.6.tar.gz", hash = "sha256:f1bde7cac074c650fc82f31777d4a4cfd0df2512c68bc4128f259c75d3daada4", size = 23950, upload-time = "2025-06-08T07:43:35.461Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d8/d4/8cf1ba773a91fc17bab1fd46b75bbdef780c4cccbbb8230e617980a0362c/crispy_bootstrap5-2025.6-py3-none-any.whl", hash = "sha256:a343aa128b4383f35f00295b94de2b10862f2a4f24eda21fa6ead45234c07050", size = 24794, upload-time = "2025-06-08T07:43:34.206Z" },
]
[[package]]
name = "crispy-tailwind"
version = "1.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "django-crispy-forms" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/ac/a307ae5ce869d7151b90d4b8b042a48eb454a936dacc695f6418486e5bd8/crispy-tailwind-1.0.3.tar.gz", hash = "sha256:2bc9f616d406e4b003f25d46fcb0079f1c2522719d97adb107667271d849459a", size = 19172, upload-time = "2024-02-13T09:52:37.895Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/ca/11f65e24f3c182dfaf90fd3710d2dcca0fbc3026923e47b43f52a4a2349b/crispy_tailwind-1.0.3-py3-none-any.whl", hash = "sha256:31427f66b1c4fd0d6fb040f4197cfb97d104cdbe7641ea2dea940c0057c4db4b", size = 25700, upload-time = "2024-02-13T09:52:35.928Z" },
]
[[package]]
name = "cryptography"
version = "46.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
{ url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
{ url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
{ url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
{ url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
{ url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
{ url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
{ url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
{ url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
{ url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
{ url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
{ url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
{ url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
{ url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
{ url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
{ url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
{ url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
{ url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
{ url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
{ url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
{ url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
{ url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
{ url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
{ url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
{ url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
{ url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
{ url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
{ url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
{ url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
{ url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
{ url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
{ url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
{ url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
{ url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
{ url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
{ url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
{ url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
{ url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
{ url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
{ url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
{ url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
{ url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
{ url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
{ url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
{ url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
]
[[package]]
name = "django"
version = "6.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b5/9b/016f7e55e855ee738a352b05139d4f8b278d0b451bd01ebef07456ef3b0e/django-6.0.1.tar.gz", hash = "sha256:ed76a7af4da21551573b3d9dfc1f53e20dd2e6c7d70a3adc93eedb6338130a5f", size = 11069565, upload-time = "2026-01-06T18:55:53.069Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/b5/814ed98bd21235c116fd3436a7ed44d47560329a6d694ec8aac2982dbb93/django-6.0.1-py3-none-any.whl", hash = "sha256:a92a4ff14f664a896f9849009cb8afaca7abe0d6fc53325f3d1895a15253433d", size = 8338791, upload-time = "2026-01-06T18:55:46.175Z" },
]
[[package]]
name = "django-crispy-forms"
version = "2.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/79/a1/6a638d13717e4d4f8df169dade0fa51bdc65d9825df39d98ce709a776b49/django_crispy_forms-2.5.tar.gz", hash = "sha256:066e72a8f152a1334f1c811cc37740868efe3265e5a218f79079ef89f848c3d8", size = 1097999, upload-time = "2025-11-06T20:44:01.921Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/58/ac3a11950baaf75c1f3242e3af9dfe45201f6ee10c113dd37a9c000876d2/django_crispy_forms-2.5-py3-none-any.whl", hash = "sha256:adc99d5901baca09479c53bf536b3909e80a9f2bb299438a223de4c106ebf1f9", size = 31464, upload-time = "2025-11-06T20:44:00.795Z" },
]
[[package]]
name = "django-datatable-view"
version = "2.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/05/cc/f26f6bf8cf70110fe40d86039ac01a6f407260eca1ea67ad56e529a39071/django-datatable-view-2.1.6.tar.gz", hash = "sha256:b84ba6809b35b1d018eb502b3fed5b9dec219ed87120ee54f04a601551a341af", size = 50474, upload-time = "2021-05-28T21:21:45.338Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/ae/026ee8aee1ad6b3d06407cccb2c3036d47a6b319b5b03986d221059e48bf/django_datatable_view-2.1.6-py2.py3-none-any.whl", hash = "sha256:74b4ac9e99ebba50f5558b62434a21fa28c681b0c9f40b875c96ff497aeba11b", size = 59862, upload-time = "2021-05-28T21:21:43.278Z" },
]
[[package]]
name = "django-environ"
version = "0.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/04/65d2521842c42f4716225f20d8443a50804920606aec018188bbee30a6b0/django_environ-0.12.0.tar.gz", hash = "sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a", size = 56804, upload-time = "2025-01-13T17:03:37.74Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957, upload-time = "2025-01-13T17:03:32.918Z" },
]
[[package]]
name = "django-filter"
version = "25.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2c/e4/465d2699cd388c0005fb8d6ae6709f239917c6d8790ac35719676fffdcf3/django_filter-25.2.tar.gz", hash = "sha256:760e984a931f4468d096f5541787efb8998c61217b73006163bf2f9523fe8f23", size = 143818, upload-time = "2025-10-05T09:51:31.521Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/40/6a02495c5658beb1f31eb09952d8aa12ef3c2a66342331ce3a35f7132439/django_filter-25.2-py3-none-any.whl", hash = "sha256:9c0f8609057309bba611062fe1b720b4a873652541192d232dd28970383633e3", size = 94145, upload-time = "2025-10-05T09:51:29.728Z" },
]
[[package]]
name = "django-pipeline"
version = "4.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "setuptools" },
{ name = "wheel" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1c/7b/2f78ccdad9f45a3d4f709950160f9d7e5009b2b8bec8ef636025ec89b62e/django_pipeline-4.1.0.tar.gz", hash = "sha256:aa1d79df6f215b78396cdd50ed162f8741dc4993e9fba2c78483d9b6f1e722b4", size = 72180, upload-time = "2025-09-13T11:47:45.332Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/89/fd40adbf5cc16550007ad67bffd7493c9976f7e576bf431d1bc537cfa976/django_pipeline-4.1.0-py3-none-any.whl", hash = "sha256:bdb84feb8db73b9fe8298fd9d0f6e50f30d78eb28a8ed28f73ca5d154080c3d5", size = 75523, upload-time = "2025-09-13T11:42:55.754Z" },
]
[[package]]
name = "django-tables2"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/ab/3533314e818b04a9e1f04ae167845fec509f4e78ebaa6cf507d12eabcce3/django_tables2-2.8.0.tar.gz", hash = "sha256:0dea3401bb99a0164ba09e20d59a7d90856fdc05e5ae2da9a14d0fa14d99257c", size = 129204, upload-time = "2025-11-21T10:17:43.813Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/8b/047bb29359953b1c76f8886377845538144033d87da622c6b743f597a50b/django_tables2-2.8.0-py3-none-any.whl", hash = "sha256:860633b0f448216af73fca6005c7e38dc9b46931dc36c08a9281a71ee250b1ee", size = 95968, upload-time = "2025-11-21T10:17:42.363Z" },
]
[[package]]
name = "dnspython"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
]
[[package]]
name = "feedparser"
version = "6.0.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "sgmllib3k" },
]
sdist = { url = "https://files.pythonhosted.org/packages/dc/79/db7edb5e77d6dfbc54d7d9df72828be4318275b2e580549ff45a962f6461/feedparser-6.0.12.tar.gz", hash = "sha256:64f76ce90ae3e8ef5d1ede0f8d3b50ce26bcce71dd8ae5e82b1cd2d4a5f94228", size = 286579, upload-time = "2025-09-10T13:33:59.486Z" }
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 = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "nanoid"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/0250bf5935d88e214df469d35eccc0f6ff7e9db046fc8a9aeb4b2a192775/nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68", size = 3290, upload-time = "2018-11-20T14:45:51.578Z" }
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 = "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/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" },
{ url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" },
{ url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" },
{ url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" },
{ url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" },
{ url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" },
{ url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" },
{ url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" },
{ url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" },
{ url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" },
{ url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" },
{ url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" },
{ url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" },
{ url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" },
{ url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" },
{ url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" },
{ url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" },
{ url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" },
{ url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" },
{ url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" },
{ url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" },
{ url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" },
{ url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" },
]
[[package]]
name = "publicsuffix"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d5/70/8124bab47b4b83c5846e124e91e8958696200acabc7404d3765f44212f8d/publicsuffix-1.1.1.tar.gz", hash = "sha256:22ce1d65ab6af5e9b2122e2443facdb93fb5c4abf24138099cb10fe7989f43b6", size = 66870, upload-time = "2019-12-01T13:44:51.867Z" }
[[package]]
name = "pycparser"
version = "2.23"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
[[package]]
name = "pytz"
version = "2025.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
]
[[package]]
name = "servicecrm"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "asgiref" },
{ name = "cffi" },
{ name = "crispy-bootstrap5" },
{ name = "crispy-tailwind" },
{ name = "cryptography" },
{ name = "django" },
{ name = "django-crispy-forms" },
{ name = "django-datatable-view" },
{ name = "django-environ" },
{ name = "django-filter" },
{ name = "django-pipeline" },
{ name = "django-tables2" },
{ name = "dnspython" },
{ name = "feedparser" },
{ name = "nanoid" },
{ name = "pillow" },
{ name = "publicsuffix" },
{ name = "pycparser" },
{ name = "python-dateutil" },
{ name = "pytz" },
{ name = "sgmllib3k" },
{ name = "six" },
{ name = "sqlparse" },
{ name = "tablib" },
{ name = "typing-extensions" },
{ name = "tzdata" },
{ name = "uvicorn" },
]
[package.metadata]
requires-dist = [
{ name = "asgiref", specifier = ">=3.11.0" },
{ name = "cffi", specifier = ">=2.0.0" },
{ name = "crispy-bootstrap5", specifier = ">=2025.6" },
{ name = "crispy-tailwind", specifier = ">=1.0.3" },
{ name = "cryptography", specifier = ">=46.0.3" },
{ name = "django", specifier = ">=6.0.1" },
{ name = "django-crispy-forms", specifier = ">=2.5" },
{ name = "django-datatable-view", specifier = ">=2.1.6" },
{ name = "django-environ", specifier = ">=0.12.0" },
{ name = "django-filter", specifier = ">=25.2" },
{ name = "django-pipeline", specifier = ">=4.1.0" },
{ name = "django-tables2", specifier = ">=2.8.0" },
{ name = "dnspython", specifier = ">=2.8.0" },
{ name = "feedparser", specifier = ">=6.0.12" },
{ name = "nanoid", specifier = ">=2.0.0" },
{ name = "pillow", specifier = ">=12.1.0" },
{ name = "publicsuffix", specifier = ">=1.1.1" },
{ name = "pycparser", specifier = ">=2.23" },
{ name = "python-dateutil", specifier = ">=2.9.0.post0" },
{ name = "pytz", specifier = ">=2025.2" },
{ name = "sgmllib3k", specifier = ">=1.0.0" },
{ name = "six", specifier = ">=1.17.0" },
{ name = "sqlparse", specifier = ">=0.5.5" },
{ name = "tablib", specifier = ">=3.9.0" },
{ name = "typing-extensions", specifier = ">=4.15.0" },
{ name = "tzdata", specifier = ">=2025.3" },
{ name = "uvicorn", specifier = ">=0.40.0" },
]
[[package]]
name = "setuptools"
version = "80.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
]
[[package]]
name = "sgmllib3k"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750, upload-time = "2010-08-24T14:33:52.445Z" }
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "sqlparse"
version = "0.5.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
]
[[package]]
name = "tablib"
version = "3.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/11/00/416d2ba54d7d58a7f7c61bf62dfeb48fd553cf49614daf83312f2d2c156e/tablib-3.9.0.tar.gz", hash = "sha256:1b6abd8edb0f35601e04c6161d79660fdcde4abb4a54f66cc9f9054bd55d5fe2", size = 125565, upload-time = "2025-10-15T18:21:56.263Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/6b/32e51d847148b299088fc42d3d896845fd09c5247190133ea69dbe71ba51/tablib-3.9.0-py3-none-any.whl", hash = "sha256:eda17cd0d4dda614efc0e710227654c60ddbeb1ca92cdcfc5c3bd1fc5f5a6e4a", size = 49580, upload-time = "2025-10-15T18:21:44.185Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "tzdata"
version = "2025.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
]
[[package]]
name = "uvicorn"
version = "0.40.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
]
[[package]]
name = "wheel"
version = "0.45.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" }
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" },
]