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!

+
+ {% for parish in parishes %} + {{ parish.name }} + {% endfor %} +
+
+{% 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 %} +
+ + +
+
+ {% 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ą

+
+ {% csrf_token %} + {{ form|crispy }} +

+ +

+
+
+ {% csrf_token %} + Zmień dane konta + +
+{% 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ę

+
+ {% csrf_token %} + {{ form|crispy }} +

+ + Powrót +

+
+{% 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ę

+
+ {% csrf_token %} + {{ form | crispy }} +

+ + Powrót +

+
+{% 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:'' }} +

+
+ +
+ {% csrf_token %} + {{ form|crispy }} +

+ +

+
+

+

+ {% csrf_token %} + +
+

+{% 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

+
+ {% csrf_token %} + {{ form|crispy }} +

+ + Powrót +

+
+ {% comment %}
+ {% csrf_token %} + +
{% 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" }, +]