gold eggs backend
Some checks failed
Build and Push to Docker Hub / build-test-push (push) Failing after 1m55s
Some checks failed
Build and Push to Docker Hub / build-test-push (push) Failing after 1m55s
This commit is contained in:
6
core/http/__init__.py
Executable file
6
core/http/__init__.py
Executable file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HttpConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "core.http"
|
||||
3
core/http/admin/__init__.py
Normal file
3
core/http/admin/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .core import * # noqa
|
||||
from .another import * # noqa
|
||||
from .user import * # noqa
|
||||
66
core/http/admin/another.py
Normal file
66
core/http/admin/another.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from django.contrib import admin
|
||||
from django.db import models as db_model
|
||||
from django_select2 import forms as django_select2
|
||||
from import_export import admin as import_export
|
||||
from modeltranslation import admin as modeltranslation
|
||||
|
||||
from core.http import forms, models
|
||||
|
||||
|
||||
class PostInline(admin.TabularInline):
|
||||
model = models.Post.comments.through
|
||||
fields = ["comment"]
|
||||
extra = 1
|
||||
|
||||
|
||||
class TagsInline(admin.TabularInline):
|
||||
model = models.Post.tags.through
|
||||
extra = 1
|
||||
|
||||
|
||||
class PostAdmin(
|
||||
modeltranslation.TabbedTranslationAdmin,
|
||||
import_export.ImportExportModelAdmin,
|
||||
): # noqa
|
||||
fields: tuple = ("title", "desc", "image", "tags")
|
||||
search_fields: list = ["title", "desc"]
|
||||
list_filter = ["title"]
|
||||
required_languages: tuple = ("uz",)
|
||||
form = forms.PostAdminForm
|
||||
inlines = [PostInline]
|
||||
formfield_overrides = {
|
||||
db_model.ManyToManyField: {
|
||||
"widget": django_select2.Select2MultipleWidget
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TagsAdmin(import_export.ImportExportModelAdmin):
|
||||
fields: tuple = ("name",)
|
||||
search_fields: list = ["name"]
|
||||
|
||||
|
||||
class FrontendInline(admin.TabularInline):
|
||||
model = models.FrontendTranslation.comments.through
|
||||
fields = ["comment"]
|
||||
extra = 1
|
||||
|
||||
|
||||
class FrontendTranslationAdmin(
|
||||
modeltranslation.TabbedTranslationAdmin,
|
||||
import_export.ImportExportModelAdmin,
|
||||
): # noqa
|
||||
fields: tuple = ("key", "value")
|
||||
required_languages: tuple = ("uz",)
|
||||
list_display = ["key", "value"]
|
||||
inlines = [FrontendInline]
|
||||
|
||||
|
||||
class SmsConfirmAdmin(admin.ModelAdmin):
|
||||
list_display = ["phone", "code", "resend_count", "try_count"]
|
||||
search_fields = ["phone", "code"]
|
||||
|
||||
|
||||
class CommentAdmin(import_export.ImportExportModelAdmin):
|
||||
list_display = ["text"]
|
||||
search_fields = ["text"]
|
||||
22
core/http/admin/core.py
Normal file
22
core/http/admin/core.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Admin panel register
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import models as db_models
|
||||
|
||||
from core.http import models
|
||||
from core.http.admin import user
|
||||
from core.http.admin import another
|
||||
|
||||
admin.site.unregister(db_models.Group)
|
||||
admin.site.register(db_models.Group, user.GroupAdmin)
|
||||
|
||||
admin.site.register(models.Tags, another.TagsAdmin)
|
||||
admin.site.register(models.Post, another.PostAdmin)
|
||||
admin.site.register(models.User, user.CustomUserAdmin)
|
||||
admin.site.register(models.Comment, another.CommentAdmin)
|
||||
admin.site.register(models.SmsConfirm, another.SmsConfirmAdmin)
|
||||
admin.site.register(
|
||||
models.FrontendTranslation, another.FrontendTranslationAdmin
|
||||
) # noqa
|
||||
47
core/http/admin/user.py
Normal file
47
core/http/admin/user.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from django.contrib.auth import admin
|
||||
from import_export import admin as import_export
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
|
||||
from core.http.forms import CustomUserCreationForm
|
||||
|
||||
|
||||
class CustomUserAdmin(admin.UserAdmin, ImportExportModelAdmin):
|
||||
add_form = CustomUserCreationForm
|
||||
list_display = ["id", "phone", "first_name", "last_name", "role"]
|
||||
search_fields = ["phone", "first_name", "last_name"]
|
||||
list_filter = ["role"]
|
||||
fieldsets = (
|
||||
(None, {"fields": ("username", "phone", "password")}),
|
||||
(
|
||||
"Personal info",
|
||||
{"fields": ("first_name", "last_name", "email", "role", "avatar", "fcm_token")},
|
||||
),
|
||||
(
|
||||
"Permissions",
|
||||
{
|
||||
"fields": (
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"user_permissions",
|
||||
)
|
||||
},
|
||||
),
|
||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": ("phone", "password1", "password2"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class GroupAdmin(import_export.ImportExportModelAdmin):
|
||||
list_display = ["name"]
|
||||
search_fields = ["name"]
|
||||
filter_horizontal = ("permissions",)
|
||||
0
core/http/database/__init__.py
Executable file
0
core/http/database/__init__.py
Executable file
1
core/http/database/factory/__init__.py
Executable file
1
core/http/database/factory/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
from .core import * # noqa
|
||||
31
core/http/database/factory/core.py
Executable file
31
core/http/database/factory/core.py
Executable file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Create a new fake User/Post
|
||||
"""
|
||||
|
||||
from core.http import models
|
||||
from core.utils import factory
|
||||
|
||||
|
||||
class UserFactory(factory.BaseFaker):
|
||||
model = models.User
|
||||
|
||||
def handle(self):
|
||||
"""
|
||||
Factory method
|
||||
"""
|
||||
return {
|
||||
"first_name": self.faker.first_name(),
|
||||
"username": self.faker.user_name(),
|
||||
"phone": self.faker.phone_number(),
|
||||
}
|
||||
|
||||
|
||||
class PostFactory(factory.BaseFaker):
|
||||
model = models.Post
|
||||
|
||||
def handle(self):
|
||||
return {
|
||||
"title": self.faker.name(),
|
||||
"desc": self.faker.text(),
|
||||
"image": self.faker.image_url(),
|
||||
}
|
||||
1
core/http/database/seeder/__init__.py
Executable file
1
core/http/database/seeder/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
from .core import * # noqa
|
||||
11
core/http/database/seeder/core.py
Executable file
11
core/http/database/seeder/core.py
Executable file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Create a new user/superuser
|
||||
"""
|
||||
|
||||
from core.http import models
|
||||
|
||||
|
||||
class UserSeeder:
|
||||
def run(self):
|
||||
models.User.objects.create_user("998943990509", "2309")
|
||||
models.User.objects.create_superuser("998888112309", "2309")
|
||||
2
core/http/forms/__init__.py
Normal file
2
core/http/forms/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .another import * # noqa
|
||||
from .user import * # noqa
|
||||
17
core/http/forms/another.py
Normal file
17
core/http/forms/another.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Project base forms
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django_ckeditor_5 import widgets
|
||||
|
||||
from core.http import models
|
||||
|
||||
|
||||
class PostAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = models.Post
|
||||
widgets = {
|
||||
"desc": widgets.CKEditor5Widget(),
|
||||
}
|
||||
fields = "__all__"
|
||||
8
core/http/forms/user.py
Normal file
8
core/http/forms/user.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from core.http.models import User
|
||||
|
||||
|
||||
class CustomUserCreationForm(UserCreationForm):
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = User
|
||||
fields = ("id", "phone")
|
||||
1
core/http/management/__init__.py
Executable file
1
core/http/management/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
from .core import * # noqa
|
||||
0
core/http/management/commands/__init__.py
Executable file
0
core/http/management/commands/__init__.py
Executable file
16
core/http/management/commands/clearcache.py
Executable file
16
core/http/management/commands/clearcache.py
Executable file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Clear cache command
|
||||
"""
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from core.utils import console
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Clear all caches"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
cache.clear()
|
||||
console.Console.success("Cache cleared successfully")
|
||||
10
core/http/management/core.py
Executable file
10
core/http/management/core.py
Executable file
@@ -0,0 +1,10 @@
|
||||
from django.db.models.manager import BaseManager as BManager
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
|
||||
class BaseQuerySet(QuerySet):
|
||||
pass
|
||||
|
||||
|
||||
class BaseManager(BManager):
|
||||
pass
|
||||
1
core/http/managers/__init__.py
Normal file
1
core/http/managers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .user import * # noqa
|
||||
24
core/http/managers/user.py
Normal file
24
core/http/managers/user.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from django.contrib.auth import base_user
|
||||
|
||||
|
||||
class UserManager(base_user.BaseUserManager):
|
||||
|
||||
def create_user(self, phone, password=None, **extra_fields):
|
||||
if not phone:
|
||||
raise ValueError("The phone number must be set")
|
||||
|
||||
user = self.model(phone=phone, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, phone, password=None, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", True)
|
||||
extra_fields.setdefault("is_superuser", True)
|
||||
|
||||
if extra_fields.get("is_staff") is not True:
|
||||
raise ValueError("Superuser must have is_staff=True.")
|
||||
if extra_fields.get("is_superuser") is not True:
|
||||
raise ValueError("Superuser must have is_superuser=True.")
|
||||
|
||||
return self.create_user(phone, password, **extra_fields)
|
||||
276
core/http/migrations/0001_initial.py
Normal file
276
core/http/migrations/0001_initial.py
Normal file
@@ -0,0 +1,276 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-22 11:17
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BaseComment",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"polymorphic_ctype",
|
||||
models.ForeignKey(
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="polymorphic_%(app_label)s.%(class)s_set+",
|
||||
to="contenttypes.contenttype",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
"base_manager_name": "objects",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Comment",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("text", models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SmsConfirm",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("code", models.IntegerField()),
|
||||
("try_count", models.IntegerField(default=0)),
|
||||
("resend_count", models.IntegerField(default=0)),
|
||||
("phone", models.CharField(max_length=20)),
|
||||
("expire_time", models.DateTimeField(blank=True, null=True)),
|
||||
("unlock_time", models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"resend_unlock_time",
|
||||
models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Tags",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Tag",
|
||||
"verbose_name_plural": "Tags",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"password",
|
||||
models.CharField(max_length=128, verbose_name="password"),
|
||||
),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_superuser",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"first_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name="first name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name="last name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True,
|
||||
max_length=254,
|
||||
verbose_name="email address",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_staff",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates whether the user can log into this admin site.",
|
||||
verbose_name="staff status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name="date joined",
|
||||
),
|
||||
),
|
||||
("phone", models.CharField(max_length=255, unique=True)),
|
||||
(
|
||||
"username",
|
||||
models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("validated_at", models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "user",
|
||||
"verbose_name_plural": "users",
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FrontendTranslation",
|
||||
fields=[
|
||||
(
|
||||
"basecomment_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="http.basecomment",
|
||||
),
|
||||
),
|
||||
("key", models.CharField(max_length=255, unique=True)),
|
||||
("value", models.TextField()),
|
||||
("value_uz", models.TextField(null=True)),
|
||||
("value_ru", models.TextField(null=True)),
|
||||
("value_en", models.TextField(null=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Frontend Translation",
|
||||
"verbose_name_plural": "Frontend Translations",
|
||||
},
|
||||
bases=("http.basecomment",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="basecomment",
|
||||
name="comments",
|
||||
field=models.ManyToManyField(to="http.comment"),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Post",
|
||||
fields=[
|
||||
(
|
||||
"basecomment_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="http.basecomment",
|
||||
),
|
||||
),
|
||||
("title", models.CharField(max_length=255)),
|
||||
("title_uz", models.CharField(max_length=255, null=True)),
|
||||
("title_ru", models.CharField(max_length=255, null=True)),
|
||||
("title_en", models.CharField(max_length=255, null=True)),
|
||||
("desc", models.TextField()),
|
||||
("desc_uz", models.TextField(null=True)),
|
||||
("desc_ru", models.TextField(null=True)),
|
||||
("desc_en", models.TextField(null=True)),
|
||||
("image", models.ImageField(blank=True, upload_to="posts/")),
|
||||
("tags", models.ManyToManyField(to="http.tags")),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
"base_manager_name": "objects",
|
||||
},
|
||||
bases=("http.basecomment",),
|
||||
),
|
||||
]
|
||||
34
core/http/migrations/0002_user_avatar_user_role.py
Normal file
34
core/http/migrations/0002_user_avatar_user_role.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-23 10:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("http", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="avatar",
|
||||
field=models.ImageField(
|
||||
blank=True, null=True, upload_to="avatars/"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="role",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("admin", "Admin"),
|
||||
("market", "Market"),
|
||||
("courier", "Courier"),
|
||||
("sklad", "Sklad"),
|
||||
],
|
||||
default="market",
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
]
|
||||
27
core/http/migrations/0003_alter_user_role.py
Normal file
27
core/http/migrations/0003_alter_user_role.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-23 11:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("http", "0002_user_avatar_user_role"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="role",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("admin", "Admin"),
|
||||
("market", "Market"),
|
||||
("courier", "Courier"),
|
||||
("sklad", "Sklad"),
|
||||
],
|
||||
default="admin",
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
]
|
||||
23
core/http/migrations/0004_alter_user_avatar.py
Normal file
23
core/http/migrations/0004_alter_user_avatar.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-22 13:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("http", "0003_alter_user_role"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="avatar",
|
||||
field=models.ImageField(
|
||||
blank=True,
|
||||
default="avatars/golden_eggs.png",
|
||||
null=True,
|
||||
upload_to="avatars/",
|
||||
),
|
||||
),
|
||||
]
|
||||
18
core/http/migrations/0005_user_fcm_token.py
Normal file
18
core/http/migrations/0005_user_fcm_token.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.8 on 2024-08-29 08:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("http", "0004_alter_user_avatar"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="fcm_token",
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
0
core/http/migrations/__init__.py
Normal file
0
core/http/migrations/__init__.py
Normal file
3
core/http/models/__init__.py
Normal file
3
core/http/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .another import * # noqa
|
||||
from .base import * # noqa
|
||||
from .user import * # noqa
|
||||
47
core/http/models/another.py
Normal file
47
core/http/models/another.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from django.db import models
|
||||
from polymorphic import models as polymorphic
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
class Tags(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Tag")
|
||||
verbose_name_plural = _("Tags")
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
text = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.text
|
||||
|
||||
|
||||
class BaseComment(polymorphic.PolymorphicModel):
|
||||
comments = models.ManyToManyField(Comment)
|
||||
|
||||
|
||||
class Post(BaseComment):
|
||||
title = models.CharField(max_length=255)
|
||||
desc = models.TextField()
|
||||
image = models.ImageField(upload_to="posts/", blank=True)
|
||||
tags = models.ManyToManyField(Tags)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class FrontendTranslation(BaseComment):
|
||||
key = models.CharField(max_length=255, unique=True)
|
||||
value = models.TextField()
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Frontend Translation")
|
||||
verbose_name_plural = _("Frontend Translations")
|
||||
9
core/http/models/base.py
Normal file
9
core/http/models/base.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class AbstractBaseModel(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
110
core/http/models/user.py
Normal file
110
core/http/models/user.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import math
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.http import managers
|
||||
|
||||
|
||||
class User(auth_models.AbstractUser):
|
||||
ROLE_CHOICES = (
|
||||
("admin", _("Admin")),
|
||||
("market", _("Market")),
|
||||
("courier", _("Courier")),
|
||||
("sklad", _("Sklad")),
|
||||
)
|
||||
|
||||
phone = models.CharField(max_length=255, unique=True)
|
||||
username = models.CharField(max_length=255, null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
validated_at = models.DateTimeField(null=True, blank=True)
|
||||
USERNAME_FIELD = "phone"
|
||||
objects = managers.UserManager()
|
||||
|
||||
avatar = models.ImageField(
|
||||
upload_to="avatars/",
|
||||
null=True,
|
||||
blank=True,
|
||||
default="avatars/golden_eggs.png",
|
||||
)
|
||||
role = models.CharField(
|
||||
max_length=10,
|
||||
choices=ROLE_CHOICES,
|
||||
default="admin",
|
||||
)
|
||||
fcm_token = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.phone} | {self.id}"
|
||||
|
||||
|
||||
class SmsConfirm(models.Model):
|
||||
SMS_EXPIRY_SECONDS = 120
|
||||
RESEND_BLOCK_MINUTES = 10
|
||||
TRY_BLOCK_MINUTES = 2
|
||||
RESEND_COUNT = 5
|
||||
TRY_COUNT = 10
|
||||
|
||||
code = models.IntegerField()
|
||||
try_count = models.IntegerField(default=0)
|
||||
resend_count = models.IntegerField(default=0)
|
||||
phone = models.CharField(max_length=20)
|
||||
expire_time = models.DateTimeField(null=True, blank=True)
|
||||
unlock_time = models.DateTimeField(null=True, blank=True)
|
||||
resend_unlock_time = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def sync_limits(self):
|
||||
if self.resend_count >= self.RESEND_COUNT:
|
||||
self.try_count = 0
|
||||
self.resend_count = 0
|
||||
self.resend_unlock_time = datetime.now() + timedelta(
|
||||
minutes=self.RESEND_BLOCK_MINUTES
|
||||
)
|
||||
elif self.try_count >= self.TRY_COUNT:
|
||||
self.try_count = 0
|
||||
self.unlock_time = datetime.now() + timedelta(
|
||||
minutes=self.TRY_BLOCK_MINUTES
|
||||
)
|
||||
|
||||
if (
|
||||
self.resend_unlock_time is not None
|
||||
and self.resend_unlock_time.timestamp()
|
||||
< datetime.now().timestamp()
|
||||
):
|
||||
self.resend_unlock_time = None
|
||||
|
||||
if (
|
||||
self.unlock_time is not None
|
||||
and self.unlock_time.timestamp() < datetime.now().timestamp()
|
||||
):
|
||||
self.unlock_time = None
|
||||
self.save()
|
||||
|
||||
def is_expired(self):
|
||||
return (
|
||||
self.expire_time.timestamp() < datetime.now().timestamp()
|
||||
if hasattr(self.expire_time, "timestamp")
|
||||
else None
|
||||
)
|
||||
|
||||
def is_block(self):
|
||||
return self.unlock_time is not None
|
||||
|
||||
def reset_limits(self):
|
||||
self.try_count = 0
|
||||
self.resend_count = 0
|
||||
self.unlock_time = None
|
||||
|
||||
def interval(self, time):
|
||||
expire = time.timestamp() - datetime.now().timestamp()
|
||||
minutes = math.floor(expire / 60)
|
||||
expire -= minutes * 60
|
||||
expire = math.floor(expire)
|
||||
|
||||
return f"{minutes:02d}:{expire:02d}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.phone} | {self.code}"
|
||||
1
core/http/permissions/__init__.py
Normal file
1
core/http/permissions/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .base_permissions import * # noqa
|
||||
16
core/http/permissions/base_permissions.py
Normal file
16
core/http/permissions/base_permissions.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from rest_framework.permissions import BasePermission
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class IsRole(BasePermission):
|
||||
message = _("You must be an admin to perform this action.")
|
||||
|
||||
def __init__(self, role):
|
||||
super().__init__()
|
||||
self.role = role
|
||||
|
||||
def __call__(self):
|
||||
return self
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return request.user.role in self.role
|
||||
4
core/http/serializers/__init__.py
Normal file
4
core/http/serializers/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .another import * # noqa
|
||||
from .auth import * # noqa
|
||||
from .generics import * # noqa
|
||||
from .user import * # noqa
|
||||
15
core/http/serializers/another.py
Normal file
15
core/http/serializers/another.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.http import models
|
||||
|
||||
|
||||
class PostSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.Post
|
||||
fields = ["title", "desc", "image"]
|
||||
|
||||
|
||||
class FrontendTransactionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.FrontendTranslation
|
||||
fields = ["key", "value"]
|
||||
66
core/http/serializers/auth.py
Normal file
66
core/http/serializers/auth.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from rest_framework import exceptions, serializers
|
||||
|
||||
from core.http import models
|
||||
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=255)
|
||||
password = serializers.CharField(max_length=255)
|
||||
|
||||
|
||||
class RegisterSerializer(serializers.ModelSerializer):
|
||||
phone = serializers.CharField(max_length=255)
|
||||
|
||||
def validate_phone(self, value):
|
||||
user = models.User.objects.filter(
|
||||
phone=value, validated_at__isnull=False
|
||||
)
|
||||
if user.exists():
|
||||
return exceptions.ValidationError(
|
||||
_("Telefon raqami allaqachon ro'yxatdan o'tgan."),
|
||||
code="unique",
|
||||
)
|
||||
return value
|
||||
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ["first_name", "last_name", "phone", "password"]
|
||||
extra_kwargs = {
|
||||
"first_name": {
|
||||
"required": True,
|
||||
},
|
||||
"last_name": {"required": True},
|
||||
}
|
||||
|
||||
|
||||
class ConfirmSerializer(serializers.Serializer):
|
||||
code = serializers.IntegerField(min_value=1000, max_value=9999)
|
||||
phone = serializers.CharField(max_length=255)
|
||||
|
||||
|
||||
class ResetPasswordSerializer(serializers.Serializer):
|
||||
phone = serializers.CharField(max_length=255)
|
||||
|
||||
def validate_phone(self, value):
|
||||
user = models.User.objects.filter(phone=value)
|
||||
if user.exists():
|
||||
return value
|
||||
|
||||
raise serializers.ValidationError(_("Foydalanuvchi mavjud emas"))
|
||||
|
||||
|
||||
class ResetConfirmationSerializer(serializers.Serializer):
|
||||
code = serializers.IntegerField(min_value=1000, max_value=9999)
|
||||
phone = serializers.CharField(max_length=255)
|
||||
password = serializers.CharField(max_length=255)
|
||||
|
||||
def validate_phone(self, value):
|
||||
user = models.User.objects.filter(phone=value)
|
||||
if user.exists():
|
||||
return value
|
||||
raise serializers.ValidationError(_("User does not exist"))
|
||||
|
||||
|
||||
class ResendSerializer(serializers.Serializer):
|
||||
phone = serializers.CharField(max_length=255)
|
||||
17
core/http/serializers/generics.py
Normal file
17
core/http/serializers/generics.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from rest_framework import exceptions
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
from core import enums
|
||||
from core.utils import exception
|
||||
|
||||
|
||||
class GenericSerializer(serializers.Serializer):
|
||||
def to_internal_value(self, data):
|
||||
try:
|
||||
return super().to_internal_value(data)
|
||||
except exceptions.ValidationError as e:
|
||||
key, value = next(iter(e.detail.items()))
|
||||
exception.ResponseException(
|
||||
value[0], error_code=enums.Codes.INVALID_PARAMETER_VALUE
|
||||
)
|
||||
35
core/http/serializers/user.py
Normal file
35
core/http/serializers/user.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.http import models
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = (
|
||||
"id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"phone",
|
||||
"email",
|
||||
"role",
|
||||
"username",
|
||||
"avatar",
|
||||
)
|
||||
|
||||
|
||||
class UserUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = (
|
||||
"first_name",
|
||||
"last_name",
|
||||
"username",
|
||||
"phone",
|
||||
)
|
||||
|
||||
|
||||
class UserAvatarUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ("avatar",)
|
||||
1
core/http/tasks/__init__.py
Normal file
1
core/http/tasks/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .sms import * # noqa
|
||||
25
core/http/tasks/sms.py
Normal file
25
core/http/tasks/sms.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Base celery tasks
|
||||
"""
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from core.services import sms_service
|
||||
from core.utils import console
|
||||
|
||||
|
||||
@shared_task
|
||||
def SendConfirm(phone, code):
|
||||
try:
|
||||
service: sms_service.SendService = sms_service.SendService()
|
||||
service.send_sms(
|
||||
phone, _("Sizning Tasdiqlash ko'dingiz: %(code)s") % {"code": code}
|
||||
)
|
||||
console.Console().success(f"Success: {phone}-{code}")
|
||||
except Exception as e:
|
||||
console.Console().error(
|
||||
"Error: {phone}-{code}\n\n{error}".format(
|
||||
phone=phone, code=code, error=e
|
||||
)
|
||||
) # noqa
|
||||
0
core/http/templatetags/__init__.py
Executable file
0
core/http/templatetags/__init__.py
Executable file
54
core/http/templatetags/vite.py
Executable file
54
core/http/templatetags/vite.py
Executable file
@@ -0,0 +1,54 @@
|
||||
import json
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.templatetags import static
|
||||
from django.utils import safestring
|
||||
|
||||
from common.env import env
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
def getScript(url: object) -> str:
|
||||
ext: str = str(url).split(".")[-1]
|
||||
|
||||
if env("VITE_LIVE"):
|
||||
url = f"http://{env('VITE_HOST')}:{env('VITE_PORT')}/{url}"
|
||||
else:
|
||||
url: str = static.static(f"vite/{url}")
|
||||
|
||||
if ext == "css":
|
||||
script: str = f"<link rel='stylesheet' type='text/css' href='{url}'>"
|
||||
else:
|
||||
script: str = (
|
||||
"<script type='module' type='text/javascript' src='{"
|
||||
"}'></script>"
|
||||
).format(url)
|
||||
return script
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def vite_load(*args):
|
||||
try:
|
||||
fd = open(f"{settings.VITE_APP_DIR}/manifest.json")
|
||||
manifest = json.load(fd)
|
||||
except Exception:
|
||||
raise Exception(
|
||||
f"Vite manifest file not found or invalid. Maybe your"
|
||||
f" {settings.VITE_APP_DIR}/manifest.json file is empty?"
|
||||
)
|
||||
if not env("VITE_LIVE"):
|
||||
imports_files = "".join(
|
||||
[getScript(file["file"]) for file in manifest.values()]
|
||||
)
|
||||
|
||||
else:
|
||||
imports_files = "".join([getScript(file) for file in args])
|
||||
imports_files += f""" <script type="module"
|
||||
src="http://{env('VITE_HOST')}:{env('VITE_PORT')}/@vite/client"></script> <script
|
||||
type="module" src="{static.static(
|
||||
"js/vite-refresh.js")}"></script>
|
||||
"""
|
||||
|
||||
return safestring.mark_safe(imports_files)
|
||||
2
core/http/translation/__init__.py
Normal file
2
core/http/translation/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .core import * # noqa
|
||||
from .another import * # noqa
|
||||
16
core/http/translation/another.py
Normal file
16
core/http/translation/another.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Django model translation resources
|
||||
"""
|
||||
|
||||
from modeltranslation import translator
|
||||
|
||||
|
||||
class PostTranslationOption(translator.TranslationOptions):
|
||||
fields = (
|
||||
"title",
|
||||
"desc",
|
||||
)
|
||||
|
||||
|
||||
class FrontendTranslationOption(translator.TranslationOptions):
|
||||
fields = ("value",)
|
||||
14
core/http/translation/core.py
Normal file
14
core/http/translation/core.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Register models
|
||||
"""
|
||||
|
||||
from modeltranslation.translator import translator
|
||||
|
||||
from core.http import models
|
||||
from core.http.translation import another
|
||||
|
||||
|
||||
translator.register(models.Post, another.PostTranslationOption)
|
||||
translator.register(
|
||||
models.FrontendTranslation, another.FrontendTranslationOption
|
||||
) # noqa
|
||||
2
core/http/views/__init__.py
Normal file
2
core/http/views/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .auth import * # noqa
|
||||
from .generics import * # noqa
|
||||
25
core/http/views/auth.py
Normal file
25
core/http/views/auth.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from rest_framework import views
|
||||
from rest_framework import request
|
||||
from rest_framework import throttling
|
||||
|
||||
from core import enums
|
||||
from core import services
|
||||
from core.http import serializers
|
||||
from core.http.views import generics as http_views
|
||||
|
||||
|
||||
class AbstractSendSms(views.APIView, http_views.ApiResponse):
|
||||
serializer_class = serializers.ResendSerializer
|
||||
throttle_classes = [throttling.UserRateThrottle]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.service = services.UserService()
|
||||
|
||||
def post(self, request: request.Request):
|
||||
ser = self.serializer_class(data=request.data)
|
||||
ser.is_valid(raise_exception=True)
|
||||
phone = ser.data.get("phone")
|
||||
self.service.send_confirmation(phone)
|
||||
return self.success(_(enums.Messages.SEND_MESSAGE) % {"phone": phone})
|
||||
67
core/http/views/generics.py
Normal file
67
core/http/views/generics.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from rest_framework import generics, response, status
|
||||
|
||||
from core.exceptions import BreakException
|
||||
|
||||
|
||||
class ApiResponse:
|
||||
def response(
|
||||
self,
|
||||
success=True,
|
||||
message="",
|
||||
data=None,
|
||||
status_code=status.HTTP_200_OK,
|
||||
**kwargs
|
||||
):
|
||||
if data is None:
|
||||
data = {}
|
||||
response_data = {
|
||||
"success": success,
|
||||
"message": message,
|
||||
"data": data,
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
return response.Response(data=response_data, status=status_code)
|
||||
|
||||
def success(
|
||||
self, message="", data=None, status_code=status.HTTP_200_OK, **kwargs
|
||||
):
|
||||
return self.response(True, message, data, status_code, **kwargs)
|
||||
|
||||
def error(
|
||||
self,
|
||||
message="",
|
||||
data=None,
|
||||
error_code=0,
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
exception=None,
|
||||
**kwargs
|
||||
):
|
||||
if isinstance(exception, BreakException):
|
||||
raise exception
|
||||
return self.response(
|
||||
False, message, data, status_code, error_code=error_code, **kwargs
|
||||
) # noqa
|
||||
|
||||
|
||||
class ListApiView(generics.ListAPIView, ApiResponse):
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return self.success(data=serializer.data)
|
||||
|
||||
|
||||
class CreateApiView(generics.CreateAPIView, ApiResponse):
|
||||
def create(self, request, *args, **kwargs):
|
||||
super().create(request, *args, **kwargs)
|
||||
return self.success(
|
||||
self.message
|
||||
if hasattr(self, "message")
|
||||
else "Muvaffaqiyatli yaratildi"
|
||||
) # noqa
|
||||
29
core/http/views/user.py
Normal file
29
core/http/views/user.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from rest_framework.generics import UpdateAPIView
|
||||
|
||||
from core.http.models import User
|
||||
from core.http.serializers.user import (
|
||||
UserAvatarUpdateSerializer,
|
||||
UserUpdateSerializer,
|
||||
)
|
||||
|
||||
|
||||
class UserUpdateView(UpdateAPIView):
|
||||
serializer_class = UserUpdateSerializer
|
||||
queryset = User.objects.all()
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
|
||||
class UserAvatarUpdateView(UpdateAPIView):
|
||||
serializer_class = UserAvatarUpdateSerializer
|
||||
queryset = User.objects.all()
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(user=self.request.user)
|
||||
Reference in New Issue
Block a user