Compare commits

..

2 commits

Author SHA1 Message Date
Nikola Kubeczkova
6df90a66d3 add wagtail 2025-03-13 08:14:46 +01:00
Nikola Kubeczkova
b9aac50aa4 add wagtail 2025-03-12 16:30:38 +01:00
70 changed files with 1000 additions and 1499 deletions

5
.gitignore vendored
View file

@ -2,7 +2,4 @@ node_modules/
.env .env
.idea/ .idea/
services/backend/db.sqlite3 services/backend/db.sqlite3
__pycache__/ __pycache__/
htmlcov/
media/
.coverage

View file

@ -2,10 +2,9 @@
## Running ## Running
1. create .env from .env.example 1. `docker compose run --rm frontend npm install`
2. `docker compose run --rm frontend npm install` 2. `docker compose build`
3. `docker compose build` 3. `docker compose up`
4. `docker compose up`
## Adding new frontend packages ## Adding new frontend packages
@ -13,5 +12,4 @@
## Adding new backend packages ## Adding new backend packages
`docker compose run --rm backend uv add <package>` `docker compose run --rm backend uv add <package>`
`docker compose build` :)

15
TODO Normal file
View file

@ -0,0 +1,15 @@
Nastylovat:
- footer
- lazyload načítat o něco dříve (neli zrušit alespoň mimo galerii)
Naprogramovat:
- tlačítko "načíst další aktuality" v komponentě News (možná přidat i novou stránku /aktuality
- přidat v galerii zvětsovač na obrázky
- přidat volání na api
Naplánovat:
- kurzy + cena: vyskakovací okno na rezervaci s jménem lekce? jak to vyřešit s kalendářem? (stránka /rezervace)
DAVID:
- přidat posílání mailu panu vrchnímu

View file

@ -20,9 +20,6 @@ env = environ.Env()
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
@ -31,11 +28,7 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
SECRET_KEY = env.str('SECRET_KEY', 'django-insecure-x&7qy$na7*@u_4(izkfaz2yiea9+t(nf&#p9cnlq6x)_)jkacf') SECRET_KEY = env.str('SECRET_KEY', 'django-insecure-x&7qy$na7*@u_4(izkfaz2yiea9+t(nf&#p9cnlq6x)_)jkacf')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = True
DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": lambda request: False,
"IS_RUNNING_TESTS": False,
}
DEBUG_TOOLBAR_PANELS = [ DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.history.HistoryPanel', 'debug_toolbar.panels.history.HistoryPanel',
@ -69,6 +62,20 @@ CORS_ALLOWED_ORIGINS = [
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'wagtail.contrib.forms',
'wagtail.contrib.redirects',
'wagtail.embeds',
'wagtail.sites',
'wagtail.users',
'wagtail.snippets',
'wagtail.documents',
'wagtail.images',
'wagtail.search',
'wagtail.admin',
'wagtail',
'taggit',
'modelcluster',
'corsheaders', 'corsheaders',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
@ -79,7 +86,6 @@ INSTALLED_APPS = [
'tko.apps.TkoConfig', 'tko.apps.TkoConfig',
'debug_toolbar', 'debug_toolbar',
'rest_framework', 'rest_framework',
'post_office',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -92,6 +98,8 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware', 'debug_toolbar.middleware.DebugToolbarMiddleware',
'wagtail.contrib.redirects.middleware.RedirectMiddleware',
] ]
ROOT_URLCONF = 'backend.urls' ROOT_URLCONF = 'backend.urls'
@ -123,7 +131,6 @@ DATABASES = {
'default': env.db('DATABASE_URL') 'default': env.db('DATABASE_URL')
} }
EMAIL_BACKEND = 'post_office.EmailBackend'
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
@ -161,12 +168,20 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
# STATICFILES_DIRS = [ STATICFILES_DIRS = [
# os.path.join(BASE_DIR, 'static') os.path.join(BASE_DIR, 'static')
# ] ]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
WAGTAIL_SITE_NAME = 'Taneční klub Ostrava'

View file

@ -15,21 +15,25 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path, include
from debug_toolbar.toolbar import debug_toolbar_urls from debug_toolbar.toolbar import debug_toolbar_urls
from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.documents import urls as wagtaildocs_urls
from django.conf.urls.static import static from django.conf.urls.static import static
from django.conf import settings from django.conf import settings
from tko.views import ContactView, NewArticleListView, AllArticleListView, EventListView, GalleryView from tko.views import ContactView, NewArticleListView, AllArticleListView, EventListView
urlpatterns = [ urlpatterns = [
path('', include(wagtailadmin_urls)),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('create-contact/', ContactView.as_view(), name='create-contact'), path('create-contact/', ContactView.as_view(), name='create-contact'),
path('load-articles/', NewArticleListView.as_view(), name='load-articles'), path('load-articles/', NewArticleListView.as_view(), name='load-articles'),
path('load-all-articles/', AllArticleListView.as_view(), name='load-all-articles'), path('load-all-articles/', AllArticleListView.as_view(), name='load-all-articles'),
path('load-events/', EventListView.as_view(), name='load-events'), path('load-events/', EventListView.as_view(), name='load-events'),
path('load-gallery/', GalleryView.as_view(), name='load-gallery'),
] + debug_toolbar_urls() ] + debug_toolbar_urls()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -4,8 +4,7 @@ version = "0.1.0"
description = "Add your description here" description = "Add your description here"
requires-python = ">=3.12" requires-python = ">=3.12"
authors = [ authors = [
{name = "Jakub Kropáček", email = "kropikuba@gmail.com"}, {name = "Jakub Kropáček", email = "kropikuba@gmail.com"}
{name = "Nikola Kubeczková", email = "kubeczkova.n@gmail.com"}
] ]
dependencies = [ dependencies = [
"django>=5.1.5", "django>=5.1.5",
@ -16,10 +15,7 @@ dependencies = [
"django-environ>=0.12.0", "django-environ>=0.12.0",
"wait-for-it>=2.3.0", "wait-for-it>=2.3.0",
"django-cors-headers>=4.7.0", "django-cors-headers>=4.7.0",
"django-post-office>=3.9.1", "wagtail>=6.4.1",
"pytest-django>=4.10.0",
"factory-boy>=3.3.3",
"pytest-cov>=6.0.0",
] ]
[tool.uv] [tool.uv]
@ -28,6 +24,3 @@ package = false
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "backend.settings"

View file

@ -19,15 +19,9 @@ class ContactAdmin(admin.ModelAdmin):
return False return False
class ArticleImageInline(admin.TabularInline): # Or admin.StackedInline for a different layout
model = models.ArticleImage
extra = 1
@admin.register(models.Article) @admin.register(models.Article)
class ArticleAdmin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'date', 'author'] list_display = ['title', 'date', 'author']
inlines = [ArticleImageInline]
@admin.register(models.Event) @admin.register(models.Event)

View file

@ -0,0 +1,39 @@
# Generated by Django 5.1.5 on 2025-03-12 17:04
import datetime
import django.db.models.deletion
import wagtail.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tko', '0004_alter_article_image'),
('wagtailcore', '0094_alter_page_locale'),
('wagtailimages', '0027_image_description'),
]
operations = [
migrations.AlterField(
model_name='article',
name='date',
field=models.DateField(auto_now_add=True),
),
migrations.CreateModel(
name='ArticlePage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('name', models.CharField(max_length=100)),
('content', wagtail.fields.RichTextField()),
('date', models.DateField(default=datetime.date.today)),
('author', models.CharField(max_length=100)),
('active_to', models.DateField(null=True)),
('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='wagtailimages.image')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]

View file

@ -1,32 +0,0 @@
# Generated by Django 5.1.5 on 2025-03-05 15:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tko', '0004_alter_article_image'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='image',
),
migrations.AlterField(
model_name='article',
name='date',
field=models.DateField(auto_now_add=True),
),
migrations.CreateModel(
name='ArticleImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.FileField(default='default.png', upload_to='images/%Y')),
('is_main', models.BooleanField(default=False)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='tko.article')),
],
),
]

View file

@ -0,0 +1,47 @@
# Generated by Django 5.1.5 on 2025-03-12 19:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tko', '0005_alter_article_date_articlepage'),
('wagtailcore', '0094_alter_page_locale'),
('wagtailimages', '0027_image_description'),
]
operations = [
migrations.CreateModel(
name='EventPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
('start_date', models.DateTimeField()),
('end_date', models.DateTimeField()),
('color', models.CharField(max_length=100)),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
migrations.RemoveField(
model_name='articlepage',
name='active_to',
),
migrations.AlterField(
model_name='articlepage',
name='content',
field=models.CharField(max_length=500),
),
migrations.RemoveField(
model_name='articlepage',
name='image',
),
migrations.AddField(
model_name='articlepage',
name='image',
field=models.ManyToManyField(to='wagtailimages.image'),
),
]

View file

@ -1,27 +0,0 @@
# Generated by Django 5.1.5 on 2025-03-10 16:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tko', '0005_remove_article_image_alter_article_date_articleimage'),
]
operations = [
migrations.RemoveField(
model_name='articleimage',
name='is_main',
),
migrations.AddField(
model_name='article',
name='image',
field=models.FileField(blank=True, default='default.png', upload_to='images/%Y'),
),
migrations.AlterField(
model_name='articleimage',
name='image',
field=models.FileField(upload_to='images/%Y'),
),
]

View file

@ -1,27 +0,0 @@
# Generated by Django 5.1.5 on 2025-03-10 18:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tko', '0006_remove_articleimage_is_main_article_image_and_more'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='image',
),
migrations.AddField(
model_name='articleimage',
name='main',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='articleimage',
name='image',
field=models.FileField(default='default.png', upload_to='images/%Y'),
),
]

View file

