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 %} +
@@ -9,6 +6,9 @@

Register

{% csrf_token %} @@ -49,4 +49,3 @@

Register

{% include 'components/latest_posts.html' %}
-{% 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 }}
- + author logo {{ 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 -
-
+ +
+
+
+ +
- {% empty %} -
Now posts
- {% endfor %} +
+
+
+ + {% 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 @@