First commit

This commit is contained in:
Andros Fenollosa
2022-05-15 20:58:36 +02:00
commit 96b4a45f1d
167 changed files with 31526 additions and 0 deletions

0
app/__init__.py Normal file
View File

0
app/website/__init__.py Normal file
View File

127
app/website/actions.py Normal file
View File

@ -0,0 +1,127 @@
from .models import Post, Comment
from .forms import SearchForm, CommentForm
from asgiref.sync import async_to_sync
from django.template.loader import render_to_string
from django.urls import reverse
POST_PER_PAGE = 5
def send_page(self, data={}):
"""Render HTML and send page to client"""
# Prepare context data for page
page = data["page"]
context = {}
data_reverse = {}
match page:
case "all posts":
context = {
"posts": Post.objects.all()[:POST_PER_PAGE],
"form": SearchForm(),
"next_page": 2,
"is_last_page": (Post.objects.count() // POST_PER_PAGE) == 2,
}
case "single post":
post = Post.objects.get(id=data["id"])
context = {
"post": post,
"form": CommentForm(),
"comments": Comment.objects.filter(post=post),
}
data_reverse = {"slug": post.slug}
# Render HTML nav and send to client
context.update({"active_nav": page})
self.send_html(
{
"selector": "#nav",
"html": render_to_string("components/_nav.html", context),
}
)
# Render HTML page and send to client
template_page = page.replace(" ", "_")
self.send_html(
{
"selector": "#main",
"html": render_to_string(f"pages/{template_page}.html", context),
"url": reverse(page, kwargs=data_reverse),
}
)
def search(self, data={}):
"""Search for posts"""
# Prepare context data for page
context = {
"posts": Post.objects.filter(title__icontains=data["search"])[:POST_PER_PAGE]
}
# Render HTML page and send to client
self.send_html(
{
"selector": "#all-posts",
"html": render_to_string("components/all_posts/list.html", context),
}
)
def add_next_posts(self, data={}):
"""Add next posts from pagination"""
# Prepare context data for page
page = int(data["page"]) if "page" in data else 1
start_of_slice = (page - 1) * POST_PER_PAGE
end_of_slice = start_of_slice + POST_PER_PAGE
context = {
"posts": Post.objects.all()[start_of_slice:end_of_slice],
"next_page": page + 1,
"is_last_page": (Post.objects.count() // POST_PER_PAGE) == page,
}
# Add and render HTML with new posts
self.send_html(
{
"selector": "#all-posts",
"html": render_to_string("components/all_posts/list.html", context),
"append": True,
}
)
# Update paginator
self.send_html(
{
"selector": "#paginator",
"html": render_to_string(
"components/all_posts/_button_paginator.html", context
),
}
)
def add_comment(self, data):
"""Add new comment to database"""
# Add post
data_with_post = data.copy()
post = Post.objects.get(id=data["post_id"])
data_with_post["post"] = post
# Set initial values by CommentForm
form = CommentForm(data_with_post)
# Check if form is valid
if form.is_valid():
# Save comment
form.save()
# Render HTML with new comment to all clients
async_to_sync(self.channel_layer.group_send)(
self.room_name,
{
"type": "send.html", # Run "send_html()" method
"selector": "#comments",
"html": render_to_string(
"components/_single_comment.html", {"comment": data}
),
"append": True,
"broadcast": True,
"url": reverse("single post", kwargs={"slug": post.slug}),
},
)

6
app/website/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class SimpleAppConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "app.website"

50
app/website/consumers.py Normal file
View File

@ -0,0 +1,50 @@
# app/website/consumers.py
from channels.generic.websocket import JsonWebsocketConsumer
from asgiref.sync import async_to_sync
import app.website.actions as actions
class BlogConsumer(JsonWebsocketConsumer):
room_name = "broadcast"
def connect(self):
"""Event when client connects"""
# Accept the connection
self.accept()
# Assign the Broadcast group
async_to_sync(self.channel_layer.group_add)(self.room_name, self.channel_name)
def disconnect(self, close_code):
"""Event when client disconnects"""
pass
def receive_json(self, data_received):
"""
Event when data is received
All information will arrive in 2 variables:
"action", with the action to be taken
"data" with the information
"""
# Get the data
data = data_received["data"]
# Depending on the action we will do one task or another.
match data_received["action"]:
case "Change page":
actions.send_page(self, data)
case "Search":
actions.search(self, data)
case "Add next posts":
actions.add_next_posts(self, data)
case "Add comment":
actions.add_comment(self, data)
def send_html(self, event):
"""Event: Send html to client"""
data = {
"selector": event["selector"],
"html": event["html"],
"append": "append" in event and event["append"],
"broadcast": event["broadcast"] if "broadcast" in event else False,
"url": event["url"] if "url" in event else "",
}
self.send_json(data)

21
app/website/feed.py Normal file
View File

@ -0,0 +1,21 @@
from django.contrib.syndication.views import Feed
from django.urls import reverse
from .models import Post
class LatestEntriesFeed(Feed):
title = "My blog"
link = "/feed/"
description = "Updates to posts."
def items(self):
return Post.objects.all()[:5]
def item_title(self, item):
return item.title
def item_description(self, item):
return item.summary
def item_link(self, item):
return reverse("single post", kwargs={"slug": item.slug})

44
app/website/forms.py Normal file
View File

@ -0,0 +1,44 @@
from django import forms
from .models import Comment
class SearchForm(forms.Form):
search = forms.CharField(
label="Search",
max_length=255,
required=False,
widget=forms.TextInput(
attrs={
"id": "search",
"class": "input",
"placeholder": "Title...",
}
),
)
class CommentForm(forms.ModelForm):
author = forms.CharField(
widget=forms.TextInput(
attrs={
"id": "author",
"class": "input",
"placeholder": "Your name...",
}
),
)
content = forms.CharField(
widget=forms.Textarea(
attrs={
"id": "content",
"class": "input",
"placeholder": "Your comment...",
}
),
)
class Meta:
model = Comment
fields = ("author", "content", "post")

View File

@ -0,0 +1,35 @@
# Generated by Django 4.0 on 2022-04-29 06:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200, unique=True)),
('author', models.CharField(max_length=20)),
('content', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20)),
('content', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='website.post')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0 on 2022-04-29 06:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('website', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='comment',
old_name='name',
new_name='author',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.0 on 2022-05-15 15:58
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('website', '0002_rename_name_comment_author'),
]
operations = [
migrations.AlterModelOptions(
name='post',
options={'ordering': ['-created_at']},
),
]

View File

37
app/website/models.py Normal file
View File

@ -0,0 +1,37 @@
from django.db import models
from django.utils.text import slugify
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
author = models.CharField(max_length=20)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
@property
def slug(self):
return slugify(self.title)
@property
def summary(self):
return self.content[:100] + "..."
def get_absolute_url(self):
return reverse("single post", kwargs={"slug": self.slug})
def __str__(self):
return self.title
class Comment(models.Model):
author = models.CharField(max_length=20)
content = models.TextField()
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name

View File

@ -0,0 +1,23 @@
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Example website</title>
<link rel="stylesheet" href="{% static 'css/main.css' %}">
<script defer src="{% static 'js/index.js' %}"></script>
</head>
<body
data-host="{{ request.get_host }}"
data-scheme="{{ request.scheme }}"
>
<div class="container">
<header>
<nav id="nav" class="nav">{% include 'components/_nav.html' %}</nav>
</header>
<main id="main">{% include page %}</main>
<footer class="footer">My footer</footer>
</div>
</body>
</html>

View File

@ -0,0 +1,3 @@
{% for comment in comments %}
{% include "components/_single_comment.html" with comment=comment %}
{% endfor %}

View File

@ -0,0 +1,20 @@
<ul class="nav__ul">
<li>
<a
href="#"
class="nav__link nav__link--page{% if active_nav == "all posts" %} active{% endif %}"
data-target="all posts"
>
All posts
</a>
</li>
<li>
<a
href="#"
class="nav__link nav__link--page{% if active_nav == "about us" %} active{% endif %}"
data-target="about us"
>
About us
</a>
</li>
</ul>

View File

@ -0,0 +1,5 @@
<article>
<h2>{{ comment.author }}</h2>
<p>{{ comment.content }}</p>
<p>{{ comment.created_at }}</p>
</article>

View File

@ -0,0 +1,3 @@
{% if not is_last_page %}
<button class="button" id="paginator" data-next-page="{{ next_page }}">More posts</button>
{% endif %}

View File

@ -0,0 +1,4 @@
<form id="search-form" action="">
{{ form.search }}
<input class="button" type="submit" value="Search">
</form>

View File

@ -0,0 +1,14 @@
{% for post in posts %}
<article>
<header>
<h2>{{ post.title }}</h2>
</header>
<p>{{ post.summary }}</p>
<p>{{ post.author }}</p>
<footer>
<p>
<a class="post-item__link" href="#" data-page="single post" data-id="{{ post.id }}">Read more</a>
</p>
</footer>
</article>
{% endfor %}

View File

@ -0,0 +1 @@
<h1>404</h1>

View File

@ -0,0 +1,34 @@
<h1>About us</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad animi aut beatae commodi consectetur cumque ipsam iste
labore laudantium magni molestiae nobis nulla quod quos tempore totam velit, voluptas voluptates!</p>
<p>Cumque distinctio dolor dolorem doloremque labore laborum libero magnam maiores maxime nam numquam obcaecati pariatur perferendis provident quae quia quis, quod, recusandae repudiandae rerum sed similique sunt vel voluptates voluptatum.</p>
<p>Consequatur, consequuntur cum dignissimos distinctio doloremque ducimus odit temporibus veniam. Assumenda cumque, deserunt dicta ea eaque enim eveniet, incidunt inventore laboriosam magnam mollitia necessitatibus nobis nulla numquam odit vero voluptate!</p>
<p>Accusantium aliquam architecto debitis deleniti dicta, eius enim exercitationem expedita facilis nulla numquam odio quam quis quo temporibus veritatis voluptas voluptate! Culpa cumque deserunt dolore id impedit itaque, maxime necessitatibus.</p>
<p>Aspernatur dignissimos dolor enim error esse facere fugit, ipsa iure mollitia nemo optio perspiciatis placeat quae quaerat quisquam recusandae reiciendis reprehenderit sequi sint sit tempora vel veritatis vero voluptatibus voluptatum.</p>
<p>Ab atque delectus deserunt dolorem dolores ducimus earum, esse eveniet exercitationem facilis illo in ipsam maxime nemo nesciunt nobis nulla quia quod quos rem sapiente soluta totam ullam unde vel?</p>
<p>Aut autem dicta dolorem doloremque dolores eos eum expedita facere facilis illum, inventore laboriosam laudantium magnam mollitia nam non nostrum odit possimus quas, quibusdam quo repudiandae soluta totam velit voluptas.</p>
<p>Eaque eos ipsum libero quod recusandae rerum saepe sunt veniam. Accusamus amet, aperiam aspernatur id odit quas quia tempora voluptatum. Autem doloribus ducimus ea esse fugit inventore, nihil quisquam ullam?</p>
<p>Atque ducimus ea itaque odio quis quos recusandae ullam! Assumenda culpa, deserunt doloribus ipsa neque quo rerum temporibus veritatis voluptates. Alias aperiam dolorem impedit inventore maiores porro repellendus tempora vitae!</p>
<p>Accusamus aliquam amet assumenda at dolorem doloribus ea eaque, earum illo illum in incidunt maiores nesciunt nulla odio quidem, ratione reiciendis sapiente sequi similique sint sunt velit veniam voluptatem voluptatibus?</p>
<p>Debitis dolore fugiat ipsum mollitia odit officia provident, quisquam reprehenderit sed voluptates. Aut dicta ea est iure iusto quos tenetur totam! Aliquam culpa facilis ipsa modi, omnis quidem saepe similique.</p>
<p>A animi aspernatur autem debitis dicta, dolores ea earum enim eos est fugit illo incidunt iste iure nam nostrum quisquam repellat! Doloremque, iure laboriosam. Beatae esse id iste nemo quod.</p>
<p>Ab accusamus aut blanditiis consectetur et harum hic id ipsum labore nemo nihil nostrum nulla numquam porro quo quod rem repudiandae sapiente sed sit tempora, veniam vero, vitae voluptas voluptatum?</p>
<p>Accusantium aliquam aliquid amet, asperiores aspernatur assumenda commodi dicta dolor ea eius esse inventore ipsam itaque natus nesciunt nobis nostrum obcaecati, odit optio recusandae rem sit soluta tempore unde velit.</p>
<p>Ab accusamus accusantium aut cupiditate delectus deleniti et eveniet excepturi illo incidunt inventore ipsum labore quis similique ut, vel vitae? Beatae eum explicabo itaque iusto mollitia sed soluta velit voluptatibus?</p>
<p>Beatae blanditiis consequatur debitis dicta distinctio dolor ducimus ea earum enim iste laborum magni maxime, odio, optio, quia quisquam quo suscipit tempora tempore vel. Labore minima similique unde. Ex, sint.</p>
<p>Accusamus aspernatur assumenda cum distinctio ea earum fuga laboriosam necessitatibus odit pariatur quaerat recusandae, sint sit temporibus, tenetur veniam voluptas. At dolores magni repudiandae? Facilis illo magni pariatur rem repellendus.
</p>
<p>Accusantium animi autem dicta, ducimus eaque eum expedita fugiat fugit harum modi nam neque nesciunt nisi nulla odio, pariatur provident quidem sequi, sunt voluptatem. Animi consequuntur dolor impedit odio sequi?</p>
<p>Asperiores corporis cum deleniti dolor dolorem est ex facere illo iusto maxime modi nisi repellendus rerum saepe, sequi totam ut voluptates! Distinctio excepturi, iste nesciunt odit perspiciatis porro quas quasi.</p>
<p>A asperiores, cupiditate deserunt dolor doloribus ipsum minima mollitia nemo quis reiciendis! Illum itaque modi molestiae nisi numquam officia ratione, voluptatem. Corporis minima modi numquam odio rem sunt veniam, voluptas?</p>
<p>Ad dolorum hic molestias odit officia placeat quas quibusdam, reiciendis voluptatibus. Explicabo, illum inventore molestiae odit recusandae repellat repellendus! Accusamus amet at dolore id, mollitia nihil optio sequi tempora unde.</p>
<p> Eveniet excepturi illum nemo non sunt. Accusantium, consequatur, dolorem facere incidunt labore laboriosam neque, non omnis provident quod quos sed velit? Ab animi corporis ex exercitationem ipsam nam quae tenetur.</p>
<p> Asperiores cumque ex fuga fugit similique, ut voluptate. Aliquid aspernatur at aut culpa explicabo fugit necessitatibus nemo nihil, non! Adipisci animi illum ipsa laboriosam nihil nobis reiciendis, repellendus unde ut.</p>
<p>Aperiam assumenda aut beatae, cumque delectus dolores eius, facere laboriosam laudantium libero nam odio optio repellat suscipit veniam! A adipisci amet autem earum perspiciatis quae reprehenderit soluta unde vel voluptates?</p>
<p>Accusamus adipisci alias asperiores commodi consectetur consequatur consequuntur dignissimos dolore earum et eum, ex hic id inventore laboriosam nesciunt officia officiis optio pariatur quisquam recusandae repudiandae saepe sequi totam ullam.</p>
<p>Ab accusamus adipisci architecto asperiores blanditiis deleniti, dignissimos earum ex explicabo facilis fuga inventore iste iure laborum magnam obcaecati officia officiis perspiciatis provident quas repellat ullam voluptas voluptatem. Atque, pariatur.</p>
<p>Animi at, cupiditate debitis dolores eaque excepturi illo impedit inventore ipsa libero magni minima natus nemo numquam officia possimus quam quis reiciendis sapiente sequi sunt suscipit velit veniam vitae voluptatem.</p>
<p>Ab dolorum esse ipsam officia possimus repellat? Aliquid impedit nisi quae quas repellendus veniam. Molestiae quisquam, sapiente? Nesciunt quae reprehenderit similique soluta voluptatibus voluptatum! Adipisci dolor est iusto necessitatibus veniam?</p>
<p> Commodi dolorum laudantium nemo omnis provident sunt ut! Distinctio, doloremque vitae? Alias aliquam deserunt dignissimos eaque et facere hic ipsam modi neque nisi numquam provident quas quisquam reiciendis sed, vitae.</p>
<p>A ad blanditiis corporis eius, est facere in ipsa laudantium libero necessitatibus nisi rerum suscipit veniam! Doloremque, facere, possimus? Excepturi in minima modi nobis, non repudiandae sapiente unde. Aspernatur, molestiae.
</p>

View File

@ -0,0 +1,20 @@
<h1>All posts</h1>
<hr>
{# Search #}
<section id="form-search">
{% include "components/all_posts/form_search.html" %}
</section>
{# End search #}
<hr>
<section>
{# List posts #}
<div id="all-posts">
{% include "components/all_posts/list.html" %}
</div>
{# End list posts #}
{# Paginator #}
<div id="paginator">
{% include "components/all_posts/_button_paginator.html" %}
</div>
{# End paginator #}
</section>

View File

@ -0,0 +1,27 @@
<section>
{# Post #}
<article>
<header>
<h1>{{ post.title }}</h1>
</header>
<div>{{ post.content }}</div>
<footer>
<p>{{ post.author }}</p>
</footer>
</article>
{# End post #}
{# Comments #}
<div id="comments">
<h2>Comments</h2>
<form id="comment-form" action="" data-post-id="{{ post.id }}">
{{ form.author }}
{{ form.content }}
<input class="button" type="submit" value="Add">
</form>
<div id="list-of-comments">
{% include "components/_list_of_comments.html" %}
</div>
</div>
{# End comments #}
</section>

46
app/website/views.py Normal file
View File

@ -0,0 +1,46 @@
from django.shortcuts import render
from .forms import SearchForm, CommentForm
from .models import Post, Comment
from .actions import POST_PER_PAGE
def all_posts(request):
return render(
request,
"base.html",
{
"posts": Post.objects.all()[:5],
"page": "pages/all_posts.html",
"active_nav": "all posts",
"form": SearchForm(),
"next_page": 2,
"is_last_page": (Post.objects.count() // POST_PER_PAGE) == 2,
},
)
def single_post(request, slug):
post = list(filter(lambda post: post.slug == slug, Post.objects.all()))[0]
return render(
request,
"base.html",
{
"post": post,
"page": "pages/single_post.html",
"active_nav": "single post",
"comments": Comment.objects.filter(post=post),
"form": CommentForm(),
},
)
def about(request):
return render(
request,
"base.html",
{"page": "pages/about_us.html", "active_nav": "about us"},
)
def page_not_found(request, exception):
return render(request, "base.html", {"page": "pages/404.html"})