@ -1,6 +1,11 @@
from django.db import models from django.db import models
from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField
from wagtail.models import Page
from datetime import date
# Create your models here.
class Contact(models.Model): class Contact(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
email = models.EmailField() email = models.EmailField()
@ -14,23 +19,16 @@ class Contact(models.Model):
class Article(models.Model): class Article(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
content = models.TextField() content = models.TextField()
image = models.FileField(default="default.png", blank=True)
date = models.DateField(auto_now_add=True) date = models.DateField(auto_now_add=True)
author = models.CharField(max_length=100) author = models.CharField(max_length=100)
active_to = models.DateField(null=True, blank=True) # do not show some invitation after this date active_to = models.DateField(null=True, blank=True) # do not show some invitation after this date
def __str__(self): def __str__(self):
return self.title return self.title
class ArticleImage(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='images')
image = models.FileField(upload_to="images/%Y", default="default.png")
main = models.BooleanField(default=False)
def __str__(self):
return f"Image for {self.article.title}, {self.article.date}"
class Event(models.Model): class Event(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
start_date = models.DateTimeField() start_date = models.DateTimeField()
@ -39,3 +37,21 @@ class Event(models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
class EventPage(Page):
start_date = models.DateTimeField()
end_date = models.DateTimeField()
color = models.CharField(max_length=100)
content_panels = Page.content_panels + ["start_date", "end_date", "color"]
class ArticlePage(Page):
name = models.CharField(max_length=100)
content = models.CharField(max_length=500)
image = models.ManyToManyField('wagtailimages.Image')
date = models.DateField(default=date.today)
author = models.CharField(max_length=100)
content_panels = Page.content_panels + ["name", "content", "image", "date", "author", "expire_at"]

View file

@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.timezone import localtime from django.utils.timezone import localtime
from tko.models import Contact, Article, Event, ArticleImage from tko.models import Contact, Article, Event
class ContactSerializer(serializers.ModelSerializer): class ContactSerializer(serializers.ModelSerializer):
@ -10,41 +10,17 @@ class ContactSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
class ArticleImageSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField(read_only=True)
class Meta:
model = ArticleImage
fields = ['image', 'title']
@staticmethod
def get_title(obj):
return obj.article.title if obj.article else ''
class ArticleListSerializer(serializers.ModelSerializer): class ArticleListSerializer(serializers.ModelSerializer):
date = serializers.SerializerMethodField() date = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
images = ArticleImageSerializer(many=True, read_only=True)
class Meta: class Meta:
model = Article model = Article
fields = ["id", "author", "content", "date", "image", "title", "images"] fields = ["id", "author", "content", "date", "image", "title"]
@staticmethod @staticmethod
def get_date(obj): def get_date(obj):
return obj.date.strftime("%-d. %-m. %Y") return obj.date.strftime("%-d. %-m. %Y")
def get_image(self, obj):
main_image = next(iter(obj.images.all()), None)
if main_image:
return ArticleImageSerializer(main_image, context=self.context).data
return {
"image": "http://localhost:8000/media/images/image.jpg",
"title": "Výchozí obrázek",
}
class EventListSerializer(serializers.ModelSerializer): class EventListSerializer(serializers.ModelSerializer):
start = serializers.SerializerMethodField() start = serializers.SerializerMethodField()

View file

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

View file

@ -1,62 +0,0 @@
from random import randint
import factory
import datetime
from django.contrib.auth.models import User
from factory.fuzzy import FuzzyDate
from datetime import date
from tko.models import Article, Event, ArticleImage, Contact
class ContactFactory(factory.django.DjangoModelFactory):
class Meta:
model = Contact
name = factory.Faker("name")
email = factory.Faker("email")
phone_number = factory.Faker("phone_number")
content = factory.Faker("text")
class ArticleFactory(factory.django.DjangoModelFactory):
class Meta:
model = Article
title = factory.Faker("name")
content = factory.Faker("text")
date = FuzzyDate(datetime.date(2021, 1, 1), date.today())
author = factory.Faker("name")
active_to = FuzzyDate(datetime.date(2021, 1, 1), datetime.date(2025, 1, 1))
class GalleryFactory(factory.django.DjangoModelFactory):
class Meta:
model = ArticleImage
article = factory.SubFactory(ArticleFactory)
image = factory.django.FileField(filename='image.png')
main = factory.Faker("boolean")
class EventFactory(factory.django.DjangoModelFactory):
class Meta:
model = Event
title = factory.Faker("name")
start_date = FuzzyDate(datetime.date(2021, 1, 1), datetime.date(2025, 1, 1))
end_date = factory.LazyAttribute(
lambda obj: obj.start_date + datetime.timedelta(minutes=randint(5, 5 * 24 * 60))
)
color = factory.Faker("color")
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Faker("name")
email = factory.Faker("email")
password = "password"
is_superuser = True

View file

@ -1,37 +0,0 @@
import pytest
from rest_framework.test import APIClient
from django.urls import reverse
from tko.admin import ContactAdmin
from tko.models import Contact
from tko.tests.factories import UserFactory
from django.contrib.admin.sites import site
# Create your tests here.
@pytest.mark.django_db(transaction=True)
class TestAdmin:
url = reverse("admin:index")
client = APIClient()
def test_has_add_permission(self):
user = UserFactory()
admin = ContactAdmin(Contact, site)
request = self.client.get(self.url)
request.user = user
assert admin.has_add_permission(request) is False
def test_has_delete_permission(self):
user = UserFactory()
admin = ContactAdmin(Contact, site)
request = self.client.get(self.url)
request.user = user
assert admin.has_delete_permission(request) is False
def test_has_change_permission(self):
user = UserFactory()
admin = ContactAdmin(Contact, site)
request = self.client.get(self.url)
request.user = user
assert admin.has_change_permission(request) is False

View file

@ -1,53 +0,0 @@
import pytest
from rest_framework.test import APIClient
from django.urls import reverse
from tko.tests.factories import ArticleFactory
@pytest.mark.django_db(transaction=True)
class TestLoadArticle:
client = APIClient()
@staticmethod
def create_article(old, new):
for _ in range(new): ArticleFactory(active_to=None)
for _ in range(old): ArticleFactory()
@pytest.mark.parametrize(
("articles", "length", "result"),
[
((2, 5), 2, 200),
((2, 1), 1, 200),
((5, 0), 0, 200),
((3, 1), 1, 200),
]
)
def test_load_articles(self, articles, length, result):
self.create_article(*articles)
response = self.client.get(reverse("load-articles"))
assert response.status_code == result
assert len(response.data) == length
@pytest.mark.parametrize(
("articles", "length", "result"),
[
((2, 5), 5, 200),
((2, 1), 1, 200),
((5, 0), 0, 200),
((3, 1), 1, 200),
]
)
def test_load_all_articles(self, articles, length, result):
self.create_article(*articles)
response = self.client.get(
path=reverse("load-all-articles")
)
assert response.status_code == result
assert len(response.data) == length
def test_str_article(self):
title = "Test_article"
assert str(ArticleFactory(title=title)) == title

View file

@ -1,40 +0,0 @@
import pytest
from rest_framework.test import APIClient
from django.urls import reverse
from tko.tests.factories import ContactFactory
# Create your tests here.
@pytest.mark.django_db(transaction=True)
class TestContact:
url = reverse("create-contact")
client = APIClient()
@pytest.mark.parametrize(
("name", "email", "phone", "message", "result"),
[
("name", "email@tko.cz", "770707505", "message", 200),
("name", "email@tko.cz", "", "message", 400),
("name", "", "770707505", "message", 400),
("name", "", "", "message", 400),
("name ahoj", "email@tko.cz", "+420770707505", "message skdo nsdkl skd sakdksd", 200),
("", "email", "770707505", "message", 400),
])
def test_create_contact(self, name, email, phone, message, result):
response = self.client.post(
path=self.url,
data={
"name": name,
"email": email,
"phone_number": phone,
"content": message,
}
)
assert response.status_code == result
if result == 200:
assert str(ContactFactory(
name=name,
email=email,
phone_number=phone,
)) == f"{name}, {email}, {phone}"

View file

@ -1,36 +0,0 @@
import pytest
from rest_framework.test import APIClient
from django.urls import reverse
from tko.tests.factories import EventFactory
# Create your tests here.
@pytest.mark.django_db(transaction=True)
class TestEvent:
url = reverse("load-events")
client = APIClient()
@staticmethod
def create_events(length):
EventFactory.create_batch(size=length)
@pytest.mark.parametrize(
("length", "result"),
[
(2, 200),
(6, 200),
]
)
def test_load_events(self, length, result):
self.create_events(length)
response = self.client.get(self.url)
assert response.status_code == result
assert len(response.data) == length
def test_str_article_image(self):
title = "Test_event"
assert str(
EventFactory(title=title)
) == title

View file

@ -1,65 +0,0 @@
import datetime
import pytest
from rest_framework.test import APIClient
from django.urls import reverse
from tko.tests.factories import GalleryFactory, ArticleFactory
# Create your tests here.
@pytest.mark.django_db(transaction=True)
class TestGallery:
url = reverse("load-gallery")
client = APIClient()
@staticmethod
def create_images(old, new):
for _ in range(new):
article = ArticleFactory(active_to=None)
for _ in range(new): GalleryFactory(article=article)
for _ in range(old):
article = ArticleFactory()
for _ in range(old): GalleryFactory(article=article)
@pytest.mark.parametrize(
("gallery", "length", "result"),
[
((2, 5), 29, 200),
((2, 1), 5, 200),
((4, 2), 20, 200),
((3, 1), 10, 200),
]
)
def test_load_gallery(self, gallery, length, result):
self.create_images(*gallery)
response = self.client.get(self.url)
assert response.status_code == result
assert len(response.data) == length
@pytest.mark.parametrize(
("gallery", "length", "result"),
[
((2, 5), 5, 200),
((2, 1), 1, 200),
((4, 0), 0, 200),
((3, 1), 1, 200),
]
)
def test_load_article_images(self, gallery, length, result):
old, new = gallery
self.create_images(*gallery)
response = self.client.get(reverse("load-articles"))
for article in response.data:
assert len(article["images"]) == new
assert response.status_code == result
def test_str_article_image(self):
title = "Test_article"
assert str(
GalleryFactory(article=ArticleFactory(title=title))
) == f"Image for {title}, {datetime.date.today()}"

View file

@ -1,38 +1,21 @@
from django.utils import timezone from django.utils import timezone
from django.db.models import Q, Prefetch from django.db.models import Q
from post_office import mail
from rest_framework.generics import ListAPIView, CreateAPIView from rest_framework.generics import ListAPIView, CreateAPIView
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework import permissions
from rest_framework.response import Response
from tko.models import Article, Event, ArticleImage from tko.models import Article, Event
from tko.serializers import ArticleListSerializer, EventListSerializer, ContactSerializer, ArticleImageSerializer from tko.serializers import ArticleListSerializer, EventListSerializer, ContactSerializer
class ContactView(CreateAPIView): class ContactView(CreateAPIView):
serializer_class = ContactSerializer serializer_class = ContactSerializer
permission_classes = [permissions.AllowAny]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
mail.send(
recipients="kubeczkova.n@gmail.com",
subject="Zpráva z webu TKO",
message=f"Jméno: {serializer.data['name']}\n"
f"Email: {serializer.data['email']}\n"
f"Telefón: {serializer.data['phone_number']}\n\n"
f"Zpráva: {serializer.data['content']}",
)
return Response(serializer.data)
class NewArticleListView(ListAPIView): class NewArticleListView(ListAPIView):
serializer_class = ArticleListSerializer serializer_class = ArticleListSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self): def get_queryset(self):
return Article.objects.filter( return Article.objects.filter(
@ -42,21 +25,15 @@ class NewArticleListView(ListAPIView):
class AllArticleListView(ListAPIView): class AllArticleListView(ListAPIView):
serializer_class = ArticleListSerializer serializer_class = ArticleListSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self): def get_queryset(self):
return Article.objects.filter( return Article.objects.filter(
Q(active_to__gte=timezone.now()) | Q(active_to__isnull=True) Q(active_to__gte=timezone.now()) | Q(active_to__isnull=True)
).order_by('-date').prefetch_related( ).order_by('-date')
Prefetch('images', queryset=ArticleImage.objects.order_by('-main'))
)
class GalleryView(ListAPIView):
permission=[AllowAny]
queryset = ArticleImage.objects.all().order_by("-article_id", "main")
serializer_class = ArticleImageSerializer
class EventListView(ListAPIView): class EventListView(ListAPIView):
queryset = Event.objects.all() queryset = Event.objects.all()
serializer_class = EventListSerializer serializer_class = EventListSerializer
permission_classes = [permissions.AllowAny]

View file

@ -0,0 +1,23 @@
from wagtail import hooks
from wagtail.admin.viewsets.pages import PageListingViewSet
from tko.models import ArticlePage
from wagtail.admin.wagtail_hooks import ExplorerMenuItem
class ArticlePageListingViewSet(PageListingViewSet):
icon = "globe"
menu_label = "Articles"
add_to_admin_menu = True
model = ArticlePage
article_page_listing_viewset = ArticlePageListingViewSet("article_pages")
@hooks.register('register_admin_viewset')
def register_article_page_listing_viewset():
return article_page_listing_viewset
# @hooks.register('construct_main_menu')
# def hide_snippets_menu_item(request, menu_items):
# menu_items[:] = [item for item in menu_items if not isinstance(item, ExplorerMenuItem)]

View file

@ -2,6 +2,15 @@ version = 1
revision = 1 revision = 1
requires-python = ">=3.12" requires-python = ">=3.12"
[[package]]
name = "anyascii"
version = "0.3.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9f/52/93b9ea99063f7cf37fb67f5e3f49480686cbe7f228c48b9d713326223b6e/anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730", size = 214052 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4f/7b/a9a747e0632271d855da379532b05a62c58e979813814a57fa3b3afeb3a4/anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4", size = 289923 },
]
[[package]] [[package]]
name = "anyio" name = "anyio"
version = "4.8.0" version = "4.8.0"
@ -34,13 +43,10 @@ dependencies = [
{ name = "django-cors-headers" }, { name = "django-cors-headers" },
{ name = "django-debug-toolbar" }, { name = "django-debug-toolbar" },
{ name = "django-environ" }, { name = "django-environ" },
{ name = "django-post-office" },
{ name = "djangorestframework" }, { name = "djangorestframework" },
{ name = "factory-boy" },
{ name = "psycopg2-binary" }, { name = "psycopg2-binary" },
{ name = "pytest-cov" },
{ name = "pytest-django" },
{ name = "uvicorn", extra = ["standard"] }, { name = "uvicorn", extra = ["standard"] },
{ name = "wagtail" },
{ name = "wait-for-it" }, { name = "wait-for-it" },
] ]
@ -50,31 +56,68 @@ requires-dist = [
{ name = "django-cors-headers", specifier = ">=4.7.0" }, { name = "django-cors-headers", specifier = ">=4.7.0" },
{ name = "django-debug-toolbar", specifier = ">=5.0.1" }, { name = "django-debug-toolbar", specifier = ">=5.0.1" },
{ name = "django-environ", specifier = ">=0.12.0" }, { name = "django-environ", specifier = ">=0.12.0" },
{ name = "django-post-office", specifier = ">=3.9.1" },
{ name = "djangorestframework", specifier = ">=3.15.2" }, { name = "djangorestframework", specifier = ">=3.15.2" },
{ name = "factory-boy", specifier = ">=3.3.3" },
{ name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "psycopg2-binary", specifier = ">=2.9.10" },
{ name = "pytest-cov", specifier = ">=6.0.0" },
{ name = "pytest-django", specifier = ">=4.10.0" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" },
{ name = "wagtail", specifier = ">=6.4.1" },
{ name = "wait-for-it", specifier = ">=2.3.0" }, { name = "wait-for-it", specifier = ">=2.3.0" },
] ]
[[package]] [[package]]
name = "bleach" name = "beautifulsoup4"
version = "6.2.0" version = "4.13.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "webencodings" }, { name = "soupsieve" },
{ name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 },
] ]
[package.optional-dependencies] [[package]]
css = [ name = "certifi"
{ name = "tinycss2" }, 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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
{ url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
{ url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
{ url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 },
{ url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 },
{ url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 },
{ url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 },
{ url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 },
{ url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 },
{ url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 },
{ url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 },
{ url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 },
{ url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
] ]
[[package]] [[package]]
@ -99,42 +142,12 @@ wheels = [
] ]
[[package]] [[package]]
name = "coverage" name = "defusedxml"
version = "7.7.1" version = "0.7.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6b/bf/3effb7453498de9c14a81ca21e1f92e6723ce7ebdc5402ae30e4dcc490ac/coverage-7.7.1.tar.gz", hash = "sha256:199a1272e642266b90c9f40dec7fd3d307b51bf639fa0d15980dc0b3246c1393", size = 810332 } sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/cf/b0/4eaba302a86ec3528231d7cfc954ae1929ec5d42b032eb6f5b5f5a9155d2/coverage-7.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eff187177d8016ff6addf789dcc421c3db0d014e4946c1cc3fbf697f7852459d", size = 211253 }, { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 },
{ url = "https://files.pythonhosted.org/packages/fd/68/21b973e6780a3f2457e31ede1aca6c2f84bda4359457b40da3ae805dcf30/coverage-7.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2444fbe1ba1889e0b29eb4d11931afa88f92dc507b7248f45be372775b3cef4f", size = 211504 },
{ url = "https://files.pythonhosted.org/packages/d1/b4/c19e9c565407664390254252496292f1e3076c31c5c01701ffacc060e745/coverage-7.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:177d837339883c541f8524683e227adcaea581eca6bb33823a2a1fdae4c988e1", size = 245566 },
{ url = "https://files.pythonhosted.org/packages/7b/0e/f9829cdd25e5083638559c8c267ff0577c6bab19dacb1a4fcfc1e70e41c0/coverage-7.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15d54ecef1582b1d3ec6049b20d3c1a07d5e7f85335d8a3b617c9960b4f807e0", size = 242455 },
{ url = "https://files.pythonhosted.org/packages/29/57/a3ada2e50a665bf6d9851b5eb3a9a07d7e38f970bdd4d39895f311331d56/coverage-7.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c82b27c56478d5e1391f2e7b2e7f588d093157fa40d53fd9453a471b1191f2", size = 244713 },
{ url = "https://files.pythonhosted.org/packages/0f/d3/f15c7d45682a73eca0611427896016bad4c8f635b0fc13aae13a01f8ed9d/coverage-7.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:315ff74b585110ac3b7ab631e89e769d294f303c6d21302a816b3554ed4c81af", size = 244476 },
{ url = "https://files.pythonhosted.org/packages/19/3b/64540074e256082b220e8810fd72543eff03286c59dc91976281dc0a559c/coverage-7.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4dd532dac197d68c478480edde74fd4476c6823355987fd31d01ad9aa1e5fb59", size = 242695 },
{ url = "https://files.pythonhosted.org/packages/8a/c1/9cad25372ead7f9395a91bb42d8ae63e6cefe7408eb79fd38797e2b763eb/coverage-7.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:385618003e3d608001676bb35dc67ae3ad44c75c0395d8de5780af7bb35be6b2", size = 243888 },
{ url = "https://files.pythonhosted.org/packages/66/c6/c3e6c895bc5b95ccfe4cb5838669dbe5226ee4ad10604c46b778c304d6f9/coverage-7.7.1-cp312-cp312-win32.whl", hash = "sha256:63306486fcb5a827449464f6211d2991f01dfa2965976018c9bab9d5e45a35c8", size = 213744 },
{ url = "https://files.pythonhosted.org/packages/cc/8a/6df2fcb4c3e38ec6cd7e211ca8391405ada4e3b1295695d00aa07c6ee736/coverage-7.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:37351dc8123c154fa05b7579fdb126b9f8b1cf42fd6f79ddf19121b7bdd4aa04", size = 214546 },
{ url = "https://files.pythonhosted.org/packages/ec/2a/1a254eaadb01c163b29d6ce742aa380fc5cfe74a82138ce6eb944c42effa/coverage-7.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eebd927b86761a7068a06d3699fd6c20129becf15bb44282db085921ea0f1585", size = 211277 },
{ url = "https://files.pythonhosted.org/packages/cf/00/9636028365efd4eb6db71cdd01d99e59f25cf0d47a59943dbee32dd1573b/coverage-7.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a79c4a09765d18311c35975ad2eb1ac613c0401afdd9cb1ca4110aeb5dd3c4c", size = 211551 },
{ url = "https://files.pythonhosted.org/packages/6f/c8/14aed97f80363f055b6cd91e62986492d9fe3b55e06b4b5c82627ae18744/coverage-7.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1c65a739447c5ddce5b96c0a388fd82e4bbdff7251396a70182b1d83631019", size = 245068 },
{ url = "https://files.pythonhosted.org/packages/d6/76/9c5fe3f900e01d7995b0cda08fc8bf9773b4b1be58bdd626f319c7d4ec11/coverage-7.7.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392cc8fd2b1b010ca36840735e2a526fcbd76795a5d44006065e79868cc76ccf", size = 242109 },
{ url = "https://files.pythonhosted.org/packages/c0/81/760993bb536fb674d3a059f718145dcd409ed6d00ae4e3cbf380019fdfd0/coverage-7.7.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bb47cc9f07a59a451361a850cb06d20633e77a9118d05fd0f77b1864439461b", size = 244129 },
{ url = "https://files.pythonhosted.org/packages/00/be/1114a19f93eae0b6cd955dabb5bee80397bd420d846e63cd0ebffc134e3d/coverage-7.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b4c144c129343416a49378e05c9451c34aae5ccf00221e4fa4f487db0816ee2f", size = 244201 },
{ url = "https://files.pythonhosted.org/packages/06/8d/9128fd283c660474c7dc2b1ea5c66761bc776b970c1724989ed70e9d6eee/coverage-7.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bc96441c9d9ca12a790b5ae17d2fa6654da4b3962ea15e0eabb1b1caed094777", size = 242282 },
{ url = "https://files.pythonhosted.org/packages/d4/2a/6d7dbfe9c1f82e2cdc28d48f4a0c93190cf58f057fa91ba2391b92437fe6/coverage-7.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d03287eb03186256999539d98818c425c33546ab4901028c8fa933b62c35c3a", size = 243570 },
{ url = "https://files.pythonhosted.org/packages/cf/3e/29f1e4ce3bb951bcf74b2037a82d94c5064b3334304a3809a95805628838/coverage-7.7.1-cp313-cp313-win32.whl", hash = "sha256:8fed429c26b99641dc1f3a79179860122b22745dd9af36f29b141e178925070a", size = 213772 },
{ url = "https://files.pythonhosted.org/packages/bc/3a/cf029bf34aefd22ad34f0e808eba8d5830f297a1acb483a2124f097ff769/coverage-7.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:092b134129a8bb940c08b2d9ceb4459af5fb3faea77888af63182e17d89e1cf1", size = 214575 },
{ url = "https://files.pythonhosted.org/packages/92/4c/fb8b35f186a2519126209dce91ab8644c9a901cf04f8dfa65576ca2dd9e8/coverage-7.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3154b369141c3169b8133973ac00f63fcf8d6dbcc297d788d36afbb7811e511", size = 212113 },
{ url = "https://files.pythonhosted.org/packages/59/90/e834ffc86fd811c5b570a64ee1895b20404a247ec18a896b9ba543b12097/coverage-7.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:264ff2bcce27a7f455b64ac0dfe097680b65d9a1a293ef902675fa8158d20b24", size = 212333 },
{ url = "https://files.pythonhosted.org/packages/a5/a1/27f0ad39569b3b02410b881c42e58ab403df13fcd465b475db514b83d3d3/coverage-7.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba8480ebe401c2f094d10a8c4209b800a9b77215b6c796d16b6ecdf665048950", size = 256566 },
{ url = "https://files.pythonhosted.org/packages/9f/3b/21fa66a1db1b90a0633e771a32754f7c02d60236a251afb1b86d7e15d83a/coverage-7.7.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:520af84febb6bb54453e7fbb730afa58c7178fd018c398a8fcd8e269a79bf96d", size = 252276 },
{ url = "https://files.pythonhosted.org/packages/d6/e5/4ab83a59b0f8ac4f0029018559fc4c7d042e1b4552a722e2bfb04f652296/coverage-7.7.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d96127ae01ff571d465d4b0be25c123789cef88ba0879194d673fdea52f54e", size = 254616 },
{ url = "https://files.pythonhosted.org/packages/db/7a/4224417c0ccdb16a5ba4d8d1fcfaa18439be1624c29435bb9bc88ccabdfb/coverage-7.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0ce92c5a9d7007d838456f4b77ea159cb628187a137e1895331e530973dcf862", size = 255707 },
{ url = "https://files.pythonhosted.org/packages/51/20/ff18a329ccaa3d035e2134ecf3a2e92a52d3be6704c76e74ca5589ece260/coverage-7.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0dab4ef76d7b14f432057fdb7a0477e8bffca0ad39ace308be6e74864e632271", size = 253876 },
{ url = "https://files.pythonhosted.org/packages/e4/e8/1d6f1a6651672c64f45ffad05306dad9c4c189bec694270822508049b2cb/coverage-7.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7e688010581dbac9cab72800e9076e16f7cccd0d89af5785b70daa11174e94de", size = 254687 },
{ url = "https://files.pythonhosted.org/packages/6b/ea/1b9a14cf3e2bc3fd9de23a336a8082091711c5f480b500782d59e84a8fe5/coverage-7.7.1-cp313-cp313t-win32.whl", hash = "sha256:e52eb31ae3afacdacfe50705a15b75ded67935770c460d88c215a9c0c40d0e9c", size = 214486 },
{ url = "https://files.pythonhosted.org/packages/cc/bb/faa6bcf769cb7b3b660532a30d77c440289b40636c7f80e498b961295d07/coverage-7.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a6b6b3bd121ee2ec4bd35039319f3423d0be282b9752a5ae9f18724bc93ebe7c", size = 215647 },
{ url = "https://files.pythonhosted.org/packages/52/26/9f53293ff4cc1d47d98367ce045ca2e62746d6be74a5c6851a474eabf59b/coverage-7.7.1-py3-none-any.whl", hash = "sha256:822fa99dd1ac686061e1219b67868e25d9757989cf2259f735a4802497d6da31", size = 203006 },
] ]
[[package]] [[package]]
@ -187,16 +200,90 @@ wheels = [
] ]
[[package]] [[package]]
name = "django-post-office" name = "django-filter"
version = "3.9.1" version = "25.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "bleach", extra = ["css"] },
{ name = "django" }, { name = "django" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/de/e9/7369fb453269ded8d2fff58299fe653033702bd15a427bf35353d4606df3/django-post_office-3.9.1.tar.gz", hash = "sha256:3d8f502f7829e4cf83e830d9efd6909bb44690af6bc41c7e4fc5a85d7b04df10", size = 76624 } sdist = { url = "https://files.pythonhosted.org/packages/b5/40/c702a6fe8cccac9bf426b55724ebdf57d10a132bae80a17691d0cf0b9bac/django_filter-25.1.tar.gz", hash = "sha256:1ec9eef48fa8da1c0ac9b411744b16c3f4c31176c867886e4c48da369c407153", size = 143021 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/58/12/19fdf17fb1a0d5e4418798cf9ed66b8aee39da564e875ec025627e08abdf/django_post_office-3.9.1-py3-none-any.whl", hash = "sha256:0082c4dd17854f66077ef457cf868c9bc5b6247de99d5755c45ddea1c518ed6f", size = 85671 }, { url = "https://files.pythonhosted.org/packages/07/a6/70dcd68537c434ba7cb9277d403c5c829caf04f35baf5eb9458be251e382/django_filter-25.1-py3-none-any.whl", hash = "sha256:4fa48677cf5857b9b1347fed23e355ea792464e0fe07244d1fdfb8a806215b80", size = 94114 },
]
[[package]]
name = "django-modelcluster"
version = "6.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/74/6a/08b0c902916d401b59d89f355949ef98e539d6abc6029138d819eb55d702/django-modelcluster-6.4.tar.gz", hash = "sha256:0102d00e6b884721ba21e32edb716548e0dead7880e130aa2e04854bd384a42f", size = 28966 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/c3/74539a9481e9311393e16d8f2d880f384e43c1535c05e0fa949b2fe487ef/django_modelcluster-6.4-py2.py3-none-any.whl", hash = "sha256:839b0ddb4a1a6f5fc7d9f2f029614a84350b41315bdb80a9c8b755baaa2297f2", size = 29041 },
]
[[package]]
name = "django-permissionedforms"
version = "0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/4b/364fe61a161ead607dc6af311901ba8e62f537f8fdab94b5252cb6efe6d7/django-permissionedforms-0.1.tar.gz", hash = "sha256:4340bb20c4477fffb13b4cc5cccf9f1b1010b64f79956c291c72d2ad2ed243f8", size = 5856 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/5b/216157ff053f955b15b9dcdc13bfb6e406666445164fee9e332e141be96d/django_permissionedforms-0.1-py2.py3-none-any.whl", hash = "sha256:d341a961a27cc77fde8cc42141c6ab55cc1f0cb886963cc2d6967b9674fa47d6", size = 5744 },
]
[[package]]
name = "django-stubs-ext"
version = "5.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9f/06/7b210e0073c6cb8824bde82afc25f268e8c410a99d3621297f44fa3f6a6c/django_stubs_ext-5.1.3.tar.gz", hash = "sha256:3e60f82337f0d40a362f349bf15539144b96e4ceb4dbd0239be1cd71f6a74ad0", size = 9613 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/52/50125afcf29382b7f9d88a992e44835108dd2f1694d6d17d6d3d6fe06c81/django_stubs_ext-5.1.3-py3-none-any.whl", hash = "sha256:64561fbc53e963cc1eed2c8eb27e18b8e48dcb90771205180fe29fc8a59e55fd", size = 9034 },
]
[[package]]
name = "django-taggit"
version = "6.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/a6/f1beaf8f552fe90c153cc039316ebab942c23dfbc88588dde081fefca816/django_taggit-6.1.0.tar.gz", hash = "sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3", size = 38151 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/34/4185c345530b91d05cb82e05d07148f481a5eb5dc2ac44e092b3daa6f206/django_taggit-6.1.0-py3-none-any.whl", hash = "sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0", size = 75749 },
]
[[package]]
name = "django-tasks"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "django-stubs-ext" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/76/63/46f3c2deb2a9df48d46c2f7a067f1286adc674a2a16e99c0344e327a2d4b/django_tasks-0.6.1.tar.gz", hash = "sha256:4086e7eb9e965f79c4ac76f5c3690ec3bf41c461585237b71b4bde729ced9826", size = 26575 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/a3/f9c6634f67c5bcd309b17fe756ee4a321779806ab515e7ccc6333a439275/django_tasks-0.6.1-py3-none-any.whl", hash = "sha256:b3648e28bdcda809cb7831f3aff98aa46c327025447c462b8943cce9dfbb0281", size = 36330 },
]
[[package]]
name = "django-treebeard"
version = "4.7.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bb/24/eaccbce17355380cb3a8fe6ad92a85b76453dc1f0ecd04f48bfe8929065b/django-treebeard-4.7.1.tar.gz", hash = "sha256:846e462904b437155f76e04907ba4e48480716855f88b898df4122bdcfbd6e98", size = 294139 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/79/259966820614746cc4d81762edf97a53bf1e3b8e74797c010d310c6f4a8f/django_treebeard-4.7.1-py3-none-any.whl", hash = "sha256:995c7120153ab999898fe3043bbdcd8a0fc77cc106eb94de7350e9d02c885135", size = 93210 },
] ]
[[package]] [[package]]
@ -212,27 +299,30 @@ wheels = [
] ]
[[package]] [[package]]
name = "factory-boy" name = "draftjs-exporter"
version = "3.3.3" version = "5.1.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/d1/52/8b98525ab5477410bdbaf279c5fe0a99108211d40818bb769460784c1c41/draftjs_exporter-5.1.0.tar.gz", hash = "sha256:9f44b8dcecb702540e3aab24af2fad8683aec910fe0034c12cfab5d716ac5f84", size = 33500 }
{ name = "faker" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/98/75cacae9945f67cfe323829fc2ac451f64517a8a330b572a06a323997065/factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03", size = 164146 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/27/8d/2bc5f5546ff2ccb3f7de06742853483ab75bf74f36a92254702f8baecc79/factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc", size = 37036 }, { url = "https://files.pythonhosted.org/packages/dc/99/26d5524aaa3e89266e0af19332053aa9f8a61b1c39b29c0dc709f43fbb29/draftjs_exporter-5.1.0-py3-none-any.whl", hash = "sha256:c32932b7933b994fd5ea74c1decf47b5c41e13ba06363f5b69b76ef10137d4e9", size = 26302 },
] ]
[[package]] [[package]]
name = "faker" name = "et-xmlfile"
version = "37.1.0" version = "2.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 }
{ name = "tzdata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/a6/b77f42021308ec8b134502343da882c0905d725a4d661c7adeaf7acaf515/faker-37.1.0.tar.gz", hash = "sha256:ad9dc66a3b84888b837ca729e85299a96b58fdaef0323ed0baace93c9614af06", size = 1875707 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/a1/8936bc8e79af80ca38288dd93ed44ed1f9d63beb25447a4c59e746e01f8d/faker-37.1.0-py3-none-any.whl", hash = "sha256:dc2f730be71cb770e9c715b13374d80dbcee879675121ab51f9683d262ae9a1c", size = 1918783 }, { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 },
]
[[package]]
name = "filetype"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 },
] ]
[[package]] [[package]]
@ -276,30 +366,90 @@ wheels = [
] ]
[[package]] [[package]]
name = "iniconfig" name = "laces"
version = "2.1.0" version = "0.1.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/74/9a/9192d6a74e2c6db4f705dd98f56be488e47373172c13f4916aeabc4d68b8/laces-0.1.2.tar.gz", hash = "sha256:3218e09c1889ae5cf3fc7a82f5bb63ec0c7879889b6a9760bfc42323c694b84d", size = 29264 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, { url = "https://files.pythonhosted.org/packages/60/fe/31f76f5cb2579bdda208aa257ce5482653f22ab1bad3e128fe2f803fa2f1/laces-0.1.2-py3-none-any.whl", hash = "sha256:980cdaf9a31e883a2b8198132e2388931a4eb8814f5bfa5d8bba13ff9f657b7c", size = 22462 },
] ]
[[package]] [[package]]
name = "packaging" name = "openpyxl"
version = "24.2" version = "3.1.5"
source = { registry = "https://pypi.org/simple" } 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 } dependencies = [
{ name = "et-xmlfile" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 },
] ]
[[package]] [[package]]
name = "pluggy" name = "pillow"
version = "1.5.0" version = "11.1.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 },
{ url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 },
{ url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 },
{ url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 },
{ url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 },
{ url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 },
{ url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 },
{ url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 },
{ url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 },
{ url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 },
{ url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 },
{ url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 },
{ url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 },
{ url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 },
{ url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 },
]
[[package]]
name = "pillow-heif"
version = "0.21.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pillow" },
]
sdist = { url = "https://files.pythonhosted.org/packages/65/f5/993804c7c626256e394f2dcb90ee739862ae22151bd7df00e014f5206573/pillow_heif-0.21.0.tar.gz", hash = "sha256:07aee1bff05e5d61feb989eaa745ae21b367011fd66ee48f7732931f8a12b49b", size = 16178019 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/e0/5fc46d46c564cc955e83eb7ba7de4686270f88c242529673b4b30084a364/pillow_heif-0.21.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:0aaea6ea45257cf74e76666b80b6109f8f56217009534726fa7f6a5694ebd563", size = 5400784 },
{ url = "https://files.pythonhosted.org/packages/f1/ab/9f7095e8c66d6cbb8a9dfeaf107c06e4b570d5fe8cbb8388196499d1578e/pillow_heif-0.21.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f28c2c934f547823de3e204e48866c571d81ebb6b3e8646c32fe2104c570c7b2", size = 3967392 },
{ url = "https://files.pythonhosted.org/packages/85/1b/37817bc4ab5f58386ce335851807d97ed407c81dabed0281cd2941ed5647/pillow_heif-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e10ab63559346fc294b9612502221ddd6bfac8cd74091ace7328fefc1163a167", size = 6965387 },
{ url = "https://files.pythonhosted.org/packages/07/db/1107f9a5c7c8d627367b4741f3bdb67917f9e6bb10ffd76498d2b828432e/pillow_heif-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2a015cfe4afec75551190d93c99dda13410aec89dc468794885b90f870f657", size = 7802240 },
{ url = "https://files.pythonhosted.org/packages/5c/be/2e05c9038236933091be982894e1098fea6e00a0951b923fa6876516c1e5/pillow_heif-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:41693f5d87ed2b5fd01df4a6215045aff14d148a750aa0708c77e71139698154", size = 8301374 },
{ url = "https://files.pythonhosted.org/packages/c5/e6/0ea6a7596c0d1bee265b51f233b4858cb2fe87fb6fa715a31bc412efe4c5/pillow_heif-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b27031c561ee3485a119c769fc2ef41d81fae1de530857beef935683e09615e", size = 9051087 },
{ url = "https://files.pythonhosted.org/packages/ff/a3/126e24519825c063bb7e70ad28644ae522cea917d2a3e17413ef4bce99cb/pillow_heif-0.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:60196c08e9c256e81054c5da468eb5a0266c931b8564c96283a43e5fd2d7ce0e", size = 8691151 },
{ url = "https://files.pythonhosted.org/packages/c1/87/229c4c3f558e9fd827f5ac7716e190c71ff94440a9dcb41576240aaff55f/pillow_heif-0.21.0-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:9e67aae3c22a90bc7dfd42c9f0033c53a7d358e0f0d5d29aa42f2f193162fb01", size = 5400786 },
{ url = "https://files.pythonhosted.org/packages/91/07/825f0ada5976faa92fdadd837522d907e01b39e6aed096178eaeda6b2f5f/pillow_heif-0.21.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ee2d68cbc0df8ba6fd9103ac6b550ebafcaa3a179416737a96becf6e5f079586", size = 3967393 },
{ url = "https://files.pythonhosted.org/packages/3f/de/e5e50e0debb5765aa6b1ea0eaad19c347972b9f7954a4ef37f1dc2304317/pillow_heif-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e5c0df7b8c84e4a8c249ba45ceca2453f205028d8a6525612ec6dd0553d925d", size = 6965345 },
{ url = "https://files.pythonhosted.org/packages/ac/c6/3683070be2a9f3ac5c58058fcd91687dea652613b4358ddce6b687012617/pillow_heif-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaedb7f16f3f18fbb315648ba576d0d7bb26b18b50c16281665123c38f73101e", size = 7802164 },
{ url = "https://files.pythonhosted.org/packages/a1/cf/06a9e7e7c24b12f1109f26afa17f4969175ef8e0b98be8d524458914c0fb/pillow_heif-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6724d6a2561f36b06e14e1cd396c004d32717e81528cb03565491ac8679ed760", size = 8301428 },
{ url = "https://files.pythonhosted.org/packages/bf/c5/d3f8d90577085682183028ccc4cb2010d8accd0c5efab0e96146bb480acf/pillow_heif-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf2e2b0abad455a0896118856e82a8d5358dfe5480bedd09ddd6a04b23773899", size = 9051141 },
{ url = "https://files.pythonhosted.org/packages/8b/38/4ee2ed6584b6a00cac9f805f36610691c37d58df609d221d2708e49cf23b/pillow_heif-0.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:1b6ba6c3c4de739a1abf4f7fe0cdd04acd9e0c7fc661985b9a5288d94893a4b1", size = 8691151 },
] ]
[[package]] [[package]]
@ -333,46 +483,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 },
] ]
[[package]]
name = "pytest"
version = "8.3.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
]
[[package]]
name = "pytest-cov"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
]
[[package]]
name = "pytest-django"
version = "4.10.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a5/10/a096573b4b896f18a8390d9dafaffc054c1f613c60bf838300732e538890/pytest_django-4.10.0.tar.gz", hash = "sha256:1091b20ea1491fd04a310fc9aaff4c01b4e8450e3b157687625e16a6b5f3a366", size = 84710 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/4c/a4fe18205926216e1aebe1f125cba5bce444f91b6e4de4f49fa87e322775/pytest_django-4.10.0-py3-none-any.whl", hash = "sha256:57c74ef3aa9d89cae5a5d73fbb69a720a62673ade7ff13b9491872409a3f5918", size = 23975 },
]
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.0.1" version = "1.0.1"
@ -408,6 +518,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
] ]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
]
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@ -417,6 +542,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
] ]
[[package]]
name = "soupsieve"
version = "2.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
]
[[package]] [[package]]
name = "sqlparse" name = "sqlparse"
version = "0.5.3" version = "0.5.3"
@ -427,15 +561,12 @@ wheels = [
] ]
[[package]] [[package]]
name = "tinycss2" name = "telepath"
version = "1.4.0" version = "0.3.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/65/58/5de8765687d6f9dfcbb72c89251036c3b901126404f872b936b13377a3fe/telepath-0.3.1.tar.gz", hash = "sha256:925c0609e0a8a6488ec4a55b19d485882cf72223b2b19fe2359a50fddd813c9c", size = 11622 }
{ name = "webencodings" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, { url = "https://files.pythonhosted.org/packages/5f/50/0f246d316f223b14ba5a88fbe2ff0b480d4a6064b866d48008dd57fd5930/telepath-0.3.1-py38-none-any.whl", hash = "sha256:c280aa8e77ad71ce80e96500a4e4d4a32f35b7e0b52e896bb5fde9a5bcf0699a", size = 10926 },
] ]
[[package]] [[package]]
@ -456,6 +587,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 }, { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 },
] ]
[[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 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.34.0" version = "0.34.0"
@ -500,6 +640,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 },
] ]
[[package]]
name = "wagtail"
version = "6.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyascii" },
{ name = "beautifulsoup4" },
{ name = "django" },
{ name = "django-filter" },
{ name = "django-modelcluster" },
{ name = "django-permissionedforms" },
{ name = "django-taggit" },
{ name = "django-tasks" },
{ name = "django-treebeard" },
{ name = "djangorestframework" },
{ name = "draftjs-exporter" },
{ name = "laces" },
{ name = "openpyxl" },
{ name = "pillow" },
{ name = "requests" },
{ name = "telepath" },
{ name = "willow", extra = ["heif"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/4f/0f/4ad523cf1c2aee06e32e279b1b98d3a4e5577ee952acbc486034d6f191f2/wagtail-6.4.1.tar.gz", hash = "sha256:cec3e6d4920a6d178fa1eb6f4af80b370fdd5570ed924b979104e157f10ca097", size = 6630337 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/44/e2383decf738b3d5491084f6ae758f0115d3bdeddb6f059f8724078c4950/wagtail-6.4.1-py3-none-any.whl", hash = "sha256:85206f86b04876d5596190e042d30de0430fdb5cdd71b6005748c8a0d45066ec", size = 9110827 },
]
[[package]] [[package]]
name = "wait-for-it" name = "wait-for-it"
version = "2.3.0" version = "2.3.0"
@ -548,15 +716,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 }, { url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 },
] ]
[[package]]
name = "webencodings"
version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 },
]
[[package]] [[package]]
name = "websockets" name = "websockets"
version = "14.2" version = "14.2"
@ -587,3 +746,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/14/13/8b7fc4cb551b9cfd9890f0fd66e53c18a06240319915533b033a56a3d520/websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f", size = 164420 }, { url = "https://files.pythonhosted.org/packages/14/13/8b7fc4cb551b9cfd9890f0fd66e53c18a06240319915533b033a56a3d520/websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f", size = 164420 },
{ url = "https://files.pythonhosted.org/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416 }, { url = "https://files.pythonhosted.org/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416 },
] ]
[[package]]
name = "willow"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "defusedxml" },
{ name = "filetype" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2b/07/7937bb91ba3373133d903ec6c8a7a3fe0bec6ac964c7f2e532188e230c9b/willow-1.9.0.tar.gz", hash = "sha256:ffac1406275ae30b60e7c6cbd1245f0bc359d1b5731002b18a712aaf424a5102", size = 113373 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dd/c0/10a11f2dc0dc485a397bd3f66098805b6e39e7317f5acb72b415d1d7a559/willow-1.9.0-py3-none-any.whl", hash = "sha256:11a13097cffe501898cd434bb5761fb6cdbdb774a7853094cb56a4ba57cbbff7", size = 119156 },
]
[package.optional-dependencies]
heif = [
{ name = "pillow-heif" },
]

