From f71f15717f8cac08813fceb7848434e3752d986c Mon Sep 17 00:00:00 2001 From: Sarthak5598 Date: Thu, 31 Oct 2024 01:22:09 +0530 Subject: [PATCH 1/3] Create a blog section #2803 --- blog/__init__.py | 0 blog/admin.py | 9 +++ blog/apps.py | 6 ++ blog/migrations/0001_initial.py | 42 +++++++++++ blog/migrations/0002_alter_post_slug.py | 17 +++++ blog/migrations/__init__.py | 0 blog/models.py | 18 +++++ blog/templates/blog/post_delete.html | 11 +++ blog/templates/blog/post_details.html | 92 +++++++++++++++++++++++++ blog/templates/blog/post_form.html | 79 +++++++++++++++++++++ blog/templates/blog/post_list.html | 58 ++++++++++++++++ blog/tests.py | 1 + blog/urls.py | 11 +++ blog/views.py | 69 +++++++++++++++++++ blt/settings.py | 1 + blt/urls.py | 1 + 16 files changed, 415 insertions(+) create mode 100644 blog/__init__.py create mode 100644 blog/admin.py create mode 100644 blog/apps.py create mode 100644 blog/migrations/0001_initial.py create mode 100644 blog/migrations/0002_alter_post_slug.py create mode 100644 blog/migrations/__init__.py create mode 100644 blog/models.py create mode 100644 blog/templates/blog/post_delete.html create mode 100644 blog/templates/blog/post_details.html create mode 100644 blog/templates/blog/post_form.html create mode 100644 blog/templates/blog/post_list.html create mode 100644 blog/tests.py create mode 100644 blog/urls.py create mode 100644 blog/views.py diff --git a/blog/__init__.py b/blog/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/blog/admin.py b/blog/admin.py new file mode 100644 index 000000000..ba1409f8a --- /dev/null +++ b/blog/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from .models import Post + + +@admin.register(Post) +class PostAdmin(admin.ModelAdmin): + list_display = ("title", "author", "created_at") + prepopulated_fields = {"slug": ("title",)} diff --git a/blog/apps.py b/blog/apps.py new file mode 100644 index 000000000..6be26c734 --- /dev/null +++ b/blog/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "blog" diff --git a/blog/migrations/0001_initial.py b/blog/migrations/0001_initial.py new file mode 100644 index 000000000..087cedbdb --- /dev/null +++ b/blog/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 5.0.8 on 2024-10-30 15:57 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + 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)), + ("slug", models.SlugField(unique=True)), + ("content", models.TextField()), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/blog/migrations/0002_alter_post_slug.py b/blog/migrations/0002_alter_post_slug.py new file mode 100644 index 000000000..06c135ace --- /dev/null +++ b/blog/migrations/0002_alter_post_slug.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.8 on 2024-10-30 18:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("blog", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="post", + name="slug", + field=models.SlugField(blank=True, unique=True), + ), + ] diff --git a/blog/migrations/__init__.py b/blog/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/blog/models.py b/blog/models.py new file mode 100644 index 000000000..7844761c5 --- /dev/null +++ b/blog/models.py @@ -0,0 +1,18 @@ +from django.contrib.auth.models import User +from django.db import models +from django.urls import reverse + + +class Post(models.Model): + title = models.CharField(max_length=200) + slug = models.SlugField(unique=True, blank=True) + author = models.ForeignKey(User, on_delete=models.CASCADE) + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.title + + def get_absolute_url(self): + return reverse("post_detail", kwargs={"slug": self.slug}) diff --git a/blog/templates/blog/post_delete.html b/blog/templates/blog/post_delete.html new file mode 100644 index 000000000..3c5ad76a8 --- /dev/null +++ b/blog/templates/blog/post_delete.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block content %} +

Confirm Delete

+

Are you sure you want to delete the post "{{ post.title }}"?

+
+ {% csrf_token %} + + Cancel +
+{% endblock content %} \ No newline at end of file diff --git a/blog/templates/blog/post_details.html b/blog/templates/blog/post_details.html new file mode 100644 index 000000000..e18d47c1f --- /dev/null +++ b/blog/templates/blog/post_details.html @@ -0,0 +1,92 @@ +{% extends "base.html" %} + +{% block content %} + + +{% include "includes/sidenav.html" %} +
+

{{ post.title }}

+ +
+ {{ post.content|safe }} +
+ + +
+{% endblock content %} diff --git a/blog/templates/blog/post_form.html b/blog/templates/blog/post_form.html new file mode 100644 index 000000000..a3e6e2f41 --- /dev/null +++ b/blog/templates/blog/post_form.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} + +{% block content %} + + +{% include "includes/sidenav.html" %} +

