Merge pull request 'Attributes model added.' (#1) from feat/attributes-model into main

Reviewed-on: #1
This commit is contained in:
2026-03-09 08:22:57 +00:00
34 changed files with 765 additions and 8 deletions

1
=4.12 Normal file
View File

@@ -0,0 +1 @@
Requirement already satisfied: typing_extensions in ./.venv/lib/python3.12/site-packages (4.12.2)

View File

@@ -179,6 +179,9 @@ REST_FRAMEWORK = {
"user": "60/min", "user": "60/min",
}, },
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
]
} }

View File

@@ -40,9 +40,8 @@ def sms_code(test_user):
def test_reg_view(api_client): def test_reg_view(api_client):
data = { data = {
"phone": "998999999991", "phone": "998999999991",
"first_name": "John",
"last_name": "Doe",
"password": "password", "password": "password",
"confirm_password":"password"
} }
with patch.object(SmsService, "send_confirm", return_value=True): with patch.object(SmsService, "send_confirm", return_value=True):
response = api_client.post(reverse("auth-register"), data=data) response = api_client.post(reverse("auth-register"), data=data)

View File

@@ -1,6 +1,8 @@
import uuid import uuid
from typing import Type from typing import Type
from rest_framework.authentication import TokenAuthentication
from core.services import UserService, SmsService from core.services import UserService, SmsService
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -186,6 +188,7 @@ class MeView(BaseViewSetMixin, GenericViewSet, UserService):
class ChangePasswordView(BaseViewSetMixin, GenericViewSet): class ChangePasswordView(BaseViewSetMixin, GenericViewSet):
serializer_class = ChangePasswordSerializer serializer_class = ChangePasswordSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
authentication_classes = [TokenAuthentication]
@extend_schema( @extend_schema(
request=serializer_class, request=serializer_class,

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,28 @@
from django.contrib import admin
from unfold.admin import ModelAdmin
from core.apps.api.models import ColorModel, ProductlikeModel, SizeModel
@admin.register(SizeModel)
class SizeAdmin(ModelAdmin):
list_display = (
"id",
"__str__",
)
@admin.register(ColorModel)
class ColorAdmin(ModelAdmin):
list_display = (
"id",
"__str__",
)
@admin.register(ProductlikeModel)
class ProductlikeAdmin(ModelAdmin):
list_display = (
"id",
"__str__",
)

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,33 @@
from django_filters import rest_framework as filters
from core.apps.api.models import ColorModel, ProductlikeModel, SizeModel
class SizeFilter(filters.FilterSet):
# name = filters.CharFilter(field_name="name", lookup_expr="icontains")
class Meta:
model = SizeModel
fields = [
"name",
]
class ColorFilter(filters.FilterSet):
# name = filters.CharFilter(field_name="name", lookup_expr="icontains")
class Meta:
model = ColorModel
fields = [
"name",
]
class ProductlikeFilter(filters.FilterSet):
# name = filters.CharFilter(field_name="name", lookup_expr="icontains")
class Meta:
model = ProductlikeModel
fields = [
"name",
]

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,24 @@
from django import forms
from core.apps.api.models import ColorModel, ProductlikeModel, SizeModel
class SizeForm(forms.ModelForm):
class Meta:
model = SizeModel
fields = "__all__"
class ColorForm(forms.ModelForm):
class Meta:
model = ColorModel
fields = "__all__"
class ProductlikeForm(forms.ModelForm):
class Meta:
model = ProductlikeModel
fields = "__all__"

View File

@@ -0,0 +1,57 @@
# Generated by Django 5.2.7 on 2026-03-09 07:48
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ColorModel',
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)),
('name', models.CharField(max_length=255, verbose_name='name')),
('code', models.CharField(max_length=255, verbose_name='name')),
],
options={
'verbose_name': 'ColorModel',
'verbose_name_plural': 'ColorModels',
'db_table': 'color',
},
),
migrations.CreateModel(
name='ProductlikeModel',
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)),
('name', models.CharField(max_length=255, verbose_name='name')),
],
options={
'verbose_name': 'ProductlikeModel',
'verbose_name_plural': 'ProductlikeModels',
'db_table': 'productlike',
},
),
migrations.CreateModel(
name='SizeModel',
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)),
('name', models.CharField(max_length=255, verbose_name='name')),
],
options={
'verbose_name': 'SizeModel',
'verbose_name_plural': 'SizeModels',
'db_table': 'size',
},
),
]

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,55 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_core.models import AbstractBaseModel
from model_bakery import baker
class SizeModel(AbstractBaseModel):
name = models.CharField(verbose_name=_("name"), max_length=255)
def __str__(self):
return str(self.pk)
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "size"
verbose_name = _("SizeModel")
verbose_name_plural = _("SizeModels")
class ColorModel(AbstractBaseModel):
name = models.CharField(verbose_name=_("name"), max_length=255)
code = models.CharField(verbose_name=_("name"), max_length=255)
def __str__(self):
return str(self.pk)
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "color"
verbose_name = _("ColorModel")
verbose_name_plural = _("ColorModels")
class ProductlikeModel(AbstractBaseModel):
name = models.CharField(verbose_name=_("name"), max_length=255)
def __str__(self):
return str(self.pk)
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "productlike"
verbose_name = _("ProductlikeModel")
verbose_name_plural = _("ProductlikeModels")

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,34 @@
from rest_framework import permissions
class SizePermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True
class ColorPermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True
class ProductlikePermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,3 @@
from .color import * # noqa
from .productlike import * # noqa
from .size import * # noqa

View File

