Skip to content
Merged

Main #19

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions apps/blog/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from django.contrib import admin
from .models import Post, PostComment, PostLike, PostDislike, PostCommentLike

from unfold.admin import ModelAdmin


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
class PostAdmin(ModelAdmin):
list_display = ["title", "content", "author", "is_active"]
search_fields = ["title", "content"]
list_filter = ["author", "is_active"]
Expand All @@ -12,20 +14,20 @@ class PostAdmin(admin.ModelAdmin):


@admin.register(PostComment)
class PostCommentAdmin(admin.ModelAdmin):
class PostCommentAdmin(ModelAdmin):
pass


@admin.register(PostLike)
class PostLikeAdmin(admin.ModelAdmin):
class PostLikeAdmin(ModelAdmin):
pass


@admin.register(PostDislike)
class PostDislike(admin.ModelAdmin):
class PostDislike(ModelAdmin):
pass


@admin.register(PostCommentLike)
class PostCommentLikeAdmin(admin.ModelAdmin):
class PostCommentLikeAdmin(ModelAdmin):
pass
14 changes: 14 additions & 0 deletions apps/blog/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import django_filters

from .models import Post


class PostFilter(django_filters.FilterSet):
title = django_filters.CharFilter(lookup_expr="icontains")
description = django_filters.CharFilter(lookup_expr="icontains")
content = django_filters.CharFilter(lookup_expr="icontains")
created_at = django_filters.DateFromToRangeFilter()

class Meta:
model = Post
fields = ["title", "description", "content", "created_at"]
48 changes: 36 additions & 12 deletions apps/blog/utils.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
from django.db.models import QuerySet
from django.db.models import Q
from django.core.paginator import Paginator, Page, EmptyPage, PageNotAnInteger
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from django.core.paginator import (
Paginator,
Page,
EmptyPage,
PageNotAnInteger,
InvalidPage,
)
from django.conf import settings

from .models import PostLike, PostDislike, Post, PostComment


def get_search_model_queryset(
model_queryset: QuerySet, search_query: str = None
) -> QuerySet:
if not search_query:
def get_search_model_queryset(model_queryset: QuerySet, query: str = None) -> QuerySet:
if not query:
return model_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_vector = SearchVector("title", "description", "content")
search_query = SearchQuery(query)

if settings.DATABASES["default"]["ENGINE"] == "django.db.backends.postgresql":
# PostgreSQL search
queryset = (
model_queryset.annotate(
search=search_vector,
rank=SearchRank(search_vector, search_query),
)
.filter(search=search_query)
.order_by("-rank")
)
else:
# SQLite3 search
queryset = model_queryset.filter(
Q(title__icontains=query)
| Q(description__icontains=query)
| Q(content__icontains=query)
)

return queryset


def get_pagination_obj(model_queryset: QuerySet, page: int = 1, size: int = 4) -> Page:
Expand All @@ -29,6 +50,9 @@ def get_pagination_obj(model_queryset: QuerySet, page: int = 1, size: int = 4) -
except PageNotAnInteger:
page_obj = paginator.page(1)

except InvalidPage:
page_obj = paginator.page(1)

except EmptyPage:
page_obj = paginator.page(paginator.num_pages)

Expand Down
1 change: 1 addition & 0 deletions apps/shared/management/commands/createadmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.core.management import BaseCommand

from dotenv import load_dotenv

load_dotenv()


Expand Down
11 changes: 7 additions & 4 deletions apps/users/admin.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin # noqa
from .models import User, UserProfile

from unfold.admin import ModelAdmin


@admin.register(User)
class UserAdmin(admin.ModelAdmin):
class UserAdmin(ModelAdmin):
list_display = ["username", "post_count"]
search_fields = ["first_name", "last_name", "username"]
search_fields = ["first_name", "last_name", "username", "email"]
list_display_links = ["username"]

def get_post_count(self):
return self.post_count


@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
pass
class UserProfileAdmin(ModelAdmin):
list_display = ["user", "avatar", "bio"]
3 changes: 3 additions & 0 deletions apps/users/api_endpoints/users/User/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ def _create_user():
_check_user_error_field()
_check_user_passwords_field()
_create_user()

def test_api_user_update(self):
pass
20 changes: 20 additions & 0 deletions apps/users/api_endpoints/users/UserProfile/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from rest_framework.test import APITestCase
from rest_framework_simplejwt.tokens import RefreshToken
from apps.users.models import UserProfile, User # noqa