{% if form.instance.pk %}Edit{% else %}New{% endif %} Post

+ +
+
+ {% csrf_token %} +
+ {{ form.title.label }}
+ {{ form.title }}
+ {% for error in form.title.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ {{ form.content.label }}
+ {{ form.content }}
+ {% for error in form.content.errors %} +
{{ error }}
+ {% endfor %} +
+ + +
+
+ + + + +{% endblock content %} diff --git a/blog/templates/blog/post_list.html b/blog/templates/blog/post_list.html new file mode 100644 index 000000000..441abca46 --- /dev/null +++ b/blog/templates/blog/post_list.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% block content %} + + +{% include "includes/sidenav.html" %} +

Blog Posts

+ +{% endblock content %} \ No newline at end of file diff --git a/blog/tests.py b/blog/tests.py new file mode 100644 index 000000000..a39b155ac --- /dev/null +++ b/blog/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/blog/urls.py b/blog/urls.py new file mode 100644 index 000000000..8c2930edd --- /dev/null +++ b/blog/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.PostListView.as_view(), name="post_list"), + path("post/new/", views.PostCreateView.as_view(), name="post_create"), + path("post//", views.PostDetailView.as_view(), name="post_detail"), + path("post//edit/", views.PostUpdateView.as_view(), name="post_update"), + path("post//delete/", views.PostDeleteView.as_view(), name="post_delete"), +] diff --git a/blog/views.py b/blog/views.py new file mode 100644 index 000000000..a1874e233 --- /dev/null +++ b/blog/views.py @@ -0,0 +1,69 @@ +import markdown +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.utils.text import slugify +from django.views import generic + +from .models import Post + + +class PostListView(generic.ListView): + model = Post + template_name = "blog/post_list.html" + context_object_name = "posts" + paginate_by = 5 + + +class PostDetailView(generic.DetailView): + model = Post + template_name = "blog/post_details.html" + + def get_object(self): + post = super().get_object() + post.content = markdown.markdown(post.content) + return post + + +class PostCreateView(LoginRequiredMixin, generic.CreateView): + model = Post + fields = ["title", "content"] + template_name = "blog/post_form.html" + + def form_valid(self, form): + form.instance.author = self.request.user + + base_slug = slugify(form.instance.title) + slug = base_slug + counter = 1 + if slug == "new": + slug = f"{base_slug}-{counter}" + counter += 1 + while Post.objects.filter(slug=slug).exists(): + slug = f"{base_slug}-{counter}" + counter += 1 + form.instance.slug = slug + + return super().form_valid(form) + + +class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView): + model = Post + fields = ["title", "content"] + template_name = "blog/post_form.html" + + def form_valid(self, form): + form.instance.author = self.request.user + return super().form_valid(form) + + def test_func(self): + post = self.get_object() + return self.request.user == post.author + + +class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, generic.DeleteView): + model = Post + template_name = "blog/post_delete.html" + success_url = "/blogs" + + def test_func(self): + post = self.get_object() + return self.request.user == post.author diff --git a/blt/settings.py b/blt/settings.py index d38d2a3d4..6d6643466 100644 --- a/blt/settings.py +++ b/blt/settings.py @@ -101,6 +101,7 @@ "captcha", "dj_rest_auth", "dj_rest_auth.registration", + "blog", ) diff --git a/blt/urls.py b/blt/urls.py index bca61e59b..bef0afc5e 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -567,6 +567,7 @@ path("api/timelogsreport/", website.views.TimeLogListAPIView, name="timelogsreport"), path("time-logs/", website.views.TimeLogListView, name="time_logs"), path("sizzle-daily-log/", website.views.sizzle_daily_log, name="sizzle_daily_log"), + path("blogs/", include("blog.urls")), ] if settings.DEBUG: From ae9614eab389e31aef82139c97970a97f7f684af Mon Sep 17 00:00:00 2001 From: Sarthak5598 Date: Tue, 5 Nov 2024 19:45:06 +0530 Subject: [PATCH 2/3] added image feature and resolved old pre-commit issues --- .env.example | 2 +- blog/admin.py | 2 +- blog/migrations/0003_post_image.py | 19 ++++++ blog/models.py | 1 + blog/templates/blog/post_delete.html | 64 +++++++++++++++++-- blog/templates/blog/post_details.html | 30 +++++++-- blog/templates/blog/post_form.html | 10 ++- blog/templates/blog/post_list.html | 33 ++++++++-- blog/urls.py | 4 +- blog/views.py | 4 +- blt/urls.py | 6 +- website/templates/sizzle/sizzle.html | 3 +- .../templates/sizzle/user_sizzle_report.html | 12 ++-- website/views.py | 4 +- 14 files changed, 162 insertions(+), 32 deletions(-) create mode 100644 blog/migrations/0003_post_image.py diff --git a/.env.example b/.env.example index 8c1b9eeb6..994ea9a16 100644 --- a/.env.example +++ b/.env.example @@ -21,4 +21,4 @@ LANGCHAIN_ENDPOINT="https://api.smith.langchain.com" DATABASE_URL=postgres://user:password@localhost:5432/dbname #Sentry DSN -SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 \ No newline at end of file +SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 diff --git a/blog/admin.py b/blog/admin.py index ba1409f8a..e40e01a79 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -5,5 +5,5 @@ @admin.register(Post) class PostAdmin(admin.ModelAdmin): - list_display = ("title", "author", "created_at") + list_display = ("title", "author", "created_at", "image") prepopulated_fields = {"slug": ("title",)} diff --git a/blog/migrations/0003_post_image.py b/blog/migrations/0003_post_image.py new file mode 100644 index 000000000..ec56eb214 --- /dev/null +++ b/blog/migrations/0003_post_image.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.8 on 2024-10-31 08:48 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("blog", "0002_alter_post_slug"), + ] + + operations = [ + migrations.AddField( + model_name="post", + name="image", + field=models.ImageField(default=django.utils.timezone.now, upload_to=""), + preserve_default=False, + ), + ] diff --git a/blog/models.py b/blog/models.py index 7844761c5..ef84ae7fe 100644 --- a/blog/models.py +++ b/blog/models.py @@ -10,6 +10,7 @@ class Post(models.Model): content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + image = models.ImageField() def __str__(self): return self.title diff --git a/blog/templates/blog/post_delete.html b/blog/templates/blog/post_delete.html index 3c5ad76a8..d7b9aed4b 100644 --- a/blog/templates/blog/post_delete.html +++ b/blog/templates/blog/post_delete.html @@ -1,11 +1,65 @@ {% extends "base.html" %} {% block content %} + + +{% include "includes/sidenav.html" %}