View file

@ -6,63 +6,50 @@
} }
h1 { h1 {
font-family: 'Futura', sans-serif;
font-size: 3rem; font-size: 3rem;
color: #333; color: #333;
text-align: center; text-align: center;
margin-bottom: 1rem; margin-bottom: 1rem;
margin-top: 2rem; margin-top: 2rem;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
cursor: default;
} }
h2 { h2 {
font-family: 'Futura', sans-serif;
font-size: 1.5rem; font-size: 1.5rem;
color: #CF3476; color: #CF3476;
text-align: center; text-align: center;
word-break: break-word; margin-bottom: 0.5rem;
margin-bottom: 2rem;
cursor: default;
} }
h3 { h3 {
font-family: 'Futura', sans-serif;
font-size: 1.5rem; font-size: 1.5rem;
color: #666; color: #666;
text-align: center; text-align: center;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
cursor: default;
} }
h4 { h4 {
font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #666; color: #666;
text-align: center; text-align: center;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
cursor: default;
} }
h5 { h5 {
font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #aaa; color: #aaa;
margin-top: 1rem; margin-top: 1rem;
cursor: default;
}
@media (max-width: 600px) {
h1 {
font-size: 1.8rem; /* Smaller size for mobile */
margin-top: 1.5rem;
}
h2 {
font-size: 1.2rem; /* Slightly smaller h2 for mobile */
}
} }
.sheet__box { .sheet__box {
min-width: 25rem; min-width: 25rem;
margin-left: calc(20% - 2.5rem); margin-left: calc(20% - 2.5rem);
margin-right: calc(20% - 2.5rem); margin-right: calc(20% - 2.5rem);
margin-bottom: 3rem;
} }
.app__logo { .app__logo {
@ -71,26 +58,30 @@ h5 {
} }
.app__title { .app__title {
font-family: 'Futura', sans-serif;
font-size: 2rem; font-size: 2rem;
color: #333; color: #333;
cursor: default;
} }
.app__tab { .app__tab {
font-family: 'Futura', sans-serif;
font-size: 2rem; font-size: 2rem;
color: #CF3476; color: #CF3476;
} }
.to_left { .to_left {
font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
} }
.to_right { .to_right {
font-family: 'Futura', sans-serif;
float: right; float: right;
} }
.contact { .contact {
margin: 20px calc(20% - 40px) 50px; margin: 20px calc(20% - 40px) 50px;
min-width: 25rem;
border-radius: 15px; border-radius: 15px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
} }
@ -100,17 +91,8 @@ h5 {
font-size: 3rem; font-size: 3rem;
color: #333; color: #333;
margin-bottom: 1rem; margin-bottom: 1rem;
font-family: 'Futura', sans-serif;
font-weight: bold; font-weight: bold;
cursor: default;
}
.contact__dialog__title {
font-size: 2rem;
color: #333;
margin-bottom: 1rem;
font-weight: bold;
word-break: break-word !important;
cursor: default;
} }
.contact__button { .contact__button {
@ -119,81 +101,56 @@ h5 {
background-color: #CF3476; background-color: #CF3476;
} }
@media (max-width: 600px) {
.contact__title {
font-size: 1.5rem;
}
.contact {
margin: 10px;
padding: 1rem;
}
.v-dialog > .v-card {
border-radius: 0 !important;
height: 100vh;
padding: 1rem;
}
.contact__dialog__title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
word-break: break-word;
}
}
.articles { .articles {
display: flex; margin-left: calc(20% - 2.5rem);
justify-content: center; margin-right: calc(20% - 2.5rem);
margin: 0 auto;
max-width: 1200px;
cursor: default;
} }
.article { .article {
padding: 1rem; padding: 10px;
min-width: 40%; min-width: 40%;
border-radius: 0; border-radius: 15px;
box-shadow: 0 0 0 rgba(0, 0, 0, 0); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
margin-bottom: 1rem; margin-bottom: 1rem;
cursor: default; }
.article__image {
border-radius: 0.5rem;
} }
.article__title { .article__title {
font-size: 1.4rem; font-family: 'Futura', sans-serif;
font-size: 1.5rem;
color: #CF3476; color: #CF3476;
text-align: left;
margin-top: 0.1rem;
font-weight: bold; font-weight: bold;
cursor: default;
} }
.article__date { .article__date {
font-family: 'Futura', sans-serif;
font-size: 0.8rem; font-size: 0.8rem;
text-align: left; text-align: left;
cursor: default;
} }
.article__text { .article__text {
font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #333; text-align: left;
line-height: 1.5; min-height: 6rem;
padding: 0.5rem; color: #666;
margin-bottom: 1rem;
cursor: default;
}
.article__image:hover {
cursor: pointer;
} }
.article__sign { .article__sign {
text-align: right; font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
font-style: italic; float: right;
color: #555; padding-right: 1rem;
cursor: default; color: #333;
} }
.show_more { .show_more {
font-family: 'Futura', sans-serif;
text-align: center; text-align: center;
color: #CF3476; color: #CF3476;
background: transparent; background: transparent;
@ -207,73 +164,35 @@ h5 {
} }
.pricing { .pricing {
margin-left: auto; margin-left: calc(20% - 2.5rem);
margin-right: auto; margin-right: calc(20% - 2.5rem);
max-width: 1500px;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
} }
.pricing-box { .pricing-box {
padding: 1.5rem; padding: 1rem;
text-align: center; text-align: center;
min-width: 18rem; min-width: 25rem;
border-radius: 1rem; border-radius: 1rem;
height: 100%; height: 230px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
transition: transform 0.3s ease, box-shadow 0.3s ease;
background: #fff;
display: flex;
flex-direction: column;
justify-content: space-between;
} }
.pricing-box:hover { .pricing-box:hover {
transform: translateY(-5px); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.25);
}
.pricing-content {
flex-grow: 1;
}
.pricing-title {
min-height: 3rem;
cursor: pointer;
}
.pricing-desc {
min-height: 3rem;
} }
.pricing__price { .pricing__price {
font-size: 3rem; font-family: 'Futura', sans-serif;
font-weight: bold; font-size: 3.5rem;
color: #CF3476; color: #CF3476;
margin-top: 0.5rem; text-align: center;
} }
.pricing__subtitle { .pricing__subtitle {
font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #555; color: #333;
word-break: break-word; text-align: center;
margin-bottom: 0.5rem;
}
@media (max-width: 600px) {
.pricing-box {
min-width: 100%;
}
.pricing-title {
font-size: 1.4rem;
}
.pricing-desc {
min-height: unset;
}
} }
.trainers__parallax { .trainers__parallax {
@ -282,45 +201,12 @@ h5 {
} }
.trainers { .trainers {
margin-left: auto; min-width: 25rem;
margin-right: auto;
max-width: 1500px;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
height: 100%; height: 100%;
text-align: center; margin-left: calc(20% - 2.5rem);
padding: 2rem; margin-right: calc(20% - 2.5rem);
cursor: default;
}
.trainer-avatar {
width: 150px;
height: 150px;
margin: 20px auto;
border-radius: 50%;
border: 3px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
@media (max-width: 600px) {
.trainers__parallax {
max-height: 30rem;
}
.trainers {
max-width: 100%;
padding: 1rem;
}
.trainer-avatar {
width: 120px;
height: 120px;
}
} }
.advantage { .advantage {
@ -331,140 +217,61 @@ h5 {
margin-top: 1.2rem; margin-top: 1.2rem;
margin-left: calc(20% - 2.5rem); margin-left: calc(20% - 2.5rem);
margin-right: calc(20% - 2.5rem); margin-right: calc(20% - 2.5rem);
cursor: default;
} }
.advantage__title { .advantage__title {
padding: 0.5rem 0; font-family: 'Futura', sans-serif;
color: #CF3476; color: #CF3476;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: bold; font-weight: bold;
margin-top: 0.1rem; margin-top: 0.1rem;
cursor: default;
} }
.advantage__text { .advantage__text {
padding-bottom: 1rem; font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #333; color: #333;
cursor: default;
}
@media (max-width: 600px) {
.advantage {
min-width: 100%;
margin: 0.5rem auto;
padding: 0.5rem;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}
.advantage__title {
font-size: 1.3rem;
}
.advantage__text {
font-size: 1rem;
padding: 0 1.5rem;
}
} }
.about { .about {
min-width: 25rem;
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
height: auto; height: 100%;
max-width: 80%; margin-left: calc(20% - 2.5rem);
margin: 0 auto; margin-right: calc(20% - 2.5rem);
text-align: center;
padding: 2rem;
cursor: default;
} }
.about__parallax { .about__parallax {
max-height: 28rem; max-height: 30rem;
margin-top: 2rem; margin-top: 2rem;
} }
.history__parallax {
max-height: 34rem;
margin-top: 2rem;
}
@media (max-width: 600px) {
.history__parallax {
max-height: 58rem;
}
}
.about__title { .about__title {
font-size: 2.5rem; font-family: 'Futura', sans-serif;
font-size: 3rem;
text-align: center;
color: #fff; color: #fff;
margin-top: 1rem; margin-top: 1rem;
font-weight: bold; font-weight: bold;
word-break: break-word;
white-space: normal;
display: block;
cursor: default;
} }
.about__subtitle { .about__subtitle {
font-family: 'Futura', sans-serif;
color: #FF3D8C; color: #FF3D8C;
font-size: 1.25rem; font-size: 1.5rem;
margin-bottom: 1rem; text-align: center;
word-break: break-word; margin-bottom: 0.5rem;
font-weight: bold; font-weight: bold;
display: block;
cursor: default;
} }
.about__text { .about__text {
font-family: 'Futura', sans-serif;
color: #ddd; color: #ddd;
font-size: 1.125rem; font-size: 1.2rem;
padding: 0 1rem; text-align: center;
line-height: 1.6;
cursor: default;
}
@media (max-width: 600px) {
.about {
max-width: 95%;
padding: 1rem;
}
.about__parallax {
min-height: 32rem;
}
.about__title {
font-size: 1.5rem;
}
.about__subtitle {
font-size: 1rem;
word-break: break-word;
}
.about__text {
font-size: 0.95rem;
}
}
.masonry-gallery {
columns: 3;
column-gap: 16px;
}
@media (max-width: 600px) {
.masonry-gallery {
columns: 1;
}
}
.masonry-item {
break-inside: avoid;
margin-bottom: 16px;
} }
.footer { .footer {
background-color: black; background-color: black;
cursor: default;
} }

View file

@ -4,12 +4,12 @@
src="public/dark-dance.jpg" src="public/dark-dance.jpg"
scale="0.8" scale="0.8"
> >
<v-container> <v-container id="about">
<v-card class="about" id="about"> <v-card class="about">
<v-card-title class="about__title">Vítejte v tanečním klubu!</v-card-title> <v-card-title class="about__title">Vítejte v tanečním klubu!</v-card-title>
<v-card-subtitle class="about__subtitle">Objevte kouzlo tance s námi!</v-card-subtitle> <v-card-subtitle class="about__subtitle">Vítejte na webu Tanečního klubu Ostrava!</v-card-subtitle>
<v-card-text class="about__text"> <v-card-text class="about__text">
jste začátečník nebo zkušený tanečník, v našem klubu najdete místo, kde se můžete rozvíjet, bavit a sdílet svou vášeň pro pohyb. Nabízíme kurzy pro všechny věkové kategorie, od společenských tanců po moderní styly. Objevte kouzlo tance s námi! jste začátečník nebo zkušený tanečník, v našem klubu najdete místo, kde se můžete rozvíjet, bavit a sdílet svou vášeň pro pohyb. Nabízíme kurzy pro všechny věkové kategorie, od společenských tanců po moderní styly.
<br><br> <br><br>
Přidejte se k nám a nechte tanec proměnit váš život! 💃🕺 Přidejte se k nám a nechte tanec proměnit váš život! 💃🕺
<br><br> <br><br>

View file

@ -1,23 +1,21 @@
<template> <template>
<h1>Přínosy tance</h1> <h1>Přínosy tance: Více než jen pohyb</h1>
<h2>Více než jen pohyb</h2> <h2>Tancem k lepšímu životu</h2>
<v-card <v-card
v-for="advantage in advantages" v-for="advantage in advantages"
:key="advantage.id" :key="advantage.id"
class="advantage" class="advantage"
> >
<v-row align="center" class="pa-2"> <v-row>
<v-col cols="auto"> <v-col cols="12" md="1">
<small-icon :icon="advantage.icon" /> <small-icon :icon="advantage.icon" />
</v-col> </v-col>
<v-col> <v-col>
<v-card-title class="advantage__title"> <v-card-title class="advantage__title">
{{ advantage.title }} {{advantage.title}}
</v-card-title> </v-card-title>
</v-col> </v-col>
</v-row> </v-row>
<v-card-text class="advantage__text"> <v-card-text class="advantage__text">
{{ advantage.subtitle }} {{ advantage.subtitle }}
</v-card-text> </v-card-text>

View file

@ -1,9 +1,87 @@
<template> <template>
<v-card class="contact pa-4"> <v-card class="contact">
<v-card-title class="contact__title">Kontaktujte nás!</v-card-title> <v-form v-model="valid">
<lazy-dialog-contact-form /> <v-container>
<v-card-title class="contact__title">Kontaktujte nás!</v-card-title>
<v-row>
<v-col
cols="12"
md="4"
>
<v-text-field
v-model="fullName"
label="Jméno"
variant="underlined"
></v-text-field>
</v-col>
<v-col
cols="12"
md="4"
>
<v-text-field
v-model="email"
label="Emailová adresa"
variant="underlined"
></v-text-field>
</v-col>
<v-col
cols="12"
md="4"
>
<v-text-field
v-model="phone"
label="Telefonní číslo"
variant="underlined"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-textarea
v-model="textField"
label="Váš dotaz *"
variant="underlined"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-btn
class="contact__button"
type="submit"
@click.prevent="sendContact"
>
Poslat
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card> </v-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import './assets/css/main.css'
import {useAPI} from "~/composables/useAPI";
const valid = ref<boolean>(false);
const fullName = ref<string>("");
const email = ref<string>("");
const phone = ref<string>("");
const textField = ref<string>("");
async function sendContact() {
const { data, error } = await useAPI('create-contact/', {
method: "POST",
body: {
name: fullName.value,
email: email.value,
phone_number: phone.value,
content: textField.value,
}
});
}
</script> </script>

View file

@ -2,69 +2,32 @@
<h1 id="courses">Kurzy</h1> <h1 id="courses">Kurzy</h1>
<h2>Vyberte, co Vám nejlépe vyhovuje</h2> <h2>Vyberte, co Vám nejlépe vyhovuje</h2>
<v-row class="pricing"> <v-row class="pricing">
<v-col <v-col
cols="12" cols="12"
sm="6" md="4"
md="4" v-for="(course, index) in courses"
lg="3" :key="index"
v-for="(course, index) in courses" >
:key="index" <v-card class="pricing-box">
> <h3>{{ course.name }}</h3>
<v-card class="pricing-box" @click="openDialog = true; chosenCourse = course"> <v-card-subtitle><div class="pricing__subtitle">{{ course.time }}</div></v-card-subtitle>
<div class="pricing-content"> <v-card-title><div class="pricing__price">{{ course.price }}</div></v-card-title>
<h3 class="pricing-title">{{ course.name }}</h3> <v-card-text v-if="course.desc"><div class="pricing__subtitle">{{ course.desc }}</div></v-card-text>
<v-card-subtitle v-if="course.time"> <v-btn class="show_more">
<div class="pricing__subtitle">{{ course.time }}</div> Kontaktujte nás
</v-card-subtitle> </v-btn>
<v-card-title v-if="course.price"> </v-card>
<div class="pricing__price">{{ course.price }}</div> </v-col>
</v-card-title> </v-row>
<v-card-text v-if="course.desc"> <!-- <v-btn to="/kurzy" class="show_more">-->
<div class="pricing__subtitle pricing-desc">{{ course.desc }}</div> <!-- <v-icon icon="mdi-chevron-down"/>Více informací<v-icon icon="mdi-chevron-down"/>-->
</v-card-text> <!-- </v-btn>-->
</div>
<v-btn class="show_more">
Kontaktujte nás
</v-btn>
</v-card>
</v-col>
</v-row>
<template>
<v-dialog
v-model="openDialog"
:fullscreen="$vuetify.display.smAndDown"
scrollable
transition="dialog-bottom-transition"
>
<v-card class="contact pa-4">
<h1 class="contact__dialog__title d-flex align-center">
<span class="flex-grow-1 text-center" style="margin-left: 40px;">{{ chosenCourse.name }}</span>
<v-btn icon="mdi-close" variant="text" @click="openDialog = false"></v-btn>
</h1>
<v-card-text v-if="chosenCourse.desc" class="text-center">
<div class="pricing__subtitle text-body-1">{{ chosenCourse.desc }}</div>
</v-card-text>
<v-divider class="my-4"></v-divider>
<v-card-title class="contact__dialog__title text-center">
Kontaktujte nás!
</v-card-title>
<dialog-contact-form class="mt-2" />
</v-card>
</v-dialog>
</template>
<!-- <v-btn to="/kurzy" class="show_more">-->
<!-- <v-icon icon="mdi-chevron-down"/>Více informací<v-icon icon="mdi-chevron-down"/>-->
<!-- </v-btn>-->
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import './assets/css/main.css' import './assets/css/main.css'
const openDialog = ref<boolean>(false); // TODO: vyskakovací okno s kontaktním formulářem po rozkliku :)
const courses = [ const courses = [
{name: "Sportovní taneční klub", time: "", price: "", desc: "Připojte se k našemu tanečnímu klubu a rozvíjejte své taneční dovednosti v přátelském prostředí. Nabízíme různé styly tance pro všechny úrovně."}, {name: "Sportovní taneční klub", time: "", price: "", desc: "Připojte se k našemu tanečnímu klubu a rozvíjejte své taneční dovednosti v přátelském prostředí. Nabízíme různé styly tance pro všechny úrovně."},
@ -75,5 +38,4 @@ const courses = [
{name: "Pronájem sálu", time: "", price: "", desc: "Hledáte ideální prostor pro tanec nebo jinou aktivitu? Náš taneční sál je k dispozici k pronájmu pro Vaše akce."}, {name: "Pronájem sálu", time: "", price: "", desc: "Hledáte ideální prostor pro tanec nebo jinou aktivitu? Náš taneční sál je k dispozici k pronájmu pro Vaše akce."},
] ]
const chosenCourse = ref(courses[0]);
</script> </script>

View file

@ -1,6 +1,6 @@
<template> <template>
<v-parallax <v-parallax
class="history__parallax" class="about__parallax"
src="public/img/black-pink.jpg" src="public/img/black-pink.jpg"
> >
<v-container id="about"> <v-container id="about">

View file

@ -1,75 +1,59 @@
<template v-if="articles"> <template v-if="articles">
<h1 id="article">Aktuality</h1> <h1 id="article">Aktuality</h1>
<h2>Přečtěte si aktuality z našeho klubu</h2> <h2>Přečtěte si aktuality z našeho klubu</h2>
<v-row class="articles"> <v-row class="articles">
<v-col <v-col
v-for="article in articles" v-for="article in articles"
:key="article.id" :key="article.id"
cols="12" cols="12"
md="6" md="6"
> >
<v-card class="article"> <v-card class="article">
<v-row class="article__title">{{ article.title }}</v-row>
<v-row> <v-row>
<v-col> <v-col>
<v-img <v-img
:src="article.image.image" :lazy-src="article.image"
:lazy-src="article.image.image" :src="article.image"
class="article__image rounded-lg" aspect-ratio="1"
@click="openCarousel(article.image, article.images)" cover
class="article__image"
> >
<template v-slot:placeholder> <template v-slot:placeholder>
<v-row justify="center" align="center" class="fill-height"> <v-row>
<v-progress-circular color="grey-lighten-5" indeterminate /> <v-progress-circular
color="grey-lighten-5"
indeterminate
></v-progress-circular>
</v-row> </v-row>
</template> </template>
</v-img> </v-img>
</v-col> </v-col>
<v-col cols="12" md="8"> <v-col
cols="12"
md="8"
>
<v-row class="article__title">{{ article.title }}</v-row>
<v-row class="article__date">{{ article.date}}</v-row>
<v-row class="article__text">{{ article.content }}</v-row> <v-row class="article__text">{{ article.content }}</v-row>
<v-row class="article__sign">{{ article.author }}</v-row>
</v-col> </v-col>
</v-row> </v-row>
<v-row class="article__sign">Dne {{ article.date }}, {{ article.author }}</v-row>
</v-card> </v-card>
</v-col> </v-col>
</v-row> </v-row>
<v-btn to="/aktuality" class="show_more">
<v-dialog v-model="showCarousel" max-width="90vw" v-if="selectedImages.length > 0"> <v-icon icon="mdi-chevron-down"/>Dalsí aktuality<v-icon icon="mdi-chevron-down"/>
<dialog-carousel
v-if="selectedImage"
:image="selectedImage"
:images="selectedImages"
@isActive="showCarousel = false"
/>
</v-dialog>
<v-btn to="/aktuality" class="show_more" v-if="!props.forAll">
<v-icon icon="mdi-chevron-down" />Další aktuality<v-icon icon="mdi-chevron-down" />
</v-btn> </v-btn>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import './assets/css/main.css' import './assets/css/main.css'
import { useAPI } from "~/composables/useAPI"; import { useAPI } from "~/composables/useAPI";
const showCarousel = ref(false);
const selectedImage = ref<ArticleImage | null>(null);
const selectedImages = ref<ArticleImage[]>([]);
const props = defineProps<{
forAll: boolean
}>();
interface ArticleImage {
image: string;
title: string;
}
interface Article { interface Article {
id: number; id: number;
title: string; title: string;
image: ArticleImage; image: string;
images: ArticleImage[];
date: string; date: string;
content: string; content: string;
author: string; author: string;
@ -77,18 +61,14 @@ interface Article {
const articles = ref<Article[]>([]); const articles = ref<Article[]>([]);
function openCarousel(image: ArticleImage, images: ArticleImage[]) { const { error, data } = await useAPI<Article[]>('load-articles/', {method: "GET"});
selectedImage.value = image;
selectedImages.value = images; if ( data.value ){
showCarousel.value = true; articles.value = data.value;
} }
else if (error.value) {
const endpoint = props.forAll ? 'load-all-articles/' : 'load-articles/';
const { error, data } = await useAPI<Article[]>(endpoint, { method: "GET" });
if (data.value) {
articles.value = data.value;
} else if (error.value) {
console.error("Error loading articles:", error.value); console.error("Error loading articles:", error.value);
} }
</script> </script>

View file

@ -1,36 +1,26 @@
<template> <template>
<v-parallax class="trainers__parallax" src="public/img/black-pink.jpg"> <v-parallax
<v-container> class="trainers__parallax"
<v-card class="about" id="trainers"> src="public/img/black-pink.jpg"
>
<v-container id="trainers">
<v-card class="trainers">
<v-card-title class="about__title">Naši trenéři a lektoři</v-card-title> <v-card-title class="about__title">Naši trenéři a lektoři</v-card-title>
<v-card-subtitle class="about__subtitle">Seznamte se s námi!</v-card-subtitle> <v-card-subtitle class="about__subtitle">Seznamte se s námi!</v-card-subtitle>
<v-row>
<v-carousel style="height: auto" cycle interval="4000" hide-delimiters :show-arrows="trainerGroups.length > 1 ? 'hover' : false"> <v-col class="text-center" cols="12" md="3" v-for="lector in lectors" :key="lector.id">
<v-carousel-item v-for="(group, index) in trainerGroups" :key="index"> <v-avatar
<v-row justify="center"> color="none"
<v-col rounded="1"
v-for="(lector, index) in group" size="150"
:key="index" style="margin: 30px"
cols="12"
sm="6"
md="4"
lg="3"
class="text-center"
> >
<v-avatar <v-img :src="lector.img" cover></v-img>
color="none" </v-avatar>
rounded="1" <v-card-subtitle class="about__subtitle">{{lector.name}}</v-card-subtitle>
size="150" <v-card-text class="about__text">{{ lector.desc }}</v-card-text>
style="margin: 30px" </v-col>
> </v-row>
<v-img :src="lector.img" cover></v-img>
</v-avatar>
<div class="about__subtitle">{{ lector.name }}</div>
<div class="about__text">{{ lector.desc }}</div>
</v-col>
</v-row>
</v-carousel-item>
</v-carousel>
</v-card> </v-card>
</v-container> </v-container>
</v-parallax> </v-parallax>
@ -40,43 +30,28 @@ import './assets/css/main.css'
const lectors = [ const lectors = [
{ {
id: 1,
name: "Ondřej Gilar", name: "Ondřej Gilar",
img: "/trainers/img.png", img: "/trainers/img.png",
desc: "Trenér - latinskoamerické tance a Pro-AM", desc: "Trenér - latinskoamerické tance a Pro-AM",
}, },
{ {
id: 2,
name: "Leona Hruštincová", name: "Leona Hruštincová",
img: "/trainers/img.png", img: "/trainers/img.png",
desc: "Lektorka - tance pro děti", desc: "Lektorka - tance pro děti",
}, },
{ {
id: 1,
name: "Ondřej Gilar", name: "Ondřej Gilar",
img: "/trainers/img.png", img: "/trainers/img.png",
desc: "Trenér - latinskoamerické tance a Pro-AM", desc: "Trenér - latinskoamerické tance a Pro-AM",
}, },
{ {
id: 2,
name: "Leona Hruštincová", name: "Leona Hruštincová",
img: "/trainers/img.png", img: "/trainers/img.png",
desc: "Lektorka - tance pro děti", desc: "Lektorka - tance pro děti",
}, },
{
name: "Leona Hruštincová",
img: "/trainers/img.png",
desc: "Lektorka - tance pro děti",
},
{
name: "Ondřej Gilar",
img: "/trainers/img.png",
desc: "Trenér - latinskoamerické tance a Pro-AM",
},
] ]
const trainerGroups = computed(() => {
const groups: typeof lectors[] = [];
const perSlide = window.innerWidth >= 1280 ? 4 : window.innerWidth >= 600 ? 2 : 1;
for (let i = 0; i < lectors.length; i += perSlide) {
groups.push(lectors.slice(i, i + perSlide));
}
return groups;
});
</script> </script>

View file

@ -1,46 +0,0 @@
<template>
<v-card class="article__image">
<v-card-title>
{{ images[model] && images[model].title }}
<v-icon class="to_right" @click="$emit('isActive', false)">mdi-close</v-icon>
</v-card-title>
<v-carousel
v-model="model"
:hide-delimiters="true"
height="90vh"
>
<v-carousel-item
v-for="(item, i) in props.images"
:key="i"
:src="item.image"
cover
class="carousel__image"
/>
</v-carousel>
</v-card>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import './assets/css/main.css';
interface ArticleImage {
image: string;
title: string;
}
const props = defineProps<{
image: ArticleImage;
images: ArticleImage[];
}>();
defineEmits(["isActive"]);
const model = ref(0);
watch(() => props.image, (newImage) => {
const index = props.images.findIndex((img) => img.image === newImage.image);
if (index !== -1) model.value = index;
}, { immediate: true });
</script>

View file

@ -1,142 +0,0 @@
<template>
<v-form v-model="valid">
<v-snackbar
v-model="showSnackbar"
:color="snackbarColor"
:timeout="5000"
>
{{ snackbarMessage }}
</v-snackbar>
<v-container>
<v-row>
<v-col
cols="12"
md="4"
>
<v-text-field
dense
hide-details="auto"
v-model="fullName"
:error-messages="get(errorMessages, 'name')"
@change="hideError('name')"
label="Jméno"
variant="underlined"
></v-text-field>
</v-col>
<v-col
cols="12"
md="4"
>
<v-text-field
dense
hide-details="auto"
type="email"
autocomplete="email"
v-model="email"
:error-messages="get(errorMessages, 'email')"
@change="hideError('email')"
label="Emailová adresa"
variant="underlined"
></v-text-field>
</v-col>
<v-col
cols="12"
md="4"
>
<v-text-field
dense
hide-details="auto"
type="tel"
v-model="phone"
:error-messages="get(errorMessages, 'phone_number')"
@change="hideError('phone_number')"
label="Telefonní číslo"
variant="underlined"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-textarea
v-model="textField"
:error-messages="get(errorMessages, 'content')"
@change="hideError('content')"
label="Váš dotaz"
variant="underlined"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-btn
auto-grow
rows="3"
class="contact__button"
type="submit"
@click.prevent="sendContact"
>
Poslat
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</template>
<script setup lang="ts">
import './assets/css/main.css'
import {useAPI} from "~/composables/useAPI";
const valid = ref<boolean>(false);
const errorMessages = ref<Record<string, string[]>>({});
const fullName = ref<string>("");
const email = ref<string>("");
const phone = ref<string>("");
const textField = ref<string>("");
const showSnackbar = ref(false);
const snackbarMessage = ref("");
const snackbarColor = ref("red-accent-4")
function get(obj: any, path: string, defaultValue = undefined) {
return path.split('.').reduce((acc, part) => acc && acc[part], obj) ?? defaultValue;
}
function resetContractData() {
fullName.value = "";
email.value = "";
phone.value = "";
textField.value = "";
}
function hideError (key: string) {
delete errorMessages.value[key];
}
async function sendContact() {
const { error } = await useAPI('create-contact/', {
method: "POST",
body: {
name: fullName.value,
email: email.value,
phone_number: phone.value,
content: textField.value,
}
});
if (error.value) {
snackbarMessage.value = "Něco se pokazilo. Zkuste to znovu.";
snackbarColor.value = "red-accent-4";
errorMessages.value = error.value.data;
} else {
snackbarMessage.value = "Děkujeme! Vaše zpráva byla odeslána.";
snackbarColor.value = "success";
resetContractData();
}
showSnackbar.value = true;
}
</script>

View file

@ -1,142 +1,77 @@
<template> <template>
<ClientOnly> <ClientOnly>
<v-layout style="margin-bottom: 3rem"> <v-layout>
<v-app-bar style="position: fixed"> <v-app-bar style="position: fixed">
<v-app-bar-nav-icon
@click="drawer = !drawer"
class="d-flex d-sm-none"
/>
<div> <div>
<a href="/"><v-img class="app__logo" src="/logo.png" /></a> <a href="/"><v-img class="app__logo" src="/logo.png" /></a>
</div> </div>
<v-app-bar-title class="app__title" v-if="$vuetify.display.smAndUp"> <v-app-bar-title class="app__title">Taneční klub Ostrava</v-app-bar-title>
Taneční klub Ostrava
</v-app-bar-title>
<v-tabs <v-tabs
v-if="$vuetify.display.smAndUp" v-for="(tab, index) in tabs" :key="index"
v-model="currentTabName" v-model="currentTab"
class="app__tab" align-with-title
class="d-none d-sm-flex app__tab"
> >
<v-tab <v-tab v-if="tab.ref" :text="tab.name" :value="tab.name" @click="useGoTo(tab.ref)"></v-tab>
v-for="(tab, index) in tabs" <v-tab v-if="tab.href" :text="tab.name" :value="tab.name" :href="tab.href"></v-tab>
:key="index"
:value="tab.name"
@click="handleNavigation(tab)"
>
{{ tab.name }}
</v-tab>
</v-tabs> </v-tabs>
<!-- <v-col-->
<v-menu transition="slide-y-transition"> <!-- class="text-right"-->
<template v-slot:activator="{ props }"> <!-- @click="toggleTheme"-->
<v-app-bar-nav-icon <!-- >-->
class="d-flex d-sm-none app__tab ml-auto" <!-- <big-icon icon="mdi-lightbulb-cfl"/>-->
style="padding-right: 1rem" <!-- </v-col>-->
v-bind="props"
/>
</template>
<v-list>
<v-list-item
v-for="(item, i) in homeTabs"
:key="i"
:value="i"
@click="handleNavigation(item)"
>
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app-bar> </v-app-bar>
<v-fab
:key="currentTab.ref"
class="ms-4 mb-4"
></v-fab>
<v-navigation-drawer
v-model="drawer"
absolute
fixed
left
>
<v-list
nav
dense
>
<v-list-item v-for="(tab, index) in tabs" :key="index">
<v-list-item-title @click="useGoTo(tab.ref)">{{ tab.name }}</v-list-item-title>
</v-list-item>
</v-list>
</v-navigation-drawer>
</v-layout> </v-layout>
</ClientOnly> </ClientOnly>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, watch, nextTick } from 'vue'; import { useGoTo } from "~/composables/useGoTo";
import { useRoute, useRouter } from 'vue-router';
const route = useRoute(); const currentTab = ref({name: 'O nás', ref: "#about", href: "o-nas"});
const router = useRouter(); const drawer = ref(null)
const tabs = [
{name: 'O nás', ref: "#about", href: ""},
{name: "Trenéři", ref: "#trainers", href: ""},
{name: 'Kurzy', ref: "#courses", href: ""},
{name: 'Galerie', ref: "", href: "/galerie"},
{name: 'Aktuality', ref: "#article", href: ""},
{name: 'Kontakty', ref: "", href: "/kontakty"},
]
const homeTabs = [ // import { useTheme } from 'vuetify'
{ name: "O nás", href: "#about" }, //
{ name: "Trenéři", href: "#trainers" }, // const theme = useTheme()
{ name: "Kurzy", href: "#courses" }, //
{ name: "Galerie", href: "/galerie" }, // function toggleTheme () {
{ name: "Aktuality", href: "#article" }, // theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
{ name: "Kontakty", href: "/kontakty" }, // }
];
const otherTabs = [
{ name: "O nás", href: "/#about" },
{ name: "Trenéři", href: "/#trainers" },
{ name: "Kurzy", href: "/#courses" },
{ name: "Galerie", href: "/galerie" },
{ name: "Aktuality", href: "/aktuality" },
{ name: "Kontakty", href: "/kontakty" },
];
const tabs = computed(() => (route.path === "/" ? homeTabs : otherTabs));
type Tab = {
name: string;
href: string;
};
const currentTab = ref<Tab>({ name: "O nás", href: "#about" });
const currentTabName = computed<string>({
get: () => currentTab.value.name,
set: (newName: string) => {
const tab = tabs.value.find(t => t.name === newName);
if (tab) handleNavigation(tab);
}
});
const handleNavigation = async (tab: { name: string, href: string }) => {
currentTab.value = tab;
if (tab.href.startsWith("#")) {
await scrollToHash(tab.href);
} else if (tab.href.startsWith("/#")) {
await router.push(tab.href);
} else {
await router.push(tab.href);
}
};
async function scrollToHash(hash: string) {
await nextTick();
const el = document.querySelector(hash);
if (el) {
const yOffset = -80;
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({ top: y, behavior: 'smooth' });
}
}
onMounted(() => {
if (route.path === "/" && route.hash) {
const match = homeTabs.find(tab => tab.href === route.hash);
if (match) {
currentTab.value = match;
}
scrollToHash(route.hash);
}
});
watch(() => route.hash, (newHash) => {
if (route.path === "/" && newHash) {
const match = homeTabs.find(tab => tab.href === newHash);
if (match) currentTab.value = match;
scrollToHash(newHash);
}
});
watch(() => route.path, (newPath) => {
if (newPath !== "/") {
const match = otherTabs.find(tab => tab.href === newPath || tab.href === `${newPath}${route.hash}`);
if (match) currentTab.value = match;
}
}, { immediate: true });
</script> </script>

View file

@ -1,71 +1,40 @@
import { useRouter, useRoute } from "vue-router"; function waitForElement (selector: string, timeout = 2000) : Promise<Element> {
import { nextTick } from "vue";
export async function useGoTo(
selector: string,
props: { offset?: number } = {}
): Promise<void> {
const router = useRouter();
const route = useRoute();
const yOffset = props?.offset ?? -150;
const hash = selector.startsWith("#") ? selector : "";
const basePath = route.path.split("#")[0];
const targetPath = selector.startsWith("/") ? selector : basePath + hash;
const [pathOnly, hashOnly] = targetPath.split("#");
if (route.path !== pathOnly) {
// Navigate to target page
await router.push(pathOnly + (hashOnly ? `#${hashOnly}` : ""));
await nextTick();
if (hashOnly) {
try {
const element = await waitForElement(`#${hashOnly}`);
scrollToElement(element, yOffset);
} catch (err) {
console.warn(err);
}
}
} else {
// Same page — scroll directly
if (hashOnly || selector.startsWith("#")) {
try {
const element = await waitForElement(`#${hashOnly ?? selector}`);
scrollToElement(element, yOffset);
} catch (err) {
console.warn(err);
}
}
}
}
// Scroll to the element smoothly
function scrollToElement(element: Element, offset: number) {
const y = element.getBoundingClientRect().top + window.pageYOffset + offset;
window.scrollTo({ top: y, behavior: "smooth" });
}
// Wait for the element to exist
function waitForElement(selector: string, timeout = 2000): Promise<Element> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const startTime = Date.now(); const startTime = Date.now();
// eslint-disable-next-line prefer-const
let observer: MutationObserver; let observer: MutationObserver;
function checkElement() { function checkElement () {
const element = document.querySelector(selector); const element = document.querySelector(selector);
if (element) { if (element) {
observer.disconnect(); // Stop observing DOM changes
resolve(element); resolve(element);
} else if (Date.now() - startTime >= timeout) {
observer.disconnect(); // Stop observing DOM changes observer.disconnect(); // Stop observing DOM changes
reject(new Error(`Timeout exceeded while waiting for element '${selector}'`)); } else if (Date.now() - startTime >= timeout) {
reject(new Error(`Timeout exceeded while waiting for element with selector '${selector}'`));
observer.disconnect(); // Stop observing DOM changes
} }
} }
observer = new MutationObserver(checkElement); observer = new MutationObserver(checkElement);
observer.observe(document.body, { childList: true, subtree: true }); observer.observe(document.body, { childList: true, subtree: true });
checkElement(); // Initial check in case the element already exists checkElement(); // Check initially in case the element is already present
}); });
} }
export async function useGoTo (selector: string, props: {offset? :number} = {}): Promise<void> {
console.log(selector);
let element: Element;
try {
element = await waitForElement(selector);
} catch {
// element not found
return;
}
const yOffset = props?.offset ?? -80;
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({ top: y, behavior: "smooth" });
}

View file

@ -1,3 +1,70 @@
<template> <template v-if="articles">
<lazy-news :for-all="true"/> <h1 id="article">Aktuality</h1>
</template> <h2>Přečtěte si aktuality z našeho klubu</h2>
<v-row>
<v-col
v-for="article in articles"
:key="article.id"
cols="12"
md="6"
>
<v-card class="article">
<v-row>
<v-col>
<v-img
:lazy-src="article.image"
:src="article.image"
aspect-ratio="1"
cover
class="article__image"
>
<template v-slot:placeholder>
<v-row>
<v-progress-circular
color="grey-lighten-5"
indeterminate
></v-progress-circular>
</v-row>
</template>
</v-img>
</v-col>
<v-col
cols="12"
md="8"
>
<v-row class="article__title">{{ article.title }}</v-row>
<v-row class="article__date">{{ article.date}}</v-row>
<v-row class="article__text">{{ article.content }}</v-row>
<v-row class="article__sign">{{ article.author }}</v-row>
</v-col>
</v-row>
</v-card>
</v-col>
</v-row>
</template>
<script setup lang="ts">
import './assets/css/main.css'
import {useAPI} from "~/composables/useAPI";
interface Article {
id: number;
title: string;
image: string;
date: string;
content: string;
author: string;
}
const articles = ref<Article[]>([]);
const { error, data } = await useAPI('load-all-articles/', {method: "GET"});
if ( data.value ){
articles.value = data.value as Article[];
}
else if (error.value) {
console.error("Error loading articles:", error.value);
}
</script>

View file

@ -1,69 +1,63 @@
<template> <template>
<h1>Galerie</h1> <h1>Galerie</h1>
<div class="masonry-gallery"> <v-row>
<div <v-col
class="masonry-item" v-for="(photo, index) in photos"
v-for="(photo, index) in gallery"
:key="index" :key="index"
@click="openCarousel(photo)" cols="4"
style="padding: 0;"
> >
<v-img <v-img
:src="photo.image" :lazy-src="photo.src"
:lazy-src="photo.image" :src="photo.src"
aspect-ratio="" aspect-ratio="1"
class="article__image rounded-lg"
cover cover
> >
<template #placeholder> <template v-slot:placeholder>
<v-row justify="center" align="center" style="height: 100px;"> <v-row>
<v-progress-circular indeterminate color="grey-lighten-1" /> <v-progress-circular
color="grey-lighten-5"
indeterminate
></v-progress-circular>
</v-row> </v-row>
</template> </template>
</v-img> </v-img>
</div> </v-col>
<v-dialog v-model="showCarousel"> </v-row>
<dialog-carousel
v-if="selectedImage"
:image="selectedImage"
:images="gallery"
@isActive="showCarousel = false"
/>
</v-dialog>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import './assets/css/main.css' import './assets/css/main.css'
import { useAPI } from "~/composables/useAPI";
const showCarousel = ref(false);
const selectedImage = ref<ArticleImage | null>(null);
function openCarousel(photo: ArticleImage) {
selectedImage.value = photo;
showCarousel.value = true;
}
interface ArticleImage {
image: string;
title: string;
}
const gallery = ref<ArticleImage[]>([]);
loadImages();
async function loadImages(){
const { error, data } = await useAPI<ArticleImage[]>('load-gallery/', {method: "GET"});
if ( data.value ){
gallery.value = data.value as ArticleImage[];
}
else if (error.value) {
console.error("Error loading gallery:", error.value);
}
}
const photos = [
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
{src: "https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"},
{src: "https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"},
{src: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"},
{src: "https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg"},
{src: "https://cdn11.bigcommerce.com/s-07991/product_images/uploaded_images/latin-dance.jpg"},
]
</script> </script>

View file

@ -11,12 +11,12 @@
</h2> </h2>
</div> </div>
</v-parallax> </v-parallax>
<lazy-news :for-all="false"/> <news/>
<lazy-about/> <about/>
<lazy-courses/> <courses/>
<lazy-trainers/> <trainers/>
<lazy-advantages/> <advantages/>
<lazy-history/> <history/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View file

@ -1,4 +1,37 @@
<template> <template>
<lazy-courses /> <h1 id="courses">Kurzy</h1>
<lazy-calendar/> <h2>Vyberte, co Vám nejlépe vyhovuje</h2>
</template>
<v-row>
<v-col
cols="12"
md="4"
v-for="(course, index) in courses"
:key="index"
>
<v-card class="pricing-box">
<h3>{{ course.name }}</h3>
<v-card-subtitle><div class="pricing__subtitle">{{ course.time }}</div></v-card-subtitle>
<v-card-title><div class="pricing__price">{{ course.price }}</div></v-card-title>
<v-card-text v-if="course.desc"><div class="pricing__subtitle">{{ course.desc }}</div></v-card-text>
<v-btn class="show_more"><v-icon icon="mdi-chevron-down"/>Kontaktujte nás<v-icon icon="mdi-chevron-down"/></v-btn>
</v-card>
</v-col>
</v-row>
<calendar/>
</template>
<script setup lang="ts">
import './assets/css/main.css'
// TODO: vyskakovací okno s kontaktním formulářem po rozkliku :)
const courses = [
{name: "Sportovní taneční klub", time: "", price: "", desc: "Připojte se k našemu tanečnímu klubu a rozvíjejte své taneční dovednosti v přátelském prostředí. Nabízíme různé styly tance pro všechny úrovně."},
{name: "Svatební tance", time: "", price: "", desc: "Udělejte svůj první tanec nezapomenutelným. Pomůžeme Vám vytvořit choreografii na míru, která bude odrážet Váš jedinečný styl."},
{name: "Příprava na plesovou sezónu", time: "", price: "", desc: "Chcete zazářit na plese? Připravíme Vás na plesovou sezónu a naučíme Vás elegantní taneční kroky a etiketu."},
{name: "Individuální taneční kurzy", time: "", price: "", desc: "Učte se tančit vlastním tempem s individuálním přístupem. Naši zkušení lektoři se zaměří na Vaše potřeby a pomohou Vám dosáhnout Vašich tanečních cílů."},
{name: "Individuální lekce", time: "", price: "", desc: "Zlepšete své taneční dovednosti s intenzivními individuálními lekcemi. Zaměřte se na konkrétní taneční techniky nebo styly, které Vás zajímají."},
{name: "Pronájem sálu", time: "", price: "", desc: "Hledáte ideální prostor pro tanec nebo jinou aktivitu? Náš taneční sál je k dispozici k pronájmu pro Vaše akce."},
]
</script>

View file

@ -49,7 +49,7 @@ export default defineNuxtPlugin((app) => {
defaults: { defaults: {
SmallIcon: { SmallIcon: {
color: 'color', color: 'color',
style: [{ 'margin-left': '20px', 'font-size': '30px'}] style: [{ 'margin-left': '20px', 'margin-top': '15px', 'font-size': '30px'}]
}, },
BigIcon: { BigIcon: {
color: 'color', color: 'color',
@ -62,10 +62,6 @@ export default defineNuxtPlugin((app) => {
cs: { cs: {
calendar: { calendar: {
today: "dnes", today: "dnes",
},
carousel: {
next: "další",
prev: "předchozí",
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB