Skip to content

Commit 215736d

Browse files
Merge pull request #19 from RustamovAkrom/main
Main
2 parents c14f710 + 1564ffb commit 215736d

29 files changed

+4520
-49
lines changed

apps/blog/admin.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from django.contrib import admin
22
from .models import Post, PostComment, PostLike, PostDislike, PostCommentLike
33

4+
from unfold.admin import ModelAdmin
5+
46

57
@admin.register(Post)
6-
class PostAdmin(admin.ModelAdmin):
8+
class PostAdmin(ModelAdmin):
79
list_display = ["title", "content", "author", "is_active"]
810
search_fields = ["title", "content"]
911
list_filter = ["author", "is_active"]
@@ -12,20 +14,20 @@ class PostAdmin(admin.ModelAdmin):
1214

1315

1416
@admin.register(PostComment)
15-
class PostCommentAdmin(admin.ModelAdmin):
17+
class PostCommentAdmin(ModelAdmin):
1618
pass
1719

1820

1921
@admin.register(PostLike)
20-
class PostLikeAdmin(admin.ModelAdmin):
22+
class PostLikeAdmin(ModelAdmin):
2123
pass
2224

2325

2426
@admin.register(PostDislike)
25-
class PostDislike(admin.ModelAdmin):
27+
class PostDislike(ModelAdmin):
2628
pass
2729

2830

2931
@admin.register(PostCommentLike)
30-
class PostCommentLikeAdmin(admin.ModelAdmin):
32+
class PostCommentLikeAdmin(ModelAdmin):
3133
pass

apps/blog/filters.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import django_filters
2+
3+
from .models import Post
4+
5+
6+
class PostFilter(django_filters.FilterSet):
7+
title = django_filters.CharFilter(lookup_expr="icontains")
8+
description = django_filters.CharFilter(lookup_expr="icontains")
9+
content = django_filters.CharFilter(lookup_expr="icontains")
10+
created_at = django_filters.DateFromToRangeFilter()
11+
12+
class Meta:
13+
model = Post
14+
fields = ["title", "description", "content", "created_at"]

apps/blog/utils.py

+36-12
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,44 @@
11
from django.db.models import QuerySet
22
from django.db.models import Q
3-
from django.core.paginator import Paginator, Page, EmptyPage, PageNotAnInteger
3+
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
4+
from django.core.paginator import (
5+
Paginator,
6+
Page,
7+
EmptyPage,
8+
PageNotAnInteger,
9+
InvalidPage,
10+
)
11+
from django.conf import settings
412

513
from .models import PostLike, PostDislike, Post, PostComment
614

715

8-
def get_search_model_queryset(
9-
model_queryset: QuerySet, search_query: str = None
10-
) -> QuerySet:
11-
if not search_query:
16+
def get_search_model_queryset(model_queryset: QuerySet, query: str = None) -> QuerySet:
17+
if not query:
1218
return model_queryset
1319

14-
search_query = model_queryset.filter(
15-
Q(title__icontains=search_query)
16-
| Q(description__icontains=search_query)
17-
| Q(content__icontains=search_query)
18-
)
19-
20-
return search_query
20+
search_vector = SearchVector("title", "description", "content")
21+
search_query = SearchQuery(query)
22+
23+
if settings.DATABASES["default"]["ENGINE"] == "django.db.backends.postgresql":
24+
# PostgreSQL search
25+
queryset = (
26+
model_queryset.annotate(
27+
search=search_vector,
28+
rank=SearchRank(search_vector, search_query),
29+
)
30+
.filter(search=search_query)
31+
.order_by("-rank")
32+
)
33+
else:
34+
# SQLite3 search
35+
queryset = model_queryset.filter(
36+
Q(title__icontains=query)
37+
| Q(description__icontains=query)
38+
| Q(content__icontains=query)
39+
)
40+
41+
return queryset
2142

2243

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