Confirm Delete

-

Are you sure you want to delete the post "{{ post.title }}"?

-
+

Are you sure you want to delete the post "{{ post.title }}"?

+ {% csrf_token %} - - Cancel + + Cancel
-{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/blog/templates/blog/post_details.html b/blog/templates/blog/post_details.html index e18d47c1f..4bcc752a5 100644 --- a/blog/templates/blog/post_details.html +++ b/blog/templates/blog/post_details.html @@ -37,7 +37,7 @@ color: #444; } - .btn-update { + .b-update { display: inline-block; padding: 10px 20px; margin: 5px; @@ -50,11 +50,11 @@ text-align: center; } - .btn-update:hover { + .b-update:hover { background-color: #0056b3; } - .btn-delete { + .b-delete { display: inline-block; padding: 10px 20px; margin: 5px; @@ -67,13 +67,22 @@ text-align: center; } - .btn-delete:hover { + .b-delete:hover { background-color: #c82333; } - .btn-div{ + + .btn-div { text-align: center; margin-top: 20px; } + + .post-image img { + width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; + } {% include "includes/sidenav.html" %} @@ -83,10 +92,17 @@

{{ post.title }}

{{ post.content|safe }}
+ {% if post.image %} +
+ {{ post.title }} +
+ {% endif %} + {% if request.user == post.author %} + {% endif %} {% endblock content %} diff --git a/blog/templates/blog/post_form.html b/blog/templates/blog/post_form.html index a3e6e2f41..2dc089085 100644 --- a/blog/templates/blog/post_form.html +++ b/blog/templates/blog/post_form.html @@ -49,7 +49,7 @@

{% if form.instance.pk %}Edit{% else %}New{% endif %} Post

-
+ {% csrf_token %}
{{ form.title.label }}
@@ -67,6 +67,14 @@

{% if form.instance.pk %}Edit{% else %}New{% endif %} Post +
+ {{ form.image.label }}
+ {{ form.image }}
+ {% for error in form.image.errors %} +
{{ error }}
+ {% endfor %} +
+

diff --git a/blog/templates/blog/post_list.html b/blog/templates/blog/post_list.html index 441abca46..86081a1d3 100644 --- a/blog/templates/blog/post_list.html +++ b/blog/templates/blog/post_list.html @@ -13,19 +13,30 @@ font-size: 2.5em; color: #333; text-align: center; + margin-bottom: 20px; } .post-list { - list-style-type: none; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 15px; padding: 0; + list-style-type: none; + justify-items: center; } .post-item { - margin: 15px 0; padding: 10px; border: 1px solid #ccc; border-radius: 5px; background-color: #ffffff; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + text-align: center; + transition: transform 0.2s ease; + } + + .post-item:hover { + transform: scale(1.05); } .post-link { @@ -42,15 +53,27 @@ text-align: center; color: #666; } + + .blog_image { + width: 200px; + height: 200px; + border-radius: 5px; + margin-bottom: 10px; + } {% include "includes/sidenav.html" %}

Blog Posts

    {% for post in posts %} -
  • - {{ post.title }} by {{ post.author }} -
  • + +
    +

    {{ post.title }} by {{ post.author }}

    + {% if post.image %} + {{ post.title }} + {% endif %} +
    +
    {% empty %}

    No posts available.

    {% endfor %} diff --git a/blog/urls.py b/blog/urls.py index 8c2930edd..cba57bac1 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -1,3 +1,5 @@ +from django.conf import settings +from django.conf.urls.static import static from django.urls import path from . import views @@ -8,4 +10,4 @@ path("post//", views.PostDetailView.as_view(), name="post_detail"), path("post//edit/", views.PostUpdateView.as_view(), name="post_update"), path("post//delete/", views.PostDeleteView.as_view(), name="post_delete"), -] +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/blog/views.py b/blog/views.py index a1874e233..292640adb 100644 --- a/blog/views.py +++ b/blog/views.py @@ -25,7 +25,7 @@ def get_object(self): class PostCreateView(LoginRequiredMixin, generic.CreateView): model = Post - fields = ["title", "content"] + fields = ["title", "content", "image"] template_name = "blog/post_form.html" def form_valid(self, form): @@ -47,7 +47,7 @@ def form_valid(self, form): class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView): model = Post - fields = ["title", "content"] + fields = ["title", "content", "image"] template_name = "blog/post_form.html" def form_valid(self, form): diff --git a/blt/urls.py b/blt/urls.py index 54dfd510f..2d1b05699 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -568,7 +568,11 @@ path("time-logs/", website.views.TimeLogListView, name="time_logs"), path("sizzle-daily-log/", website.views.sizzle_daily_log, name="sizzle_daily_log"), path("blogs/", include("blog.urls")), - path("user-sizzle-report//", website.views.user_sizzle_report, name="user_sizzle_report"), + path( + "user-sizzle-report//", + website.views.user_sizzle_report, + name="user_sizzle_report", + ), ] if settings.DEBUG: diff --git a/website/templates/sizzle/sizzle.html b/website/templates/sizzle/sizzle.html index 3ce7f4115..9defe586b 100644 --- a/website/templates/sizzle/sizzle.html +++ b/website/templates/sizzle/sizzle.html @@ -120,7 +120,8 @@

    Your Sizzle Report

    {% for user in leaderboard %} - {{ user.username }} + {{ user.username }} {{ user.formatted_duration }} diff --git a/website/templates/sizzle/user_sizzle_report.html b/website/templates/sizzle/user_sizzle_report.html index 6f796c8ea..13102a3a8 100644 --- a/website/templates/sizzle/user_sizzle_report.html +++ b/website/templates/sizzle/user_sizzle_report.html @@ -14,12 +14,12 @@

    {{ user.username }}'s Sizzle Report

    {% for log in response_data %} - - {{ log.date }} - {{ log.issue_title }} - {{ log.duration }} - {{ log.start_time }} - + + {{ log.date }} + {{ log.issue_title }} + {{ log.duration }} + {{ log.start_time }} + {% endfor %} diff --git a/website/views.py b/website/views.py index f4e27bd73..ab8256156 100644 --- a/website/views.py +++ b/website/views.py @@ -2622,4 +2622,6 @@ def user_sizzle_report(request, username): response_data.append(day_data) - return render(request, "sizzle/user_sizzle_report.html", {"response_data": response_data, "user": user}) + return render( + request, "sizzle/user_sizzle_report.html", {"response_data": response_data, "user": user} + ) From 85416b2a466ded83fbe9ca6dd9e9b1c143d577ff Mon Sep 17 00:00:00 2001 From: Sarthak5598 Date: Tue, 5 Nov 2024 21:30:39 +0530 Subject: [PATCH 3/3] updated url pattern --- blog/urls.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blog/urls.py b/blog/urls.py index cba57bac1..5f59c4974 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -6,8 +6,8 @@ urlpatterns = [ path("", views.PostListView.as_view(), name="post_list"), - path("post/new/", views.PostCreateView.as_view(), name="post_create"), - path("post//", views.PostDetailView.as_view(), name="post_detail"), - path("post//edit/", views.PostUpdateView.as_view(), name="post_update"), - path("post//delete/", views.PostDeleteView.as_view(), name="post_delete"), + path("new/", views.PostCreateView.as_view(), name="post_create"), + path("/", views.PostDetailView.as_view(), name="post_detail"), + path("/edit/", views.PostUpdateView.as_view(), name="post_update"), + path("/delete/", views.PostDeleteView.as_view(), name="post_delete"), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)