wizyta oraz widok księdza

This commit is contained in:
2025-06-26 15:57:42 +02:00
commit 0c6faa8bb6
39 changed files with 1465 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
.env
# Django
db.sqlite3
media

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.13

0
README.md Normal file
View File

0
backend/__init__.py Normal file
View File

59
backend/admin.py Normal file
View File

@ -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"]

6
backend/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class BackendConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'backend'

View File

@ -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'],
},
),
]

View File

@ -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'),
),
]

View File

@ -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),
),
]

View File

@ -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'),
),
]

View File

@ -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',
},
),
]

View File

@ -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',
},
),
]

View File

65
backend/models.py Normal file
View File

@ -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"

View File

@ -0,0 +1,94 @@
<!DOCTYPE html>
{% load static i18n %}
<html lang="pl" dir="ltr" data-theme="light">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="author" content="Nikola Kubiczek" />
<title>eParafia</title>
<base href="/" />
{% block head %}
{% endblock %}
<meta name="mobile-web-app-capable" content="yes" />
<meta name="x5-page-mode" content="app" />
<meta name="browsermode" content="application" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css" />
{% comment %} <link rel="stylesheet" href="/background.css" /> {% endcomment %}
<script>
document.addEventListener('DOMContentLoaded', () => {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0)
// Add a click event on each of them
$navbarBurgers.forEach((el) => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active')
$target.classList.toggle('is-active')
})
})
// main.js
let installPrompt = null
const installButton = document.querySelector('#install')
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault()
installPrompt = event
installButton.classList.remove('is-hidden')
})
installButton.addEventListener('click', async () => {
if (!installPrompt) {
return
}
const result = await installPrompt.prompt()
console.log(`Install prompt was: ${result.outcome}`)
disableInAppInstallPrompt()
})
function disableInAppInstallPrompt() {
installPrompt = null
installButton.classList.add('is-hidden')
}
window.addEventListener('appinstalled', disableInAppInstallPrompt)
})
</script>
<style>
.navbar-link.is-active,
a.navbar-item.is-active {
color: #0a0a0a;
}
.navbar-link.is-active:not(:focus):not(:hover),
a.navbar-item.is-active:not(:focus):not(:hover) {
background-color: initial;
}
.navbar-item.has-dropdown.is-active .navbar-link,
.navbar-item.has-dropdown:focus .navbar-link,
.navbar-item.has-dropdown:hover .navbar-link {
background-color: #fafafa;
}
</style>
</head>
<body class="container section">
{% block layout %}
<main class="section container">
{% block content %}
{% endblock %}
</main>
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block content %}
<div class="has-text-centered is-flex is-flex-direction-column">
<p class="is-size-2 has-text-weight-semibold is-family-monospace">eParafia</p>
<p class="is-family-monospace">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</p>
<p class="is-size-4 has-text-weight-light">Zapraszamy!</p>
<div class="is-flex is-flex-direction-column mt-6">
{% for parish in parishes %}
<a class="button has-text-weight-semibold mx-auto my-2" href="{% url 'parish' parish.slug %}">{{ parish.name }}</a>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends 'parish/base.html' %}
{% block content %}
<div class="content">{{res | safe}}</div>
{% endblock %}

View File

@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% load pictures %}
{% block head %}
<link href="{% img_url parish.icon file_type='png' width=512 %}" rel="icon" type="image/png" sizes="512x512" />
<link href="{% img_url parish.icon file_type='png' width=192 %}" rel="icon" type="image/png" sizes="192x192" />
<link rel="manifest" href="{% url 'manifest' parish.slug %}" />
{% endblock %}
{% block layout %}
<div class="container">
<nav class="navbar is-transparent" role="navigation" aria-label="menu">
<div class="navbar-brand is-flex is-flex-wrap-wrap is-justify-content-space-between is-flex-shrink-1">
<span class="navbar-item is-is-active is-flex-shrink-1"><span class="title is-4 is-unselectable">{{ parish.name }}</span></span>
<a role="button" class="navbar-burger" data-target="navMenu" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div class="navbar-menu" id="navMenu">
<h5 class="navbar-end has-text-weight-bold">
<a class="navbar-item {{ announcements }}" href="{% url 'announcements' parish.slug %}">Ogłoszenia parafialne</a>
<a class="navbar-item {{ intentions }}" href="{% url 'intentions' parish.slug %}">Intencje</a>
{% if parish.channel %}
<a class="navbar-item {{ live }}" href="{% url 'live' parish.slug %}">Transmisja on-line</a>
{% endif %}
{% if parish.submissions %}
<a class="navbar-item {{ visit }}" href="{% url 'visit' parish.slug %}">Wizyta Duszpasterska</a>
{% endif %}
<a class="navbar-item is-clickable is-hidden" id="install">Zainstaluj</a>
</h5>
</div>
</nav>
<main class="section">
<div id="backgroundDesktop"></div>
{% block content %}
{% endblock %}
</main>
<div id="backgroundMobile"></div>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends 'parish/base.html' %}
{% block content %}
<div class="content">{{ res | safe }}</div>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends 'parish/base.html' %}
{% block content %}
<div class="is-flex-shrink-0">
<figure class="image is-16by9">
<iframe
class="has-ratio"
src="https://www.youtube-nocookie.com/embed/live_stream?channel={{parish.channel}}"
allowFullScreen
></iframe>
</figure>
</div>
{% endblock content %}

