This commit is contained in:
Nikola Kubeczkova 2025-03-12 16:27:23 +01:00
parent 04445a05a6
commit 7dffa93977
11 changed files with 169 additions and 54 deletions

View file

@ -2,9 +2,10 @@
## Running
1. `docker compose run --rm frontend npm install`
2. `docker compose build`
3. `docker compose up`
1. create .env from .env.example
2. `docker compose run --rm frontend npm install`
3. `docker compose build`
4. `docker compose up`
## Adding new frontend packages

View file

@ -21,7 +21,7 @@ from debug_toolbar.toolbar import debug_toolbar_urls
from django.conf.urls.static import static
from django.conf import settings
from tko.views import ContactView, NewArticleListView, AllArticleListView, EventListView
from tko.views import ContactView, NewArticleListView, AllArticleListView, EventListView, GalleryView
urlpatterns = [
path('admin/', admin.site.urls),
@ -29,6 +29,7 @@ urlpatterns = [
path('load-articles/', NewArticleListView.as_view(), name='load-articles'),
path('load-all-articles/', AllArticleListView.as_view(), name='load-all-articles'),
path('load-events/', EventListView.as_view(), name='load-events'),
path('load-gallery/', GalleryView.as_view(), name='load-gallery'),
] + debug_toolbar_urls()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -0,0 +1,27 @@
# 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

@ -0,0 +1,27 @@
# 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

@ -14,10 +14,8 @@ class Contact(models.Model):
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
image = models.FileField(upload_to="images/%Y", default="default.png", blank=True)
date = models.DateField(auto_now_add=True)
author = models.CharField(max_length=100)
active_to = models.DateField(null=True, blank=True) # do not show some invitation after this date
def __str__(self):
@ -26,7 +24,8 @@ class Article(models.Model):
class ArticleImage(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='images')
image = models.FileField(upload_to="images/%Y")
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}"

View file

@ -11,13 +11,20 @@ class ContactSerializer(serializers.ModelSerializer):
class ArticleImageSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField(read_only=True)
class Meta:
model = ArticleImage
fields = ['image']
fields = ['image', 'title']
@staticmethod
def get_title(obj):
return obj.article.title
class ArticleListSerializer(serializers.ModelSerializer):
date = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
images = ArticleImageSerializer(many=True, read_only=True)
class Meta:
@ -28,6 +35,11 @@ class ArticleListSerializer(serializers.ModelSerializer):
def get_date(obj):
return obj.date.strftime("%-d. %-m. %Y")
@staticmethod
def get_image(obj):
main_image = obj.images.order_by("main").first()
return main_image.image.url if main_image else None
class EventListSerializer(serializers.ModelSerializer):
start = serializers.SerializerMethodField()

View file

@ -4,8 +4,8 @@ from django.db.models import Q
from rest_framework.generics import ListAPIView, CreateAPIView
from rest_framework import permissions
from tko.models import Article, Event
from tko.serializers import ArticleListSerializer, EventListSerializer, ContactSerializer
from tko.models import Article, Event, ArticleImage
from tko.serializers import ArticleListSerializer, EventListSerializer, ContactSerializer, ArticleImageSerializer
class ContactView(CreateAPIView):
@ -33,6 +33,12 @@ class AllArticleListView(ListAPIView):
).order_by('-date')
class GalleryView(ListAPIView):
queryset = ArticleImage.objects.all().order_by("-article_id", "main")
serializer_class = ArticleImageSerializer
permission_classes = [permissions.AllowAny]
class EventListView(ListAPIView):
queryset = Event.objects.all()
serializer_class = EventListSerializer

View file

@ -118,6 +118,12 @@ h5 {
border-radius: 0.5rem;
}
.carousel__image img {
border-radius: 0.5rem;
object-fit: contain !important;
padding: 1rem;
}
.article__title {
font-family: 'Futura', sans-serif;
font-size: 1.5rem;

View file

@ -12,20 +12,26 @@
<v-row>
<v-col>
<v-img
:lazy-src="article.image.image"
:src="article.image.image"
:lazy-src="article.image"
:src="article.image"
aspect-ratio="1"
cover
class="article__image"
@click="showCarousel = true"
>
<template v-slot:placeholder>
<v-row>
<v-progress-circular
color="grey-lighten-5"
indeterminate
></v-progress-circular>
<v-row justify="center" align="center" class="fill-height">
<v-progress-circular color="grey-lighten-5" indeterminate></v-progress-circular>
</v-row>
</template>
<v-dialog v-model="showCarousel">
<dialog-carousel
:image="article.image"
:images="article.images"
:title="article.title"
@isActive="showCarousel = false"
/>
</v-dialog>
</v-img>
</v-col>
<v-col
@ -47,9 +53,10 @@
</template>
<script setup lang="ts">
import './assets/css/main.css'
import { useAPI } from "~/composables/useAPI";
const showCarousel = ref(false);
interface ArticleImage {
image: string;
}
@ -75,5 +82,4 @@ else if (error.value) {
console.error("Error loading articles:", error.value);
}
</script>

View file

@ -0,0 +1,35 @@
<template>
<v-card class="article__image">
<v-card-title>{{ title }}
<v-icon class="to_right" @click="$emit('isActive', false)">mdi-close</v-icon>
</v-card-title>
<v-carousel
:hide-delimiters="true"
height="90vh"
>
<v-carousel-item
v-for="(item, i) in images"
:key="i"
:src="item.image"
cover
class="carousel__image"
></v-carousel-item>
</v-carousel>
</v-card>
</template>
<script setup lang="ts">
import './assets/css/main.css';
interface ArticleImage {
image: string;
}
defineProps<{
image: string;
images: ArticleImage[];
title: string;
}>();
defineEmits(["isActive"])
</script>

View file

@ -2,16 +2,17 @@
<h1>Galerie</h1>
<v-row>
<v-col
v-for="(photo, index) in photos"
v-for="(photo, index) in gallery"
:key="index"
cols="4"
style="padding: 0;"
>
<v-img
:lazy-src="photo.src"
:src="photo.src"
:lazy-src="photo.image"
:src="photo.image"
aspect-ratio="1"
cover
@click="showCarousel = true"
>
<template v-slot:placeholder>
<v-row>
@ -21,43 +22,37 @@
></v-progress-circular>
</v-row>
</template>
<v-dialog v-model="showCarousel">
<dialog-carousel
:image="photo.image"
:images="gallery"
:title="photo.title"
@isActive="showCarousel = false"
/>
</v-dialog>
</v-img>
</v-col>
</v-row>
</template>
<script setup lang="ts">
import './assets/css/main.css'
import { useAPI } from "~/composables/useAPI";
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"},
]
const showCarousel = ref(false);
interface ArticleImage {
image: string;
title: string;
}
const gallery = ref<ArticleImage[]>([]);
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);
}
</script>