53+
except InvalidPage:
54+
page_obj = paginator.page(1)
55+
3256
except EmptyPage:
3357
page_obj = paginator.page(paginator.num_pages)
3458

apps/shared/management/commands/createadmin.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.core.management import BaseCommand
55

66
from dotenv import load_dotenv
7+
78
load_dotenv()
89

910

apps/users/admin.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
from django.contrib import admin
2+
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin # noqa
23
from .models import User, UserProfile
34

5+
from unfold.admin import ModelAdmin
6+
47

58
@admin.register(User)
6-
class UserAdmin(admin.ModelAdmin):
9+
class UserAdmin(ModelAdmin):
710
list_display = ["username", "post_count"]
8-
search_fields = ["first_name", "last_name", "username"]
11+
search_fields = ["first_name", "last_name", "username", "email"]
912
list_display_links = ["username"]
1013

1114
def get_post_count(self):
1215
return self.post_count
1316

1417

1518
@admin.register(UserProfile)
16-
class UserProfileAdmin(admin.ModelAdmin):
17-
pass
19+
class UserProfileAdmin(ModelAdmin):
20+
list_display = ["user", "avatar", "bio"]

apps/users/api_endpoints/users/User/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ def _create_user():
102102
_check_user_error_field()
103103
_check_user_passwords_field()
104104
_create_user()
105+
106+
def test_api_user_update(self):
107+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from rest_framework.test import APITestCase
2+
from rest_framework_simplejwt.tokens import RefreshToken
3+
from apps.users.models import UserProfile, User # noqa
4+
5+
6+
class UserProfileApiTestCase(APITestCase):
7+
def setUp(self) -> None:
8+
self.username = "Admin"
9+
self.email = "admin@gmail.com"
10+
self.password = "password"
11+
user = User.objects.create(
12+
username=self.username,
13+
email=self.email,
14+
)
15+
user.set_password(self.password)
16+
user.save()
17+
self.user = user
18+
refresh = RefreshToken.for_user(self.user)
19+
self.token = str(refresh.access_token)
20+
return super().setUp()

apps/users/middleware.py

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
class JWTAuthMiddleware(MiddlewareMixin):
1212
def process_request(self, request):
13+
# If admin auth for session
1314
if request.path.startswith("/admin"):
1415
return
1516

core/asgi.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
"""
2-
ASGI config for config project.
3-
4-
It exposes the ASGI callable as a module-level variable named ``application``.
5-
6-
For more information on this file, see
7-
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
8-
"""
9-
101
import os
112

123
from django.core.asgi import get_asgi_application
134

14-
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
5+
from dotenv import load_dotenv
6+
7+
load_dotenv()
8+
9+
os.environ.setdefault(
10+
"DJANGO_SETTINGS_MODULE",
11+
os.getenv("DJANGO_SETTINGS_MODULE", "core.settings.development"),
12+
)
1513

1614
application = get_asgi_application()
15+
16+
app = application

core/config/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
from .apps import * # noqa
22
from .jwt import * # noqa
33
from .rest_framework import * # noqa
4+
from .unfold_navigation import * # noqa
5+
from .unfold import * # noqa
6+
7+
# from .cheditor5 import * # noqa

core/config/apps.py

+18
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,25 @@
1717
]
1818

1919
THIRD_PARTY_APPS = [
20+
# Admin panel
21+
"unfold",
22+
"unfold.contrib.filters",
23+
"unfold.contrib.forms",
24+
"unfold.contrib.import_export",
25+
"unfold.contrib.guardian",
26+
"unfold.contrib.simple_history",
27+
# Translation
28+
"modeltranslation",
29+
#
30+
"django_ckeditor_5",
31+
# Translation pannel
32+
"rosetta",
33+
# DRF Swaggers
34+
"drf_spectacular",
35+
"drf_spectacular_sidecar",
36+
# Rest Framework
2037
"rest_framework",
38+
# Rest Framework JWT (Json web token)s
2139
"rest_framework_simplejwt",
2240
"rest_framework_simplejwt.token_blacklist",
2341
]

