diff --git a/apps/blog/migrations/0001_initial.py b/apps/blog/migrations/0001_initial.py
index 9c0d523..860719a 100644
--- a/apps/blog/migrations/0001_initial.py
+++ b/apps/blog/migrations/0001_initial.py
@@ -1,65 +1,65 @@
-# Generated by Django 5.1.1 on 2024-11-13 12:31
-
-from django.db import migrations, models
-
-
-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')),
- ('created_at', models.DateTimeField(auto_now_add=True)),
- ('updated_at', models.DateTimeField(auto_now=True)),
- ('title', models.CharField(db_index=True, max_length=120, verbose_name='title')),
- ('slug', models.SlugField(max_length=255, unique=True, verbose_name='slug')),
- ('status', models.CharField(choices=[('df', 'Draft'), ('pb', 'Published')], default='df', max_length=2, verbose_name='status')),
- ('description', models.CharField(blank=True, max_length=300, null=True, verbose_name='description')),
- ('content', models.TextField(verbose_name='content')),
- ('publisher_at', models.DateField(verbose_name='publisher at')),
- ('is_active', models.BooleanField(default=True, verbose_name='active')),
- ('watching', models.BigIntegerField(default=0, verbose_name='watching')),
- ],
- options={
- 'verbose_name': 'Post',
- 'verbose_name_plural': 'Posts',
- 'db_table': 'posts',
- },
- ),
- migrations.CreateModel(
- name='PostComment',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(auto_now_add=True)),
- ('updated_at', models.DateTimeField(auto_now=True)),
- ('message', models.TextField(verbose_name='message')),
- ],
- options={
- 'abstract': False,
- },
- ),
- migrations.CreateModel(
- name='PostCommentLike',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ],
- ),
- migrations.CreateModel(
- name='PostDislike',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ],
- ),
- migrations.CreateModel(
- name='PostLike',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ],
- ),
- ]
+# Generated by Django 5.1.1 on 2024-11-13 12:31
+
+from django.db import migrations, models
+
+
+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')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('title', models.CharField(db_index=True, max_length=120, verbose_name='title')),
+ ('slug', models.SlugField(max_length=255, unique=True, verbose_name='slug')),
+ ('status', models.CharField(choices=[('df', 'Draft'), ('pb', 'Published')], default='df', max_length=2, verbose_name='status')),
+ ('description', models.CharField(blank=True, max_length=300, null=True, verbose_name='description')),
+ ('content', models.TextField(verbose_name='content')),
+ ('publisher_at', models.DateField(verbose_name='publisher at')),
+ ('is_active', models.BooleanField(default=True, verbose_name='active')),
+ ('watching', models.BigIntegerField(default=0, verbose_name='watching')),
+ ],
+ options={
+ 'verbose_name': 'Post',
+ 'verbose_name_plural': 'Posts',
+ 'db_table': 'posts',
+ },
+ ),
+ migrations.CreateModel(
+ name='PostComment',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('message', models.TextField(verbose_name='message')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='PostCommentLike',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PostDislike',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PostLike',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ],
+ ),
+ ]
diff --git a/apps/blog/sitemaps.py b/apps/blog/sitemaps.py
index 91d5a08..bb91bca 100644
--- a/apps/blog/sitemaps.py
+++ b/apps/blog/sitemaps.py
@@ -7,7 +7,7 @@ class PostSitemap(Sitemap):
priority = 0.9
def items(self):
- return Post.objects.all()
+ return Post.published.all()
def lastmod(self, obj):
return obj.updated_at
diff --git a/apps/blog/utils.py b/apps/blog/utils.py
index 5bf0bea..9f568b7 100644
--- a/apps/blog/utils.py
+++ b/apps/blog/utils.py
@@ -1,4 +1,5 @@
from django.db.models import QuerySet
+from django.db.models import Q
from django.core.paginator import Paginator, Page, EmptyPage, PageNotAnInteger
from .models import PostLike, PostDislike, Post, PostComment
@@ -7,19 +8,24 @@
def get_search_model_queryset(
model_queryset: QuerySet, search_query: str = None
) -> QuerySet:
- if search_query is None:
+ if not search_query:
return model_queryset
- search_for_title = model_queryset.filter(title__icontains=search_query)
- if not search_for_title:
- search_for_content = model_queryset.filter(content__icontains=search_query)
- if not search_for_content:
- queryset = search_for_content
- else:
- queryset = search_for_content
- else:
- queryset = search_for_title
- return queryset
+ search_query = model_queryset.filter(
+ Q(title__icontains=search_query) | Q(description__icontains=search_query) | Q(content__icontains=search_query)
+ )
+
+ return search_query
+ # search_for_title = model_queryset.filter(title__icontains=search_query)
+ # if not search_for_title:
+ # search_for_content = model_queryset.filter(content__icontains=search_query)
+ # if not search_for_content:
+ # queryset = search_for_content
+ # else:
+ # queryset = search_for_content
+ # else:
+ # queryset = search_for_title
+ # return queryset
def get_pagination_obj(model_queryset: QuerySet, page: int = 1, size: int = 4) -> Page:
diff --git a/apps/blog/views.py b/apps/blog/views.py
index d0e3f74..32310c9 100644
--- a/apps/blog/views.py
+++ b/apps/blog/views.py
@@ -1,19 +1,23 @@
import datetime
+from functools import wraps
+from typing import Any
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView, DeleteView
from django.contrib import messages
+from django.http import HttpRequest
from django.views import View
from django.urls import reverse
from apps.users.models import User
+from apps.shared.mixins import CustomHtmxMixin, render_htmx_or_default
+from .models import Post
from .forms import (
PostCreateUpdateForm,
SettingsUserForm,
SettingsUserProfileForm,
)
-from .models import Post
from .utils import (
get_search_model_queryset,
get_pagination_obj,
@@ -23,52 +27,48 @@
)
-class CustomHtmxMixin:
- def dispatch(self, request, *args, **kwargs):
- # import pdb; pdb.set_trace()
- self.template_htmx = self.template_name
- if not self.request.META.get("HTTP_HX_REQUEST"):
- self.template_name = "blog/include_blog.html"
- return super().dispatch(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- kwargs["template_htmx"] = self.template_htmx
- return super().get_context_data(**kwargs)
-
-
-class HomePageView(TemplateView):
+class HomePageView(CustomHtmxMixin, TemplateView):
template_name = "blog/home.html"
- def get(self, request):
- if request.user is not None and request.user.is_authenticated:
- posts = Post.published.exclude(author=request.user)
+ def get_context_data(self, **kwargs):
+ if self.request.user is not None and self.request.user.is_authenticated:
+ posts = Post.published.exclude(author=self.request.user)
else:
posts = Post.published.all()
- search_query = request.GET.get("search_query", None)
- page = request.GET.get("page", 1)
- size = request.GET.get("size", 4)
+ search_query = self.request.GET.get("search_query", None)
+ page = self.request.GET.get("page", 1)
+ size = self.request.GET.get("size", 4)
posts = get_search_model_queryset(posts, search_query)
page_obj = get_pagination_obj(posts, page, size)
- return render(
- request,
- "blog/home.html",
- {
- "page_obj": page_obj,
- "size_value": size,
- "search_query_value": search_query,
- },
- )
+ kwargs["title"] = "Home"
+ kwargs["page_obj"] = page_obj
+ kwargs["size_value"] = size
+ kwargs["search_query_value"] = search_query
+
+ return super().get_context_data(**kwargs)
-class AboutPageView(TemplateView):
+class AboutPageView(CustomHtmxMixin, TemplateView):
template_name = "blog/about.html"
+
+ def get_context_data(self, **kwargs):
+ kwargs["title"] = "About"
+ return super().get_context_data(**kwargs)
-class PostDetailPageView(View):
+class ContactsPageView(CustomHtmxMixin, TemplateView):
+ template_name = "blog/contacts.html"
+
+ def get_context_data(self, **kwargs):
+ kwargs["title"] = "Contacts"
+ return super().get_context_data(**kwargs)
+
+
+class PostDetailPageView(CustomHtmxMixin, View):
template_name = "blog/post_detail.html"
def get(self, request, slug):
@@ -77,18 +77,26 @@ def get(self, request, slug):
post_comments = post.post_comments.all().order_by("-created_at")
post.watching += 1
post.save()
- return render(
- request,
- "blog/post_detail.html",
- {"post": post, "post_comments": post_comments},
- )
+ context = {
+ "title": post.title,
+ "post": post,
+ "post_comments": post_comments,
+ "template_htmx": self.template_htmx
+ }
+
+ return render(request, self.template_name, context)
-class PostCreatePageView(LoginRequiredMixin, TemplateView):
+
+class PostCreatePageView(CustomHtmxMixin, LoginRequiredMixin, TemplateView):
template_name = "blog/post_create.html"
- def get(self, request):
- return render(request, "blog/post_create.html", {"form": PostCreateUpdateForm()})
+ def get_context_data(self, **kwargs):
+ kwargs["form"] = PostCreateUpdateForm()
+ kwargs["title"] = "Post Create"
+ return super().get_context_data(**kwargs)
+ # def get(self, request):
+ # return render(request, self.template_name, {"form": PostCreateUpdateForm()})
def post(self, request):
form = PostCreateUpdateForm(request.POST)
@@ -115,30 +123,20 @@ def post(self, request):
return redirect(reverse("blog:post_create"))
-class UserPostsPageView(LoginRequiredMixin, View):
- template_name = "blog/user_posts.html"
-
- def get(self, request):
-
- search_query_for_user_posts = request.GET.get(
- "search_query_for_user_posts", None
- )
- posts = Post.objects.filter(author=request.user, is_active=True)
-
- if search_query_for_user_posts is not None:
- posts = get_search_model_queryset(posts, search_query_for_user_posts)
-
- return render(request, "blog/user_posts.html", {"posts": posts})
-
-
-class PostUpdateView(LoginRequiredMixin, View):
+class PostUpdateView(CustomHtmxMixin, LoginRequiredMixin, TemplateView):
template_name = "blog/post_update.html"
def get(self, request, slug):
post = get_object_or_404(Post, slug=slug, is_active=True)
form = PostCreateUpdateForm(instance=post)
- return render(request, "blog/post_update.html", {"form": form, "post": post})
+ context = {
+ "title": "Update " + post.title,
+ "template_htmx": self.template_htmx,
+ "form": form,
+ "post": post
+ }
+ return render(request, self.template_name, context)
def post(self, request, slug):
post = get_object_or_404(Post, slug=slug)
@@ -152,7 +150,28 @@ def post(self, request, slug):
return redirect(reverse("blog:post_update", kwargs={"slug": slug}))
-class PostDeletePageView(LoginRequiredMixin, DeleteView):
+class UserPostsPageView(CustomHtmxMixin, LoginRequiredMixin, TemplateView):
+ template_name = "blog/user_posts.html"
+
+ def get(self, request):
+
+ search_query_for_user_posts = request.GET.get(
+ "search_query_for_user_posts", None
+ )
+ posts = Post.objects.filter(author=request.user, is_active=True)
+
+ if search_query_for_user_posts is not None:
+ posts = get_search_model_queryset(posts, search_query_for_user_posts)
+
+ context = {
+ "title": "My posts",
+ "template_htmx": self.template_htmx,
+ "posts": posts
+ }
+ return render(request, self.template_name, context)
+
+
+class PostDeletePageView(CustomHtmxMixin, LoginRequiredMixin, DeleteView):
template_name = "blog/post_confirm_delete.html"
model = Post
@@ -163,22 +182,21 @@ def post(self, request, slug):
return redirect("blog:user_posts")
-class ContactsPageView(TemplateView):
- template_name = "blog/contacts.html"
-
-
-class SettingsPageView(LoginRequiredMixin, View):
- template_name = "blog/settings.py"
+class SettingsPageView(CustomHtmxMixin, LoginRequiredMixin, View):
+ template_name = "blog/settings.html"
def get(self, request):
user = get_object_or_404(User, pk=request.user.pk)
user_form = SettingsUserForm(instance=user)
user_profile_form = SettingsUserProfileForm(instance=user.profiles)
- return render(
- request,
- "blog/settings.html",
- {"user_form": user_form, "user_profile_form": user_profile_form},
- )
+ context = {
+ "title": "Settings",
+ "template_htmx": self.template_htmx,
+ "user_form": user_form,
+ "user_profile_form": user_profile_form
+ }
+
+ return render(request, self.template_name, context)
def post(self, request):
user = get_object_or_404(User, pk=request.user.pk)
diff --git a/apps/shared/mixins.py b/apps/shared/mixins.py
new file mode 100644
index 0000000..4067366
--- /dev/null
+++ b/apps/shared/mixins.py
@@ -0,0 +1,34 @@
+from functools import wraps
+
+from django.http import HttpRequest
+from django.shortcuts import render
+
+
+class CustomHtmxMixin:
+ def dispatch(self, request, *args, **kwargs):
+ # import pdb; pdb.set_trace()
+ self.template_htmx = self.template_name
+ if not self.request.META.get("HTTP_HX_REQUEST"):
+ self.template_name = "htmx_blog.html"
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ kwargs["template_htmx"] = self.template_htmx
+ return super().get_context_data(**kwargs)
+
+
+def render_htmx_or_default(template_name, htmx_template_name=None):
+ def decorator(view_func):
+ @wraps
+ def _wrapped_view(request: HttpRequest, *args, **kwargs):
+ response = view_func(request, *args, **kwargs)
+
+ if request.headers.get("HX-Request"):
+ if htmx_template_name:
+ return render(request, htmx_template_name, response.context_data)
+ return render(request, template_name, response.context_data)
+ return response
+ return _wrapped_view
+ return decorator
+
+
\ No newline at end of file
diff --git a/apps/users/forms.py b/apps/users/forms.py
index de3d564..317c5bf 100644
--- a/apps/users/forms.py
+++ b/apps/users/forms.py
@@ -40,17 +40,19 @@ class RegisterForm(forms.ModelForm):
widget=forms.PasswordInput(attrs={"id": "password", "type": "password"}),
)
- def save(self, commit=True):
- user = super().save(commit)
-
+ def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
+ if password1 and password2 and password1 != password2:
+ raise ValidationError("Passwords must be match!")
+ return password2
+
+ def save(self, commit=True):
+ user = super().save(commit=False)
- if password1 == password2:
- user.set_password(password1)
- user.save()
- else:
- return ValidationError("Passwords must be match!")
+ user.set_password(self.cleaned_data["password1"])
+ user.save()
+ return user
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -59,9 +61,4 @@ def __init__(self, *args, **kwargs):
class Meta:
model = User
- fields = (
- "username",
- "password1",
- "password2",
- "email",
- )
+ fields = ("username", "password1", "password2", "email", )
diff --git a/apps/users/migrations/0001_initial.py b/apps/users/migrations/0001_initial.py
index cad35aa..c2e6c61 100644
--- a/apps/users/migrations/0001_initial.py
+++ b/apps/users/migrations/0001_initial.py
@@ -1,64 +1,64 @@
-# Generated by Django 5.1.1 on 2024-11-13 12:31
-
-import django.contrib.auth.models
-import django.contrib.auth.validators
-import django.db.models.deletion
-import django.utils.timezone
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ('auth', '0012_alter_user_first_name_max_length'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='User',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('password', models.CharField(max_length=128, verbose_name='password')),
- ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
- ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
- ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
- ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
- ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
- ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
- ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
- ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
- ('created_at', models.DateTimeField(auto_now_add=True)),
- ('updated_at', models.DateTimeField(auto_now=True)),
- ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
- ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
- ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
- ],
- options={
- 'verbose_name': 'User',
- 'verbose_name_plural': 'Users',
- 'db_table': 'users',
- },
- managers=[
- ('objects', django.contrib.auth.models.UserManager()),
- ],
- ),
- migrations.CreateModel(
- name='UserProfile',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(auto_now_add=True)),
- ('updated_at', models.DateTimeField(auto_now=True)),
- ('avatar', models.ImageField(blank=True, default='avatars/default/logo.png', max_length=250, null=True, upload_to='avatars/', verbose_name='avatar')),
- ('bio', models.CharField(blank=True, max_length=170, null=True, verbose_name='bio')),
- ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profiles', to=settings.AUTH_USER_MODEL)),
- ],
- options={
- 'verbose_name': 'User Profile',
- 'verbose_name_plural': 'User Profiles',
- 'db_table': 'user_profiles',
- },
- ),
- ]
+# Generated by Django 5.1.1 on 2024-11-13 12:31
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+import django.db.models.deletion
+import django.utils.timezone
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('auth', '0012_alter_user_first_name_max_length'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('password', models.CharField(max_length=128, verbose_name='password')),
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
+ ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
+ ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
+ ],
+ options={
+ 'verbose_name': 'User',
+ 'verbose_name_plural': 'Users',
+ 'db_table': 'users',
+ },
+ managers=[
+ ('objects', django.contrib.auth.models.UserManager()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='UserProfile',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('avatar', models.ImageField(blank=True, default='avatars/default/logo.png', max_length=250, null=True, upload_to='avatars/', verbose_name='avatar')),
+ ('bio', models.CharField(blank=True, max_length=170, null=True, verbose_name='bio')),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profiles', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'User Profile',
+ 'verbose_name_plural': 'User Profiles',
+ 'db_table': 'user_profiles',
+ },
+ ),
+ ]
diff --git a/apps/users/tests.py b/apps/users/tests.py
index 29cfafb..a8dca2a 100644
--- a/apps/users/tests.py
+++ b/apps/users/tests.py
@@ -1 +1,15 @@
-from django.test import TestCase # noqa
\ No newline at end of file
+from django.test import TestCase
+
+from .models import User, UserProfile
+
+
+class TestUsers(TestCase):
+ def setUp(self) -> None:
+ User.objects.create(
+ username="User",
+ email="user@example.com",
+ password="password"
+ )
+ return super().setUp()
+
+
\ No newline at end of file
diff --git a/apps/users/views.py b/apps/users/views.py
index 920f822..6d42695 100644
--- a/apps/users/views.py
+++ b/apps/users/views.py
@@ -4,20 +4,25 @@
from django.contrib import messages
from django.views import View
from django.contrib.auth import authenticate
-from .forms import RegisterForm, LoginForm
-from .models import User
+
+from apps.shared.mixins import CustomHtmxMixin
from apps.blog.utils import get_search_model_queryset
from apps.blog.models import Post
+from .forms import RegisterForm, LoginForm
+from .models import User
from .services import get_jwt_login_response, get_jwt_logout_response
-from rest_framework_simplejwt.tokens import RefreshToken
-
-class RegisterPageView(View):
+class RegisterPageView(CustomHtmxMixin, View):
template_name = "auth/register.html"
def get(self, request):
- return render(request, "auth/register.html", {"form": RegisterForm()})
+ context = {
+ "title": "Registration",
+ "template_htmx": self.template_htmx,
+ "form": RegisterForm()
+ }
+ return render(request, self.template_name, context)
def post(self, request):
@@ -32,11 +37,16 @@ def post(self, request):
return render(request, "auth/register.html", {"form": form})
-class LoginPageView(View):
+class LoginPageView(CustomHtmxMixin, View):
template_name = "auth/login.html"
def get(self, request):
- return render(request, "auth/login.html", {"form": LoginForm()})
+ context = {
+ "title": "Login",
+ "template_htmx": self.template_htmx,
+ "form": LoginForm()
+ }
+ return render(request, self.template_name, context)
def post(self, request):
form = LoginForm(request.POST)
@@ -64,11 +74,15 @@ def post(self, request):
return render(request, "auth/login.html", {"form": form})
-class LogoutPageView(LoginRequiredMixin, View):
+class LogoutPageView(CustomHtmxMixin, LoginRequiredMixin, View):
template_name = "auth/logout.html"
def get(self, request):
- return render(request, "auth/logout.html")
+ context = {
+ "title": "Logout",
+ "template_htmx": self.template_htmx
+ }
+ return render(request, self.template_name, context)
def post(self, request):
response = redirect(reverse("blog:home"))
@@ -79,7 +93,7 @@ def post(self, request):
return response
-class UserProfilePageView(View):
+class UserProfilePageView(CustomHtmxMixin, View):
template_name = "blog/profile.html"
def get(self, request, username):
@@ -91,4 +105,10 @@ def get(self, request, username):
if search_query is not None:
posts = get_search_model_queryset(posts, search_query)
- return render(request, "blog/profile.html", {"posts": posts, "user": user})
+ context = {
+ "title": str(user),
+ "template_htmx": self.template_htmx,
+ "posts": posts,
+ "user": user
+ }
+ return render(request, self.template_name, context)
diff --git a/core/config/rest_framework.py b/core/config/rest_framework.py
index 47dd4a1..f9cdb5f 100644
--- a/core/config/rest_framework.py
+++ b/core/config/rest_framework.py
@@ -1,5 +1,8 @@
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
+ ),
+ "DEFAULT_PERMISSION_CLASSES": (
+ "rest_framework.permissions.AllowAny",
)
}
\ No newline at end of file
diff --git a/core/settings/production.py b/core/settings/production.py
index 2b43434..eb013d8 100644
--- a/core/settings/production.py
+++ b/core/settings/production.py
@@ -3,36 +3,20 @@
from .base import * # noqa
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
-EMAIL_HOST = "'smtp.google.com'"
+EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
EMAIL_USE_TLS = True
-DATABASES_ENVIRON = os.getenv("DATABASE_ENVIRON")
-if DATABASES_ENVIRON == "postgresql":
- DATABASES = {
- "default": {
- "ENGINE": f"django.db.backends.postgresql",
- "NAME": str(os.getenv("DATABASE_NAME")),
- "USER": str(os.getenv("DATABASE_USER")),
- "PASSWORD": str(os.getenv("DATABASE_PASSWORD")),
- "HOST": str(os.getenv("DATABASE_HOST")),
- "PORT": int(os.getenv("DATABASE_PORT"))
- }
- }
-elif DATABASES_ENVIRON == 'mysql':
- DATABASES = {
- "default": {
- "ENGINE": f"django.db.backends.mysql",
- "NAME": str(os.getenv("DATABASE_NAME")),
- "USER": str(os.getenv("DATABASE_USER")),
- "PASSWORD": str(os.getenv("DATABASE_PASSWORD")),
- "HOST": str(os.getenv("DATABASE_HOST")),
- "PORT": int(os.getenv("DATABASE_PORT")),
- "OPTIONS": {
- "sql_mode": "STRICT_TRANS_TABLES",
- }
- }
+DATABASES = {
+ "default": {
+ "ENGINE": f"django.db.backends.postgresql",
+ "NAME": str(os.getenv("DATABASE_NAME")),
+ "USER": str(os.getenv("DATABASE_USER")),
+ "PASSWORD": str(os.getenv("DATABASE_PASSWORD")),
+ "HOST": str(os.getenv("DATABASE_HOST")),
+ "PORT": int(os.getenv("DATABASE_PORT"))
}
+}
diff --git a/core/urls.py b/core/urls.py
index 37b2d80..30b26cb 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -27,8 +27,9 @@
),
]
-urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
-urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+if settings.DEBUG:
+ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
handler400 = "apps.shared.views.bad_request_view" # noqa
diff --git a/templates/auth/login.html b/templates/auth/login.html
index 5d46414..8b432ef 100644
--- a/templates/auth/login.html
+++ b/templates/auth/login.html
@@ -1,9 +1,4 @@
-{% extends 'base.html' %}
-{% load static %}
-{% block title %} Login {% endblock %}
-
-{% block content %}
@@ -38,4 +33,3 @@
Login
{% include 'components/latest_posts.html' %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/auth/logout.html b/templates/auth/logout.html
index 33e2119..4a87167 100644
--- a/templates/auth/logout.html
+++ b/templates/auth/logout.html
@@ -1,8 +1,4 @@
-{% extends 'base.html' %}
-{% block title %}Log out{% endblock %}
-
-{% block content %}
@@ -23,4 +19,3 @@
Log out ?
-{% endblock %}
\ No newline at end of file
diff --git a/templates/auth/register.html b/templates/auth/register.html
index 044bd69..956bb95 100644
--- a/templates/auth/register.html
+++ b/templates/auth/register.html
@@ -1,7 +1,4 @@
-{% extends 'base.html' %}
-{% load static %}
-{% block title %} Register {% endblock %}
-{% block content %}
+
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/about.html b/templates/blog/about.html
index 18ea68a..6f8997f 100644
--- a/templates/blog/about.html
+++ b/templates/blog/about.html
@@ -1,7 +1,3 @@
-{% extends 'base.html' %}
-{% load static %}
-{% block title %} About {% endblock %}
-{% block content %}
@@ -34,4 +30,3 @@
Our Sidebar
-{% endblock %}
diff --git a/templates/blog/contacts.html b/templates/blog/contacts.html
index 4dd8e35..ccf2dd4 100644
--- a/templates/blog/contacts.html
+++ b/templates/blog/contacts.html
@@ -1,9 +1,3 @@
-{% extends 'base.html' %}
-{% load static %}
-
-{% block title %} Contacts {% endblock %}
-
-{% block content %}
@@ -13,4 +7,3 @@
{% include 'components/latest_posts.html' %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/home.html b/templates/blog/home.html
index c69b228..e2e98cc 100644
--- a/templates/blog/home.html
+++ b/templates/blog/home.html
@@ -1,7 +1,4 @@
-{% extends 'base.html' %}
-{% load static %}
-{% block title %}Home{% endblock %}
-{% block content %}
+
@@ -20,7 +17,12 @@
{{ massage }}
-
+
{{ post.author }}
@@ -32,7 +34,12 @@ {{ massage }}
{{ post.title }}
{{ post.publisher_at }}
{{ post.description|truncatewords_html:30 }}
- Continue reading
+ Continue reading
{{ post.watching }}
{{ post.like_count }}
@@ -52,4 +59,3 @@
Post not found
{% include 'components/paginators.html' %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/include_blog.html b/templates/blog/include_blog.html
deleted file mode 100644
index 2ebf2ae..0000000
--- a/templates/blog/include_blog.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends 'blog/base.html' %}
-
-{% block content %}
-{% include template_htmx %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/post_create.html b/templates/blog/post_create.html
index 3b30ce8..674119e 100644
--- a/templates/blog/post_create.html
+++ b/templates/blog/post_create.html
@@ -1,8 +1,3 @@
-{% extends 'base.html' %}
-{% load static %}
-{% block title %} New post {% endblock %}
-
-{% block content %}
@@ -41,4 +36,3 @@
{% include 'components/latest_posts.html' %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/post_detail.html b/templates/blog/post_detail.html
index 0f6df8e..318f66a 100644
--- a/templates/blog/post_detail.html
+++ b/templates/blog/post_detail.html
@@ -1,9 +1,5 @@
-{% extends 'base.html' %}
{% load static blog_tags %}
-{% block title %} Post Detail {% endblock %}
-
-{% block content %}
@@ -102,4 +98,3 @@
{{ comment.user }}
{% include 'components/latest_posts.html' %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/post_update.html b/templates/blog/post_update.html
index 6830792..2cefd49 100644
--- a/templates/blog/post_update.html
+++ b/templates/blog/post_update.html
@@ -1,8 +1,3 @@
-{% extends 'base.html' %}
-{% load static %}
-{% block title %} Post update {% endblock %}
-
-{% block content %}
@@ -39,4 +34,3 @@
{% include 'components/latest_posts.html' %}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/profile.html b/templates/blog/profile.html
index b3b1386..ad6c7bf 100644
--- a/templates/blog/profile.html
+++ b/templates/blog/profile.html
@@ -1,9 +1,3 @@
-{% extends 'base.html' %}
-{% load static %}
-
-{% block title %} Profile {% endblock %}
-
-{% block content %}
@@ -79,4 +73,3 @@
{{ post.title }}
-{% endblock %}
\ No newline at end of file
diff --git a/templates/blog/settings.html b/templates/blog/settings.html
index d537890..36004a0 100644
--- a/templates/blog/settings.html
+++ b/templates/blog/settings.html
@@ -1,9 +1,3 @@
-{% extends 'base.html' %}
-{% load static %}
-
-{% block title %} Settings {% endblock %}
-
-{% block content %}
@@ -20,4 +14,4 @@
{% include 'components/latest_posts.html' %}
- {% endblock %}
+
diff --git a/templates/blog/user_posts.html b/templates/blog/user_posts.html
index a3aa264..51e3859 100644
--- a/templates/blog/user_posts.html
+++ b/templates/blog/user_posts.html
@@ -1,52 +1,49 @@
-{% extends 'base.html' %}
-{% load static %}
-{% block title %} User posts {% endblock %}
-
-{% block content %}
-
-
-
-
-
+
-
- {% for post in posts %}
-
-
-
- {% if post.status == 'pb' %}
-
Published
- {% elif post.status == 'df' %}
-
Draft
- {% endif %}
-
{{ post.title }}
-
{{ post.publisher_at }}
-
{{ post.description|truncatewords_html:30 }}
-
Continue reading
-
-
+
+
+
+
+ {% for post in posts %}
+
+
+
+ {% if post.status == 'pb' %}
+
Published
+ {% elif post.status == 'df' %}
+
Draft
+ {% endif %}
+
{{ post.title }}
+
{{ post.publisher_at }}
+
{{ post.description|truncatewords_html:30 }}
+
Continue reading
- {% include 'components/latest_posts.html' %}
+
-
-{% endblock %}
\ No newline at end of file
+ {% empty %}
+
+ Now posts
+
+ {% endfor %}
+
+ {% include 'components/latest_posts.html' %}
+
+
\ No newline at end of file
diff --git a/templates/components/headers.html b/templates/components/headers.html
index fe48312..25dc244 100644
--- a/templates/components/headers.html
+++ b/templates/components/headers.html
@@ -1,7 +1,13 @@
diff --git a/templates/components/paginators.html b/templates/components/paginators.html
index b761be9..6dc6494 100644
--- a/templates/components/paginators.html
+++ b/templates/components/paginators.html
@@ -5,7 +5,13 @@
{% if page_obj.has_previous %}
-
+
«
@@ -23,11 +29,23 @@
{% elif i >= page_obj.number|add:'-2' and i <= page_obj.number|add:'2' %}
- {{ i }}
+ {{ i }}
{% elif i == 1 or i == page_obj.paginator.num_pages %}
- {{ i }}
+ {{ i }}
{% elif i == page_obj.number|add:'-3' or i == page_obj.number|add:'3' %}
...
@@ -37,7 +55,12 @@
{% if page_obj.has_next %}
-
+
»
diff --git a/templates/htmx_blog.html b/templates/htmx_blog.html
new file mode 100644
index 0000000..59b8f53
--- /dev/null
+++ b/templates/htmx_blog.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+
+{% block title %} {{ title }} {% endblock %}
+
+{% block content %}
+{% include template_htmx %}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/including_blog.html b/templates/including_blog.html
deleted file mode 100644
index 2ebf2ae..0000000
--- a/templates/including_blog.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends 'blog/base.html' %}
-
-{% block content %}
-{% include template_htmx %}
-{% endblock %}
\ No newline at end of file