commit 0c6faa8bb63cfbe3b82484d9ba485210d94b4fac
Author: yaemiku
Date: Thu Jun 26 15:57:42 2025 +0200
wizyta oraz widok księdza
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ad6670
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# Python-generated files
+__pycache__/
+*.py[oc]
+build/
+dist/
+wheels/
+*.egg-info
+
+# Virtual environments
+.venv
+.env
+
+# Django
+db.sqlite3
+media
\ No newline at end of file
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..24ee5b1
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.13
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/backend/__init__.py b/backend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/admin.py b/backend/admin.py
new file mode 100644
index 0000000..663a38a
--- /dev/null
+++ b/backend/admin.py
@@ -0,0 +1,59 @@
+from django.contrib import admin
+from .models import Parish, Submission, Priest
+from authtools.models import User
+
+# Register your models here.
+
+
+@admin.register(Priest)
+class PriestAdmin(admin.ModelAdmin):
+ list_display = ["priest", "parish"]
+
+ def get_form(self, request, obj=None, **kwargs):
+ form = super(PriestAdmin, self).get_form(request, obj, **kwargs)
+ form.base_fields["priest"].queryset = User.objects.filter(is_staff=True)
+ return form
+
+
+@admin.register(Submission)
+class SubmissionAdmin(admin.ModelAdmin):
+ list_display = ["id", "parish", "user", "name", "phone", "adres"]
+
+ def get_exclude(self, request, obj=None):
+ if not request.user.is_superuser:
+ return ["parish", "user"]
+
+ return []
+
+ def get_queryset(self, request):
+ qs = super().get_queryset(request)
+
+ if not request.user.is_superuser:
+ p = request.user.priest_rel.parish if request.user.priest_rel else None
+ return qs.filter(parish=p)
+
+ return qs
+
+ def get_changeform_initial_data(self, request):
+ initial = super(SubmissionAdmin, self).get_changeform_initial_data(request)
+ if not request.user.is_superuser:
+ p = request.user.priest_rel.parish if request.user.priest_rel else None
+ return {"parish": p, **initial}
+
+ return initial
+
+ def get_form(self, request, obj=None, **kwargs):
+ form = super(SubmissionAdmin, self).get_form(request, obj, **kwargs)
+
+ return form
+
+ def save_model(self, request, obj, form, change):
+ if not request.user.is_superuser:
+ p = request.user.priest_rel.parish if request.user.priest_rel else None
+ obj.parish = p
+ super().save_model(request, obj, form, change)
+
+
+@admin.register(Parish)
+class ParishAdmin(admin.ModelAdmin):
+ list_display = ["slug", "name", "visible", "submissions"]
diff --git a/backend/apps.py b/backend/apps.py
new file mode 100644
index 0000000..6a3779f
--- /dev/null
+++ b/backend/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class BackendConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'backend'
diff --git a/backend/migrations/0001_initial.py b/backend/migrations/0001_initial.py
new file mode 100644
index 0000000..4090846
--- /dev/null
+++ b/backend/migrations/0001_initial.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.1.6 on 2025-02-14 18:36
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Parish',
+ fields=[
+ ('slug', models.SlugField(primary_key=True, serialize=False, verbose_name='Identyfikator')),
+ ('name', models.CharField(max_length=512, verbose_name='Nazwa')),
+ ('submissions', models.BooleanField(default=False, verbose_name='Kolęda aktywna')),
+ ('announcements', models.TextField(blank=True, null=True, verbose_name='Skrypt - Ogłoszenia')),
+ ('intentions', models.TextField(blank=True, null=True, verbose_name='Skrypt - Intencje')),
+ ('streets', models.JSONField(default=list, verbose_name='Ulice')),
+ ],
+ options={
+ 'verbose_name': 'Parafia',
+ 'verbose_name_plural': 'Parafie',
+ 'ordering': ['slug'],
+ },
+ ),
+ ]
diff --git a/backend/migrations/0002_parish_channel.py b/backend/migrations/0002_parish_channel.py
new file mode 100644
index 0000000..2ccde5b
--- /dev/null
+++ b/backend/migrations/0002_parish_channel.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.6 on 2025-02-14 18:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='parish',
+ name='channel',
+ field=models.CharField(blank=True, max_length=64, null=True, verbose_name='Identyfikator kanału'),
+ ),
+ ]
diff --git a/backend/migrations/0003_parish_icon_parish_icon_height_parish_icon_width.py b/backend/migrations/0003_parish_icon_parish_icon_height_parish_icon_width.py
new file mode 100644
index 0000000..f37780c
--- /dev/null
+++ b/backend/migrations/0003_parish_icon_parish_icon_height_parish_icon_width.py
@@ -0,0 +1,29 @@
+# Generated by Django 5.1.6 on 2025-02-14 19:33
+
+import pictures.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0002_parish_channel'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='parish',
+ name='icon',
+ field=pictures.models.PictureField(aspect_ratios=[None], breakpoints={'l': 1200, 'm': 992, 's': 768, 'xl': 1400, 'xs': 576}, container_width=1200, file_types=['WEBP', 'PNG'], grid_columns=12, height_field='icon_height', null=True, pixel_densities=[1, 2], upload_to='icons', verbose_name='Ikona', width_field='icon_width'),
+ ),
+ migrations.AddField(
+ model_name='parish',
+ name='icon_height',
+ field=models.PositiveIntegerField(null=True),
+ ),
+ migrations.AddField(
+ model_name='parish',
+ name='icon_width',
+ field=models.PositiveIntegerField(null=True),
+ ),
+ ]
diff --git a/backend/migrations/0004_parish_visible.py b/backend/migrations/0004_parish_visible.py
new file mode 100644
index 0000000..965abd8
--- /dev/null
+++ b/backend/migrations/0004_parish_visible.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.6 on 2025-02-14 21:07
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0003_parish_icon_parish_icon_height_parish_icon_width'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='parish',
+ name='visible',
+ field=models.BooleanField(default=False, verbose_name='Parafia widoczna'),
+ ),
+ ]
diff --git a/backend/migrations/0005_submission.py b/backend/migrations/0005_submission.py
new file mode 100644
index 0000000..491b112
--- /dev/null
+++ b/backend/migrations/0005_submission.py
@@ -0,0 +1,33 @@
+# Generated by Django 5.1.6 on 2025-02-14 23:37
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0004_parish_visible'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Submission',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Imię i nazwisko')),
+ ('phone', models.CharField(max_length=32, verbose_name='Numer telefonu')),
+ ('street', models.CharField(max_length=128, verbose_name='Ulica')),
+ ('street_number', models.PositiveIntegerField(verbose_name='Numer domu')),
+ ('apartement_number', models.PositiveIntegerField(blank=True, null=True, verbose_name='Numer mieszkania')),
+ ('parish', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submission', to='backend.parish', verbose_name='Parafia')),
+ ('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='submission', to=settings.AUTH_USER_MODEL, verbose_name='Użytkownik')),
+ ],
+ options={
+ 'verbose_name': 'Zgłoszenie',
+ 'verbose_name_plural': 'Zgłoszenia',
+ },
+ ),
+ ]
diff --git a/backend/migrations/0006_alter_parish_icon_alter_parish_streets_and_more.py b/backend/migrations/0006_alter_parish_icon_alter_parish_streets_and_more.py
new file mode 100644
index 0000000..4fce0c7
--- /dev/null
+++ b/backend/migrations/0006_alter_parish_icon_alter_parish_streets_and_more.py
@@ -0,0 +1,50 @@
+# Generated by Django 5.1.6 on 2025-06-26 13:19
+
+import django.core.validators
+import django.db.models.deletion
+import pictures.models
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0005_submission'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='parish',
+ name='icon',
+ field=pictures.models.PictureField(aspect_ratios=[None], blank=True, breakpoints={'l': 1200, 'm': 992, 's': 768, 'xl': 1400, 'xs': 576}, container_width=1200, file_types=['WEBP', 'PNG'], grid_columns=12, height_field='icon_height', null=True, pixel_densities=[1, 2], upload_to='icons', verbose_name='Ikona', width_field='icon_width'),
+ ),
+ migrations.AlterField(
+ model_name='parish',
+ name='streets',
+ field=models.JSONField(blank=True, default=list, null=True, verbose_name='Ulice'),
+ ),
+ migrations.AlterField(
+ model_name='submission',
+ name='phone',
+ field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator(message="Numer telefonu musi być postaci: '(+48)123456789'", regex='^\\+?1?\\d{9,15}$')], verbose_name='Numer telefonu'),
+ ),
+ migrations.AlterField(
+ model_name='submission',
+ name='user',
+ field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='submission', to=settings.AUTH_USER_MODEL, verbose_name='Użytkownik'),
+ ),
+ migrations.CreateModel(
+ name='Priest',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('parish', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='priest', to='backend.parish', verbose_name='Parafia')),
+ ('priest', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Ksiądz')),
+ ],
+ options={
+ 'verbose_name': 'Ksiądz',
+ 'verbose_name_plural': 'Księża',
+ },
+ ),
+ ]
diff --git a/backend/migrations/__init__.py b/backend/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/models.py b/backend/models.py
new file mode 100644
index 0000000..d878f5d
--- /dev/null
+++ b/backend/models.py
@@ -0,0 +1,65 @@
+from django.db import models
+from pictures.models import PictureField
+from authtools.models import User
+from django.core.validators import RegexValidator
+
+# Create your models here.
+
+
+class Parish(models.Model):
+ slug = models.SlugField("Identyfikator", primary_key=True)
+ name = models.CharField("Nazwa", max_length=512)
+ visible = models.BooleanField("Parafia widoczna", default=False)
+
+ icon = PictureField(
+ "Ikona", upload_to="icons", height_field="icon_height", width_field="icon_width", null=True, blank=True
+ )
+ icon_width = models.PositiveIntegerField(null=True)
+ icon_height = models.PositiveIntegerField(null=True)
+
+ channel = models.CharField("Identyfikator kanału", max_length=64, blank=True, null=True)
+ submissions = models.BooleanField("Kolęda aktywna", default=False)
+ announcements = models.TextField("Skrypt - Ogłoszenia", blank=True, null=True)
+ intentions = models.TextField("Skrypt - Intencje", blank=True, null=True)
+ streets = models.JSONField("Ulice", default=list, null=True, blank=True)
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ verbose_name = "Parafia"
+ verbose_name_plural = "Parafie"
+ ordering = ["slug"]
+
+
+class Submission(models.Model):
+ user = models.OneToOneField(
+ User, on_delete=models.CASCADE, related_name="submission", verbose_name="Użytkownik", null=True, blank=True
+ )
+ parish = models.ForeignKey(Parish, on_delete=models.CASCADE, related_name="submission", verbose_name="Parafia")
+
+ name = models.CharField("Imię i nazwisko", max_length=256)
+ phone_regex = RegexValidator(regex=r"^\+?1?\d{9,15}$", message="Numer telefonu musi być postaci: '(+48)123456789'")
+ phone = models.CharField("Numer telefonu", max_length=32, validators=[phone_regex])
+ street = models.CharField("Ulica", max_length=128)
+ street_number = models.PositiveIntegerField("Numer domu")
+ apartement_number = models.PositiveIntegerField("Numer mieszkania", null=True, blank=True)
+
+ @property
+ def adres(self):
+ return (
+ f"{self.street} {self.street_number}{'/' + str(self.apartement_number) if self.apartement_number else ''}"
+ )
+
+ class Meta:
+ verbose_name = "Zgłoszenie"
+ verbose_name_plural = "Zgłoszenia"
+
+
+class Priest(models.Model):
+ priest = models.OneToOneField(User, on_delete=models.CASCADE, related_name="priest_rel", verbose_name="Ksiądz")
+ parish = models.ForeignKey(Parish, on_delete=models.CASCADE, related_name="priest_rel", verbose_name="Parafia")
+
+ class Meta:
+ verbose_name = "Ksiądz"
+ verbose_name_plural = "Księża"
diff --git a/backend/templates/base.html b/backend/templates/base.html
new file mode 100644
index 0000000..8806e0f
--- /dev/null
+++ b/backend/templates/base.html
@@ -0,0 +1,94 @@
+
+{% load static i18n %}
+
+
+
+
+
+
+
+
+ eParafia
+
+
+ {% block head %}
+
+ {% endblock %}
+
+
+
+
+
+ {% comment %} {% endcomment %}
+
+
+
+
+
+ {% block layout %}
+
+ {% block content %}
+ {% endblock %}
+
+ {% endblock %}
+
+
diff --git a/backend/templates/index.html b/backend/templates/index.html
new file mode 100644
index 0000000..1ba80f6
--- /dev/null
+++ b/backend/templates/index.html
@@ -0,0 +1,16 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
eParafia
+
+
Dziękujemy za korzystanie z naszej aplikacji! Poniżej znajduje się lista odnośników do wszystkich zarejestrowanych z nami parafii. Klikając w przycisk zostaniecie państwo bezpośrednio przekierowani do aplikacji danej parafii
+
+
Zapraszamy!
+
+
+{% endblock %}
diff --git a/backend/templates/parish/announcements.html b/backend/templates/parish/announcements.html
new file mode 100644
index 0000000..84f79f9
--- /dev/null
+++ b/backend/templates/parish/announcements.html
@@ -0,0 +1,5 @@
+{% extends 'parish/base.html' %}
+
+{% block content %}
+ {{res | safe}}
+{% endblock %}
diff --git a/backend/templates/parish/base.html b/backend/templates/parish/base.html
new file mode 100644
index 0000000..a2fbe2e
--- /dev/null
+++ b/backend/templates/parish/base.html
@@ -0,0 +1,45 @@
+{% extends 'base.html' %}
+{% load pictures %}
+
+{% block head %}
+
+
+
+{% endblock %}
+
+{% block layout %}
+
+
+
+
{{ parish.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
+{% endblock %}
diff --git a/backend/templates/parish/intentions.html b/backend/templates/parish/intentions.html
new file mode 100644
index 0000000..f6a922f
--- /dev/null
+++ b/backend/templates/parish/intentions.html
@@ -0,0 +1,5 @@
+{% extends 'parish/base.html' %}
+
+{% block content %}
+ {{ res | safe }}
+{% endblock %}
diff --git a/backend/templates/parish/live.html b/backend/templates/parish/live.html
new file mode 100644
index 0000000..3a02f6a
--- /dev/null
+++ b/backend/templates/parish/live.html
@@ -0,0 +1,14 @@
+{% extends 'parish/base.html' %}
+
+{% block content %}
+
+
+
+
+
+
+{% endblock content %}
\ No newline at end of file
diff --git a/backend/templates/parish/manifest.json b/backend/templates/parish/manifest.json
new file mode 100644
index 0000000..e54e54c
--- /dev/null
+++ b/backend/templates/parish/manifest.json
@@ -0,0 +1,22 @@
+{% load pictures %}
+
+{
+ "name": "{{ parish.name }}",
+ "display": "standalone",
+ "scope": "{% url 'parish' parish.slug %}",
+ "start_url": "{% url 'parish' parish.slug %}",
+ "icons": [
+ {
+ "src": "{% img_url parish.icon file_type="png" width=192 %}",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable any"
+ },
+ {
+ "src": "{% img_url parish.icon file_type="png" width=512 %}",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable any"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/templates/parish/visit/auth.html b/backend/templates/parish/visit/auth.html
new file mode 100644
index 0000000..e38fd28
--- /dev/null
+++ b/backend/templates/parish/visit/auth.html
@@ -0,0 +1,8 @@
+{% extends 'parish/base.html' %}
+
+{% block content %}
+
+ Zaloguj się
+ Zarejestruj się
+
+{% endblock %}
diff --git a/backend/templates/parish/visit/form.html b/backend/templates/parish/visit/form.html
new file mode 100644
index 0000000..621ab51
--- /dev/null
+++ b/backend/templates/parish/visit/form.html
@@ -0,0 +1,18 @@
+{% extends 'parish/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+ Zapisz się na wizytę duszpasterską
+
+
+{% endblock %}
diff --git a/backend/templates/parish/visit/login.html b/backend/templates/parish/visit/login.html
new file mode 100644
index 0000000..d9b4617
--- /dev/null
+++ b/backend/templates/parish/visit/login.html
@@ -0,0 +1,14 @@
+{% extends 'parish/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+ Zaloguj się
+
+{% endblock %}
diff --git a/backend/templates/parish/visit/signup.html b/backend/templates/parish/visit/signup.html
new file mode 100644
index 0000000..baa1cf9
--- /dev/null
+++ b/backend/templates/parish/visit/signup.html
@@ -0,0 +1,14 @@
+{% extends 'parish/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+ Zarejestruj się
+
+{% endblock %}
diff --git a/backend/templates/parish/visit/submitted.html b/backend/templates/parish/visit/submitted.html
new file mode 100644
index 0000000..5918fe3
--- /dev/null
+++ b/backend/templates/parish/visit/submitted.html
@@ -0,0 +1,28 @@
+{% extends 'parish/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+ Twoje zgłoszenie
+
+
+ Imię i nazwisko: {{ submission.name }}, Telefon: {{ submission.phone }}
+
+
+ Adres: {{ submission.street }} {{ submission.street_number }}{% if submission.apartement_number %}/{% endif %}{{ submission.apartement_number|default_if_none:'' }}
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/backend/templates/parish/visit/update.html b/backend/templates/parish/visit/update.html
new file mode 100644
index 0000000..13d2cec
--- /dev/null
+++ b/backend/templates/parish/visit/update.html
@@ -0,0 +1,18 @@
+{% extends 'parish/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+ Zmień dane konta
+
+ {% comment %} {% endcomment %}
+{% endblock %}
diff --git a/backend/tests.py b/backend/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/backend/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/backend/views.py b/backend/views.py
new file mode 100644
index 0000000..a488fd5
--- /dev/null
+++ b/backend/views.py
@@ -0,0 +1,266 @@
+from django.shortcuts import render, redirect
+from django.urls import path
+from .models import Parish, Submission
+import requests # noqa: F401
+from lxml import html # noqa: F401
+from django.http import HttpResponse
+from django.views.generic import CreateView, DeleteView, UpdateView
+from authtools.forms import UserCreationForm
+from authtools.models import User
+from django.contrib.auth.views import LoginView, LogoutView
+from django.urls import reverse_lazy
+from django import forms
+
+
+# Create your views here.
+
+
+def favicon(request):
+ return HttpResponse(None)
+
+
+def manifest(request, slug):
+ return render(
+ request, "parish/manifest.json", {"parish": Parish.objects.get(slug=slug)}, content_type="application/json"
+ )
+
+
+def index(request):
+ parishes = Parish.objects.filter(visible=True)
+ return render(request, "index.html", {"parishes": parishes})
+
+
+def announcements(request, slug):
+ p = Parish.objects.get(slug=slug)
+ ldict = {}
+ exec(p.announcements, globals(), ldict)
+
+ return render(
+ request, "parish/announcements.html", {"parish": p, "announcements": "is-active", "res": ldict.get("res", "")}
+ )
+
+
+def intentions(request, slug):
+ p = Parish.objects.get(slug=slug)
+ ldict = {}
+ exec(p.intentions, globals(), ldict)
+
+ return render(
+ request, "parish/intentions.html", {"parish": p, "intentions": "is-active", "res": ldict.get("res", "")}
+ )
+
+
+def live(request, slug):
+ p = Parish.objects.get(slug=slug)
+ if not p.channel:
+ return redirect("announcements", slug=slug)
+
+ return render(request, "parish/live.html", {"parish": p, "live": "is-active"})
+
+
+def visit(request, slug):
+ p = Parish.objects.get(slug=slug)
+ c = {"parish": p, "visit": "is-active"}
+ if not p.submissions:
+ return redirect("announcements", slug=slug)
+
+ if not request.user.is_authenticated:
+ return render(request, "parish/visit/auth.html", c)
+
+ if not Submission.objects.filter(user=request.user).exists():
+ return VisitCreateView.as_view()(request, slug=slug)
+
+ c["submission"] = Submission.objects.get(user=request.user)
+ return VisitDeleteView.as_view()(request, slug=slug)
+
+
+class SubmissionForm(forms.ModelForm):
+ def __init__(self, slug, *args, **kwargs):
+ super(SubmissionForm, self).__init__(*args, **kwargs)
+ streets = Parish.objects.get(slug=slug).streets
+ choices = zip(streets, streets)
+ self.fields["street"] = forms.ChoiceField(label="Ulica", choices=choices)
+
+ class Meta:
+ model = Submission
+ fields = [
+ "name",
+ "phone",
+ "street",
+ "street_number",
+ "apartement_number",
+ ]
+
+
+class VisitCreateView(CreateView):
+ form_class = SubmissionForm
+ template_name = "parish/visit/form.html"
+
+ def form_valid(self, form):
+ form.instance.parish = Parish.objects.get(slug=self.kwargs["slug"])
+ form.instance.user = self.request.user
+ return super().form_valid(form)
+
+ def get_initial(self):
+ return {"name": self.request.user.name}
+
+ def get_success_url(self):
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ return reverse_lazy("visit", args=[p.slug])
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ context["parish"] = p
+ context["visit"] = "is-active"
+ return context
+
+ def get_form(self, form_class=None):
+ form = super().get_form(form_class)
+ form.fields["name"].disabled = True
+ return form
+
+ def get_form_kwargs(self):
+ return {**super().get_form_kwargs(), **self.kwargs}
+
+
+class VisitDeleteView(DeleteView):
+ model = Submission
+ template_name = "parish/visit/submitted.html"
+
+ def get_object(self):
+ return Submission.objects.get(user=self.request.user)
+
+ def get_success_url(self):
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ return reverse_lazy("visit", args=[p.slug])
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ context["parish"] = p
+ context["visit"] = "is-active"
+ return context
+
+
+class VisitLoginView(LoginView):
+ redirect_authenticated_user = True
+ template_name = "parish/visit/login.html"
+
+ def get_success_url(self):
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ return reverse_lazy("visit", args=[p.slug])
+
+ def dispatch(self, request, *args, **kwargs):
+ p = Parish.objects.get(slug=kwargs["slug"])
+ if self.request.user.is_authenticated:
+ return redirect("visit", slug=p.slug)
+
+ if not p.submissions:
+ return redirect("announcements", slug=p.slug)
+
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ context["parish"] = p
+ context["visit"] = "is-active"
+ return context
+
+
+class VisitSignUpView(CreateView):
+ form_class = UserCreationForm
+ template_name = "parish/visit/signup.html"
+
+ def get_success_url(self):
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ return reverse_lazy("visit", args=[p.slug])
+
+ def dispatch(self, request, *args, **kwargs):
+ p = Parish.objects.get(slug=kwargs["slug"])
+ if self.request.user.is_authenticated:
+ return redirect("visit", slug=p.slug)
+
+ if not p.submissions:
+ return redirect("announcements", slug=p.slug)
+
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ context["parish"] = p
+ context["visit"] = "is-active"
+ return context
+
+
+class UserUpdateForm(forms.ModelForm):
+ class Meta:
+ model = User
+ fields = ["email", "name"]
+
+
+class VisitAccountView(UpdateView):
+ form_class = UserUpdateForm
+ template_name = "parish/visit/update.html"
+
+ def get_object(self):
+ return User.objects.get(id=self.request.user.id)
+
+ def get_success_url(self):
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ return reverse_lazy("visit", args=[p.slug])
+
+ def dispatch(self, request, *args, **kwargs):
+ p = Parish.objects.get(slug=kwargs["slug"])
+ # if self.request.user.is_authenticated:
+ # return redirect("visit", slug=p.slug)
+
+ if not p.submissions:
+ return redirect("announcements", slug=p.slug)
+
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ context["parish"] = p
+ context["visit"] = "is-active"
+ return context
+
+ def get_form(self, form_class=None):
+ form = super().get_form(form_class)
+ form.fields["name"].label = "Imię i nazwisko"
+ return form
+
+
+class VisitLogoutView(LogoutView):
+ template_name = "parish/visit/auth.html"
+
+ def get_next_page(self):
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ return reverse_lazy("visit", args=[p.slug])
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ p = Parish.objects.get(slug=self.kwargs["slug"])
+ context["parish"] = p
+ context["visit"] = "is-active"
+ return context
+
+
+urlpatterns = [
+ path("", index, name="index"),
+ path("favicon.ico", favicon),
+ path("/", announcements, name="parish"),
+ path("/ogloszenia/", announcements, name="announcements"),
+ path("/intencje/", intentions, name="intentions"),
+ path("/transmisja/", live, name="live"),
+ path("/wizyta/", visit, name="visit"),
+ path("/wizyta/logowanie/", VisitLoginView.as_view(), name="visit_login"),
+ path("/wizyta/rejestracja/", VisitSignUpView.as_view(), name="visit_signup"),
+ path("/wizyta/konto/", VisitAccountView.as_view(), name="visit_account"),
+ path("/wizyta/wylogowanie/", VisitLogoutView.as_view(), name="visit_logout"),
+ path("/manifest.json", manifest, name="manifest"),
+]
diff --git a/eparafia/__init__.py b/eparafia/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/eparafia/asgi.py b/eparafia/asgi.py
new file mode 100644
index 0000000..78e181a
--- /dev/null
+++ b/eparafia/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for eparafia project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eparafia.settings')
+
+application = get_asgi_application()
diff --git a/eparafia/settings.py b/eparafia/settings.py
new file mode 100644
index 0000000..343eb29
--- /dev/null
+++ b/eparafia/settings.py
@@ -0,0 +1,150 @@
+"""
+Django settings for eparafia project.
+
+Generated by 'django-admin startproject' using Django 5.1.6.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.1/ref/settings/
+"""
+
+import os
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = os.getenv("SECRET_KEY")
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = os.getenv("DEBUG") == "True"
+
+ALLOWED_HOSTS = ["*"]
+CSRF_TRUSTED_ORIGINS = ["http://*", "https://*"]
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ "authtools",
+ "backend",
+ "crispy_forms",
+ "crispy_bulma",
+ "pictures",
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "django_browser_reload",
+ "django_cleanup.apps.CleanupConfig",
+]
+
+MIDDLEWARE = [
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
+ "django_browser_reload.middleware.BrowserReloadMiddleware",
+]
+
+ROOT_URLCONF = "eparafia.urls"
+
+TEMPLATES = [
+ {
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = "eparafia.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
+
+DATABASES = {
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": BASE_DIR / "db.sqlite3",
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+ }
+]
+AUTH_USER_MODEL = "authtools.User"
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.1/topics/i18n/
+
+LANGUAGE_CODE = "pl-PL"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.1/howto/static-files/
+
+STATIC_URL = "static/"
+STATIC_ROOT = BASE_DIR / "static"
+
+MEDIA_URL = "media/"
+MEDIA_ROOT = BASE_DIR / "media"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+
+PICTURES = {
+ "BREAKPOINTS": {
+ "xs": 576,
+ "s": 768,
+ "m": 992,
+ "l": 1200,
+ "xl": 1400,
+ },
+ "GRID_COLUMNS": 12,
+ "CONTAINER_WIDTH": 1200,
+ "FILE_TYPES": ["WEBP", "PNG"],
+ "PIXEL_DENSITIES": [1, 2],
+ "USE_PLACEHOLDERS": False,
+ "QUEUE_NAME": "pictures",
+ "PROCESSOR": "pictures.tasks.process_picture",
+}
+
+CRISPY_ALLOWED_TEMPLATE_PACKS = ("bulma",)
+
+CRISPY_TEMPLATE_PACK = "bulma"
diff --git a/eparafia/urls.py b/eparafia/urls.py
new file mode 100644
index 0000000..274821c
--- /dev/null
+++ b/eparafia/urls.py
@@ -0,0 +1,34 @@
+"""
+URL configuration for eparafia project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/5.1/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+
+from django.contrib import admin
+from django.urls import path, include
+from pictures.conf import get_settings
+from django.conf import settings
+from django.conf.urls.static import static
+
+
+urlpatterns = [
+ path("admin/", admin.site.urls),
+ path("", include("backend.views")),
+ path("__reload__/", include("django_browser_reload.urls")),
+] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+
+if get_settings().USE_PLACEHOLDERS:
+ urlpatterns += [
+ path("_pictures/", include("pictures.urls")),
+ ]
diff --git a/eparafia/wsgi.py b/eparafia/wsgi.py
new file mode 100644
index 0000000..913ee13
--- /dev/null
+++ b/eparafia/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for eparafia project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eparafia.settings')
+
+application = get_wsgi_application()
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..e8caf0c
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eparafia.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..fd39f57
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,19 @@
+[project]
+name = "eparafia"
+version = "0.1.0"
+description = "Add your description here"
+readme = "README.md"
+requires-python = ">=3.13"
+dependencies = [
+ "beautifulsoup4>=4.13.4",
+ "crispy-bulma>=0.11.0",
+ "django>=5.1.6",
+ "django-authtools>=2.0.1",
+ "django-browser-reload>=1.18.0",
+ "django-cleanup>=9.0.0",
+ "django-crispy-forms>=2.3",
+ "django-pictures>=1.4.0",
+ "gunicorn>=23.0.0",
+ "lxml>=5.3.1",
+ "requests>=2.32.3",
+]
diff --git a/static/icon-192x192.png b/static/icon-192x192.png
new file mode 100644
index 0000000..bbda803
Binary files /dev/null and b/static/icon-192x192.png differ
diff --git a/static/icon-512x512.png b/static/icon-512x512.png
new file mode 100644
index 0000000..ec100bf
Binary files /dev/null and b/static/icon-512x512.png differ
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..be80242
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,314 @@
+version = 1
+revision = 2
+requires-python = ">=3.13"
+
+[[package]]
+name = "asgiref"
+version = "3.8.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186, upload-time = "2024-03-22T14:39:36.863Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.13.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "soupsieve" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2025.1.31"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" },
+ { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" },
+ { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" },
+ { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" },
+ { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" },
+ { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" },
+]
+
+[[package]]
+name = "crispy-bulma"
+version = "0.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+ { name = "django-crispy-forms" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/90/6b/660e9b1e6cab7a3a63434edbdba85e21e7f217e1857b3026a84219ac61a7/crispy-bulma-0.11.0.tar.gz", hash = "sha256:27eccd09a5a77754d64462f61ff585c00feb8c15f65d0d8f9676ab26f6bc3562", size = 35747, upload-time = "2023-12-06T20:39:29.969Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/6c/8734b21a4a0ea2dbfd563d30e9f245f81e72b7a19fb17f1ed32a3391d1e2/crispy_bulma-0.11.0-py3-none-any.whl", hash = "sha256:bc5406d64649a3da9c61d1aa969cb2c39bb3a1802ba8ee4bccebe94a84ca94e1", size = 20571, upload-time = "2023-12-06T20:39:23.992Z" },
+]
+
+[[package]]
+name = "django"
+version = "5.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "asgiref" },
+ { name = "sqlparse" },
+ { name = "tzdata", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/e4/901f54ee114a080371a49bd08fa688d301aaffd9751febaf4ae855fc8fcd/Django-5.1.6.tar.gz", hash = "sha256:1e39eafdd1b185e761d9fab7a9f0b9fa00af1b37b25ad980a8aa0dac13535690", size = 10700620, upload-time = "2025-02-05T14:16:25.948Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/75/6f/d2c216d00975e2604b10940937b0ba6b2c2d9b3cc0cc633e414ae3f14b2e/Django-5.1.6-py3-none-any.whl", hash = "sha256:8d203400bc2952fbfb287c2bbda630297d654920c72a73cc82a9ad7926feaad5", size = 8277066, upload-time = "2025-02-05T14:16:00.563Z" },
+]
+
+[[package]]
+name = "django-authtools"
+version = "2.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/54/57/6a40c459ae93032f63efae348891d0b43877126f6600e27b1c511dd03c45/django-authtools-2.0.1.tar.gz", hash = "sha256:e344cb6be7fd5155208e291eddfcb83510efd4bad3913cb5031347b000a34c4c", size = 38956, upload-time = "2024-03-19T18:46:58.316Z" }
+
+[[package]]
+name = "django-browser-reload"
+version = "1.18.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "asgiref" },
+ { name = "django" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3b/41/84eaa4f2b7f764e56e01c4ee49aa12bdea30ae50fd0d3ef7c2690b0c4ec2/django_browser_reload-1.18.0.tar.gz", hash = "sha256:c5f0b134723cbf2a0dc9ae1ee1d38e42db28fe23c74cdee613ba3ef286d04735", size = 14319, upload-time = "2025-02-06T22:14:40.799Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e4/9d/1322dc4bce4982d1eadd3a62802c996ae0303aad13d9ad88c1d35025f73d/django_browser_reload-1.18.0-py3-none-any.whl", hash = "sha256:ed4cc2fb83c3bf6c30b54107a1a6736c0b896e62e4eba666d81005b9f2ecf6f8", size = 12230, upload-time = "2025-02-06T22:14:36.87Z" },
+]
+
+[[package]]
+name = "django-cleanup"
+version = "9.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4b/01/b15a8de8b9ec75ea157ec58f86411894ca1182305fabaee31193076e7f62/django_cleanup-9.0.0.tar.gz", hash = "sha256:bb9fb560aaf62959c81e31fa40885c36bbd5854d5aa21b90df2c7e4ba633531e", size = 17917, upload-time = "2024-09-18T21:58:06.089Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/87/d7/a83dc87c2383e125da29948f7bccf5b30126c087a5a831316482407a960f/django_cleanup-9.0.0-py3-none-any.whl", hash = "sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c", size = 10726, upload-time = "2024-09-18T21:58:04.626Z" },
+]
+
+[[package]]
+name = "django-crispy-forms"
+version = "2.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/f6/5bce7ae3512171c7c0ca3de31689e2a1ced8b030f156fcf13d2870e5468e/django_crispy_forms-2.3.tar.gz", hash = "sha256:2db17ae08527201be1273f0df789e5f92819e23dd28fec69cffba7f3762e1a38", size = 278849, upload-time = "2024-07-19T13:08:16.264Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4c/3b/5dc3faf8739d1ce7a73cedaff508b4af8f6aa1684120ded6185ca0c92734/django_crispy_forms-2.3-py3-none-any.whl", hash = "sha256:efc4c31e5202bbec6af70d383a35e12fc80ea769d464fb0e7fe21768bb138a20", size = 31411, upload-time = "2024-07-19T13:08:14.498Z" },
+]
+
+[[package]]
+name = "django-pictures"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "django" },
+ { name = "pillow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/79/e8/65f6aa27a56c410aa84e1c32820016c3b0df84a0510d7a8686037a4c3b17/django_pictures-1.4.0.tar.gz", hash = "sha256:7133ac74a1c054cd5666c921fe76eee4d00d171c68d19d508ddf196f862f2d77", size = 20900, upload-time = "2024-12-17T13:01:09.435Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/cd/249c7cf59e38f60d1bdd6a3ec9151a7eafcc988487d41e814d57f6ff511a/django_pictures-1.4.0-py3-none-any.whl", hash = "sha256:4ef318525e70c1d2ae9f6f7ca370e99518283ee501ea77984cbb2da7c212c288", size = 21926, upload-time = "2024-12-17T13:01:07.965Z" },
+]
+
+[[package]]
+name = "eparafia"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "beautifulsoup4" },
+ { name = "crispy-bulma" },
+ { name = "django" },
+ { name = "django-authtools" },
+ { name = "django-browser-reload" },
+ { name = "django-cleanup" },
+ { name = "django-crispy-forms" },
+ { name = "django-pictures" },
+ { name = "gunicorn" },
+ { name = "lxml" },
+ { name = "requests" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "beautifulsoup4", specifier = ">=4.13.4" },
+ { name = "crispy-bulma", specifier = ">=0.11.0" },
+ { name = "django", specifier = ">=5.1.6" },
+ { name = "django-authtools", specifier = ">=2.0.1" },
+ { name = "django-browser-reload", specifier = ">=1.18.0" },
+ { name = "django-cleanup", specifier = ">=9.0.0" },
+ { name = "django-crispy-forms", specifier = ">=2.3" },
+ { name = "django-pictures", specifier = ">=1.4.0" },
+ { name = "gunicorn", specifier = ">=23.0.0" },
+ { name = "lxml", specifier = ">=5.3.1" },
+ { name = "requests", specifier = ">=2.32.3" },
+]
+
+[[package]]
+name = "gunicorn"
+version = "23.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
+]
+
+[[package]]
+name = "lxml"
+version = "5.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ef/f6/c15ca8e5646e937c148e147244817672cf920b56ac0bf2cc1512ae674be8/lxml-5.3.1.tar.gz", hash = "sha256:106b7b5d2977b339f1e97efe2778e2ab20e99994cbb0ec5e55771ed0795920c8", size = 3678591, upload-time = "2025-02-10T07:51:41.769Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/1c/724931daa1ace168e0237b929e44062545bf1551974102a5762c349c668d/lxml-5.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c093c7088b40d8266f57ed71d93112bd64c6724d31f0794c1e52cc4857c28e0e", size = 8171881, upload-time = "2025-02-10T07:46:40.653Z" },
+ { url = "https://files.pythonhosted.org/packages/67/0c/857b8fb6010c4246e66abeebb8639eaabba60a6d9b7c606554ecc5cbf1ee/lxml-5.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0884e3f22d87c30694e625b1e62e6f30d39782c806287450d9dc2fdf07692fd", size = 4440394, upload-time = "2025-02-10T07:46:44.037Z" },
+ { url = "https://files.pythonhosted.org/packages/61/72/c9e81de6a000f9682ccdd13503db26e973b24c68ac45a7029173237e3eed/lxml-5.3.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1637fa31ec682cd5760092adfabe86d9b718a75d43e65e211d5931809bc111e7", size = 5037860, upload-time = "2025-02-10T07:46:47.919Z" },
+ { url = "https://files.pythonhosted.org/packages/24/26/942048c4b14835711b583b48cd7209bd2b5f0b6939ceed2381a494138b14/lxml-5.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a364e8e944d92dcbf33b6b494d4e0fb3499dcc3bd9485beb701aa4b4201fa414", size = 4782513, upload-time = "2025-02-10T07:46:50.696Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/65/27792339caf00f610cc5be32b940ba1e3009b7054feb0c4527cebac228d4/lxml-5.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:779e851fd0e19795ccc8a9bb4d705d6baa0ef475329fe44a13cf1e962f18ff1e", size = 5305227, upload-time = "2025-02-10T07:46:53.503Z" },
+ { url = "https://files.pythonhosted.org/packages/18/e1/25f7aa434a4d0d8e8420580af05ea49c3e12db6d297cf5435ac0a054df56/lxml-5.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4393600915c308e546dc7003d74371744234e8444a28622d76fe19b98fa59d1", size = 4829846, upload-time = "2025-02-10T07:46:56.262Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/ed/faf235e0792547d24f61ee1448159325448a7e4f2ab706503049d8e5df19/lxml-5.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:673b9d8e780f455091200bba8534d5f4f465944cbdd61f31dc832d70e29064a5", size = 4949495, upload-time = "2025-02-10T07:46:59.189Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/e1/8f572ad9ed6039ba30f26dd4c2c58fb90f79362d2ee35ca3820284767672/lxml-5.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a570f6a99e96c457f7bec5ad459c9c420ee80b99eb04cbfcfe3fc18ec6423", size = 4773415, upload-time = "2025-02-10T07:47:03.53Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/75/6b57166b9d1983dac8f28f354e38bff8d6bcab013a241989c4d54c72701b/lxml-5.3.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:71f31eda4e370f46af42fc9f264fafa1b09f46ba07bdbee98f25689a04b81c20", size = 5337710, upload-time = "2025-02-10T07:47:06.385Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/71/4aa56e2daa83bbcc66ca27b5155be2f900d996f5d0c51078eaaac8df9547/lxml-5.3.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:42978a68d3825eaac55399eb37a4d52012a205c0c6262199b8b44fcc6fd686e8", size = 4897362, upload-time = "2025-02-10T07:47:09.24Z" },
+ { url = "https://files.pythonhosted.org/packages/65/10/3fa2da152cd9b49332fd23356ed7643c9b74cad636ddd5b2400a9730d12b/lxml-5.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8b1942b3e4ed9ed551ed3083a2e6e0772de1e5e3aca872d955e2e86385fb7ff9", size = 4977795, upload-time = "2025-02-10T07:47:12.101Z" },
+ { url = "https://files.pythonhosted.org/packages/de/d2/e1da0f7b20827e7b0ce934963cb6334c1b02cf1bb4aecd218c4496880cb3/lxml-5.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85c4f11be9cf08917ac2a5a8b6e1ef63b2f8e3799cec194417e76826e5f1de9c", size = 4858104, upload-time = "2025-02-10T07:47:15.998Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/35/063420e1b33d3308f5aa7fcbdd19ef6c036f741c9a7a4bd5dc8032486b27/lxml-5.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:231cf4d140b22a923b1d0a0a4e0b4f972e5893efcdec188934cc65888fd0227b", size = 5416531, upload-time = "2025-02-10T07:47:19.862Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/83/93a6457d291d1e37adfb54df23498101a4701834258c840381dd2f6a030e/lxml-5.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5865b270b420eda7b68928d70bb517ccbe045e53b1a428129bb44372bf3d7dd5", size = 5273040, upload-time = "2025-02-10T07:47:24.29Z" },
+ { url = "https://files.pythonhosted.org/packages/39/25/ad4ac8fac488505a2702656550e63c2a8db3a4fd63db82a20dad5689cecb/lxml-5.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7bebc2275016cddf3c997bf8a0f7044160714c64a9b83975670a04e6d2252", size = 5050951, upload-time = "2025-02-10T07:47:27.143Z" },
+ { url = "https://files.pythonhosted.org/packages/82/74/f7d223c704c87e44b3d27b5e0dde173a2fcf2e89c0524c8015c2b3554876/lxml-5.3.1-cp313-cp313-win32.whl", hash = "sha256:d0751528b97d2b19a388b302be2a0ee05817097bab46ff0ed76feeec24951f78", size = 3485357, upload-time = "2025-02-10T07:47:29.738Z" },
+ { url = "https://files.pythonhosted.org/packages/80/83/8c54533b3576f4391eebea88454738978669a6cad0d8e23266224007939d/lxml-5.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:91fb6a43d72b4f8863d21f347a9163eecbf36e76e2f51068d59cd004c506f332", size = 3814484, upload-time = "2025-02-10T07:47:33.3Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "24.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "11.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715, upload-time = "2025-01-02T08:13:58.407Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640, upload-time = "2025-01-02T08:11:58.329Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437, upload-time = "2025-01-02T08:12:01.797Z" },
+ { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605, upload-time = "2025-01-02T08:12:05.224Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173, upload-time = "2025-01-02T08:12:08.281Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145, upload-time = "2025-01-02T08:12:11.411Z" },
+ { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340, upload-time = "2025-01-02T08:12:15.29Z" },
+ { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906, upload-time = "2025-01-02T08:12:17.485Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759, upload-time = "2025-01-02T08:12:20.382Z" },
+ { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657, upload-time = "2025-01-02T08:12:23.922Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304, upload-time = "2025-01-02T08:12:28.069Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117, upload-time = "2025-01-02T08:12:30.064Z" },
+ { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060, upload-time = "2025-01-02T08:12:32.362Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192, upload-time = "2025-01-02T08:12:34.361Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805, upload-time = "2025-01-02T08:12:36.99Z" },
+ { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623, upload-time = "2025-01-02T08:12:41.912Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191, upload-time = "2025-01-02T08:12:45.186Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494, upload-time = "2025-01-02T08:12:47.098Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595, upload-time = "2025-01-02T08:12:50.47Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651, upload-time = "2025-01-02T08:12:53.356Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
+]
+
+[[package]]
+name = "sqlparse"
+version = "0.5.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.14.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2025.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950, upload-time = "2025-01-21T19:49:38.686Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762, upload-time = "2025-01-21T19:49:37.187Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" },
+]