Compare commits
34 Commits
bab8f253e7
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| f340b56218 | |||
| fcba840ce9 | |||
| 0d99e508ac | |||
| 315f50e18f | |||
| a931980d09 | |||
| d01dd1034b | |||
| 9d494ab34e | |||
| 1a844132e6 | |||
| 85dcbd8808 | |||
| 5f0df931a7 | |||
| d38d2cd800 | |||
| 6204a57f86 | |||
| 4af4b0c02f | |||
| 6402a5b418 | |||
| 09d66613ea | |||
| b1787200d7 | |||
| 8337f68a01 | |||
| 2022808579 | |||
| 97e7098b9e | |||
| 02fd95fe1f | |||
| 94f129c446 | |||
| 1211f6ebb5 | |||
| e8e900c393 | |||
| 900f23e5f6 | |||
| f619856e41 | |||
| a08c6ad800 | |||
| f04130d769 | |||
| bdc2fbe79b | |||
| 26128b6ac5 | |||
| 6c24770e93 | |||
| 74ded25aa0 | |||
| 93a005bfd4 | |||
| 23f6e6e72a | |||
| 6d176efece |
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@@ -31,7 +31,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
stage('Checkout Code') {
|
stage('Checkout Code') {
|
||||||
steps {
|
steps {
|
||||||
git branch: 'main', credentialsId: 'ssh', url: 'git@github.com:JscorpTech/uzxarid.git'
|
git branch: 'dev', credentialsId: 'muhammadvadud', url: 'https://gitea.felixits.uz/uzxarid/backend.git'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Prepare') {
|
stage('Prepare') {
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ CHANNEL_LAYERS = {
|
|||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"hosts": [(env.str("REDIS_HOST", "redis"), env.int("REDIS_PORT", 6379))],
|
"hosts": [
|
||||||
|
(
|
||||||
|
env.str("REDIS_HOST", "redis"),
|
||||||
|
env.int("REDIS_PORT_6379_TCP_PORT", 6379)
|
||||||
|
)
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ if env.bool("SILK_ENABLED", False):
|
|||||||
"silk.middleware.SilkyMiddleware",
|
"silk.middleware.SilkyMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
ROOT_URLCONF = "config.urls"
|
ROOT_URLCONF = "config.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
@@ -119,7 +118,6 @@ DATE_FORMAT = "d.m.y"
|
|||||||
TIME_FORMAT = "H:i:s"
|
TIME_FORMAT = "H:i:s"
|
||||||
DATE_INPUT_FORMATS = ["%d.%m.%Y", "%Y.%d.%m", "%Y.%d.%m"]
|
DATE_INPUT_FORMATS = ["%d.%m.%Y", "%Y.%d.%m", "%Y.%d.%m"]
|
||||||
|
|
||||||
|
|
||||||
SEEDERS = ["core.apps.accounts.seeder.UserSeeder"]
|
SEEDERS = ["core.apps.accounts.seeder.UserSeeder"]
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
@@ -156,8 +154,6 @@ SILKY_PYTHON_PROFILER = True
|
|||||||
MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en")
|
MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en")
|
||||||
MODELTRANSLATION_DEFAULT_LANGUAGE = "uz"
|
MODELTRANSLATION_DEFAULT_LANGUAGE = "uz"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JST_LANGUAGES = [
|
JST_LANGUAGES = [
|
||||||
{
|
{
|
||||||
"code": "uz",
|
"code": "uz",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from core.apps.accounts.models import SearchHistory
|
from core.apps.accounts.models import SearchHistory, UserLike, UserNotification, Notification, Business
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SearchHistory)
|
@admin.register(SearchHistory)
|
||||||
@@ -10,3 +10,35 @@ class SearchHistoryAdmin(ModelAdmin):
|
|||||||
"id",
|
"id",
|
||||||
"__str__",
|
"__str__",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(UserLike)
|
||||||
|
class UserLikeAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(UserNotification)
|
||||||
|
class UserNotificationAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Notification)
|
||||||
|
class NotificationAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Business)
|
||||||
|
class BusinessAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class CustomUserAdmin(admin.UserAdmin, ModelAdmin):
|
|||||||
"user_permissions",
|
"user_permissions",
|
||||||
"role",
|
"role",
|
||||||
"validated_at",
|
"validated_at",
|
||||||
|
"account_type",
|
||||||
|
"avatar",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
31
core/apps/accounts/migrations/0005_userlike.py
Normal file
31
core/apps/accounts/migrations/0005_userlike.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-26 10:04
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0004_business_searchhistory'),
|
||||||
|
('api', '0013_alter_feedback_comment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserLike',
|
||||||
|
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)),
|
||||||
|
('ad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to='api.admodel', verbose_name='Ad')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'User Like',
|
||||||
|
'verbose_name_plural': 'User Likes',
|
||||||
|
'db_table': 'user_like',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-26 10:06
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0005_userlike'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='business',
|
||||||
|
old_name='address_name',
|
||||||
|
new_name='address',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='business',
|
||||||
|
old_name='latitude',
|
||||||
|
new_name='lat',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='business',
|
||||||
|
old_name='longitude',
|
||||||
|
new_name='long',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-26 12:24
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0006_rename_address_name_business_address_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Notification',
|
||||||
|
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(max_length=255, verbose_name='Title')),
|
||||||
|
('description', models.TextField(verbose_name='Description')),
|
||||||
|
('notification_type', models.CharField(choices=[('System', 'System'), ('Another', 'Another')], max_length=255, verbose_name='Type')),
|
||||||
|
('long', models.FloatField(verbose_name='Long')),
|
||||||
|
('lat', models.FloatField(verbose_name='Lat')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Notification',
|
||||||
|
'verbose_name_plural': 'Notifications',
|
||||||
|
'db_table': 'notification',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserNotification',
|
||||||
|
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)),
|
||||||
|
('is_read', models.BooleanField(default=False, verbose_name='Read')),
|
||||||
|
('notification', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.notification', verbose_name='Notification')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'User Notification',
|
||||||
|
'verbose_name_plural': 'User Notifications',
|
||||||
|
'db_table': 'user_notification',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/apps/accounts/migrations/0008_user_avatar.py
Normal file
18
core/apps/accounts/migrations/0008_user_avatar.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-28 10:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0007_notification_usernotification'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='avatar',
|
||||||
|
field=models.ImageField(default='resources/static/images/default.png', upload_to='avatars/', verbose_name='Avatar'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/apps/accounts/migrations/0009_alter_user_avatar.py
Normal file
18
core/apps/accounts/migrations/0009_alter_user_avatar.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-28 10:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0008_user_avatar'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='avatar',
|
||||||
|
field=models.ImageField(default='avatars/default.png', upload_to='avatars/', verbose_name='Avatar'),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
core/apps/accounts/migrations/0010_alter_business_user.py
Normal file
20
core/apps/accounts/migrations/0010_alter_business_user.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-28 11:02
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0009_alter_user_avatar'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='business',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='business', to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -5,3 +5,4 @@ from .address import * # noqa
|
|||||||
from .business import * # noqa
|
from .business import * # noqa
|
||||||
from .user_like import * # noqa
|
from .user_like import * # noqa
|
||||||
from .search_history import * # noqa
|
from .search_history import * # noqa
|
||||||
|
from .notification import * # noqa
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ from django.contrib.auth import get_user_model
|
|||||||
|
|
||||||
class Business(AbstractBaseModel):
|
class Business(AbstractBaseModel):
|
||||||
name = models.CharField(max_length=255, verbose_name=_('Business Name'))
|
name = models.CharField(max_length=255, verbose_name=_('Business Name'))
|
||||||
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
|
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name="business",
|
||||||
|
verbose_name=_('User'))
|
||||||
work_time = models.CharField(max_length=255, verbose_name=_('Work Time'))
|
work_time = models.CharField(max_length=255, verbose_name=_('Work Time'))
|
||||||
contact = models.CharField(max_length=255, verbose_name=_('Contact'))
|
contact = models.CharField(max_length=255, verbose_name=_('Contact'))
|
||||||
instagram = models.CharField(max_length=255, verbose_name=_('Instagram'))
|
instagram = models.CharField(max_length=255, verbose_name=_('Instagram'))
|
||||||
facebook = models.CharField(max_length=255, verbose_name=_('Facebook'))
|
facebook = models.CharField(max_length=255, verbose_name=_('Facebook'))
|
||||||
telegram = models.CharField(max_length=255, verbose_name=_('Telegram'))
|
telegram = models.CharField(max_length=255, verbose_name=_('Telegram'))
|
||||||
bio = models.TextField(verbose_name=_('Bio'))
|
bio = models.TextField(verbose_name=_('Bio'))
|
||||||
address_name = models.CharField(max_length=255, verbose_name=_('Address Name'))
|
address = models.CharField(max_length=255, verbose_name=_('Address Name'))
|
||||||
longitude = models.FloatField(verbose_name=_('Longitude'))
|
long = models.FloatField(verbose_name=_('Longitude'))
|
||||||
latitude = models.FloatField(verbose_name=_('Latitude'))
|
lat = models.FloatField(verbose_name=_('Latitude'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from django_core.models.base import AbstractBaseModel
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from core.apps.accounts.choices import NotificationType
|
from core.apps.accounts.choices import NotificationType
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
class Notification(AbstractBaseModel):
|
class Notification(AbstractBaseModel):
|
||||||
@@ -12,6 +13,10 @@ class Notification(AbstractBaseModel):
|
|||||||
long = models.FloatField(verbose_name=_("Long"))
|
long = models.FloatField(verbose_name=_("Long"))
|
||||||
lat = models.FloatField(verbose_name=_("Lat"))
|
lat = models.FloatField(verbose_name=_("Lat"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _baker(cls):
|
||||||
|
return baker.make(cls)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|
||||||
@@ -26,6 +31,10 @@ class UserNotification(AbstractBaseModel):
|
|||||||
notification = models.ForeignKey(Notification, verbose_name=_("Notification"), on_delete=models.CASCADE)
|
notification = models.ForeignKey(Notification, verbose_name=_("Notification"), on_delete=models.CASCADE)
|
||||||
is_read = models.BooleanField(verbose_name=_("Read"), default=False)
|
is_read = models.BooleanField(verbose_name=_("Read"), default=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _baker(cls):
|
||||||
|
return baker.make(cls)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ from django.db import models
|
|||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
class SearchHistory(AbstractBaseModel):
|
class SearchHistory(AbstractBaseModel):
|
||||||
value = models.CharField(verbose_name=_('Search History'), max_length=255)
|
value = models.CharField(verbose_name=_('Search History'), max_length=255)
|
||||||
user = models.ForeignKey(get_user_model(), verbose_name=_('User'), on_delete=models.CASCADE)
|
user = models.ForeignKey(get_user_model(), verbose_name=_('User'), on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _baker(cls):
|
||||||
|
return baker.make(cls)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from django.db import models
|
|||||||
|
|
||||||
from ..choices import RoleChoice, AccountType
|
from ..choices import RoleChoice, AccountType
|
||||||
from ..managers import UserManager
|
from ..managers import UserManager
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
class User(auth_models.AbstractUser):
|
class User(auth_models.AbstractUser):
|
||||||
@@ -18,9 +19,13 @@ class User(auth_models.AbstractUser):
|
|||||||
choices=RoleChoice,
|
choices=RoleChoice,
|
||||||
default=RoleChoice.USER,
|
default=RoleChoice.USER,
|
||||||
)
|
)
|
||||||
|
avatar = models.ImageField("Avatar", upload_to="avatars/", default="avatars/default.png")
|
||||||
USERNAME_FIELD = "phone"
|
USERNAME_FIELD = "phone"
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _baker(cls):
|
||||||
|
return baker.make(cls)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.phone
|
return self.phone
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db import models
|
||||||
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
|
class UserLike(AbstractBaseModel):
|
||||||
|
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"), related_name="likes")
|
||||||
|
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"), related_name="likes")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _baker(cls):
|
||||||
|
return baker.make(cls)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.pk)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "user_like"
|
||||||
|
verbose_name = _("User Like")
|
||||||
|
verbose_name_plural = _("User Likes")
|
||||||
|
|||||||
@@ -19,12 +19,6 @@ class LoginSerializer(serializers.Serializer):
|
|||||||
class RegisterSerializer(serializers.ModelSerializer):
|
class RegisterSerializer(serializers.ModelSerializer):
|
||||||
phone = serializers.CharField(max_length=255)
|
phone = serializers.CharField(max_length=255)
|
||||||
|
|
||||||
def validate_phone(self, value):
|
|
||||||
user = get_user_model().objects.filter(phone=value, validated_at__isnull=False)
|
|
||||||
if user.exists():
|
|
||||||
raise exceptions.ValidationError(_("Phone number already registered."), code="unique")
|
|
||||||
return value
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = get_user_model()
|
model = get_user_model()
|
||||||
fields = ["phone"]
|
fields = ["phone"]
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
from .category import * # noqa
|
from .category import * # noqa
|
||||||
|
from .ad import * # noqa
|
||||||
|
from .ad_items import * # noqa
|
||||||
|
from .feedback import * # noqa
|
||||||
|
from .banner import * # noqa
|
||||||
|
|||||||
1
core/apps/api/admin/ad/__init__.py
Normal file
1
core/apps/api/admin/ad/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .ad import * # noqa
|
||||||
23
core/apps/api/admin/ad/ad.py
Normal file
23
core/apps/api/admin/ad/ad.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import AdModel, AdImage, AdVariant
|
||||||
|
|
||||||
|
|
||||||
|
class AdImageInline(admin.TabularInline):
|
||||||
|
model = AdImage
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
class AdVariantInline(admin.TabularInline):
|
||||||
|
model = AdVariant
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AdModel)
|
||||||
|
class AdModelAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
inlines = [AdImageInline, AdVariantInline]
|
||||||
5
core/apps/api/admin/ad_items/__init__.py
Normal file
5
core/apps/api/admin/ad_items/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .tags import * # noqa
|
||||||
|
from .ad_top_plan import * # noqa
|
||||||
|
from .ad_images import * # noqa
|
||||||
|
from .ad_variant import * # noqa
|
||||||
|
from .ad_options import * # noqa
|
||||||
12
core/apps/api/admin/ad_items/ad_images.py
Normal file
12
core/apps/api/admin/ad_items/ad_images.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import AdImage
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AdImage)
|
||||||
|
class AdImageAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
12
core/apps/api/admin/ad_items/ad_options.py
Normal file
12
core/apps/api/admin/ad_items/ad_options.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import AdOption
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AdOption)
|
||||||
|
class AdOptionAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
12
core/apps/api/admin/ad_items/ad_top_plan.py
Normal file
12
core/apps/api/admin/ad_items/ad_top_plan.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import AdTopPlan
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AdTopPlan)
|
||||||
|
class AdTopPlanAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
12
core/apps/api/admin/ad_items/ad_variant.py
Normal file
12
core/apps/api/admin/ad_items/ad_variant.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import AdVariant
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AdVariant)
|
||||||
|
class AdVariantAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
12
core/apps/api/admin/ad_items/tags.py
Normal file
12
core/apps/api/admin/ad_items/tags.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import Tags
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Tags)
|
||||||
|
class TagsAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
1
core/apps/api/admin/banner/__init__.py
Normal file
1
core/apps/api/admin/banner/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .banner import * # noqa
|
||||||
12
core/apps/api/admin/banner/banner.py
Normal file
12
core/apps/api/admin/banner/banner.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import Banner
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Banner)
|
||||||
|
class BannerAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
1
core/apps/api/admin/feedback/__init__.py
Normal file
1
core/apps/api/admin/feedback/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .feedback import * # noqa
|
||||||
20
core/apps/api/admin/feedback/feedback.py
Normal file
20
core/apps/api/admin/feedback/feedback.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from core.apps.api.models import Feedback, FeedbackImages
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Feedback)
|
||||||
|
class FeedbackAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(FeedbackImages)
|
||||||
|
class FeedbackImagesAdmin(ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"__str__",
|
||||||
|
)
|
||||||
@@ -1 +1,2 @@
|
|||||||
from .category import * # noqa
|
from .category import * # noqa
|
||||||
|
from .ad import * # noqa
|
||||||
|
|||||||
85
core/apps/api/filters/ad.py
Normal file
85
core/apps/api/filters/ad.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from django_filters import rest_framework as filters
|
||||||
|
from django.db.models import (
|
||||||
|
F, Case, When, FloatField, ExpressionWrapper, Subquery, OuterRef
|
||||||
|
)
|
||||||
|
from core.apps.api.models import AdVariant, AdModel
|
||||||
|
|
||||||
|
|
||||||
|
class AdFilter(filters.FilterSet):
|
||||||
|
min_price = filters.NumberFilter(field_name="real_price", lookup_expr="gte")
|
||||||
|
max_price = filters.NumberFilter(field_name="real_price", lookup_expr="lte")
|
||||||
|
|
||||||
|
size = filters.CharFilter(method="filter_by_size")
|
||||||
|
color = filters.CharFilter(method="filter_by_color")
|
||||||
|
category = filters.CharFilter(field_name="category__name", lookup_expr="icontains")
|
||||||
|
created_at = filters.DateTimeFilter(field_name="created_at", lookup_expr="gte")
|
||||||
|
has_discount = filters.BooleanFilter(method="filter_has_discount")
|
||||||
|
has_normal_user = filters.BooleanFilter(method="filter_has_normal_user")
|
||||||
|
has_business_user = filters.BooleanFilter(method="filter_has_business_user")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AdModel
|
||||||
|
fields = ["min_price", "max_price"]
|
||||||
|
|
||||||
|
def filter_has_business_user(self, queryset, name, value):
|
||||||
|
return queryset.filter(
|
||||||
|
user__account_type="business"
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_has_normal_user(self, queryset, name, value):
|
||||||
|
return queryset.filter(
|
||||||
|
user__account_type="personal"
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_has_discount(self, queryset, name, value):
|
||||||
|
return queryset.filter(
|
||||||
|
variants__discount__gte=1
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def filter_by_size(self, queryset, name, value):
|
||||||
|
return queryset.filter(
|
||||||
|
variants__variant="Size",
|
||||||
|
variants__value__iexact=value
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def filter_by_color(self, queryset, name, value):
|
||||||
|
return queryset.filter(
|
||||||
|
variants__variant="Color",
|
||||||
|
variants__value__iexact=value
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
variant_real_price_expr = Case(
|
||||||
|
When(discount=-1, then=F("price")),
|
||||||
|
When(
|
||||||
|
discount__gte=0,
|
||||||
|
then=ExpressionWrapper(
|
||||||
|
F("price") - (F("price") * F("discount") / 100),
|
||||||
|
output_field=FloatField()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
output_field=FloatField()
|
||||||
|
)
|
||||||
|
|
||||||
|
cheapest_variant_qs = (
|
||||||
|
AdVariant.objects
|
||||||
|
.filter(ad=OuterRef("pk"))
|
||||||
|
.annotate(real_price=variant_real_price_expr)
|
||||||
|
.order_by("real_price")
|
||||||
|
.values("real_price")[:1]
|
||||||
|
)
|
||||||
|
|
||||||
|
ad_real_price = F("price")
|
||||||
|
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
real_price=Case(
|
||||||
|
When(
|
||||||
|
variants__isnull=False,
|
||||||
|
then=Subquery(cheapest_variant_qs)
|
||||||
|
),
|
||||||
|
default=ad_real_price,
|
||||||
|
output_field=FloatField()
|
||||||
|
)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
return super().filter_queryset(queryset)
|
||||||
18
core/apps/api/migrations/0005_admodel_star.py
Normal file
18
core/apps/api/migrations/0005_admodel_star.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 10:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0004_category_category_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='admodel',
|
||||||
|
name='star',
|
||||||
|
field=models.FloatField(default=0.0, verbose_name='Star'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
core/apps/api/migrations/0006_alter_adimage_ad.py
Normal file
19
core/apps/api/migrations/0006_alter_adimage_ad.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 10:30
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0005_admodel_star'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adimage',
|
||||||
|
name='ad',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.admodel', verbose_name='Ad'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
core/apps/api/migrations/0007_alter_advariant_ad.py
Normal file
19
core/apps/api/migrations/0007_alter_advariant_ad.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 10:35
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0006_alter_adimage_ad'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='advariant',
|
||||||
|
name='ad',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='api.admodel'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
core/apps/api/migrations/0008_adimage_ad_variant.py
Normal file
19
core/apps/api/migrations/0008_adimage_ad_variant.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 10:43
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0007_alter_advariant_ad'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adimage',
|
||||||
|
name='ad_variant',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.advariant', verbose_name='Ad Variant'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
core/apps/api/migrations/0009_alter_adimage_ad_variant.py
Normal file
19
core/apps/api/migrations/0009_alter_adimage_ad_variant.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 10:45
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0008_adimage_ad_variant'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adimage',
|
||||||
|
name='ad_variant',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='api.advariant', verbose_name='Ad Variant'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 11:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0009_alter_adimage_ad_variant'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='admodel',
|
||||||
|
name='star',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='admodel',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(default=1, upload_to='', verbose_name='Image'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
19
core/apps/api/migrations/0011_alter_feedback_ad.py
Normal file
19
core/apps/api/migrations/0011_alter_feedback_ad.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 11:23
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0010_remove_admodel_star_admodel_image'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='feedback',
|
||||||
|
name='ad',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='api.admodel', verbose_name='Ad'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-25 11:38
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0011_alter_feedback_ad'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='feedback',
|
||||||
|
old_name='command',
|
||||||
|
new_name='comment',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/apps/api/migrations/0013_alter_feedback_comment.py
Normal file
18
core/apps/api/migrations/0013_alter_feedback_comment.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-26 10:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0012_rename_command_feedback_comment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='feedback',
|
||||||
|
name='comment',
|
||||||
|
field=models.CharField(max_length=255, verbose_name='Comment'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
core/apps/api/migrations/0014_admodel_description.py
Normal file
19
core/apps/api/migrations/0014_admodel_description.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-28 11:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0013_alter_feedback_comment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='admodel',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(default=1, verbose_name='Description'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 07:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0013_alter_feedback_comment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='name_en',
|
||||||
|
field=models.CharField(max_length=255, null=True, verbose_name='Category Name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='name_ru',
|
||||||
|
field=models.CharField(max_length=255, null=True, verbose_name='Category Name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='name_uz',
|
||||||
|
field=models.CharField(max_length=255, null=True, verbose_name='Category Name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
core/apps/api/migrations/0015_alter_adoption_ad.py
Normal file
19
core/apps/api/migrations/0015_alter_adoption_ad.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-28 11:45
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0014_admodel_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adoption',
|
||||||
|
name='ad',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='api.admodel', verbose_name='Ad'),
|
||||||
|
),
|
||||||
|
]
|
||||||
14
core/apps/api/migrations/0016_merge_20251202_1732.py
Normal file
14
core/apps/api/migrations/0016_merge_20251202_1732.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-02 12:32
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0014_category_name_en_category_name_ru_category_name_uz'),
|
||||||
|
('api', '0015_alter_adoption_ad'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
@@ -3,6 +3,7 @@ from django_core.models.base import AbstractBaseModel
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from core.apps.api.choices.ad_type import AdType, AdCategoryType
|
from core.apps.api.choices.ad_type import AdType, AdCategoryType
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
class AdModel(AbstractBaseModel):
|
class AdModel(AbstractBaseModel):
|
||||||
@@ -16,6 +17,12 @@ class AdModel(AbstractBaseModel):
|
|||||||
physical_product = models.BooleanField(verbose_name=_("Physical product"), default=False)
|
physical_product = models.BooleanField(verbose_name=_("Physical product"), default=False)
|
||||||
plan = models.ForeignKey("api.AdTopPlan", on_delete=models.CASCADE, verbose_name=_("Plan"))
|
plan = models.ForeignKey("api.AdTopPlan", on_delete=models.CASCADE, verbose_name=_("Plan"))
|
||||||
tags = models.ManyToManyField("api.Tags", verbose_name=_("Tags"))
|
tags = models.ManyToManyField("api.Tags", verbose_name=_("Tags"))
|
||||||
|
image = models.ImageField(verbose_name=_("Image"))
|
||||||
|
description = models.TextField(verbose_name=_("Description"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _baker(cls):
|
||||||
|
return baker.make(cls)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from django.db import models
|
|||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from core.apps.api.choices import AdCategoryType
|
from core.apps.api.choices import AdCategoryType
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
class Category(AbstractBaseModel):
|
class Category(AbstractBaseModel):
|
||||||
@@ -13,12 +14,9 @@ class Category(AbstractBaseModel):
|
|||||||
category_type = models.CharField(max_length=255, verbose_name=_('Category Type'), choices=AdCategoryType,
|
category_type = models.CharField(max_length=255, verbose_name=_('Category Type'), choices=AdCategoryType,
|
||||||
default=AdCategoryType.PRODUCT)
|
default=AdCategoryType.PRODUCT)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
@classmethod
|
||||||
if self.parent:
|
def _baker(cls):
|
||||||
self.level = self.parent.level + 1
|
return baker.make(cls)
|
||||||
else:
|
|
||||||
self.level = 0
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from core.apps.api.models.ad.ad import AdModel
|
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
|
||||||
|
|
||||||
class AdImage(AbstractBaseModel):
|
class AdImage(AbstractBaseModel):
|
||||||
image = models.ImageField(verbose_name=_("Image"), upload_to="ads/images/")
|
image = models.ImageField(verbose_name=_("Image"), upload_to="ads/images/")
|
||||||
ad = models.ForeignKey(AdModel, verbose_name=_("Ad"), on_delete=models.CASCADE)
|
ad = models.ForeignKey(AdModel, verbose_name=_("Ad"), related_name="images",
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
ad_variant = models.ForeignKey("api.AdVariant", verbose_name=_("Ad Variant"), null=True, blank=True,
|
||||||
|
related_name="images",
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from core.apps.api.models import AdModel
|
|||||||
class AdOption(AbstractBaseModel):
|
class AdOption(AbstractBaseModel):
|
||||||
name = models.CharField(_("Name"), max_length=255)
|
name = models.CharField(_("Name"), max_length=255)
|
||||||
value = models.CharField(_("Value"), max_length=255)
|
value = models.CharField(_("Value"), max_length=255)
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE)
|
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, related_name="options", verbose_name=_("Ad"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from core.apps.api.choices.ad_variant_type import AdVariantType
|
|||||||
|
|
||||||
|
|
||||||
class AdVariant(AbstractBaseModel):
|
class AdVariant(AbstractBaseModel):
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE)
|
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, related_name="variants")
|
||||||
variant = models.CharField(max_length=255, choices=AdVariantType, db_index=True)
|
variant = models.CharField(max_length=255, choices=AdVariantType, db_index=True)
|
||||||
value = models.CharField(max_length=255)
|
value = models.CharField(max_length=255)
|
||||||
is_available = models.CharField(max_length=255)
|
is_available = models.CharField(max_length=255)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_core.models.base import AbstractBaseModel
|
from django_core.models.base import AbstractBaseModel
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
class Banner(AbstractBaseModel):
|
class Banner(AbstractBaseModel):
|
||||||
@@ -12,6 +13,10 @@ class Banner(AbstractBaseModel):
|
|||||||
bg_color = models.CharField(verbose_name=_("BG Color"), max_length=255)
|
bg_color = models.CharField(verbose_name=_("BG Color"), max_length=255)
|
||||||
text_color = models.CharField(verbose_name=_("Text Color"), max_length=255)
|
text_color = models.CharField(verbose_name=_("Text Color"), max_length=255)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _baker(cls):
|
||||||
|
return baker.make(cls)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ from core.apps.api.models.ad import AdModel
|
|||||||
class Feedback(AbstractBaseModel):
|
class Feedback(AbstractBaseModel):
|
||||||
star = models.IntegerField(default=0, verbose_name=_("Star"))
|
star = models.IntegerField(default=0, verbose_name=_("Star"))
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"))
|
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, verbose_name=_("User"))
|
||||||
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"))
|
ad = models.ForeignKey(AdModel, on_delete=models.CASCADE, verbose_name=_("Ad"), related_name="feedback")
|
||||||
command = models.CharField(max_length=255, verbose_name=_("Command"))
|
comment = models.CharField(max_length=255, verbose_name=_("Comment"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
from .category import * # noqa
|
from .category import * # noqa
|
||||||
from .search import * # noqa
|
from .search import * # noqa
|
||||||
|
from .ad import * # noqa
|
||||||
|
from .user import * # noqa
|
||||||
|
from .notification import * # noqa
|
||||||
|
from .banner import * # noqa
|
||||||
|
|||||||
2
core/apps/api/serializers/ad/__init__.py
Normal file
2
core/apps/api/serializers/ad/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .home_api import * # noqa
|
||||||
|
from .ad import * # noqa
|
||||||
270
core/apps/api/serializers/ad/ad.py
Normal file
270
core/apps/api/serializers/ad/ad.py
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from django.db.models import Avg
|
||||||
|
|
||||||
|
from core.apps.accounts.choices import AccountType
|
||||||
|
from core.apps.api.models import AdModel, AdVariant, Category, AdImage, AdOption
|
||||||
|
from core.apps.accounts.models import UserLike
|
||||||
|
from core.apps.api.choices import AdVariantType
|
||||||
|
|
||||||
|
|
||||||
|
class AdOptionSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AdOption
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"value",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Category
|
||||||
|
fields = ["id", "name"]
|
||||||
|
|
||||||
|
|
||||||
|
class AdImageSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AdImage
|
||||||
|
fields = [
|
||||||
|
"image",
|
||||||
|
"ad_variant"
|
||||||
|
]
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
data = super().to_representation(instance)
|
||||||
|
|
||||||
|
if instance.ad_variant is None:
|
||||||
|
data.pop("ad_variant", None)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class AdVariantSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AdVariant
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"variant",
|
||||||
|
"value",
|
||||||
|
"is_available",
|
||||||
|
"price",
|
||||||
|
"discount",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAdSerializer(serializers.ModelSerializer):
|
||||||
|
is_liked = serializers.SerializerMethodField()
|
||||||
|
star = serializers.SerializerMethodField()
|
||||||
|
comment_count = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AdModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"price",
|
||||||
|
"image",
|
||||||
|
"is_liked",
|
||||||
|
"star",
|
||||||
|
"comment_count",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_star(self, obj):
|
||||||
|
avg = obj.feedback.aggregate(avg=Avg("star"))["avg"]
|
||||||
|
return avg or 0
|
||||||
|
|
||||||
|
def get_comment_count(self, obj):
|
||||||
|
count = obj.feedback.count()
|
||||||
|
return count or 0
|
||||||
|
|
||||||
|
def get_is_liked(self, obj):
|
||||||
|
request = self.context.get("request")
|
||||||
|
user = getattr(request, "user", None)
|
||||||
|
|
||||||
|
if not user or not user.is_authenticated:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return UserLike.objects.filter(user=user, ad=obj).exists()
|
||||||
|
|
||||||
|
|
||||||
|
class ListAdSerializer(BaseAdSerializer):
|
||||||
|
price = serializers.SerializerMethodField()
|
||||||
|
discount = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta(BaseAdSerializer.Meta):
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"price",
|
||||||
|
"image",
|
||||||
|
"star",
|
||||||
|
"comment_count",
|
||||||
|
"discount",
|
||||||
|
"is_liked",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_first_variant(self, obj):
|
||||||
|
if not hasattr(self, "_variant_cache"):
|
||||||
|
self._variant_cache = {}
|
||||||
|
if obj.id not in self._variant_cache:
|
||||||
|
self._variant_cache[obj.id] = obj.variants.order_by("price").first()
|
||||||
|
return self._variant_cache[obj.id]
|
||||||
|
|
||||||
|
def get_price(self, obj):
|
||||||
|
variant = self._get_first_variant(obj)
|
||||||
|
if not variant:
|
||||||
|
return obj.price
|
||||||
|
return variant.price if variant else 0
|
||||||
|
|
||||||
|
def get_discount(self, obj):
|
||||||
|
variant = self._get_first_variant(obj)
|
||||||
|
return variant.discount if variant else -1.0
|
||||||
|
|
||||||
|
|
||||||
|
class FullListAdSerializer(serializers.Serializer):
|
||||||
|
ads = ListAdSerializer(many=True)
|
||||||
|
categories = serializers.SerializerMethodField()
|
||||||
|
colors = serializers.SerializerMethodField()
|
||||||
|
sizes = serializers.SerializerMethodField()
|
||||||
|
min_price = serializers.SerializerMethodField()
|
||||||
|
max_price = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_categories(self, obj):
|
||||||
|
ads = obj.get("ads", [])
|
||||||
|
|
||||||
|
category_ids = set()
|
||||||
|
categories = []
|
||||||
|
|
||||||
|
for ad in ads:
|
||||||
|
category = ad.category
|
||||||
|
if category and category.id not in category_ids:
|
||||||
|
category_ids.add(category.id)
|
||||||
|
categories.append(category)
|
||||||
|
|
||||||
|
return CategorySerializer(categories, many=True).data
|
||||||
|
|
||||||
|
def get_colors(self, obj):
|
||||||
|
ads = obj.get("ads", [])
|
||||||
|
color_values = set()
|
||||||
|
|
||||||
|
for ad in ads:
|
||||||
|
variants = getattr(ad, "variants", [])
|
||||||
|
for v in variants.all():
|
||||||
|
if v.variant == AdVariantType.COLOR:
|
||||||
|
color_values.add(v.value)
|
||||||
|
|
||||||
|
return list(color_values)
|
||||||
|
|
||||||
|
def get_sizes(self, obj):
|
||||||
|
ads = obj.get("ads", [])
|
||||||
|
size_values = set()
|
||||||
|
|
||||||
|
for ad in ads:
|
||||||
|
variants = getattr(ad, "variants", [])
|
||||||
|
for v in variants.all():
|
||||||
|
if v.variant == AdVariantType.SIZE:
|
||||||
|
size_values.add(v.value)
|
||||||
|
|
||||||
|
return list(size_values)
|
||||||
|
|
||||||
|
def get_min_price(self, obj):
|
||||||
|
ads = obj.get("ads", [])
|
||||||
|
prices = []
|
||||||
|
|
||||||
|
for ad in ads:
|
||||||
|
ad_data = ListAdSerializer(ad, context=self.context).data
|
||||||
|
price = ad_data.get("price")
|
||||||
|
if price is not None:
|
||||||
|
prices.append(price)
|
||||||
|
|
||||||
|
return min(prices) if prices else None
|
||||||
|
|
||||||
|
def get_max_price(self, obj):
|
||||||
|
ads = obj.get("ads", [])
|
||||||
|
prices = []
|
||||||
|
|
||||||
|
for ad in ads:
|
||||||
|
ad_data = ListAdSerializer(ad, context=self.context).data
|
||||||
|
price = ad_data.get("price")
|
||||||
|
if price is not None:
|
||||||
|
prices.append(price)
|
||||||
|
|
||||||
|
return max(prices) if prices else None
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveAdSerializer(BaseAdSerializer):
|
||||||
|
variants = AdVariantSerializer(many=True, read_only=True)
|
||||||
|
images = serializers.SerializerMethodField()
|
||||||
|
colors = serializers.SerializerMethodField()
|
||||||
|
sizes = serializers.SerializerMethodField()
|
||||||
|
creator = serializers.SerializerMethodField()
|
||||||
|
options = AdOptionSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta(BaseAdSerializer.Meta):
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"price",
|
||||||
|
"image",
|
||||||
|
"star",
|
||||||
|
"comment_count",
|
||||||
|
"is_liked",
|
||||||
|
"images",
|
||||||
|
"variants",
|
||||||
|
"colors",
|
||||||
|
"sizes",
|
||||||
|
"creator",
|
||||||
|
"description",
|
||||||
|
"options"
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_images(self, obj):
|
||||||
|
objects = obj.images.all()
|
||||||
|
return AdImageSerializer(objects, many=True, context=self.context).data
|
||||||
|
|
||||||
|
def get_colors(self, obj):
|
||||||
|
color_values = set()
|
||||||
|
|
||||||
|
variants = getattr(obj, "variants", [])
|
||||||
|
for v in variants.all():
|
||||||
|
if v.variant == AdVariantType.COLOR:
|
||||||
|
color_values.add(v.value)
|
||||||
|
|
||||||
|
return list(color_values)
|
||||||
|
|
||||||
|
def get_sizes(self, obj):
|
||||||
|
size_values = set()
|
||||||
|
|
||||||
|
variants = getattr(obj, "variants", [])
|
||||||
|
for v in variants.all():
|
||||||
|
if v.variant == AdVariantType.SIZE:
|
||||||
|
size_values.add(v.value)
|
||||||
|
return list(size_values)
|
||||||
|
|
||||||
|
def get_creator(self, obj):
|
||||||
|
user = obj.user
|
||||||
|
user_type = user.account_type
|
||||||
|
request = self.context.get("request")
|
||||||
|
|
||||||
|
avatar_url = request.build_absolute_uri(user.avatar.url) if user.avatar else None
|
||||||
|
|
||||||
|
if user_type == AccountType.BUSINESS:
|
||||||
|
return {
|
||||||
|
"username": user.business.name,
|
||||||
|
"avatar": avatar_url,
|
||||||
|
"create_at": user.validated_at,
|
||||||
|
"last_live": "endi qo'shamiz! waiting pls ))"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
username = f"{user.first_name} {user.last_name}"
|
||||||
|
return {
|
||||||
|
"username": username,
|
||||||
|
"avatar": avatar_url,
|
||||||
|
"create_at": user.validated_at,
|
||||||
|
"last_live": "endi qo'shamiz! waiting pls ))"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateAdSerializer(BaseAdSerializer):
|
||||||
|
class Meta(BaseAdSerializer.Meta): ...
|
||||||
83
core/apps/api/serializers/ad/home_api.py
Normal file
83
core/apps/api/serializers/ad/home_api.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from django.db.models import Avg
|
||||||
|
from core.apps.api.models import AdModel, AdVariant
|
||||||
|
from core.apps.accounts.models import UserLike
|
||||||
|
|
||||||
|
|
||||||
|
class AdVariantSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AdVariant
|
||||||
|
fields = [
|
||||||
|
"variant",
|
||||||
|
"value",
|
||||||
|
"is_available",
|
||||||
|
"price",
|
||||||
|
"discount",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseHomeAdSerializer(serializers.ModelSerializer):
|
||||||
|
star = serializers.SerializerMethodField()
|
||||||
|
comment_count = serializers.SerializerMethodField()
|
||||||
|
price = serializers.SerializerMethodField()
|
||||||
|
discount = serializers.SerializerMethodField()
|
||||||
|
is_liked = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AdModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"price",
|
||||||
|
"image",
|
||||||
|
"star",
|
||||||
|
"comment_count",
|
||||||
|
"discount",
|
||||||
|
"is_liked",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_first_variant(self, obj):
|
||||||
|
if not hasattr(self, "_variant_cache"):
|
||||||
|
self._variant_cache = {}
|
||||||
|
if obj.id not in self._variant_cache:
|
||||||
|
self._variant_cache[obj.id] = obj.variants.order_by("price").first()
|
||||||
|
return self._variant_cache[obj.id]
|
||||||
|
|
||||||
|
def get_price(self, obj):
|
||||||
|
variant = self._get_first_variant(obj)
|
||||||
|
if not variant:
|
||||||
|
return obj.price
|
||||||
|
return variant.price if variant else 0
|
||||||
|
|
||||||
|
def get_discount(self, obj):
|
||||||
|
variant = self._get_first_variant(obj)
|
||||||
|
return variant.discount if variant else -1.0
|
||||||
|
|
||||||
|
def get_star(self, obj):
|
||||||
|
avg = obj.feedback.aggregate(avg=Avg("star"))["avg"]
|
||||||
|
return avg or 0
|
||||||
|
|
||||||
|
def get_comment_count(self, obj):
|
||||||
|
count = obj.feedback.count()
|
||||||
|
return count or 0
|
||||||
|
|
||||||
|
def get_is_liked(self, obj):
|
||||||
|
request = self.context.get("request")
|
||||||
|
user = getattr(request, "user", None)
|
||||||
|
|
||||||
|
if not user or not user.is_authenticated:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return UserLike.objects.filter(user=user, ad=obj).exists()
|
||||||
|
|
||||||
|
|
||||||
|
class ListHomeAdSerializer(BaseHomeAdSerializer):
|
||||||
|
class Meta(BaseHomeAdSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveHomeAdSerializer(BaseHomeAdSerializer):
|
||||||
|
class Meta(BaseHomeAdSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreateHomeAdSerializer(BaseHomeAdSerializer):
|
||||||
|
class Meta(BaseHomeAdSerializer.Meta): ...
|
||||||
1
core/apps/api/serializers/banner/__init__.py
Normal file
1
core/apps/api/serializers/banner/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .banner import * # noqa
|
||||||
28
core/apps/api/serializers/banner/banner.py
Normal file
28
core/apps/api/serializers/banner/banner.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from core.apps.api.models import Banner
|
||||||
|
|
||||||
|
|
||||||
|
class BaseBannerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Banner
|
||||||
|
fields = [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"mobile_image",
|
||||||
|
"desktop_image",
|
||||||
|
"link",
|
||||||
|
"bg_color",
|
||||||
|
"text_color",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ListBannerSerializer(BaseBannerSerializer):
|
||||||
|
class Meta(BaseBannerSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveBannerSerializer(BaseBannerSerializer):
|
||||||
|
class Meta(BaseBannerSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreateBannerSerializer(BaseBannerSerializer):
|
||||||
|
class Meta(BaseBannerSerializer.Meta): ...
|
||||||
1
core/apps/api/serializers/notification/__init__.py
Normal file
1
core/apps/api/serializers/notification/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .natification import * # noqa
|
||||||
49
core/apps/api/serializers/notification/natification.py
Normal file
49
core/apps/api/serializers/notification/natification.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from core.apps.accounts.models import UserNotification, Notification
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Notification
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"long",
|
||||||
|
"lat"
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserNotificationSerializer(serializers.ModelSerializer):
|
||||||
|
notification = NotificationSerializer(many=False, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UserNotification
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"is_read",
|
||||||
|
"notification",
|
||||||
|
"created_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ListUserNotificationSerializer(BaseUserNotificationSerializer):
|
||||||
|
class Meta(BaseUserNotificationSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUserNotificationSerializer(BaseUserNotificationSerializer):
|
||||||
|
class Meta(BaseUserNotificationSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreateUserNotificationSerializer(BaseUserNotificationSerializer):
|
||||||
|
class Meta(BaseUserNotificationSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateUserNotificationSerializer(BaseUserNotificationSerializer):
|
||||||
|
class Meta(BaseUserNotificationSerializer.Meta):
|
||||||
|
fields = [
|
||||||
|
"is_read"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
from .search import * # noqa
|
from .search import * # noqa
|
||||||
|
from .search_ads import * # noqa
|
||||||
|
|||||||
58
core/apps/api/serializers/search/search_ads.py
Normal file
58
core/apps/api/serializers/search/search_ads.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSearchAdsSerializer(serializers.ModelSerializer):
|
||||||
|
category = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AdModel
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"image",
|
||||||
|
"category"
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_category(self, obj):
|
||||||
|
request = self.context.get("request")
|
||||||
|
|
||||||
|
lang = request.headers.get("Accept-Language", "uz")
|
||||||
|
lang = lang.split(",")[0].split("-")[0]
|
||||||
|
|
||||||
|
if lang not in ["uz", "ru", "en"]:
|
||||||
|
lang = "uz"
|
||||||
|
|
||||||
|
category = obj.category
|
||||||
|
if not category:
|
||||||
|
return None
|
||||||
|
|
||||||
|
chain = []
|
||||||
|
current = category
|
||||||
|
while current:
|
||||||
|
chain.append(current)
|
||||||
|
current = current.parent
|
||||||
|
|
||||||
|
chain = list(reversed(chain))
|
||||||
|
|
||||||
|
result = None
|
||||||
|
for cat in reversed(chain):
|
||||||
|
result = {
|
||||||
|
"id": cat.id,
|
||||||
|
"name": getattr(cat, f"name_{lang}"),
|
||||||
|
"children": result
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ListSearchAdsSerializer(BaseSearchAdsSerializer):
|
||||||
|
class Meta(BaseSearchAdsSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveSearchAdsSerializer(BaseSearchAdsSerializer):
|
||||||
|
class Meta(BaseSearchAdsSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSearchAdsSerializer(BaseSearchAdsSerializer):
|
||||||
|
class Meta(BaseSearchAdsSerializer.Meta): ...
|
||||||
1
core/apps/api/serializers/user/__init__.py
Normal file
1
core/apps/api/serializers/user/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .ad_like import * # noqa
|
||||||
45
core/apps/api/serializers/user/ad_like.py
Normal file
45
core/apps/api/serializers/user/ad_like.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from core.apps.accounts.models import UserLike
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
from core.apps.api.serializers.ad.home_api import ListHomeAdSerializer
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserLikeSerializer(serializers.ModelSerializer):
|
||||||
|
ad = ListHomeAdSerializer(many=False, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UserLike
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"ad",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ListUserLikeSerializer(BaseUserLikeSerializer):
|
||||||
|
class Meta(BaseUserLikeSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUserLikeSerializer(BaseUserLikeSerializer):
|
||||||
|
class Meta(BaseUserLikeSerializer.Meta): ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreateUserLikeSerializer(BaseUserLikeSerializer):
|
||||||
|
ad = serializers.PrimaryKeyRelatedField(queryset=AdModel.objects.all())
|
||||||
|
|
||||||
|
class Meta(BaseUserLikeSerializer.Meta): ...
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
user = self.context["request"].user
|
||||||
|
ad = data["ad"]
|
||||||
|
|
||||||
|
if UserLike.objects.filter(user=user, ad=ad).exists():
|
||||||
|
raise ValidationError({"detail": _("Siz bu e’longa allaqachon like bosgansiz.")})
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
validated_data['user'] = self.context['request'].user
|
||||||
|
like = UserLike.objects.create(**validated_data)
|
||||||
|
return like
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from .category import * # noqa
|
||||||
|
from .ad import * # noqa
|
||||||
|
from .search import * # noqa
|
||||||
|
from .user import * # noqa
|
||||||
|
from .banner import * # noqa
|
||||||
|
|||||||
2
core/apps/api/tests/ad/__init__.py
Normal file
2
core/apps/api/tests/ad/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .test_home_api import * # noqa
|
||||||
|
from .test_ad import * # noqa
|
||||||
58
core/apps/api/tests/ad/test_ad.py
Normal file
58
core/apps/api/tests/ad/test_ad.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return AdModel._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
##client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("ads-list"),
|
||||||
|
"retrieve": reverse("ads-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-not-found": reverse("ads-detail", kwargs={"pk": 1000}),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve_not_found(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve-not-found"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert data_resp["status"] is False
|
||||||
58
core/apps/api/tests/ad/test_home_api.py
Normal file
58
core/apps/api/tests/ad/test_home_api.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return AdModel._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
##client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("home-ad-list"),
|
||||||
|
"retrieve": reverse("home-ad-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-not-found": reverse("home-ad-detail", kwargs={"pk": 1000}),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve_not_found(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve-not-found"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert data_resp["status"] is False
|
||||||
1
core/apps/api/tests/banner/__init__.py
Normal file
1
core/apps/api/tests/banner/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .test_banner import * # noqa
|
||||||
58
core/apps/api/tests/banner/test_banner.py
Normal file
58
core/apps/api/tests/banner/test_banner.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.api.models import Banner
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return Banner._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
## client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("banner-list"),
|
||||||
|
"retrieve": reverse("banner-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-not-found": reverse("banner-detail", kwargs={"pk": 1000}),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve_not_found(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve-not-found"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert data_resp["status"] is False
|
||||||
1
core/apps/api/tests/category/__init__.py
Normal file
1
core/apps/api/tests/category/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .test_category import * # noqa
|
||||||
88
core/apps/api/tests/category/test_category.py
Normal file
88
core/apps/api/tests/category/test_category.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.api.models import Category
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return Category._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
##client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("category-list"),
|
||||||
|
"retrieve": reverse("category-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-not-found": reverse("category-detail", kwargs={"pk": 1000}),
|
||||||
|
"list-category-home": reverse("category-home-list"),
|
||||||
|
"retrieve-category-home": reverse("category-home-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-category-home-not-found": reverse("category-home-detail", kwargs={"pk": 1000}),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve_not_found(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve-not-found"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert data_resp["status"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_category_home_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list-category-home"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_category_home_retrieve(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve-category-home"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_category_home_retrieve_not_found(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve-category-home-not-found"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert data_resp["status"] is False
|
||||||
2
core/apps/api/tests/search/__init__.py
Normal file
2
core/apps/api/tests/search/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .test_search_history import * # noqa
|
||||||
|
from .test_search_ads import * # noqa
|
||||||
38
core/apps/api/tests/search/test_search_ads.py
Normal file
38
core/apps/api/tests/search/test_search_ads.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return AdModel._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
##client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("search-ads-list"),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
56
core/apps/api/tests/search/test_search_history.py
Normal file
56
core/apps/api/tests/search/test_search_history.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.accounts.models import SearchHistory
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return SearchHistory._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("search-history-list"),
|
||||||
|
"retrieve": reverse("search-history-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-not-found": reverse("search-history-detail", kwargs={"pk": 1000}),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_create(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.post(urls["list"], data={"value": "test-text"})
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_destroy(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.delete(urls["retrieve"])
|
||||||
|
assert response.status_code == 204
|
||||||
2
core/apps/api/tests/user/__init__.py
Normal file
2
core/apps/api/tests/user/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .test_user_like import * # noqa
|
||||||
|
from .test_user_notification import * # noqa
|
||||||
61
core/apps/api/tests/user/test_user_like.py
Normal file
61
core/apps/api/tests/user/test_user_like.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.accounts.models import UserLike, AdModel
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return UserLike._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ad(db):
|
||||||
|
return AdModel._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("user-like-list"),
|
||||||
|
"retrieve": reverse("user-like-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-not-found": reverse("user-like-detail", kwargs={"pk": 1000}),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_create(data, ad):
|
||||||
|
urls, client, instance = data
|
||||||
|
response = client.post(urls["list"], data={"ad": ad.pk})
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_destroy(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.delete(urls["retrieve"])
|
||||||
|
assert response.status_code == 204
|
||||||
78
core/apps/api/tests/user/test_user_notification.py
Normal file
78
core/apps/api/tests/user/test_user_notification.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core.apps.accounts.models import UserNotification
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance(db):
|
||||||
|
return UserNotification._baker()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_client(instance):
|
||||||
|
client = APIClient()
|
||||||
|
client.force_authenticate(user=instance.user)
|
||||||
|
return client, instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def data(api_client):
|
||||||
|
client, instance = api_client
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
"list": reverse("notification-list"),
|
||||||
|
"retrieve": reverse("notification-detail", kwargs={"pk": instance.pk}),
|
||||||
|
"retrieve-not-found": reverse("notification-detail", kwargs={"pk": 1000}),
|
||||||
|
"notification-read": reverse("notification-notification-read", kwargs={"pk": instance.pk}),
|
||||||
|
"all-read": reverse("notification-all-read"),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["list"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retrieve_not_found(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.get(urls["retrieve-not-found"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert data_resp["status"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_notification_reads(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.post(urls["notification-read"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_all_read(data):
|
||||||
|
urls, client, _ = data
|
||||||
|
response = client.post(urls["all-read"])
|
||||||
|
data_resp = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert data_resp["status"] is True
|
||||||
1
core/apps/api/translation/__init__.py
Normal file
1
core/apps/api/translation/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .category import * # noqa
|
||||||
10
core/apps/api/translation/category.py
Normal file
10
core/apps/api/translation/category.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from modeltranslation.translator import TranslationOptions, register
|
||||||
|
|
||||||
|
from core.apps.api.models import Category
|
||||||
|
|
||||||
|
|
||||||
|
@register(Category)
|
||||||
|
class CategoryTranslation(TranslationOptions):
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
]
|
||||||
@@ -1,9 +1,26 @@
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from core.apps.api.views import CategoryViewSet, SearchHistoryViewSet
|
from core.apps.api.views import (
|
||||||
|
BannerViewSet,
|
||||||
|
CategoryHomeApiViewSet,
|
||||||
|
CategoryViewSet,
|
||||||
|
HomeAdApiView,
|
||||||
|
NotificationViewSet,
|
||||||
|
SearchAdsViewSet,
|
||||||
|
SearchHistoryViewSet,
|
||||||
|
UserLikeViewSet,
|
||||||
|
AdsView,
|
||||||
|
)
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
|
router.register("search-ads", SearchAdsViewSet, basename="search-ads")
|
||||||
|
router.register("ads", AdsView, basename="ads")
|
||||||
|
router.register("banner", BannerViewSet, basename="banner")
|
||||||
|
router.register("notification", NotificationViewSet, basename="notification")
|
||||||
|
router.register("user-like", UserLikeViewSet, basename="user-like")
|
||||||
router.register("category", CategoryViewSet, basename="category")
|
router.register("category", CategoryViewSet, basename="category")
|
||||||
|
router.register("category-home", CategoryHomeApiViewSet, basename="category-home")
|
||||||
router.register("search-history", SearchHistoryViewSet, basename="search-history")
|
router.register("search-history", SearchHistoryViewSet, basename="search-history")
|
||||||
|
router.register("home-ad", HomeAdApiView, basename="home-ad")
|
||||||
urlpatterns = [path("", include(router.urls))]
|
urlpatterns = [path("", include(router.urls))]
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
from .category import * # noqa
|
from .category import * # noqa
|
||||||
from .search import * # noqa
|
from .search import * # noqa
|
||||||
|
from .ad import * # noqa
|
||||||
|
from .user import * # noqa
|
||||||
|
from .notification import * # noqa
|
||||||
|
from .banner import * # noqa
|
||||||
|
|||||||
2
core/apps/api/views/ad/__init__.py
Normal file
2
core/apps/api/views/ad/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .home_api import * # noqa
|
||||||
|
from .ad import * # noqa
|
||||||
43
core/apps/api/views/ad/ad.py
Normal file
43
core/apps/api/views/ad/ad.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from django_core.mixins import BaseViewSetMixin
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from core.apps.api.filters import AdFilter
|
||||||
|
from core.apps.api.serializers.ad.ad import (
|
||||||
|
FullListAdSerializer,
|
||||||
|
RetrieveAdSerializer,
|
||||||
|
CreateAdSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=["Ads"])
|
||||||
|
class AdsView(BaseViewSetMixin, ReadOnlyModelViewSet):
|
||||||
|
queryset = AdModel.objects.all().order_by("-created_at")
|
||||||
|
serializer_class = FullListAdSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_class = AdFilter
|
||||||
|
|
||||||
|
action_permission_classes = {}
|
||||||
|
action_serializer_class = {
|
||||||
|
"list": FullListAdSerializer,
|
||||||
|
"retrieve": RetrieveAdSerializer,
|
||||||
|
"create": CreateAdSerializer,
|
||||||
|
}
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
page = self.paginate_queryset(queryset)
|
||||||
|
|
||||||
|
if page is not None:
|
||||||
|
data = {"ads": page}
|
||||||
|
serializer = FullListAdSerializer(data, context={"request": request})
|
||||||
|
return self.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
data = {"ads": queryset}
|
||||||
|
serializer = FullListAdSerializer(data, context={"request": request})
|
||||||
|
response = self.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
return response
|
||||||
24
core/apps/api/views/ad/home_api.py
Normal file
24
core/apps/api/views/ad/home_api.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from django_core.mixins import BaseViewSetMixin
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
from core.apps.api.serializers.ad.home_api import (
|
||||||
|
ListHomeAdSerializer,
|
||||||
|
CreateHomeAdSerializer,
|
||||||
|
RetrieveHomeAdSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=["Home Ad Api"])
|
||||||
|
class HomeAdApiView(BaseViewSetMixin, ReadOnlyModelViewSet):
|
||||||
|
queryset = AdModel.objects.all()
|
||||||
|
serializer_class = ListHomeAdSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
|
action_permission_classes = {}
|
||||||
|
action_serializer_class = {
|
||||||
|
"list": ListHomeAdSerializer,
|
||||||
|
"retrieve": RetrieveHomeAdSerializer,
|
||||||
|
"create": CreateHomeAdSerializer,
|
||||||
|
}
|
||||||
1
core/apps/api/views/banner/__init__.py
Normal file
1
core/apps/api/views/banner/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .banner import * # noqa
|
||||||
25
core/apps/api/views/banner/banner.py
Normal file
25
core/apps/api/views/banner/banner.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from django_core.mixins import BaseViewSetMixin
|
||||||
|
|
||||||
|
from core.apps.api.models import Banner
|
||||||
|
from core.apps.api.serializers.banner import (
|
||||||
|
ListBannerSerializer,
|
||||||
|
RetrieveBannerSerializer,
|
||||||
|
CreateBannerSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Banner'])
|
||||||
|
class BannerViewSet(BaseViewSetMixin, ReadOnlyModelViewSet):
|
||||||
|
queryset = Banner.objects.all()
|
||||||
|
serializer_class = ListBannerSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
|
action_permission_classes = {}
|
||||||
|
action_serializers = {
|
||||||
|
'list': ListBannerSerializer,
|
||||||
|
'retrieve': RetrieveBannerSerializer,
|
||||||
|
'create': CreateBannerSerializer,
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ from core.apps.api.serializers.category import (
|
|||||||
|
|
||||||
@extend_schema(tags=["Category"])
|
@extend_schema(tags=["Category"])
|
||||||
class CategoryViewSet(BaseViewSetMixin, ReadOnlyModelViewSet):
|
class CategoryViewSet(BaseViewSetMixin, ReadOnlyModelViewSet):
|
||||||
|
queryset = Category.objects.filter(level=0)
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
serializer_class = ListCategorySerializer
|
serializer_class = ListCategorySerializer
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
@@ -28,18 +29,19 @@ class CategoryViewSet(BaseViewSetMixin, ReadOnlyModelViewSet):
|
|||||||
"create": CreateCategorySerializer,
|
"create": CreateCategorySerializer,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
qs = Category.objects.all()
|
|
||||||
|
|
||||||
if not self.request.query_params:
|
@extend_schema(tags=["Category"])
|
||||||
qs = qs.filter(level=0)
|
class CategoryHomeApiViewSet(BaseViewSetMixin, ReadOnlyModelViewSet):
|
||||||
return qs
|
queryset = Category.objects.all()
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
serializer_class = ListCategoryNoChildSerializer
|
||||||
|
pagination_class = None
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
filterset_class = CategoryFilter
|
||||||
|
|
||||||
def get_serializer_class(self):
|
action_permission_classes = {}
|
||||||
if "show_home" in self.request.query_params:
|
action_serializer_class = {
|
||||||
return ListCategoryNoChildSerializer
|
"list": ListCategoryNoChildSerializer,
|
||||||
|
"retrieve": RetrieveCategorySerializer,
|
||||||
if hasattr(self, 'action_serializer_class'):
|
"create": CreateCategorySerializer,
|
||||||
return self.action_serializer_class.get(self.action, self.serializer_class)
|
}
|
||||||
|
|
||||||
return super().get_serializer_class()
|
|
||||||
|
|||||||
1
core/apps/api/views/notification/__init__.py
Normal file
1
core/apps/api/views/notification/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .notification import * # noqa
|
||||||
54
core/apps/api/views/notification/notification.py
Normal file
54
core/apps/api/views/notification/notification.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from xmlrpc.client import Fault
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
from django_core.mixins.base import BaseViewSetMixin
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from core.apps.accounts.models import UserNotification
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from core.apps.api.serializers.notification import (
|
||||||
|
ListUserNotificationSerializer,
|
||||||
|
CreateUserNotificationSerializer,
|
||||||
|
RetrieveUserNotificationSerializer,
|
||||||
|
UpdateUserNotificationSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=["Notification"])
|
||||||
|
class NotificationViewSet(BaseViewSetMixin, ReadOnlyModelViewSet):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
serializer_class = ListUserNotificationSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
|
||||||
|
action_permission_classes = {}
|
||||||
|
action_serializer_class = {
|
||||||
|
"list": ListUserNotificationSerializer,
|
||||||
|
"retrieve": RetrieveUserNotificationSerializer,
|
||||||
|
"create": CreateUserNotificationSerializer,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = UserNotification.objects.filter(user=self.request.user).order_by("-created_at")
|
||||||
|
return qs
|
||||||
|
|
||||||
|
@action(detail=True, methods=["post"])
|
||||||
|
def notification_read(self, request, pk=None):
|
||||||
|
notification = self.get_object()
|
||||||
|
serializer = UpdateUserNotificationSerializer(
|
||||||
|
notification,
|
||||||
|
data={"is_read": True},
|
||||||
|
partial=True
|
||||||
|
)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@action(detail=False, methods=["post"])
|
||||||
|
def all_read(self, request):
|
||||||
|
user = request.user
|
||||||
|
UserNotification.objects.filter(user=user, is_read=False).update(is_read=True)
|
||||||
|
return Response({"detail": _("Barcha xabarlar o'qilgan qilindi!")}, status=status.HTTP_200_OK)
|
||||||
@@ -1 +1,2 @@
|
|||||||
from .search import * # noqa
|
from .search import * # noqa
|
||||||
|
from .search_ads import * # noqa
|
||||||
|
|||||||
48
core/apps/api/views/search/search_ads.py
Normal file
48
core/apps/api/views/search/search_ads.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from rest_framework import mixins
|
||||||
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
from django_core.mixins.base import BaseViewSetMixin
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from core.apps.api.models import AdModel
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.filters import SearchFilter
|
||||||
|
from core.apps.api.serializers.search import (
|
||||||
|
ListSearchAdsSerializer,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['Search Ads'])
|
||||||
|
class SearchAdsViewSet(BaseViewSetMixin, mixins.ListModelMixin, GenericViewSet):
|
||||||
|
queryset = AdModel.objects.all().order_by('-created_at')
|
||||||
|
serializer_class = ListSearchAdsSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
http_method_names = ['get']
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
search_fields = ["name"]
|
||||||
|
|
||||||
|
action_permission_classes = {}
|
||||||
|
action_serializer_class = {
|
||||||
|
'list': ListSearchAdsSerializer,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
|
||||||
|
search_text = self.request.query_params.get('search')
|
||||||
|
|
||||||
|
if search_text:
|
||||||
|
field = f"name"
|
||||||
|
queryset = queryset.filter(**{f"{field}__icontains": search_text})
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
response = super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
|
if isinstance(response.data, dict) and "results" in response.data:
|
||||||
|
response.data["results"] = response.data["results"][:5]
|
||||||
|
else:
|
||||||
|
response.data = response.data[:5]
|
||||||
|
|
||||||
|
return response
|
||||||
1
core/apps/api/views/user/__init__.py
Normal file
1
core/apps/api/views/user/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .ad_like import * # noqa
|
||||||
28
core/apps/api/views/user/ad_like.py
Normal file
28
core/apps/api/views/user/ad_like.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from rest_framework import mixins
|
||||||
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
from django_core.mixins.base import BaseViewSetMixin
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from core.apps.accounts.models import UserLike
|
||||||
|
from core.apps.api.serializers.user.ad_like import (
|
||||||
|
ListUserLikeSerializer,
|
||||||
|
CreateUserLikeSerializer,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['User Like'])
|
||||||
|
class UserLikeViewSet(BaseViewSetMixin, mixins.ListModelMixin, mixins.CreateModelMixin,
|
||||||
|
mixins.DestroyModelMixin, GenericViewSet):
|
||||||
|
serializer_class = ListUserLikeSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
http_method_names = ['get', 'post', 'delete']
|
||||||
|
action_permission_classes = {}
|
||||||
|
action_serializer_class = {
|
||||||
|
'list': ListUserLikeSerializer,
|
||||||
|
'create': CreateUserLikeSerializer,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = UserLike.objects.filter(user=self.request.user).order_by('-id')
|
||||||
|
return queryset
|
||||||
@@ -24,8 +24,13 @@ class UserService(sms.SmsService):
|
|||||||
"phone": phone,
|
"phone": phone,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if user.is_superuser:
|
||||||
|
user.is_superuser = True
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
user.set_unusable_password()
|
user.set_unusable_password()
|
||||||
user.save()
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
def send_confirmation(self, phone) -> bool:
|
def send_confirmation(self, phone) -> bool:
|
||||||
try:
|
try:
|
||||||
@@ -40,6 +45,7 @@ class UserService(sms.SmsService):
|
|||||||
"""
|
"""
|
||||||
Create user if user not found
|
Create user if user not found
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if user.validated_at is None:
|
if user.validated_at is None:
|
||||||
user.validated_at = datetime.now()
|
user.validated_at = datetime.now()
|
||||||
user.save()
|
user.save()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
from .cache import * # noqa
|
from .cache import * # noqa
|
||||||
from .console import * # noqa
|
from .console import * # noqa
|
||||||
from .core import * # noqa
|
from .core import * # noqa
|
||||||
|
from .language import * # noqa
|
||||||
|
|||||||
8
core/utils/language.py
Normal file
8
core/utils/language.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
def get_request_lang(request):
|
||||||
|
lang = request.headers.get("Accept-Language", "uz")
|
||||||
|
lang = lang.split(",")[0].split("-")[0].lower()
|
||||||
|
|
||||||
|
if lang not in ["uz", "ru", "en"]:
|
||||||
|
lang = "uz"
|
||||||
|
|
||||||
|
return lang
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user