@@ -0,0 +1,28 @@
from rest_framework import serializers
from core.apps.api.models import ColorModel
class BaseColorSerializer(serializers.ModelSerializer):
class Meta:
model = ColorModel
fields = [
"id",
"name",
]
class ListColorSerializer(BaseColorSerializer):
class Meta(BaseColorSerializer.Meta): ...
class RetrieveColorSerializer(BaseColorSerializer):
class Meta(BaseColorSerializer.Meta): ...
class CreateColorSerializer(BaseColorSerializer):
class Meta(BaseColorSerializer.Meta):
fields = [
"id",
"name",
]

View File

@@ -0,0 +1,28 @@
from rest_framework import serializers
from core.apps.api.models import ProductlikeModel
class BaseProductlikeSerializer(serializers.ModelSerializer):
class Meta:
model = ProductlikeModel
fields = [
"id",
"name",
]
class ListProductlikeSerializer(BaseProductlikeSerializer):
class Meta(BaseProductlikeSerializer.Meta): ...
class RetrieveProductlikeSerializer(BaseProductlikeSerializer):
class Meta(BaseProductlikeSerializer.Meta): ...
class CreateProductlikeSerializer(BaseProductlikeSerializer):
class Meta(BaseProductlikeSerializer.Meta):
fields = [
"id",
"name",
]

View File

@@ -0,0 +1,28 @@
from rest_framework import serializers
from core.apps.api.models import SizeModel
class BaseSizeSerializer(serializers.ModelSerializer):
class Meta:
model = SizeModel
fields = [
"id",
"name",
]
class ListSizeSerializer(BaseSizeSerializer):
class Meta(BaseSizeSerializer.Meta): ...
class RetrieveSizeSerializer(BaseSizeSerializer):
class Meta(BaseSizeSerializer.Meta): ...
class CreateSizeSerializer(BaseSizeSerializer):
class Meta(BaseSizeSerializer.Meta):
fields = [
"id",
"name",
]

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,16 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from core.apps.api.models import ColorModel, ProductlikeModel, SizeModel
@receiver(post_save, sender=SizeModel)
def SizeSignal(sender, instance, created, **kwargs): ...
@receiver(post_save, sender=ColorModel)
def ColorSignal(sender, instance, created, **kwargs): ...
@receiver(post_save, sender=ProductlikeModel)
def ProductlikeSignal(sender, instance, created, **kwargs): ...

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,3 @@
from .test_color import * # noqa
from .test_productlike import * # noqa
from .test_size import * # noqa

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import ColorModel
@pytest.fixture
def instance(db):
return ColorModel._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("color-list"),
"retrieve": reverse("color-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("color-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_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import ProductlikeModel
@pytest.fixture
def instance(db):
return ProductlikeModel._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("productlike-list"),
"retrieve": reverse("productlike-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("productlike-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_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.api.models import SizeModel
@pytest.fixture
def instance(db):
return SizeModel._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("size-list"),
"retrieve": reverse("size-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("size-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_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,18 @@
from modeltranslation.translator import TranslationOptions, register
from core.apps.api.models import ColorModel, ProductlikeModel, SizeModel
@register(SizeModel)
class SizeTranslation(TranslationOptions):
fields = []
@register(ColorModel)
class ColorTranslation(TranslationOptions):
fields = []
@register(ProductlikeModel)
class ProductlikeTranslation(TranslationOptions):
fields = []

View File

@@ -1,9 +1,10 @@
from django.urls import path, include from django.urls import include, path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .views import ColorView, ProductlikeView, SizeView
router = DefaultRouter() router = DefaultRouter()
router.register("productlike", ProductlikeView, basename="productlike")
router.register("color", ColorView, basename="color")
urlpatterns = [ router.register("size", SizeView, basename="size")
path("", include(router.urls)), urlpatterns = [path("", include(router.urls))]
]

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,22 @@
# from django.core.exceptions import ValidationError
class SizeValidator:
def __init__(self): ...
def __call__(self):
return True
class ColorValidator:
def __init__(self): ...
def __call__(self):
return True
class ProductlikeValidator:
def __init__(self): ...
def __call__(self):
return True

View File

@@ -0,0 +1 @@
from .attributes import * # noqa

View File

@@ -0,0 +1,59 @@
from django_core.mixins import BaseViewSetMixin
from drf_spectacular.utils import extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import ReadOnlyModelViewSet
from core.apps.api.models import ColorModel, ProductlikeModel, SizeModel
from core.apps.api.serializers.attributes import (
CreateColorSerializer,
CreateProductlikeSerializer,
CreateSizeSerializer,
ListColorSerializer,
ListProductlikeSerializer,
ListSizeSerializer,
RetrieveColorSerializer,
RetrieveProductlikeSerializer,
RetrieveSizeSerializer,
)
@extend_schema(tags=["size"])
class SizeView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = SizeModel.objects.all()
serializer_class = ListSizeSerializer
permission_classes = [AllowAny]
action_permission_classes = {}
action_serializer_class = {
"list": ListSizeSerializer,
"retrieve": RetrieveSizeSerializer,
"create": CreateSizeSerializer,
}
@extend_schema(tags=["color"])
class ColorView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = ColorModel.objects.all()
serializer_class = ListColorSerializer
permission_classes = [AllowAny]
action_permission_classes = {}
action_serializer_class = {
"list": ListColorSerializer,
"retrieve": RetrieveColorSerializer,
"create": CreateColorSerializer,
}
@extend_schema(tags=["productlike"])
class ProductlikeView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = ProductlikeModel.objects.all()
serializer_class = ListProductlikeSerializer
permission_classes = [AllowAny]
action_permission_classes = {}
action_serializer_class = {
"list": ListProductlikeSerializer,
"retrieve": RetrieveProductlikeSerializer,
"create": CreateProductlikeSerializer,
}