Jakiś czas temu, narodził się u mnie pomysł na technologicznego bloga. Ze względu na chęć użycia w nim dynamicznego ładowania treści, które następnie były by prezentowane w modalu, przystąpiłem do pracy :)
1. Backend
W poniższym przykładzie, wykorzystamy bibliotekę Django REST framework, którą instalujemy po przez:
pip install djangorestframework django-admin-sortable2
Swoją przygodę rozpocząłem od stworzenia modelu postu oraz kategorii zrzeszającej posty.
plik: blog/models.py
from datetime import datetime
from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext_lazy as _
class Category(models.Model):
name = models.CharField(_('Name'), max_length=120)
iconTag = models.CharField(_('Html icon tag'), max_length=120)
class Meta:
verbose_name = _('Category')
verbose_name_plural = _('Category')
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Autor'), null=True, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name=_('Category'))
title = models.CharField(_('Title'), max_length=120)
description = models.TextField(_('Description'), )
createdDate = models.DateTimeField(_('Created date'), default=datetime.now, blank=True)
visible_choice = [
('P', 'Public'),
('N', 'Hidden'),
]
visible = models.CharField(
_('Visible'),
max_length=1,
choices=visible_choice,
default="P",
)
class Meta:
verbose_name = _('Post')
verbose_name_plural = _('Posts')
def __str__(self):
return self.title
Następnie dodałem serializer dla konkretnego Postu, celem umożliwienia przesyłania go dalej w sposób ustruktyzowany.
plik: blog/serializers.py
from rest_framework import serializers
from .models import Post, Category
class PostSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.name', read_only=True)
class Meta:
model = Post
fields = ['id', 'category_name', 'title', 'description', 'createdDate']
*Samo dodanie 6 linijki, wiązało się z wykorzystaniem klucza ForeignKey,który dzięki temu zwraca nam tytuł kategorii, zamiast tylko jej id.
Idąc dalej utworzyłem standardowy plik z adresami, który zwróci nam stronę główną, jak i szczegóły danego postu przez api.
plik: blog/urls.py
from django.urls import path
from . import views, apiviews
urlpatterns = [
path('', views.PostListView.as_view(), name='blog_all'),
path('api/posts/list/<id>', apiviews.PostDetailAPI.as_view(), name='api_posts_detail')
]
Tutaj standardowo, skorzystałem z generycznego widoku ListView, który zwróci nam listę postów na stronie głównej. Z racji że to blog, zastrzegamy w kodzie że chcemy wyświetlić posty które zostały opublikowane.
plik: blog/views.py
from django.views.generic.list import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/index.html'
paginate_by = 10
ordering = ['-my_order']
queryset = Post.objects.filter(visible='P')
Można tu też zauważyć sortowanie po my_order, kolumnie w której skorzystałem z możliwości tworzenia własnego porządku dla danych obiektów (więcej pod adresem Dokumentacja django-admin-sortable2)
Dodatkowo stworzyłem api, które zwróci nam listę postów jak i szczegóły danego modelu, bazując na otrzymanym w adresie strony parametrze id.
plik: blog/apiviews.py
from rest_framework import generics
from .models import Post
from .serializers import PostSerializer
class PostListAPI(generics.ListAPIView):
model = Post
serializer_class = PostSerializer
queryset = Post.objects.all()
class PostDetailAPI(generics.RetrieveAPIView):
lookup_field = 'id'
queryset = Post.objects.all()
serializer_class = PostSerializer
2. Frontend
Utworzyłem szablon renderujący z pliku views.py, listę postów oraz poniżej szkielet modalu, na którym następnie wyświetlimy posty.
plik: blog/templates/blog/index.html
{% extends 'base.html' %}
{% block content %}
<section id="hero" class="d-flex align-items-center">
<div class="container">
<div class="row icon-boxes" id="blog">
{% for article in object_list %}
<div class="col-md-6 col-lg-3 d-flex align-items-stretch mb-4" data-aos="zoom-in"
data-aos-delay="200">
<a href="#post-{{ article.id }}" class="open-post-dialog icon-box" data-toggle="modal"
data-id="{{ article.id }}">
<div class="icon">{{ article.category.iconTag | safe }}</div>
<h4 class="title">{{ article.category.name }}</h4>
<p class="description">{{ article.title }}</p>
</a>
</div>
{% empty %}
<p class="mb-2">No posts</p>
{% endfor %}
</div>
</div>
</section>
{% block endblock %}
{% block modal %}
<div class="modal fade" id="showPostDialog" tabindex="-1" aria-labelledby="postModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="category"></h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="postModalBody">
<blockquote class="blockquote">
<h4 id="title" class="mb-3"></h4>
<footer class="blockquote-footer">autor: Kamil Mirończuk</footer>
</blockquote>
<div id="content"></div>
<div class="row">
<div class="col-6">
<a href="" id="post-url" class="btn btn-primary w-100">Przejdź</a>
</div>
<div class="col-6">
<button type="button" class="btn btn-secondary w-100" data-bs-dismiss="modal">Zamknij</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Całą obsługę formularza, wykonałem z poziomu javascriptu, wykorzystując do tego funkcję fetch.
plik: blog/static/assets/js/modal.js
$(document).on("click", ".open-post-dialog", function () {
let postId = $(this).data('id');
let url = "/api/posts/list/" + postId + "?format=json";
fetch(url)
.then(res => res.json())
.then(data => {
$(".modal-content #category").html(data.category_name);
$(".modal-content #title").html(data.title);
$(".modal-content #content").html(data.description);
$( "#hero" ).addClass( "is-blurred" );
$( "#header" ).addClass( "is-blurred" );
$('#showPostDialog').modal('show');
}
).catch(err => {
throw err
}
);
$('#showPostDialog').on('hidden.bs.modal', function () {
$( "#header" ).removeClass( "is-blurred" );
$( "#hero" ).removeClass( "is-blurred" );
})
});
*Należy również pamiętać o dodaniu biblioteki jquery do strony.
W celu polepszenia odczuć podczas czytania artykułu, podczas wyświetlania modalu, zastosowałem technikę blurowania tła po przez dodanie wpisu css.
plik: static/assets/css/style.css
.is-blurred {
filter: blur(5px);
-webkit-filter: blur(5px);
}
Ogarnięcie całego projektu nie powinno przysporzyć Ci trudności. Celowo nie rozdrabniałem się nad każdą linijką, celem zmotywowania do stworzenia własnego oryginalnego bloga na podstawie powyższego kodu:)
Powodzenia!
Komentarze