class UserProfileApiTestCase(APITestCase):
def setUp(self) -> None:
self.username = "Admin"
self.email = "[email protected]"
self.password = "password"
user = User.objects.create(
username=self.username,
email=self.email,
)
user.set_password(self.password)
user.save()
self.user = user
refresh = RefreshToken.for_user(self.user)
self.token = str(refresh.access_token)
return super().setUp()
1 change: 1 addition & 0 deletions apps/users/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

class JWTAuthMiddleware(MiddlewareMixin):
def process_request(self, request):
# If admin auth for session
if request.path.startswith("/admin"):
return

Expand Down
20 changes: 10 additions & 10 deletions core/asgi.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""
ASGI config for config project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
from dotenv import load_dotenv

load_dotenv()

os.environ.setdefault(
"DJANGO_SETTINGS_MODULE",
os.getenv("DJANGO_SETTINGS_MODULE", "core.settings.development"),
)

application = get_asgi_application()

app = application
4 changes: 4 additions & 0 deletions core/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from .apps import * # noqa
from .jwt import * # noqa
from .rest_framework import * # noqa
from .unfold_navigation import * # noqa
from .unfold import * # noqa

# from .cheditor5 import * # noqa
18 changes: 18 additions & 0 deletions core/config/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,25 @@
]

THIRD_PARTY_APPS = [
# Admin panel
"unfold",
"unfold.contrib.filters",
"unfold.contrib.forms",
"unfold.contrib.import_export",
"unfold.contrib.guardian",
"unfold.contrib.simple_history",
# Translation
"modeltranslation",
#
"django_ckeditor_5",
# Translation pannel
"rosetta",
# DRF Swaggers
"drf_spectacular",
"drf_spectacular_sidecar",
# Rest Framework
"rest_framework",
# Rest Framework JWT (Json web token)s
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
]
140 changes: 140 additions & 0 deletions core/config/cheditor5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
customColorPalette = [
{"color": "hsl(4, 90%, 58%)", "label": "Red"},
{"color": "hsl(340, 82%, 52%)", "label": "Pink"},
{"color": "hsl(291, 64%, 42%)", "label": "Purple"},
{"color": "hsl(262, 52%, 47%)", "label": "Deep Purple"},
{"color": "hsl(231, 48%, 48%)", "label": "Indigo"},
{"color": "hsl(207, 90%, 54%)", "label": "Blue"},
]

CKEDITOR_5_CONFIGS = {
"default": {
"toolbar": [
"heading",
"|",
"bold",
"italic",
"link",
"bulletedList",
"numberedList",
"blockQuote",
"imageUpload",
],
},
"extends": {
"blockToolbar": [
"paragraph",
"heading1",
"heading2",
"heading3",
"|",
"bulletedList",
"numberedList",
"|",
"blockQuote",
],
"toolbar": [
"heading",
"|",
"outdent",
"indent",
"|",
"bold",
"italic",
"link",
"underline",
"strikethrough",
"code",
"subscript",
"superscript",
"highlight",
"|",
"codeBlock",
"sourceEditing",
"insertImage",
"bulletedList",
"numberedList",
"todoList",
"|",
"blockQuote",
"imageUpload",
"|",
"fontSize",
"fontFamily",
"fontColor",
"fontBackgroundColor",
"mediaEmbed",
"removeFormat",
"insertTable",
],
"image": {
"toolbar": [
"imageTextAlternative",
"|",
"imageStyle:alignLeft",
"imageStyle:alignRight",
"imageStyle:alignCenter",
"imageStyle:side",
"|",
],
"styles": [
"full",
"side",
"alignLeft",
"alignRight",
"alignCenter",
],
},
"table": {
"contentToolbar": [
"tableColumn",
"tableRow",
"mergeTableCells",
"tableProperties",
"tableCellProperties",
],
"tableProperties": {
"borderColors": customColorPalette,
"backgroundColors": customColorPalette,
},
"tableCellProperties": {
"borderColors": customColorPalette,
"backgroundColors": customColorPalette,
},
},
"heading": {
"options": [
{
"model": "paragraph",
"title": "Paragraph",
"class": "ck-heading_paragraph",
},
{
"model": "heading1",
"view": "h1",
"title": "Heading 1",
"class": "ck-heading_heading1",
},
{
"model": "heading2",
"view": "h2",
"title": "Heading 2",
"class": "ck-heading_heading2",
},
{
"model": "heading3",
"view": "h3",
"title": "Heading 3",
"class": "ck-heading_heading3",
},
]
},
},
"list": {
"properties": {
"styles": "true",
"startIndex": "true",
"reversed": "true",
}
},
}
Loading
Loading