View File

@ -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"
}
]
}

View File

@ -0,0 +1,8 @@
{% extends 'parish/base.html' %}
{% block content %}
<p class="has-text-centered">
<a class="button is-light" href="{% url 'visit_login' parish.slug %}">Zaloguj się</a>
<a class="button is-light" href="{% url 'visit_signup' parish.slug %}">Zarejestruj się</a>
</p>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends 'parish/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<p class="is-size-3 has-text-centered">Zapisz się na wizytę duszpasterską</p>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<p class="has-text-centered">
<button type="submit" class="button is-success">Zatwierdź</button>
</p>
</form>
<form action="{% url 'visit_logout' parish.slug %}" method="post" class="is-flex is-justify-content-flex-end is-gap-2">
{% csrf_token %}
<a href="{% url 'visit_account' parish.slug %}" class="button is-info">Zmień dane konta</a>
<button type="submit" class="button is-danger">Wyloguj się</button>
</form>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends 'parish/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<p class="is-size-2 has-text-centered">Zaloguj się</p>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<p class="has-text-centered">
<button type="submit" class="button is-success">Zaloguj się</button>
<a href="{% url 'visit' parish.slug %}" class="button is-light">Powrót</a>
</p>
</form>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends 'parish/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<p class="is-size-2 has-text-centered">Zarejestruj się</p>
<form method="post" class="form">
{% csrf_token %}
{{ form | crispy }}
<p class="has-text-centered">
<button type="submit" class="button is-success">Zarejestruj się</button>
<a href="{% url 'visit' parish.slug %}" class="button is-light">Powrót</a>
</p>
</form>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends 'parish/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<p class="is-size-3 has-text-centered">Twoje zgłoszenie</p>
<div class="is-size-5 has-text-centered mb-2">
<p>
<b>Imię i nazwisko:</b> {{ submission.name }}, <b>Telefon:</b> {{ submission.phone }}
</p>
<p>
<b>Adres:</b> {{ submission.street }} {{ submission.street_number }}{% if submission.apartement_number %}/{% endif %}{{ submission.apartement_number|default_if_none:'' }}
</p>
</div>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<p class="has-text-centered">
<button type="submit" class="button is-success">Wycofaj zgłoszenie</button>
</p>
</form>
<p>
<form action="{% url 'visit_logout' parish.slug %}" method="post" class="is-flex is-justify-content-flex-end">
{% csrf_token %}
<button type="submit" class="button is-danger">Wyloguj się</button>
</form>
</p>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends 'parish/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<p class="is-size-3 has-text-centered">Zmień dane konta</p>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<p class="has-text-centered">
<button type="submit" class="button is-success">Zatwierdź</button>
<a href="{% url 'visit' parish.slug %}" class="button is-secondary">Powrót</a>
</p>
</form>
{% comment %} <form action="{% url 'visit_logout' parish.slug %}" method="post" class="is-flex is-justify-content-flex-end">
{% csrf_token %}
<button type="submit" class="button is-danger">Wyloguj się</button>
</form> {% endcomment %}
{% endblock %}

3
backend/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

266
backend/views.py Normal file
View File

@ -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("<str:slug>/", announcements, name="parish"),
path("<str:slug>/ogloszenia/", announcements, name="announcements"),
path("<str:slug>/intencje/", intentions, name="intentions"),
path("<str:slug>/transmisja/", live, name="live"),
path("<str:slug>/wizyta/", visit, name="visit"),
path("<str:slug>/wizyta/logowanie/", VisitLoginView.as_view(), name="visit_login"),
path("<str:slug>/wizyta/rejestracja/", VisitSignUpView.as_view(), name="visit_signup"),
path("<str:slug>/wizyta/konto/", VisitAccountView.as_view(), name="visit_account"),
path("<str:slug>/wizyta/wylogowanie/", VisitLogoutView.as_view(), name="visit_logout"),
path("<str:slug>/manifest.json", manifest, name="manifest"),
]

0
eparafia/__init__.py Normal file
View File

16
eparafia/asgi.py Normal file
View File

@ -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()

150
eparafia/settings.py Normal file
View File

@ -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"

34
eparafia/urls.py Normal file
View File

@ -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")),
]

16
eparafia/wsgi.py Normal file
View File

@ -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()

22
manage.py Executable file
View File

@ -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()

19
pyproject.toml Normal file
View File

@ -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",
]

BIN
static/icon-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
static/icon-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

314
uv.lock generated Normal file
View File

@ -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" },
]