Skip to content

Commit

Permalink
gcp & Adding styling functionality (#429)
Browse files Browse the repository at this point in the history
  • Loading branch information
eduzen authored Sep 11, 2023
1 parent a128b12 commit 7a64e27
Show file tree
Hide file tree
Showing 20 changed files with 165 additions and 98 deletions.
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ REDIS_URL=redis://redis:6379/0
SECRET_KEY='django-insecure-)b*6^n!osj#+4-*5aag6d106&@haowpc9_c0**nvw-sg#e-c9h'
LOG_LEVEL=INFO
SENTRY_DSN=
GOOGLE_APPLICATION_CREDENTIALS=/root/keys/eduzen.json
GS_BUCKET_NAME=
GS_PROJECT_ID=
OPENAI_API_KEY=
OPENAI_ORGANIZATION=
2 changes: 1 addition & 1 deletion .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

steps:
- name: Checkout code # checking our the code at current commit that triggers the workflow
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.7.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

- name: Build and push
id: docker_build
uses: docker/build-push-action@v4.1.1
uses: docker/build-push-action@v4.2.1
with:
push: true
tags: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,4 @@ docker-compose.override.yml
zappa_settings.json
scripts/db/*
data/*
secrets
46 changes: 33 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
# My Django Blog: [eduzen.ar](http://eduzen.ar)

Welcome to my personal Django blog powered by htmx, a modern approach to full-stack development. This project is a blend of several technologies that aim to create a seamless blogging experience.

![Python application](https://github.com/eduzen/website/workflows/Python%20application/badge.svg)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)

## Features

# My django blog Repo: http://eduzen.com.ar

## Configuration:
- **Django Backend**: The power of Django for the backend ensures stability and scalability.
- **htmx**: Integrating htmx for a seamless full-stack experience without writing JavaScript.
- **ChatGPT API**: Using the ChatGPT API for dynamic content creation and engagement.
- **GitHub Actions**: Continuous integration ensuring code quality and automatic deployments.
- **Dockerized**: The entire setup is containerized using Docker for consistent development and deployment.
- **Static Typing with Mypy**: Bringing the power of static typing to Python.
- **pip-tools**: Ensuring dependencies are managed in a reliable way.
- **Pre-commit**: Automated checks before commits to ensure code quality.

1. Copy and fill with your credentials
## Getting Started

```bash
### Prerequisites

cp .env.sample .env
Ensure you have Docker and Docker Compose installed on your system.

```
### Configuration

2. Run
1. **Setup Environment Variables**:
Copy the sample environment file and fill in your credentials:
```bash
cp .env.sample .env

```bash
make start
2. **Start the Application**:
```bash
make start
```

make logs
3. **Database Migration**:
```bash
make migrate
```

make migrate
```
4. **View Logs**:
```bash
make logs
```
18 changes: 14 additions & 4 deletions blog/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from django.db import models
from django.forms import TextInput, Textarea
from django.http import HttpRequest
from django.urls import reverse
from django.utils.safestring import mark_safe
from image_cropping import ImageCroppingMixin # type: ignore
from blog.services.prettifier import json_to_pretty_html
from image_cropping import ImageCroppingMixin
from blog.services.parsers import json_to_pretty_html
from django.db.models import QuerySet
from .models import Post, Tag
from django.template.loader import render_to_string
Expand Down Expand Up @@ -39,7 +40,7 @@ class PostAdmin(ImageCroppingMixin, admin.ModelAdmin):
"tag_list",
]
list_display_links = ("title",)
readonly_fields = ("preview", "pk", "raw_body", "improve_button", "blog_link")
readonly_fields = ("preview", "pk", "raw_body", "improve_button", "blog_link", "apply_styles")
prepopulated_fields = {"slug": ("title",)}
filter_horizontal = ("tags",)
formfield_overrides = {
Expand All @@ -54,9 +55,10 @@ class PostAdmin(ImageCroppingMixin, admin.ModelAdmin):
"fields": (
("author", "created_date"),
"title",
"blog_link",
"summary",
"slug",
"published_date",
("published_date", "apply_styles"),
"text",
("image", "preview"),
"cropping",
Expand Down Expand Up @@ -110,6 +112,14 @@ def blog_link(self, obj: Post) -> str:
return "-"
return mark_safe(f"<a href='{obj.get_absolute_url()}'>Go to eduzen.ar</a>")

@mark_safe
def apply_styles(self, post: Post) -> str:
if not post.pk:
return "-"

url = reverse("post_update_styles", kwargs={"post_id": post.pk})
return f"<a href='{url}' class='button' style='background-color:green'> apply </a>"

@mark_safe
def improve_button(self, obj: Post) -> str:
if not obj.pk:
Expand Down
17 changes: 17 additions & 0 deletions blog/services/parsers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
from bs4 import BeautifulSoup, Tag
import json
from typing import Any
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import JsonLexer


def json_to_pretty_html(data: dict[str, Any]) -> str:
"""Function to display pretty version of our data"""
if not data:
return "No data"

response = json.dumps(data, sort_keys=True, indent=2)
formatter = HtmlFormatter(style="colorful")
response = highlight(response, JsonLexer(), formatter)
result = f"<style>{formatter.get_style_defs()}</style><br>{response}"
return result.strip()


def clear_all_styles(soup: BeautifulSoup) -> None:
Expand Down
17 changes: 0 additions & 17 deletions blog/services/prettifier.py

This file was deleted.

14 changes: 7 additions & 7 deletions blog/templates/blog/posts/_related_posts.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{% load i18n %}

<div class="flex justify-between items-center">
<div class="flex flex-wrap justify-between items-center">

<!-- Left div (conditional) -->
{% if page_obj.has_previous %}
<a
class="flex items-center flex-shrink-0 w-auto py-2 px-4 border rounded bg-purple-400 hover:bg-pink-400"
class="flex items-center flex-shrink-0 w-auto py-2 px-4 border rounded bg-purple-400 hover:bg-pink-400 mt-2 md:mt-0"
href="{% url 'related_posts' post_id %}?page={{ page_obj.previous_page_number }}"
hx-get="{% url 'related_posts' post_id %}?page={{ page_obj.previous_page_number }}"
hx-target="#related-post-container">
<i class="fas fa-chevron-left mr-2"></i>
{% translate "Previous"%}
</a>
{% else %}
<div class="w-10"></div> <!-- Placeholder to keep the center alignment -->
<div class="hidden md:block w-10"></div> <!-- Placeholder for larger screens only -->
{% endif %}

<!-- Center div -->
<div class="flex-grow flex justify-center space-x-4">
<div class="w-full md:flex-grow flex flex-wrap justify-center space-x-2 md:space-x-4 mt-4 md:mt-0">
{% for post in related_posts %}
<div class="card flex-shrink-0 w-1/4 bg-gray-700 rounded-xl shadow-md overflow-hidden hover:bg-pink-400">
<div class="card w-full md:flex-shrink-0 md:w-1/4 bg-gray-700 rounded-xl shadow-md overflow-hidden hover:bg-pink-400 mb-4">
<div class="p-4">
<h2 class="text-lg font-semibold">{{ post.title }}</h2>
<a href="{{ post.get_absolute_url }}" class="text-sm text-blue-500 hover:underline mt-2">Read more</a>
Expand All @@ -31,14 +31,14 @@ <h2 class="text-lg font-semibold">{{ post.title }}</h2>
<!-- Right div (conditional) -->
{% if page_obj.has_next %}
<a
class="flex items-center flex-shrink-0 w-auto py-2 px-4 border rounded bg-purple-400 hover:bg-pink-400"
class="flex items-center flex-shrink-0 w-auto py-2 px-4 border rounded bg-purple-400 hover:bg-pink-400 mt-2 md:mt-0"
href="{% url 'related_posts' post_id %}?page={{ page_obj.next_page_number }}"
hx-get="{% url 'related_posts' post_id %}?page={{ page_obj.next_page_number }}"
hx-target="#related-post-container">
{% translate "Next"%}&nbsp;
<i class="fas fa-chevron-right mr-2"></i>
</a>
{% else %}
<div class="w-10"></div> <!-- Placeholder to keep the center alignment -->
<div class="hidden md:block w-10"></div> <!-- Placeholder for larger screens only -->
{% endif %}
</div>
22 changes: 0 additions & 22 deletions blog/tests/model_test.py

This file was deleted.

2 changes: 1 addition & 1 deletion blog/tests/services/test_prettifier.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from blog.services.prettifier import json_to_pretty_html
from blog.services.parsers import json_to_pretty_html


def test_json_to_pretty_html():
Expand Down
1 change: 1 addition & 0 deletions blog/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
path("contact/", views.ContactView.as_view(), name="contact"),
path("posts/", views.PostListView.as_view(), name="post_list"),
path("posts/<slug:slug>/", views.PostDetailView.as_view(), name="post_detail"),
path("posts/<int:post_id>/improve/", views.post_update_styles, name="post_update_styles"),
path("posts/<int:post_id>/related/", views.RelatedPostsView.as_view(), name="related_posts"),
path("blog/", views.PostListView.as_view(), name="blog"),
path("blog/<slug:slug>/", views.PostDetailView.as_view(), name="blog_slug"),
Expand Down
11 changes: 11 additions & 0 deletions blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
from django.urls import reverse_lazy
from django.views.generic import DetailView, FormView, TemplateView, ListView
from django_filters.views import FilterView
from django.contrib.auth.decorators import login_required

from .filters import PostFilter
from .forms import AdvanceSearchForm, ContactForm
from .models import Post
from .services.telegram import send_contact_message
from .services.parsers import apply_styles

logger = logging.getLogger(__name__)

Expand All @@ -22,6 +24,15 @@
HALF_YEAR = 6 * MONTH


@login_required
def post_update_styles(request: HttpRequest, post_id: int) -> HttpResponse:
"""Apply styles to a post using BeautifulSoup."""
post = get_object_or_404(Post, pk=post_id)
post.text = apply_styles(post.text)
post.save()
return redirect("admin:blog_post_change", post_id)


class HtmxGetMixin:
partial_template_name: str

Expand Down
59 changes: 58 additions & 1 deletion core/tests/views/test_views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from http import HTTPStatus
from unittest.mock import patch

import pytest
from django.test import SimpleTestCase
from django.test import TestCase, Client, SimpleTestCase
from django.urls import reverse
from django.contrib.auth.models import User
from blog.tests.factories import PostFactory


@pytest.mark.parametrize("url", ("media/test.jpg", "media"))
Expand All @@ -18,3 +22,56 @@ def test_get(self):
assert response["Cache-Control"] == "max-age=31536000, immutable, public"
assert response["Content-Type"] == "image/svg+xml"
assert response.content.startswith(b"<svg")


class LanguageDropdownViewTest(SimpleTestCase):
def test_language_dropdown_renders_correct_template(self):
# Use the reverse() function to get the URL of the view.
# Assuming the name of the URL pattern for this view is 'language_dropdown'
response = self.client.get(reverse("language_dropdown"))

assert response.status_code == HTTPStatus.OK
# Check that the correct template is used
self.assertTemplateUsed(response, "core/language_dropdown.html")


class ChatGPTImprovePostTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(username="testuser", password="12345")
self.post = PostFactory.create()
self.url = reverse("chatgpt_improve_post", args=[self.post.pk])

def test_user_not_logged_in(self):
response = self.client.get(self.url)
assert response.status_code == HTTPStatus.FOUND # Should redirect to login page

@patch("core.views.improve_blog_post")
def test_post_not_found(self, mock_improve):
self.client.login(username="testuser", password="12345")
wrong_post_id_url = reverse("chatgpt_improve_post", args=[9999])
response = self.client.get(wrong_post_id_url)

assert response.status_code == HTTPStatus.OK
assert response.content.decode() == "Post not found"
mock_improve.assert_not_called() # Since the post doesn't exist, improve_blog_post shouldn't be called

@patch("core.views.improve_blog_post")
def test_post_with_suggestions(self, mock_improve):
mock_improve.return_value = None # Assume this function returns None after modifying the post
self.post.suggestions = {"suggestion": "Improved Title"}
self.post.save()

self.client.login(username="testuser", password="12345")
response = self.client.get(self.url)
assert "Improved Title" in response.content.decode()
mock_improve.assert_called_once() # Ensure the function was called

@patch("core.views.improve_blog_post")
def test_post_without_suggestions(self, mock_improve):
mock_improve.return_value = None
self.client.login(username="testuser", password="12345")
response = self.client.get(self.url)

assert response.json() == {"message": "No suggestions found!"}
mock_improve.assert_called_once()
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ services:
- .:/code
- shell_history:/root/hist
- ipy_history:/root/.ipython/
- ./secrets/eduzen.json:/root/keys/eduzen.json
ports:
- "8000:80"
- "3000:3000"
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ dev = [
"pytest-recording",
"types-redis",
"types-requests",
"hypothesis",
"django-browser-reload",
# "wdb",
]
Expand Down Expand Up @@ -153,6 +152,7 @@ django_settings_module = "website.settings.dev"
[[tool.mypy.overrides]]
module = [
"decouple",
"crispy_forms"
"crispy_forms",
"image_cropping",
]
ignore_missing_imports = true
Loading

0 comments on commit 7a64e27

Please sign in to comment.