core/config/cheditor5.py

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
customColorPalette = [
2+
{"color": "hsl(4, 90%, 58%)", "label": "Red"},
3+
{"color": "hsl(340, 82%, 52%)", "label": "Pink"},
4+
{"color": "hsl(291, 64%, 42%)", "label": "Purple"},
5+
{"color": "hsl(262, 52%, 47%)", "label": "Deep Purple"},
6+
{"color": "hsl(231, 48%, 48%)", "label": "Indigo"},
7+
{"color": "hsl(207, 90%, 54%)", "label": "Blue"},
8+
]
9+
10+
CKEDITOR_5_CONFIGS = {
11+
"default": {
12+
"toolbar": [
13+
"heading",
14+
"|",
15+
"bold",
16+
"italic",
17+
"link",
18+
"bulletedList",
19+
"numberedList",
20+
"blockQuote",
21+
"imageUpload",
22+
],
23+
},
24+
"extends": {
25+
"blockToolbar": [
26+
"paragraph",
27+
"heading1",
28+
"heading2",
29+
"heading3",
30+
"|",
31+
"bulletedList",
32+
"numberedList",
33+
"|",
34+
"blockQuote",
35+
],
36+
"toolbar": [
37+
"heading",
38+
"|",
39+
"outdent",
40+
"indent",
41+
"|",
42+
"bold",
43+
"italic",
44+
"link",
45+
"underline",
46+
"strikethrough",
47+
"code",
48+
"subscript",
49+
"superscript",
50+
"highlight",
51+
"|",
52+
"codeBlock",
53+
"sourceEditing",
54+
"insertImage",
55+
"bulletedList",
56+
"numberedList",
57+
"todoList",
58+
"|",
59+
"blockQuote",
60+
"imageUpload",
61+
"|",
62+
"fontSize",
63+
"fontFamily",
64+
"fontColor",
65+
"fontBackgroundColor",
66+
"mediaEmbed",
67+
"removeFormat",
68+
"insertTable",
69+
],
70+
"image": {
71+
"toolbar": [
72+
"imageTextAlternative",
73+
"|",
74+
"imageStyle:alignLeft",
75+
"imageStyle:alignRight",
76+
"imageStyle:alignCenter",
77+
"imageStyle:side",
78+
"|",
79+
],
80+
"styles": [
81+
"full",
82+
"side",
83+
"alignLeft",
84+
"alignRight",
85+
"alignCenter",
86+
],
87+
},
88+
"table": {
89+
"contentToolbar": [
90+
"tableColumn",
91+
"tableRow",
92+
"mergeTableCells",
93+
"tableProperties",
94+
"tableCellProperties",
95+
],
96+
"tableProperties": {
97+
"borderColors": customColorPalette,
98+
"backgroundColors": customColorPalette,
99+
},
100+
"tableCellProperties": {
101+
"borderColors": customColorPalette,
102+
"backgroundColors": customColorPalette,
103+
},
104+
},
105+
"heading": {
106+
"options": [
107+
{
108+
"model": "paragraph",
109+
"title": "Paragraph",
110+
"class": "ck-heading_paragraph",
111+
},
112+
{
113+
"model": "heading1",
114+
"view": "h1",
115+
"title": "Heading 1",
116+
"class": "ck-heading_heading1",
117+
},
118+
{
119+
"model": "heading2",
120+
"view": "h2",
121+
"title": "Heading 2",
122+
"class": "ck-heading_heading2",
123+
},
124+
{
125+
"model": "heading3",
126+
"view": "h3",
127+
"title": "Heading 3",
128+
"class": "ck-heading_heading3",
129+
},
130+
]
131+
},
132+
},
133+
"list": {
134+
"properties": {
135+
"styles": "true",
136+
"startIndex": "true",
137+
"reversed": "true",
138+
}
139+
},
140+
}

0 commit comments

Comments
 (0)