Features: 1) RUFFVCKED

This commit is contained in:
Egor Pavlovich Gorbunov 2025-07-03 02:53:01 +03:00
parent d17839abed
commit aa8f15d0ee
87 changed files with 6187 additions and 3160 deletions

View file

@ -5,6 +5,7 @@
<option name="myDocStringFormat" value="Google" /> <option name="myDocStringFormat" value="Google" />
</component> </component>
<component name="TemplatesService"> <component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS"> <option name="TEMPLATE_FOLDERS">
<list> <list>
<option value="$MODULE_DIR$/blog/templates" /> <option value="$MODULE_DIR$/blog/templates" />

View file

@ -18,56 +18,113 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='PostTag', name="PostTag",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('tag_name', models.CharField(help_text='internal tag identifier for the post tag', max_length=255, help_text="when the object first appeared on the database",
verbose_name='tag name')), verbose_name="created",
('name', models.CharField(help_text='user-friendly name for the post tag', max_length=255, unique=True, ),
verbose_name='tag display name')), ),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
),
),
(
"tag_name",
models.CharField(
help_text="internal tag identifier for the post tag", max_length=255, verbose_name="tag name"
),
),
(
"name",
models.CharField(
help_text="user-friendly name for the post tag",
max_length=255,
unique=True,
verbose_name="tag display name",
),
),
], ],
options={ options={
'verbose_name': 'post tag', "verbose_name": "post tag",
'verbose_name_plural': 'post tags', "verbose_name_plural": "post tags",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Post', name="Post",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('title', models.CharField()), help_text="when the object first appeared on the database",
('content', markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name='content')), verbose_name="created",
('file', models.FileField(blank=True, null=True, upload_to='posts/')), ),
('slug', models.SlugField(allow_unicode=True)), ),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='posts', (
to=settings.AUTH_USER_MODEL)), "modified",
('tags', models.ManyToManyField(to='blog.posttag')), django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
),
),
("title", models.CharField()),
("content", markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content")),
("file", models.FileField(blank=True, null=True, upload_to="posts/")),
("slug", models.SlugField(allow_unicode=True)),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="posts", to=settings.AUTH_USER_MODEL
),
),
("tags", models.ManyToManyField(to="blog.posttag")),
], ],
options={ options={
'verbose_name': 'post', "verbose_name": "post",
'verbose_name_plural': 'posts', "verbose_name_plural": "posts",
}, },
), ),
] ]

View file

@ -5,20 +5,21 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('blog', '0001_initial'), ("blog", "0001_initial"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='post', model_name="post",
name='slug', name="slug",
field=django_extensions.db.fields.AutoSlugField(allow_unicode=True, blank=True, editable=False, populate_from='title', unique=True), field=django_extensions.db.fields.AutoSlugField(
allow_unicode=True, blank=True, editable=False, populate_from="title", unique=True
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='post', model_name="post",
name='title', name="title",
field=models.CharField(help_text='post title', max_length=128, unique=True, verbose_name='title'), field=models.CharField(help_text="post title", max_length=128, unique=True, verbose_name="title"),
), ),
] ]

View file

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('blog', '0002_alter_post_slug_alter_post_title'), ("blog", "0002_alter_post_slug_alter_post_title"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='post', model_name="post",
name='tags', name="tags",
field=models.ManyToManyField(blank=True, related_name='posts', to='blog.posttag'), field=models.ManyToManyField(blank=True, related_name="posts", to="blog.posttag"),
), ),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("blog", "0003_alter_post_tags"), ("blog", "0003_alter_post_tags"),
] ]
@ -14,128 +13,92 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_ar_ar", name="content_ar_ar",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_cs_cz", name="content_cs_cz",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_da_dk", name="content_da_dk",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_de_de", name="content_de_de",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_en_gb", name="content_en_gb",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_en_us", name="content_en_us",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_es_es", name="content_es_es",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_fr_fr", name="content_fr_fr",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_hi_in", name="content_hi_in",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_it_it", name="content_it_it",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_ja_jp", name="content_ja_jp",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_kk_kz", name="content_kk_kz",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_nl_nl", name="content_nl_nl",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_pl_pl", name="content_pl_pl",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_pt_br", name="content_pt_br",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_ro_ro", name="content_ro_ro",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_ru_ru", name="content_ru_ru",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",
name="content_zh_hans", name="content_zh_hans",
field=markdown_field.fields.MarkdownField( field=markdown_field.fields.MarkdownField(blank=True, null=True, verbose_name="content"),
blank=True, null=True, verbose_name="content"
),
), ),
migrations.AddField( migrations.AddField(
model_name="post", model_name="post",

View file

@ -31,10 +31,8 @@ class Post(NiceModel):
is_publicly_visible = True is_publicly_visible = True
author: ForeignKey = ForeignKey( author = ForeignKey(to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="posts")
to="vibes_auth.User", on_delete=CASCADE, blank=False, null=False, related_name="posts" title = CharField(
)
title: CharField = CharField(
unique=True, max_length=128, blank=False, null=False, help_text=_("post title"), verbose_name=_("title") unique=True, max_length=128, blank=False, null=False, help_text=_("post title"), verbose_name=_("title")
) )
content: MarkdownField = MarkdownField( content: MarkdownField = MarkdownField(
@ -74,9 +72,9 @@ class Post(NiceModel):
blank=True, blank=True,
null=True, null=True,
) )
file: FileField = FileField(upload_to="posts/", blank=True, null=True) file = FileField(upload_to="posts/", blank=True, null=True)
slug: AutoSlugField = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False) slug = AutoSlugField(populate_from="title", allow_unicode=True, unique=True, editable=False)
tags: ManyToManyField = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts") tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts")
def __str__(self): def __str__(self):
return f"{self.title} | {self.author.first_name} {self.author.last_name}" return f"{self.title} | {self.author.first_name} {self.author.last_name}"
@ -115,15 +113,16 @@ class PostTag(NiceModel):
""" """
is_publicly_visible = True is_publicly_visible = True
posts: "Post"
tag_name: CharField = CharField( tag_name = CharField(
blank=False, blank=False,
null=False, null=False,
max_length=255, max_length=255,
help_text=_("internal tag identifier for the post tag"), help_text=_("internal tag identifier for the post tag"),
verbose_name=_("tag name"), verbose_name=_("tag name"),
) )
name: CharField = CharField( name = CharField(
max_length=255, max_length=255,
help_text=_("user-friendly name for the post tag"), help_text=_("user-friendly name for the post tag"),
verbose_name=_("tag display name"), verbose_name=_("tag display name"),

View file

@ -1,5 +1,4 @@
import uuid import uuid
from datetime import datetime
from django.db.models import BooleanField, Model, UUIDField from django.db.models import BooleanField, Model, UUIDField
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -7,25 +6,21 @@ from django_extensions.db.fields import CreationDateTimeField, ModificationDateT
class NiceModel(Model): class NiceModel(Model):
id = None id: None = None
uuid: uuid = UUIDField( # type: ignore uuid = UUIDField(
verbose_name=_("unique id"), verbose_name=_("unique id"),
help_text=_("unique id is used to surely identify any database object"), help_text=_("unique id is used to surely identify any database object"),
primary_key=True, primary_key=True,
default=uuid.uuid4, default=uuid.uuid4,
editable=False, editable=False,
) )
is_active: bool = BooleanField( # type: ignore is_active = BooleanField(
default=True, default=True,
verbose_name=_("is active"), verbose_name=_("is active"),
help_text=_("if set to false, this object can't be seen by users without needed permission"), help_text=_("if set to false, this object can't be seen by users without needed permission"),
) )
created: datetime = CreationDateTimeField( # type: ignore created = CreationDateTimeField(_("created"), help_text=_("when the object first appeared on the database"))
_("created"), help_text=_("when the object first appeared on the database") modified = ModificationDateTimeField(_("modified"), help_text=_("when the object was last modified"))
)
modified: datetime = ModificationDateTimeField( # type: ignore
_("modified"), help_text=_("when the object was last modified")
)
def save(self, **kwargs): def save(self, **kwargs):
self.update_modified = kwargs.pop("update_modified", getattr(self, "update_modified", True)) self.update_modified = kwargs.pop("update_modified", getattr(self, "update_modified", True))

View file

@ -1,4 +1,5 @@
from contextlib import suppress from contextlib import suppress
from typing import ClassVar, Type
from constance.admin import Config from constance.admin import Config
from constance.admin import ConstanceAdmin as BaseConstanceAdmin from constance.admin import ConstanceAdmin as BaseConstanceAdmin
@ -38,7 +39,7 @@ from evibes.settings import CONSTANCE_CONFIG
class FieldsetsMixin: class FieldsetsMixin:
general_fields: list = [] general_fields: list = []
relation_fields: list = [] relation_fields: list = []
model: Model model: ClassVar[Type[Model]]
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
if request: if request:
@ -172,7 +173,7 @@ class CategoryChildrenInline(TabularInline):
@register(AttributeGroup) @register(AttributeGroup)
class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = AttributeGroup # type: ignore model = AttributeGroup
list_display = ("name", "modified") list_display = ("name", "modified")
search_fields = ("uuid", "name") search_fields = ("uuid", "name")
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -183,7 +184,7 @@ class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Attribute) @register(Attribute)
class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Attribute # type: ignore model = Attribute
list_display = ("name", "group", "value_type", "modified") list_display = ("name", "group", "value_type", "modified")
list_filter = ("value_type", "group", "is_active") list_filter = ("value_type", "group", "is_active")
search_fields = ("uuid", "name", "group__name") search_fields = ("uuid", "name", "group__name")
@ -196,7 +197,7 @@ class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(AttributeValue) @register(AttributeValue)
class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = AttributeValue # type: ignore model = AttributeValue
list_display = ("attribute", "value", "modified") list_display = ("attribute", "value", "modified")
list_filter = ("attribute__group", "is_active") list_filter = ("attribute__group", "is_active")
search_fields = ("uuid", "value", "attribute__name") search_fields = ("uuid", "value", "attribute__name")
@ -209,7 +210,7 @@ class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Category) @register(Category)
class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin): class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin):
model = Category # type: ignore model = Category
list_display = ("indented_title", "parent", "is_active", "modified") list_display = ("indented_title", "parent", "is_active", "modified")
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
list_filter = ("is_active", "level", "created", "modified") list_filter = ("is_active", "level", "created", "modified")
@ -224,7 +225,7 @@ class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin):
@register(Brand) @register(Brand)
class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Brand # type: ignore model = Brand
list_display = ("name",) list_display = ("name",)
list_filter = ("categories", "is_active") list_filter = ("categories", "is_active")
search_fields = ("uuid", "name", "categories__name") search_fields = ("uuid", "name", "categories__name")
@ -236,7 +237,7 @@ class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Product) @register(Product)
class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Product # type: ignore model = Product
list_display = ( list_display = (
"name", "name",
"partnumber", "partnumber",
@ -275,7 +276,7 @@ class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(ProductTag) @register(ProductTag)
class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = ProductTag # type: ignore model = ProductTag
list_display = ("tag_name",) list_display = ("tag_name",)
search_fields = ("tag_name",) search_fields = ("tag_name",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -286,7 +287,7 @@ class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(CategoryTag) @register(CategoryTag)
class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = CategoryTag # type: ignore model = CategoryTag
list_display = ("tag_name",) list_display = ("tag_name",)
search_fields = ("tag_name",) search_fields = ("tag_name",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -297,7 +298,7 @@ class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Vendor) @register(Vendor)
class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Vendor # type: ignore model = Vendor
list_display = ("name", "markup_percent", "modified") list_display = ("name", "markup_percent", "modified")
list_filter = ("markup_percent", "is_active") list_filter = ("markup_percent", "is_active")
search_fields = ("name",) search_fields = ("name",)
@ -310,7 +311,7 @@ class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Feedback) @register(Feedback)
class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Feedback # type: ignore model = Feedback
list_display = ("order_product", "rating", "comment", "modified") list_display = ("order_product", "rating", "comment", "modified")
list_filter = ("rating", "is_active") list_filter = ("rating", "is_active")
search_fields = ("order_product__product__name", "comment") search_fields = ("order_product__product__name", "comment")
@ -322,7 +323,7 @@ class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Order) @register(Order)
class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Order # type: ignore model = Order
list_display = ( list_display = (
"human_readable_id", "human_readable_id",
"user", "user",
@ -350,7 +351,7 @@ class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(OrderProduct) @register(OrderProduct)
class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = OrderProduct # type: ignore model = OrderProduct
list_display = ("order", "product", "quantity", "buy_price", "status", "modified") list_display = ("order", "product", "quantity", "buy_price", "status", "modified")
list_filter = ("status",) list_filter = ("status",)
search_fields = ("order__user__email", "product__name") search_fields = ("order__user__email", "product__name")
@ -363,7 +364,7 @@ class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(PromoCode) @register(PromoCode)
class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = PromoCode # type: ignore model = PromoCode
list_display = ( list_display = (
"code", "code",
"discount_percent", "discount_percent",
@ -391,7 +392,7 @@ class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Promotion) @register(Promotion)
class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Promotion # type: ignore model = Promotion
list_display = ("name", "discount_percent", "modified") list_display = ("name", "discount_percent", "modified")
search_fields = ("name",) search_fields = ("name",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -403,7 +404,7 @@ class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Stock) @register(Stock)
class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Stock # type: ignore model = Stock
list_display = ("product", "vendor", "sku", "quantity", "price", "modified") list_display = ("product", "vendor", "sku", "quantity", "price", "modified")
list_filter = ("vendor", "quantity") list_filter = ("vendor", "quantity")
search_fields = ("product__name", "vendor__name", "sku") search_fields = ("product__name", "vendor__name", "sku")
@ -423,7 +424,7 @@ class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Wishlist) @register(Wishlist)
class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = Wishlist # type: ignore model = Wishlist
list_display = ("user", "modified") list_display = ("user", "modified")
search_fields = ("user__email",) search_fields = ("user__email",)
readonly_fields = ("uuid", "modified", "created") readonly_fields = ("uuid", "modified", "created")
@ -434,7 +435,7 @@ class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(ProductImage) @register(ProductImage)
class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
model = ProductImage # type: ignore model = ProductImage
list_display = ("alt", "product", "priority", "modified") list_display = ("alt", "product", "priority", "modified")
list_filter = ("priority",) list_filter = ("priority",)
search_fields = ("alt", "product__name") search_fields = ("alt", "product__name")
@ -447,7 +448,7 @@ class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Address) @register(Address)
class AddressAdmin(FieldsetsMixin, GISModelAdmin): class AddressAdmin(FieldsetsMixin, GISModelAdmin):
model = Address # type: ignore model = Address
list_display = ("street", "city", "region", "country", "user") list_display = ("street", "city", "region", "country", "user")
list_filter = ("country", "region") list_filter = ("country", "region")
search_fields = ("street", "city", "postal_code", "user__email") search_fields = ("street", "city", "postal_code", "user__email")
@ -507,8 +508,10 @@ class ConstanceConfig:
_meta = Meta() _meta = Meta()
site.unregister([Config]) # type: ignore # noinspection PyTypeChecker
site.register([ConstanceConfig], BaseConstanceAdmin) # type: ignore site.unregister([Config])
site.site_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore # noinspection PyTypeChecker
site.register([ConstanceConfig], BaseConstanceAdmin)
site.site_title = CONSTANCE_CONFIG["PROJECT_NAME"][0]
site.site_header = "eVibes" site.site_header = "eVibes"
site.index_title = CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore site.index_title = CONSTANCE_CONFIG["PROJECT_NAME"][0]

View file

@ -185,8 +185,8 @@ def process_query(query: str = "", request: Request | None = None) -> dict[str,
results[idx].append(hit_result) results[idx].append(hit_result)
return results return results
except NotFoundError: except NotFoundError as nfe:
raise Http404 raise Http404 from nfe
LANGUAGE_ANALYZER_MAP = { LANGUAGE_ANALYZER_MAP = {

View file

@ -96,8 +96,8 @@ class AddOrderProduct(BaseMutation):
order = order.add_product(product_uuid=product_uuid, attributes=format_attributes(attributes)) order = order.add_product(product_uuid=product_uuid, attributes=format_attributes(attributes))
return AddOrderProduct(order=order) return AddOrderProduct(order=order)
except Order.DoesNotExist: except Order.DoesNotExist as dne:
raise Http404(_(f"order {order_uuid} not found")) raise Http404(_(f"order {order_uuid} not found")) from dne
class RemoveOrderProduct(BaseMutation): class RemoveOrderProduct(BaseMutation):
@ -122,8 +122,8 @@ class RemoveOrderProduct(BaseMutation):
order = order.remove_product(product_uuid=product_uuid, attributes=format_attributes(attributes)) order = order.remove_product(product_uuid=product_uuid, attributes=format_attributes(attributes))
return AddOrderProduct(order=order) return AddOrderProduct(order=order)
except Order.DoesNotExist: except Order.DoesNotExist as dne:
raise Http404(_(f"order {order_uuid} not found")) raise Http404(_(f"order {order_uuid} not found")) from dne
class RemoveAllOrderProducts(BaseMutation): class RemoveAllOrderProducts(BaseMutation):
@ -224,8 +224,8 @@ class BuyOrder(BaseMutation):
case _: case _:
raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}")) raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}"))
except Order.DoesNotExist: except Order.DoesNotExist as dne:
raise Http404(_(f"order {order_uuid} not found")) raise Http404(_(f"order {order_uuid} not found")) from dne
class BulkOrderAction(BaseMutation): class BulkOrderAction(BaseMutation):
@ -271,8 +271,8 @@ class BulkOrderAction(BaseMutation):
return BulkOrderAction(order=order) return BulkOrderAction(order=order)
except Order.DoesNotExist: except Order.DoesNotExist as dne:
raise Http404(_(f"order {order_uuid} not found")) raise Http404(_(f"order {order_uuid} not found")) from dne
class BuyUnregisteredOrder(BaseMutation): class BuyUnregisteredOrder(BaseMutation):
@ -344,8 +344,8 @@ class AddWishlistProduct(BaseMutation):
return AddWishlistProduct(wishlist=wishlist) return AddWishlistProduct(wishlist=wishlist)
except Wishlist.DoesNotExist: except Wishlist.DoesNotExist as dne:
raise Http404(_(f"wishlist {wishlist_uuid} not found")) raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
class RemoveWishlistProduct(BaseMutation): class RemoveWishlistProduct(BaseMutation):
@ -371,8 +371,8 @@ class RemoveWishlistProduct(BaseMutation):
return RemoveWishlistProduct(wishlist=wishlist) return RemoveWishlistProduct(wishlist=wishlist)
except Wishlist.DoesNotExist: except Wishlist.DoesNotExist as dne:
raise Http404(_(f"wishlist {wishlist_uuid} not found")) raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
class RemoveAllWishlistProducts(BaseMutation): class RemoveAllWishlistProducts(BaseMutation):
@ -398,8 +398,8 @@ class RemoveAllWishlistProducts(BaseMutation):
return RemoveAllWishlistProducts(wishlist=wishlist) return RemoveAllWishlistProducts(wishlist=wishlist)
except Wishlist.DoesNotExist: except Wishlist.DoesNotExist as dne:
raise Http404(_(f"wishlist {wishlist_uuid} not found")) raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
class BuyWishlist(BaseMutation): class BuyWishlist(BaseMutation):
@ -441,8 +441,8 @@ class BuyWishlist(BaseMutation):
case _: case _:
raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}")) raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}"))
except Wishlist.DoesNotExist: except Wishlist.DoesNotExist as dne:
raise Http404(_(f"wishlist {wishlist_uuid} not found")) raise Http404(_(f"wishlist {wishlist_uuid} not found")) from dne
class BuyProduct(BaseMutation): class BuyProduct(BaseMutation):
@ -575,9 +575,9 @@ class DeleteAddress(BaseMutation):
raise PermissionDenied(permission_denied_message) raise PermissionDenied(permission_denied_message)
except Address.DoesNotExist: except Address.DoesNotExist as dne:
name = "Address" name = "Address"
raise Http404(_(f"{name} does not exist: {uuid}")) raise Http404(_(f"{name} does not exist: {uuid}")) from dne
class AutocompleteAddress(BaseMutation): class AutocompleteAddress(BaseMutation):

View file

@ -443,7 +443,7 @@ class PromoCodeType(DjangoObjectType):
description = _("promocodes") description = _("promocodes")
def resolve_discount(self: PromoCode, _info) -> float: def resolve_discount(self: PromoCode, _info) -> float:
return float(self.discount_percent) if self.discount_percent else float(self.discount_amount) return float(self.discount_percent) if self.discount_percent else float(self.discount_amount) # type: ignore [arg-type]
def resolve_discount_type(self: PromoCode, _info) -> str: def resolve_discount_type(self: PromoCode, _info) -> str:
return "percent" if self.discount_percent else "amount" return "percent" if self.discount_percent else "amount"

View file

@ -39,7 +39,7 @@ def load_po_sanitized(path: str) -> polib.POFile:
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
text = f.read() text = f.read()
except OSError as e: except OSError as e:
raise CommandError(f"{path}: cannot read file ({e})") raise CommandError(f"{path}: cannot read file ({e})") from e
# fix fuzzy flags and empty header entries # fix fuzzy flags and empty header entries
text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE) text = re.sub(r"^#,(?!\s)", "#, ", text, flags=re.MULTILINE)
parts = text.split("\n\n", 1) parts = text.split("\n\n", 1)
@ -54,7 +54,7 @@ def load_po_sanitized(path: str) -> polib.POFile:
tmp.close() tmp.close()
return polib.pofile(tmp.name) return polib.pofile(tmp.name)
except Exception as e: except Exception as e:
raise CommandError(f"{path}: syntax error after sanitization ({e})") raise CommandError(f"{path}: syntax error after sanitization ({e})") from e
finally: finally:
with contextlib.suppress(OSError): with contextlib.suppress(OSError):
os.unlink(tmp.name) os.unlink(tmp.name)
@ -97,7 +97,8 @@ class Command(BaseCommand):
root_path: str = options.get("root_path") or "/app/" root_path: str = options.get("root_path") or "/app/"
configs = list(apps.get_app_configs()) configs = list(apps.get_app_configs())
configs.append(RootDirectory()) # noinspection PyTypeChecker
configs.append(RootDirectory()) # type: ignore [arg-type]
for app_conf in configs: for app_conf in configs:
if app_conf.label not in apps_to_scan: if app_conf.label not in apps_to_scan:

View file

@ -138,7 +138,8 @@ class Command(BaseCommand):
self.stdout.write(self.style.MIGRATE_HEADING(f"→ Translating into {target_lang}")) self.stdout.write(self.style.MIGRATE_HEADING(f"→ Translating into {target_lang}"))
configs = list(apps.get_app_configs()) configs = list(apps.get_app_configs())
configs.append(RootDirectory()) # noinspection PyTypeChecker
configs.append(RootDirectory()) # type: ignore [arg-type]
for app_conf in configs: for app_conf in configs:
if app_conf.label not in target_apps: if app_conf.label not in target_apps:
@ -212,8 +213,8 @@ class Command(BaseCommand):
protected = [] protected = []
maps: list[list[str]] = [] maps: list[list[str]] = []
for e in to_trans: for entry in to_trans:
txt = source_map[e.msgid] txt = source_map[entry.msgid]
p_txt, p_map = placeholderize(txt) p_txt, p_map = placeholderize(txt)
protected.append(p_txt) protected.append(p_txt)
maps.append(p_map) maps.append(p_map)
@ -227,14 +228,14 @@ class Command(BaseCommand):
resp.raise_for_status() resp.raise_for_status()
result = resp.json() result = resp.json()
except Exception as exc: except Exception as exc:
raise CommandError(f"DeepL error: {exc} {resp.text}") raise CommandError(f"DeepL error: {exc} {resp.text}") from exc
trans = result.get("translations", []) trans = result.get("translations", [])
if len(trans) != len(to_trans): if len(trans) != len(to_trans):
raise CommandError(f"Got {len(trans)} translations, expected {len(to_trans)}") raise CommandError(f"Got {len(trans)} translations, expected {len(to_trans)}")
for e, obj, pmap in zip(to_trans, trans, maps, strict=True): for entry, obj, pmap in zip(to_trans, trans, maps, strict=True):
e.msgstr = deplaceholderize(obj["text"], pmap) entry.msgstr = deplaceholderize(obj["text"], pmap)
new_po.save(tgt_path) new_po.save(tgt_path)
self.stdout.write(self.style.SUCCESS(f"Saved {tgt_path}")) self.stdout.write(self.style.SUCCESS(f"Saved {tgt_path}"))

View file

@ -15,7 +15,7 @@ class Command(BaseCommand):
for product in Product.objects.filter(stocks__isnull=False): for product in Product.objects.filter(stocks__isnull=False):
for stock in product.stocks.all(): for stock in product.stocks.all():
try: try:
stock.price = AbstractVendor.round_price_marketologically(stock.price) # type: ignore stock.price = AbstractVendor.round_price_marketologically(stock.price)
stock.save() stock.save()
except Exception as e: except Exception as e:
self.stdout.write(self.style.WARNING(f"Couldn't fix price on {stock.uuid}")) self.stdout.write(self.style.WARNING(f"Couldn't fix price on {stock.uuid}"))

View file

@ -61,16 +61,16 @@ class Command(BaseCommand):
try: try:
module_path, model_name, field_name = target.rsplit(".", 2) module_path, model_name, field_name = target.rsplit(".", 2)
except ValueError: except ValueError as e:
raise CommandError( raise CommandError(
"Invalid target format. Use app.module.Model.field, e.g. core.models.Product.description" "Invalid target format. Use app.module.Model.field, e.g. core.models.Product.description"
) ) from e
try: try:
module = importlib.import_module(module_path) module = importlib.import_module(module_path)
model = getattr(module, model_name) model = getattr(module, model_name)
except (ImportError, AttributeError) as e: except (ImportError, AttributeError) as e:
raise CommandError(f"Could not import model '{model_name}' from '{module_path}': {e}") raise CommandError(f"Could not import model '{model_name}' from '{module_path}': {e}") from e
dest_suffix = lang.replace("-", "_") dest_suffix = lang.replace("-", "_")
dest_field = f"{field_name}_{dest_suffix}" dest_field = f"{field_name}_{dest_suffix}"

View file

@ -9,20 +9,20 @@ logger = logging.getLogger("evibes")
class AddressManager(models.Manager): class AddressManager(models.Manager):
def create(self, raw_data: str, **kwargs): # type: ignore def create(self, **kwargs):
if not raw_data: if not kwargs.get("raw_data"):
raise ValueError("'raw_data' (address string) must be provided.") raise ValueError("'raw_data' (address string) must be provided.")
params: dict[str, str | int] = { params: dict[str, str | int] = { # type: ignore [annotation-unchecked]
"format": "json", "format": "json",
"addressdetails": 1, "addressdetails": 1,
"q": raw_data, "q": kwargs.get("raw_data"),
} }
resp = requests.get(config.NOMINATIM_URL.rstrip("/") + "/search", params=params) resp = requests.get(config.NOMINATIM_URL.rstrip("/") + "/search", params=params)
resp.raise_for_status() resp.raise_for_status()
results = resp.json() results = resp.json()
if not results: if not results:
raise ValueError(f"No geocoding result for address: {raw_data}") raise ValueError(f"No geocoding result for address: {kwargs.get('raw_data')}")
data = results[0] data = results[0]
addr = data.get("address", {}) addr = data.get("address", {})
@ -51,7 +51,7 @@ class AddressManager(models.Manager):
address_line_2 = "" address_line_2 = ""
return super().get_or_create( return super().get_or_create(
raw_data=raw_data, raw_data=kwargs.get("raw_data"),
address_line=f"{address_line_1}, {address_line_2}", address_line=f"{address_line_1}, {address_line_2}",
street=street, street=street,
district=district, district=district,

File diff suppressed because it is too large Load diff

View file

@ -7,101 +7,205 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('core', '0001_initial'), ("core", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='order', model_name="order",
name='user', name="user",
field=models.ForeignKey(help_text='the user who placed the order', on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL, verbose_name='user'), field=models.ForeignKey(
help_text="the user who placed the order",
on_delete=django.db.models.deletion.CASCADE,
related_name="orders",
to=settings.AUTH_USER_MODEL,
verbose_name="user",
),
), ),
migrations.AddField( migrations.AddField(
model_name='orderproduct', model_name="orderproduct",
name='order', name="order",
field=models.ForeignKey(help_text='reference to the parent order that contains this product', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='order_products', to='core.order', verbose_name='parent order'), field=models.ForeignKey(
help_text="reference to the parent order that contains this product",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="order_products",
to="core.order",
verbose_name="parent order",
),
), ),
migrations.AddField( migrations.AddField(
model_name='feedback', model_name="feedback",
name='order_product', name="order_product",
field=models.OneToOneField(help_text='references the specific product in an order that this feedback is about', on_delete=django.db.models.deletion.CASCADE, to='core.orderproduct', verbose_name='related order product'), field=models.OneToOneField(
help_text="references the specific product in an order that this feedback is about",
on_delete=django.db.models.deletion.CASCADE,
to="core.orderproduct",
verbose_name="related order product",
),
), ),
migrations.AddField( migrations.AddField(
model_name='product', model_name="product",
name='brand', name="brand",
field=models.ForeignKey(blank=True, help_text='optionally associate this product with a brand', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.brand', verbose_name='brand'), field=models.ForeignKey(
blank=True,
help_text="optionally associate this product with a brand",
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="core.brand",
verbose_name="brand",
),
), ),
migrations.AddField( migrations.AddField(
model_name='product', model_name="product",
name='category', name="category",
field=models.ForeignKey(help_text='category this product belongs to', on_delete=django.db.models.deletion.CASCADE, to='core.category', verbose_name='category'), field=models.ForeignKey(
help_text="category this product belongs to",
on_delete=django.db.models.deletion.CASCADE,
to="core.category",
verbose_name="category",
),
), ),
migrations.AddField( migrations.AddField(
model_name='orderproduct', model_name="orderproduct",
name='product', name="product",
field=models.ForeignKey(blank=True, help_text='the specific product associated with this order line', null=True, on_delete=django.db.models.deletion.PROTECT, to='core.product', verbose_name='associated product'), field=models.ForeignKey(
blank=True,
help_text="the specific product associated with this order line",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="core.product",
verbose_name="associated product",
),
), ),
migrations.AddField( migrations.AddField(
model_name='attributevalue', model_name="attributevalue",
name='product', name="product",
field=models.ForeignKey(help_text="the specific product associated with this attribute's value", null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attributes', to='core.product', verbose_name='associated product'), field=models.ForeignKey(
help_text="the specific product associated with this attribute's value",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="attributes",
to="core.product",
verbose_name="associated product",
),
), ),
migrations.AddField( migrations.AddField(
model_name='productimage', model_name="productimage",
name='product', name="product",
field=models.ForeignKey(help_text='the product that this image represents', on_delete=django.db.models.deletion.CASCADE, related_name='images', to='core.product', verbose_name='associated product'), field=models.ForeignKey(
help_text="the product that this image represents",
on_delete=django.db.models.deletion.CASCADE,
related_name="images",
to="core.product",
verbose_name="associated product",
),
), ),
migrations.AddField( migrations.AddField(
model_name='product', model_name="product",
name='tags', name="tags",
field=models.ManyToManyField(blank=True, help_text='tags that help describe or group this product', to='core.producttag', verbose_name='product tags'), field=models.ManyToManyField(
blank=True,
help_text="tags that help describe or group this product",
to="core.producttag",
verbose_name="product tags",
),
), ),
migrations.AddField( migrations.AddField(
model_name='promocode', model_name="promocode",
name='user', name="user",
field=models.ForeignKey(blank=True, help_text='user assigned to this promocode if applicable', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='promocodes', to=settings.AUTH_USER_MODEL, verbose_name='assigned user'), field=models.ForeignKey(
blank=True,
help_text="user assigned to this promocode if applicable",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="promocodes",
to=settings.AUTH_USER_MODEL,
verbose_name="assigned user",
),
), ),
migrations.AddField( migrations.AddField(
model_name='order', model_name="order",
name='promo_code', name="promo_code",
field=models.ForeignKey(blank=True, help_text='optional promo code applied to this order', null=True, on_delete=django.db.models.deletion.PROTECT, to='core.promocode', verbose_name='applied promo code'), field=models.ForeignKey(
blank=True,
help_text="optional promo code applied to this order",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="core.promocode",
verbose_name="applied promo code",
),
), ),
migrations.AddField( migrations.AddField(
model_name='promotion', model_name="promotion",
name='products', name="products",
field=models.ManyToManyField(blank=True, help_text='select which products are included in this promotion', to='core.product', verbose_name='included products'), field=models.ManyToManyField(
blank=True,
help_text="select which products are included in this promotion",
to="core.product",
verbose_name="included products",
),
), ),
migrations.AddField( migrations.AddField(
model_name='stock', model_name="stock",
name='product', name="product",
field=models.ForeignKey(blank=True, help_text='the product associated with this stock entry', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stocks', to='core.product', verbose_name='associated product'), field=models.ForeignKey(
blank=True,
help_text="the product associated with this stock entry",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="stocks",
to="core.product",
verbose_name="associated product",
),
), ),
migrations.AddIndex( migrations.AddIndex(
model_name='vendor', model_name="vendor",
index=django.contrib.postgres.indexes.GinIndex(fields=['authentication'], name='core_vendor_authent_80dc1f_gin'), index=django.contrib.postgres.indexes.GinIndex(
fields=["authentication"], name="core_vendor_authent_80dc1f_gin"
),
), ),
migrations.AddField( migrations.AddField(
model_name='stock', model_name="stock",
name='vendor', name="vendor",
field=models.ForeignKey(help_text='the vendor supplying this product stock', on_delete=django.db.models.deletion.CASCADE, to='core.vendor', verbose_name='associated vendor'), field=models.ForeignKey(
help_text="the vendor supplying this product stock",
on_delete=django.db.models.deletion.CASCADE,
to="core.vendor",
verbose_name="associated vendor",
),
), ),
migrations.AddField( migrations.AddField(
model_name='wishlist', model_name="wishlist",
name='products', name="products",
field=models.ManyToManyField(blank=True, help_text='products that the user has marked as wanted', to='core.product', verbose_name='wishlisted products'), field=models.ManyToManyField(
blank=True,
help_text="products that the user has marked as wanted",
to="core.product",
verbose_name="wishlisted products",
),
), ),
migrations.AddField( migrations.AddField(
model_name='wishlist', model_name="wishlist",
name='user', name="user",
field=models.OneToOneField(blank=True, help_text='user who owns this wishlist', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_related_wishlist', to=settings.AUTH_USER_MODEL, verbose_name='wishlist owner'), field=models.OneToOneField(
blank=True,
help_text="user who owns this wishlist",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="user_related_wishlist",
to=settings.AUTH_USER_MODEL,
verbose_name="wishlist owner",
),
), ),
migrations.AddIndex( migrations.AddIndex(
model_name='orderproduct', model_name="orderproduct",
index=django.contrib.postgres.indexes.GinIndex(fields=['notifications', 'attributes'], name='core_orderp_notific_cd27e9_gin'), index=django.contrib.postgres.indexes.GinIndex(
fields=["notifications", "attributes"], name="core_orderp_notific_cd27e9_gin"
),
), ),
] ]

View file

@ -4,105 +4,198 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0003_alter_attribute_name_alter_attribute_name_ar_ar_and_more'), ("core", "0003_alter_attribute_name_alter_attribute_name_ar_ar_and_more"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name', name="name",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_ar_AR', name="name_ar_AR",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_cs_CZ', name="name_cs_CZ",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_da_DK', name="name_da_DK",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_de_DE', name="name_de_DE",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_en_GB', name="name_en_GB",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_en_US', name="name_en_US",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_es_ES', name="name_es_ES",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_fr_FR', name="name_fr_FR",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_hi_IN', name="name_hi_IN",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_it_IT', name="name_it_IT",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_ja_JP', name="name_ja_JP",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_kk_KZ', name="name_kk_KZ",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_nl_NL', name="name_nl_NL",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_pl_PL', name="name_pl_PL",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_pt_BR', name="name_pt_BR",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_ro_RO', name="name_ro_RO",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_ru_RU', name="name_ru_RU",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='name_zh_hans', name="name_zh_hans",
field=models.CharField(help_text='provide a clear identifying name for the product', max_length=255, null=True, verbose_name='product name'), field=models.CharField(
help_text="provide a clear identifying name for the product",
max_length=255,
null=True,
verbose_name="product name",
),
), ),
] ]

View file

@ -4,19 +4,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0004_alter_product_name_alter_product_name_ar_ar_and_more'), ("core", "0004_alter_product_name_alter_product_name_ar_ar_and_more"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='brand', model_name="brand",
name='category', name="category",
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='categories', name="categories",
field=models.ManyToManyField(blank=True, help_text='optional categories that this brand is associated with', to='core.category', verbose_name='associated categories'), field=models.ManyToManyField(
blank=True,
help_text="optional categories that this brand is associated with",
to="core.category",
verbose_name="associated categories",
),
), ),
] ]

View file

@ -4,15 +4,28 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0005_remove_brand_category_brand_categories'), ("core", "0005_remove_brand_category_brand_categories"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='order', model_name="order",
name='status', name="status",
field=models.CharField(choices=[('PENDING', 'pending'), ('FAILED', 'failed'), ('PAYMENT', 'payment'), ('CREATED', 'created'), ('DELIVERING', 'delivering'), ('FINISHED', 'finished'), ('MOMENTAL', 'momental')], default='PENDING', help_text='current status of the order in its lifecycle', max_length=64, verbose_name='order status'), field=models.CharField(
choices=[
("PENDING", "pending"),
("FAILED", "failed"),
("PAYMENT", "payment"),
("CREATED", "created"),
("DELIVERING", "delivering"),
("FINISHED", "finished"),
("MOMENTAL", "momental"),
],
default="PENDING",
help_text="current status of the order in its lifecycle",
max_length=64,
verbose_name="order status",
),
), ),
] ]

View file

@ -7,16 +7,20 @@ import core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0006_alter_order_status'), ("core", "0006_alter_order_status"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='category', model_name="category",
name='image', name="image",
field=models.ImageField(blank=True, help_text='upload an image representing this category', null=True, field=models.ImageField(
upload_to='categories/', blank=True,
help_text="upload an image representing this category",
null=True,
upload_to="categories/",
validators=[core.validators.validate_category_image_dimensions], validators=[core.validators.validate_category_image_dimensions],
verbose_name='category image'), verbose_name="category image",
),
), ),
] ]

View file

@ -9,33 +9,57 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0007_alter_category_image'), ("core", "0007_alter_category_image"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='DigitalAssetDownload', name="DigitalAssetDownload",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('num_downloads', models.IntegerField(default=0)), help_text="when the object first appeared on the database",
('order_product', verbose_name="created",
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='download', ),
to='core.orderproduct')), ),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
),
),
("num_downloads", models.IntegerField(default=0)),
(
"order_product",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE, related_name="download", to="core.orderproduct"
),
),
], ],
options={ options={
'verbose_name': 'download', "verbose_name": "download",
'verbose_name_plural': 'downloads', "verbose_name_plural": "downloads",
}, },
), ),
] ]

View file

@ -11,32 +11,57 @@ import core.utils
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0008_digitalassetdownload'), ("core", "0008_digitalassetdownload"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Documentary', name="Documentary",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('document', models.FileField(upload_to=core.utils.get_product_uuid_as_path)), help_text="when the object first appeared on the database",
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documentaries', verbose_name="created",
to='core.product')), ),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
),
),
("document", models.FileField(upload_to=core.utils.get_product_uuid_as_path)),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="documentaries", to="core.product"
),
),
], ],
options={ options={
'verbose_name': 'documentary', "verbose_name": "documentary",
'verbose_name_plural': 'documentaries', "verbose_name_plural": "documentaries",
}, },
), ),
] ]

View file

@ -4,15 +4,20 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0009_documentary'), ("core", "0009_documentary"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='product', model_name="product",
name='partnumber', name="partnumber",
field=models.CharField(default=None, help_text='part number for this product', null=True, unique=True, verbose_name='part number'), field=models.CharField(
default=None,
help_text="part number for this product",
null=True,
unique=True,
verbose_name="part number",
),
), ),
] ]

View file

@ -7,138 +7,222 @@ import core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0010_product_partnumber'), ("core", "0010_product_partnumber"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='big_logo', name="big_logo",
field=models.ImageField(blank=True, help_text='upload a big logo representing this brand', null=True, field=models.ImageField(
upload_to='brands/', blank=True,
help_text="upload a big logo representing this brand",
null=True,
upload_to="brands/",
validators=[core.validators.validate_category_image_dimensions], validators=[core.validators.validate_category_image_dimensions],
verbose_name='brand big image'), verbose_name="brand big image",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description', name="description",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_ar_AR', name="description_ar_AR",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_cs_CZ', name="description_cs_CZ",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_da_DK', name="description_da_DK",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_de_DE', name="description_de_DE",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_en_GB', name="description_en_GB",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_en_US', name="description_en_US",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_es_ES', name="description_es_ES",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_fr_FR', name="description_fr_FR",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_hi_IN', name="description_hi_IN",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_it_IT', name="description_it_IT",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_ja_JP', name="description_ja_JP",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_kk_KZ', name="description_kk_KZ",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_nl_NL', name="description_nl_NL",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_pl_PL', name="description_pl_PL",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_pt_BR', name="description_pt_BR",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_ro_RO', name="description_ro_RO",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_ru_RU', name="description_ru_RU",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='description_zh_hans', name="description_zh_hans",
field=models.TextField(blank=True, help_text='add a detailed description of the brand', null=True, field=models.TextField(
verbose_name='brand description'), blank=True,
help_text="add a detailed description of the brand",
null=True,
verbose_name="brand description",
),
), ),
migrations.AddField( migrations.AddField(
model_name='brand', model_name="brand",
name='small_logo', name="small_logo",
field=models.ImageField(blank=True, help_text='upload a logo representing this brand', null=True, field=models.ImageField(
upload_to='brands/', blank=True,
help_text="upload a logo representing this brand",
null=True,
upload_to="brands/",
validators=[core.validators.validate_category_image_dimensions], validators=[core.validators.validate_category_image_dimensions],
verbose_name='brand small image'), verbose_name="brand small image",
),
), ),
] ]

View file

@ -6,16 +6,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0011_brand_big_logo_brand_description_and_more'), ("core", "0011_brand_big_logo_brand_description_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='order', model_name="order",
name='user', name="user",
field=models.ForeignKey(blank=True, help_text='the user who placed the order', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL, verbose_name='user'), field=models.ForeignKey(
blank=True,
help_text="the user who placed the order",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="orders",
to=settings.AUTH_USER_MODEL,
verbose_name="user",
),
), ),
] ]

View file

@ -5,15 +5,21 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0012_alter_order_user'), ("core", "0012_alter_order_user"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='product', model_name="product",
name='slug', name="slug",
field=django_extensions.db.fields.AutoSlugField(allow_unicode=True, null=True, blank=True, editable=False, populate_from=('category.name', 'brand.name', 'name'), unique=True), field=django_extensions.db.fields.AutoSlugField(
allow_unicode=True,
null=True,
blank=True,
editable=False,
populate_from=("category.name", "brand.name", "name"),
unique=True,
),
), ),
] ]

View file

@ -5,15 +5,21 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0013_product_slug'), ("core", "0013_product_slug"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='slug', name="slug",
field=django_extensions.db.fields.AutoSlugField(allow_unicode=True, blank=True, editable=False, null=True, populate_from=('uuid', 'category.name', 'brand.name', 'name'), unique=True), field=django_extensions.db.fields.AutoSlugField(
allow_unicode=True,
blank=True,
editable=False,
null=True,
populate_from=("uuid", "category.name", "brand.name", "name"),
unique=True,
),
), ),
] ]

View file

@ -5,15 +5,21 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0014_alter_product_slug'), ("core", "0014_alter_product_slug"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='slug', name="slug",
field=django_extensions.db.fields.AutoSlugField(allow_unicode=True, blank=True, editable=False, null=True, populate_from=('category__name', 'name'), unique=True), field=django_extensions.db.fields.AutoSlugField(
allow_unicode=True,
blank=True,
editable=False,
null=True,
populate_from=("category__name", "name"),
unique=True,
),
), ),
] ]

View file

@ -5,15 +5,21 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0015_alter_product_slug'), ("core", "0015_alter_product_slug"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='slug', name="slug",
field=django_extensions.db.fields.AutoSlugField(allow_unicode=True, blank=True, editable=False, null=True, populate_from=('uuid', 'category__name', 'name'), unique=True), field=django_extensions.db.fields.AutoSlugField(
allow_unicode=True,
blank=True,
editable=False,
null=True,
populate_from=("uuid", "category__name", "name"),
unique=True,
),
), ),
] ]

View file

@ -7,15 +7,18 @@ import core.utils
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0016_alter_product_slug'), ("core", "0016_alter_product_slug"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='order', model_name="order",
name='human_readable_id', name="human_readable_id",
field=models.CharField(default=core.utils.generate_human_readable_id, field=models.CharField(
help_text='a human-readable identifier for the order', max_length=8, default=core.utils.generate_human_readable_id,
verbose_name='human readable id'), help_text="a human-readable identifier for the order",
max_length=8,
verbose_name="human readable id",
),
), ),
] ]

View file

@ -8,11 +8,7 @@ def fix_duplicates(apps, schema_editor):
if schema_editor: if schema_editor:
pass pass
Order = apps.get_model("core", "Order") Order = apps.get_model("core", "Order")
duplicates = ( duplicates = Order.objects.values("human_readable_id").annotate(count=Count("uuid")).filter(count__gt=1)
Order.objects.values("human_readable_id")
.annotate(count=Count("uuid"))
.filter(count__gt=1)
)
for duplicate in duplicates: for duplicate in duplicates:
h_id = duplicate["human_readable_id"] h_id = duplicate["human_readable_id"]
orders = Order.objects.filter(human_readable_id=h_id).order_by("uuid") orders = Order.objects.filter(human_readable_id=h_id).order_by("uuid")
@ -20,6 +16,7 @@ def fix_duplicates(apps, schema_editor):
new_id = order.human_readable_id new_id = order.human_readable_id
while Order.objects.filter(human_readable_id=new_id).exists(): while Order.objects.filter(human_readable_id=new_id).exists():
from core.utils import generate_human_readable_id from core.utils import generate_human_readable_id
new_id = generate_human_readable_id() new_id = generate_human_readable_id()
order.human_readable_id = new_id order.human_readable_id = new_id
order.save() order.save()
@ -35,16 +32,20 @@ def reverse_func(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0017_order_human_readable_id'), ("core", "0017_order_human_readable_id"),
] ]
operations = [ operations = [
migrations.RunPython(fix_duplicates, reverse_func), migrations.RunPython(fix_duplicates, reverse_func),
migrations.AlterField( migrations.AlterField(
model_name='order', model_name="order",
name='human_readable_id', name="human_readable_id",
field=models.CharField(default=core.utils.generate_human_readable_id, field=models.CharField(
help_text='a human-readable identifier for the order', max_length=8, unique=True, default=core.utils.generate_human_readable_id,
verbose_name='human readable id'), help_text="a human-readable identifier for the order",
max_length=8,
unique=True,
verbose_name="human readable id",
),
), ),
] ]

View file

@ -11,46 +11,86 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0018_alter_order_human_readable_id'), ("core", "0018_alter_order_human_readable_id"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Address', name="Address",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('street', models.CharField(max_length=255, null=True, verbose_name='street')), help_text="when the object first appeared on the database",
('district', models.CharField(max_length=255, null=True, verbose_name='district')), verbose_name="created",
('city', models.CharField(max_length=100, null=True, verbose_name='city')), ),
('region', models.CharField(max_length=100, null=True, verbose_name='region')), ),
('postal_code', models.CharField(max_length=20, null=True, verbose_name='postal code')), (
('country', models.CharField(max_length=40, null=True, verbose_name='country')), "modified",
('location', django.contrib.gis.db.models.fields.PointField(blank=True, geography=True, django_extensions.db.fields.ModificationDateTimeField(
help_text='geolocation point: (longitude, latitude)', auto_now=True, help_text="when the object was last modified", verbose_name="modified"
null=True, srid=4326)), ),
('raw_data', models.JSONField(blank=True, help_text='full JSON response from geocoder for this address', ),
null=True)), ("street", models.CharField(max_length=255, null=True, verbose_name="street")),
('api_response', ("district", models.CharField(max_length=255, null=True, verbose_name="district")),
models.JSONField(blank=True, help_text='stored JSON response from the geocoding service', null=True)), ("city", models.CharField(max_length=100, null=True, verbose_name="city")),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, ("region", models.CharField(max_length=100, null=True, verbose_name="region")),
to=settings.AUTH_USER_MODEL)), ("postal_code", models.CharField(max_length=20, null=True, verbose_name="postal code")),
("country", models.CharField(max_length=40, null=True, verbose_name="country")),
(
"location",
django.contrib.gis.db.models.fields.PointField(
blank=True,
geography=True,
help_text="geolocation point: (longitude, latitude)",
null=True,
srid=4326,
),
),
(
"raw_data",
models.JSONField(
blank=True, help_text="full JSON response from geocoder for this address", null=True
),
),
(
"api_response",
models.JSONField(
blank=True, help_text="stored JSON response from the geocoding service", null=True
),
),
(
"user",
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
], ],
options={ options={
'verbose_name': 'address', "verbose_name": "address",
'verbose_name_plural': 'addresses', "verbose_name_plural": "addresses",
'indexes': [models.Index(fields=['location'], name='core_addres_locatio_eb6b39_idx')], "indexes": [models.Index(fields=["location"], name="core_addres_locatio_eb6b39_idx")],
}, },
), ),
] ]

View file

@ -5,7 +5,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0019_address'), ("core", "0019_address"),
] ]
operations = [ operations = [
@ -17,29 +17,30 @@ class Migration(migrations.Migration):
), ),
reverse_sql=migrations.RunSQL.noop, reverse_sql=migrations.RunSQL.noop,
), ),
migrations.AddField( migrations.AddField(
model_name='order', model_name="order",
name='billing_address', name="billing_address",
field=models.ForeignKey( field=models.ForeignKey(
blank=True, null=True, blank=True,
null=True,
on_delete=models.deletion.CASCADE, on_delete=models.deletion.CASCADE,
related_name='billing_address_order', related_name="billing_address_order",
to='core.address', to="core.address",
verbose_name='billing address', verbose_name="billing address",
help_text='the billing address used for this order', help_text="the billing address used for this order",
), ),
), ),
migrations.AddField( migrations.AddField(
model_name='order', model_name="order",
name='shipping_address', name="shipping_address",
field=models.ForeignKey( field=models.ForeignKey(
blank=True, null=True, blank=True,
null=True,
on_delete=models.deletion.CASCADE, on_delete=models.deletion.CASCADE,
related_name='shipping_address_order', related_name="shipping_address_order",
to='core.address', to="core.address",
verbose_name='shipping address', verbose_name="shipping address",
help_text='the shipping address used for this order', help_text="the shipping address used for this order",
), ),
), ),
] ]

View file

@ -7,7 +7,7 @@ from django.db import migrations
def populate_slugs(apps, schema_editor): def populate_slugs(apps, schema_editor):
if schema_editor: if schema_editor:
pass pass
Category = apps.get_model('core', 'Category') Category = apps.get_model("core", "Category")
for category in Category.objects.all(): for category in Category.objects.all():
try: try:
if not category.slug: if not category.slug:
@ -18,15 +18,16 @@ def populate_slugs(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0021_rename_name_ar_ar_attribute_name_ar_ar_and_more'), ("core", "0021_rename_name_ar_ar_attribute_name_ar_ar_and_more"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='category', model_name="category",
name='slug', name="slug",
field=django_extensions.db.fields.AutoSlugField(allow_unicode=True, blank=True, editable=False, null=True, field=django_extensions.db.fields.AutoSlugField(
populate_from=('uuid', 'name'), unique=True), allow_unicode=True, blank=True, editable=False, null=True, populate_from=("uuid", "name"), unique=True
),
), ),
migrations.RunPython(populate_slugs, reverse_code=migrations.RunPython.noop), migrations.RunPython(populate_slugs, reverse_code=migrations.RunPython.noop),
] ]

View file

@ -5,14 +5,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0022_category_slug'), ("core", "0022_category_slug"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='address', model_name="address",
name='address_line', name="address_line",
field=models.TextField(blank=True, help_text='address line for the customer', null=True, field=models.TextField(
verbose_name='address line'), blank=True, help_text="address line for the customer", null=True, verbose_name="address line"
),
), ),
] ]

View file

@ -9,95 +9,256 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0023_address_address_line'), ("core", "0023_address_address_line"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='CategoryTag', name="CategoryTag",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('tag_name', models.CharField(help_text='internal tag identifier for the product tag', max_length=255, help_text="when the object first appeared on the database",
verbose_name='tag name')), verbose_name="created",
('name', ),
models.CharField(help_text='user-friendly name for the product tag', max_length=255, unique=True, ),
verbose_name='tag display name')), (
('name_en_gb', "modified",
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, django_extensions.db.fields.ModificationDateTimeField(
unique=True, verbose_name='tag display name')), auto_now=True, help_text="when the object was last modified", verbose_name="modified"
('name_ar_ar', ),
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, ),
unique=True, verbose_name='tag display name')), (
('name_cs_cz', "tag_name",
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, models.CharField(
unique=True, verbose_name='tag display name')), help_text="internal tag identifier for the product tag", max_length=255, verbose_name="tag name"
('name_da_dk', ),
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, ),
unique=True, verbose_name='tag display name')), (
('name_de_de', "name",
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, models.CharField(
unique=True, verbose_name='tag display name')), help_text="user-friendly name for the product tag",
('name_en_us', max_length=255,
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, unique=True,
unique=True, verbose_name='tag display name')), verbose_name="tag display name",
('name_es_es', ),
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, ),
unique=True, verbose_name='tag display name')), (
('name_fr_fr', "name_en_gb",
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, models.CharField(
unique=True, verbose_name='tag display name')), help_text="user-friendly name for the product tag",
('name_hi_in', max_length=255,
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, null=True,
unique=True, verbose_name='tag display name')), unique=True,
('name_it_it', verbose_name="tag display name",
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, ),
unique=True, verbose_name='tag display name')), ),
('name_ja_jp', (
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, "name_ar_ar",
unique=True, verbose_name='tag display name')), models.CharField(
('name_kk_kz', help_text="user-friendly name for the product tag",
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, max_length=255,
unique=True, verbose_name='tag display name')), null=True,
('name_nl_nl', unique=True,
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, verbose_name="tag display name",
unique=True, verbose_name='tag display name')), ),
('name_pl_pl', ),
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, (
unique=True, verbose_name='tag display name')), "name_cs_cz",
('name_pt_br', models.CharField(
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, help_text="user-friendly name for the product tag",
unique=True, verbose_name='tag display name')), max_length=255,
('name_ro_ro', null=True,
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, unique=True,
unique=True, verbose_name='tag display name')), verbose_name="tag display name",
('name_ru_ru', ),
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, ),
unique=True, verbose_name='tag display name')), (
('name_zh_hans', "name_da_dk",
models.CharField(help_text='user-friendly name for the product tag', max_length=255, null=True, models.CharField(
unique=True, verbose_name='tag display name')), help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_de_de",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_en_us",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_es_es",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_fr_fr",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_hi_in",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_it_it",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_ja_jp",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_kk_kz",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_nl_nl",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_pl_pl",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_pt_br",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_ro_ro",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_ru_ru",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
(
"name_zh_hans",
models.CharField(
help_text="user-friendly name for the product tag",
max_length=255,
null=True,
unique=True,
verbose_name="tag display name",
),
),
], ],
options={ options={
'verbose_name': 'category tag', "verbose_name": "category tag",
'verbose_name_plural': 'category tags', "verbose_name_plural": "category tags",
}, },
bases=(django_prometheus.models.ExportModelOperationsMixin('category_tag'), models.Model), bases=(django_prometheus.models.ExportModelOperationsMixin("category_tag"), models.Model),
), ),
migrations.AddField( migrations.AddField(
model_name='category', model_name="category",
name='tags', name="tags",
field=models.ManyToManyField(blank=True, help_text='tags that help describe or group this category', field=models.ManyToManyField(
to='core.categorytag', verbose_name='category tags'), blank=True,
help_text="tags that help describe or group this category",
to="core.categorytag",
verbose_name="category tags",
),
), ),
] ]

View file

@ -6,15 +6,19 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0024_categorytag_category_tags'), ("core", "0024_categorytag_category_tags"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='product', model_name="product",
name='category', name="category",
field=models.ForeignKey(help_text='category this product belongs to', field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='products', help_text="category this product belongs to",
to='core.category', verbose_name='category'), on_delete=django.db.models.deletion.CASCADE,
related_name="products",
to="core.category",
verbose_name="category",
),
), ),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0025_alter_product_category"), ("core", "0025_alter_product_category"),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0026_brand_slug_alter_category_slug_alter_product_slug"), ("core", "0026_brand_slug_alter_category_slug_alter_product_slug"),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0027_brand_priority_alter_brand_slug"), ("core", "0027_brand_priority_alter_brand_slug"),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0028_alter_category_slug_alter_product_slug"), ("core", "0028_alter_category_slug_alter_product_slug"),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0029_alter_category_slug"), ("core", "0029_alter_category_slug"),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0030_alter_category_slug"), ("core", "0030_alter_category_slug"),
] ]

View file

@ -5,7 +5,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0031_alter_product_slug"), ("core", "0031_alter_product_slug"),
] ]

View file

@ -6,7 +6,6 @@ import core.utils.db
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0032_alter_brand_slug_alter_category_slug_and_more"), ("core", "0032_alter_brand_slug_alter_category_slug_and_more"),
] ]

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0033_alter_category_slug"), ("core", "0033_alter_category_slug"),
] ]

View file

@ -6,7 +6,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("core", "0034_category_priority_alter_brand_priority"), ("core", "0034_category_priority_alter_brand_priority"),
] ]

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
import logging import logging
from contextlib import suppress from contextlib import suppress
from typing import Collection, Any
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.core.cache import cache from django.core.cache import cache
@ -113,7 +114,7 @@ class CategoryDetailSerializer(ModelSerializer):
return filterable_results return filterable_results
def get_children(self, obj) -> list: def get_children(self, obj) -> Collection[Any]:
request = self.context.get("request") request = self.context.get("request")
if request is not None and request.user.has_perm("view_category"): if request is not None and request.user.has_perm("view_category"):
children = obj.children.all() children = obj.children.all()

View file

@ -26,8 +26,8 @@ from core.serializers.utility import AddressSerializer
class AttributeGroupSimpleSerializer(ModelSerializer): class AttributeGroupSimpleSerializer(ModelSerializer):
parent: PrimaryKeyRelatedField = PrimaryKeyRelatedField(read_only=True) # type: ignore parent = PrimaryKeyRelatedField(read_only=True)
children: PrimaryKeyRelatedField = PrimaryKeyRelatedField(many=True, read_only=True) # type: ignore children = PrimaryKeyRelatedField(many=True, read_only=True)
class Meta: class Meta:
model = AttributeGroup model = AttributeGroup

View file

@ -89,7 +89,7 @@ class DoFeedbackSerializer(Serializer):
class CacheOperatorSerializer(Serializer): class CacheOperatorSerializer(Serializer):
key = CharField(required=True) key = CharField(required=True)
data = JSONField(required=False) # type: ignore data = JSONField(required=False)
timeout = IntegerField(required=False) timeout = IntegerField(required=False)

View file

@ -1,5 +1,4 @@
{% load tz static i18n filters conditions %} {% load tz static i18n filters conditions %}
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">

View file

@ -1,24 +1,12 @@
import logging import logging
from django.db.models import Model
from django.db.models.constants import LOOKUP_SEP from django.db.models.constants import LOOKUP_SEP
from django.utils.translation import gettext_lazy as _
from django_extensions.db.fields import AutoSlugField from django_extensions.db.fields import AutoSlugField
from slugify import slugify from slugify import slugify
logger = logging.getLogger("evibes") logger = logging.getLogger("evibes")
def list_to_queryset(model: Model, data: list):
if not isinstance(model, Model):
raise ValueError(_(f"{model} must be model"))
if not isinstance(data, list):
raise ValueError(_(f"{data} must be list object"))
pk_list = [obj.pk for obj in data]
return model.objects.filter(pk__in=pk_list)
def unicode_slugify_function(content): def unicode_slugify_function(content):
return slugify( return slugify(
text=str(content), text=str(content),

View file

@ -47,7 +47,10 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]:
except Order.DoesNotExist: except Order.DoesNotExist:
return False, f"Order not found with the given pk: {order_pk}" return False, f"Order not found with the given pk: {order_pk}"
activate(order.user.language) # type: ignore if not order.user:
return False, f"Order's user not found with the given pk: {order_pk}"
activate(order.user.language)
set_email_settings() set_email_settings()
connection = mail.get_connection() connection = mail.get_connection()
@ -64,7 +67,7 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]:
"total_price": order.total_price, "total_price": order.total_price,
}, },
), ),
to=[order.user.email], # type: ignore to=[order.user.email],
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
connection=connection, connection=connection,
) )
@ -80,7 +83,7 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
if len(ops) <= 0: if len(ops) <= 0:
return return
activate(order.user.language) # type: ignore activate(order.user.language)
set_email_settings() set_email_settings()
connection = mail.get_connection() connection = mail.get_connection()
@ -91,16 +94,16 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
template_name="digital_order_delivered_email.html", template_name="digital_order_delivered_email.html",
context={ context={
"order_uuid": order.human_readable_id, "order_uuid": order.human_readable_id,
"user_first_name": order.user.first_name, # type: ignore "user_first_name": order.user.first_name,
"order_products": ops, "order_products": ops,
"project_name": config.PROJECT_NAME, "project_name": config.PROJECT_NAME,
"contact_email": config.EMAIL_FROM, "contact_email": config.EMAIL_FROM,
"total_price": round(sum(op.buy_price for op in ops), 2), "total_price": round(sum(op.buy_price for op in ops), 2),
"display_system_attributes": order.user.has_perm("core.view_order"), # type: ignore "display_system_attributes": order.user.has_perm("core.view_order"),
"today": datetime.today(), "today": datetime.today(),
}, },
), ),
to=[order.user.email], # type: ignore to=[order.user.email],
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
connection=connection, connection=connection,
) )
@ -110,7 +113,7 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
def send_thank_you_email(ops: list[OrderProduct]): def send_thank_you_email(ops: list[OrderProduct]):
if ops: if ops:
pass pass
activate(order.user.language) # type: ignore activate(order.user.language)
set_email_settings() set_email_settings()
@ -121,6 +124,9 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]:
except Order.DoesNotExist: except Order.DoesNotExist:
return False, f"Order not found with the given pk: {order_pk}" return False, f"Order not found with the given pk: {order_pk}"
if not order.user:
return False, f"Order's user not found with the given pk: {order_pk}"
digital_ops = [] digital_ops = []
for digital_op in order.order_products.filter( for digital_op in order.order_products.filter(

View file

@ -145,7 +145,7 @@ class AbstractVendor:
return value, "string" return value, "string"
@staticmethod @staticmethod
def auto_resolver_helper(model: Brand | Category, resolving_name: str) -> Brand | Category | None: def auto_resolver_helper(model: type[Brand] | type[Category], resolving_name: str) -> Brand | Category | None:
queryset = model.objects.filter(name=resolving_name) queryset = model.objects.filter(name=resolving_name)
if not queryset.exists(): if not queryset.exists():
if len(resolving_name) > 255: if len(resolving_name) > 255:
@ -178,7 +178,7 @@ class AbstractVendor:
except Category.DoesNotExist: except Category.DoesNotExist:
pass pass
return self.auto_resolver_helper(Category, category_name) # type: ignore return self.auto_resolver_helper(Category, category_name)
def auto_resolve_brand(self, brand_name: str): def auto_resolve_brand(self, brand_name: str):
if brand_name: if brand_name:
@ -196,7 +196,7 @@ class AbstractVendor:
except Brand.DoesNotExist: except Brand.DoesNotExist:
pass pass
return self.auto_resolver_helper(Brand, brand_name) # type: ignore return self.auto_resolver_helper(Brand, brand_name)
def resolve_price( def resolve_price(
self, self,
@ -213,7 +213,7 @@ class AbstractVendor:
price = float(original_price) price = float(original_price)
if category and category.markup_percent: if category and category.markup_percent:
price *= 1 + float(category.markup_percent) / 100.0 # type: ignore price *= 1 + float(category.markup_percent) / 100.0
elif vendor and vendor.markup_percent: elif vendor and vendor.markup_percent:
price *= 1 + vendor.markup_percent / 100.0 price *= 1 + vendor.markup_percent / 100.0
@ -268,8 +268,8 @@ class AbstractVendor:
if vendor.is_active: if vendor.is_active:
return vendor return vendor
raise VendorError(f"Vendor {self.vendor_name!r} is inactive...") raise VendorError(f"Vendor {self.vendor_name!r} is inactive...")
except Vendor.DoesNotExist: except Vendor.DoesNotExist as dne:
raise Exception(f"No matching vendor found with name {self.vendor_name!r}...") raise Exception(f"No matching vendor found with name {self.vendor_name!r}...") from dne
def get_products(self): def get_products(self):
pass pass
@ -288,6 +288,7 @@ class AbstractVendor:
def prepare_for_stock_update(self, method: str = "deactivate") -> None: def prepare_for_stock_update(self, method: str = "deactivate") -> None:
products = self.get_products_queryset() products = self.get_products_queryset()
# noinspection PyUnreachableCode
match method: match method:
case "deactivate": case "deactivate":
products.update(is_active=False) products.update(is_active=False)
@ -301,6 +302,7 @@ class AbstractVendor:
def delete_inactives(self, inactivation_method: str = "deactivate"): def delete_inactives(self, inactivation_method: str = "deactivate"):
products = self.get_products_queryset() products = self.get_products_queryset()
# noinspection PyUnreachableCode
match inactivation_method: match inactivation_method:
case "deactivate": case "deactivate":
products.filter(is_active=False).delete() products.filter(is_active=False).delete()
@ -339,7 +341,7 @@ class AbstractVendor:
defaults={"is_active": True}, defaults={"is_active": True},
) )
except Attribute.MultipleObjectsReturned: except Attribute.MultipleObjectsReturned:
attribute = Attribute.objects.filter(name=key, group=attr_group).order_by("uuid").first() # type: ignore attribute = Attribute.objects.filter(name=key, group=attr_group).order_by("uuid").first() # type: ignore [assignment]
attribute.is_active = True attribute.is_active = True
attribute.value_type = attr_value_type attribute.value_type = attr_value_type
attribute.save() attribute.save()

View file

@ -102,9 +102,6 @@ class CustomGraphQLView(FileUploadGraphQLView):
This class serves as a customization extension of FileUploadGraphQLView that allows modification This class serves as a customization extension of FileUploadGraphQLView that allows modification
or enhancement of specific behaviors, particularly the context handling for GraphQL requests. or enhancement of specific behaviors, particularly the context handling for GraphQL requests.
Attributes
----------
None
""" """
def get_context(self, request): def get_context(self, request):
@ -520,8 +517,8 @@ def favicon_view(request, *args, **kwargs):
try: try:
favicon_path = os.path.join(settings.BASE_DIR, "static/favicon.png") favicon_path = os.path.join(settings.BASE_DIR, "static/favicon.png")
return FileResponse(open(favicon_path, "rb"), content_type="image/x-icon") return FileResponse(open(favicon_path, "rb"), content_type="image/x-icon")
except FileNotFoundError: except FileNotFoundError as fnfe:
raise Http404(_("favicon not found")) raise Http404(_("favicon not found")) from fnfe
def index(request, *args, **kwargs): def index(request, *args, **kwargs):

View file

@ -2,7 +2,7 @@ import logging
import uuid import uuid
from uuid import UUID from uuid import UUID
from django.db.models import Q, QuerySet from django.db.models import Q
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -378,7 +378,7 @@ class ProductViewSet(EvibesViewSet):
lookup_val = kwargs.get(self.lookup_field) lookup_val = kwargs.get(self.lookup_field)
try: try:
product = Product.objects.get(uuid=lookup_val) product = Product.objects.get(uuid=lookup_val)
feedbacks: QuerySet[Feedback] = ( # type: ignore feedbacks = (
Feedback.objects.filter(order_product__product=product) Feedback.objects.filter(order_product__product=product)
if request.user.has_perm("core.view_feedback") if request.user.has_perm("core.view_feedback")
else Feedback.objects.filter(order_product__product=product, is_active=True) else Feedback.objects.filter(order_product__product=product, is_active=True)

View file

@ -29,7 +29,7 @@ app.conf.update(
) )
app.conf.task_routes = { app.conf.task_routes = {
'core.tasks.update_products_task': {'queue': 'stock_updater'}, "core.tasks.update_products_task": {"queue": "stock_updater"},
} }
app.config_from_object("django.conf:settings", namespace="CELERY") app.config_from_object("django.conf:settings", namespace="CELERY")

View file

@ -10,9 +10,9 @@ class CustomPagination(PageNumberPagination):
{ {
"links": {"forward": self.get_next_link(), "backward": self.get_previous_link()}, "links": {"forward": self.get_next_link(), "backward": self.get_previous_link()},
"counts": { "counts": {
"total_pages": None or self.page.paginator.num_pages, # type: ignore "total_pages": None or self.page.paginator.num_pages, # type: ignore [union-attr]
"page_size": None or self.page_size, # type: ignore "page_size": None or self.page_size,
"total_items": None or self.page.paginator.count, # type: ignore "total_items": None or self.page.paginator.count, # type: ignore [union-attr]
}, },
"data": data, "data": data,
} }

View file

@ -1,5 +1,5 @@
import logging import logging
from os import getenv from os import getenv, name
from pathlib import Path from pathlib import Path
EVIBES_VERSION = "2.9.0" EVIBES_VERSION = "2.9.0"
@ -9,7 +9,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = getenv("SECRET_KEY", "SUPER_SECRET_KEY") SECRET_KEY = getenv("SECRET_KEY", "SUPER_SECRET_KEY")
DEBUG = bool(int(getenv("DEBUG", "1"))) DEBUG = bool(int(getenv("DEBUG", "1")))
ALLOWED_HOSTS = { ALLOWED_HOSTS: set = {
"app", "app",
"worker", "worker",
"beat", "beat",
@ -27,9 +27,9 @@ else:
for entry in getenv("ALLOWED_HOSTS", "").split(" "): for entry in getenv("ALLOWED_HOSTS", "").split(" "):
ALLOWED_HOSTS.add(entry) ALLOWED_HOSTS.add(entry)
ALLOWED_HOSTS = tuple(ALLOWED_HOSTS) # type: ignore ALLOWED_HOSTS: tuple = tuple(ALLOWED_HOSTS)
CSRF_TRUSTED_ORIGINS = { CSRF_TRUSTED_ORIGINS: set = {
"http://127.0.0.1", "http://127.0.0.1",
"http://api.localhost", "http://api.localhost",
"http://b2b.localhost", "http://b2b.localhost",
@ -38,12 +38,12 @@ CSRF_TRUSTED_ORIGINS = {
for entry in getenv("CSRF_TRUSTED_ORIGINS", "").split(" "): for entry in getenv("CSRF_TRUSTED_ORIGINS", "").split(" "):
CSRF_TRUSTED_ORIGINS.add(entry) CSRF_TRUSTED_ORIGINS.add(entry)
CSRF_TRUSTED_ORIGINS = tuple(CSRF_TRUSTED_ORIGINS) # type: ignore CSRF_TRUSTED_ORIGINS: tuple = tuple(CSRF_TRUSTED_ORIGINS)
if DEBUG: if DEBUG:
CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOW_ALL_ORIGINS = True
else: else:
CORS_ALLOWED_ORIGINS = { CORS_ALLOWED_ORIGINS: set = {
"http://127.0.0.1", "http://127.0.0.1",
"http://api.localhost", "http://api.localhost",
"http://b2b.localhost", "http://b2b.localhost",
@ -51,7 +51,7 @@ else:
for entry in getenv("CORS_ALLOWED_ORIGINS", "").split(" "): for entry in getenv("CORS_ALLOWED_ORIGINS", "").split(" "):
CORS_ALLOWED_ORIGINS.add(entry) CORS_ALLOWED_ORIGINS.add(entry)
CORS_ALLOWED_ORIGINS = tuple(CORS_ALLOWED_ORIGINS) # type: ignore CORS_ALLOWED_ORIGINS: tuple = tuple(CORS_ALLOWED_ORIGINS)
CORS_ALLOW_METHODS = ( CORS_ALLOW_METHODS = (
"DELETE", "DELETE",
@ -342,3 +342,7 @@ STORAGES: dict[str, dict[str, str | int | bool | None]] = {
"BACKEND": "django.core.files.storage.FileSystemStorage", "BACKEND": "django.core.files.storage.FileSystemStorage",
}, },
} }
if name == "nt":
GDAL_LIBRARY_PATH = r"C:\OSGeo4W\bin\gdal311.dll"
GEOS_LIBRARY_PATH = r"C:\OSGeo4W\bin\geos_c.dll"

View file

@ -1,11 +1,11 @@
from evibes.settings.base import * # noqa: F403 from evibes.settings.base import getenv
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django_prometheus.db.backends.postgis", "ENGINE": "django_prometheus.db.backends.postgis",
"NAME": getenv("POSTGRES_DB"), # noqa: F405 "NAME": getenv("POSTGRES_DB"),
"USER": getenv("POSTGRES_USER"), # noqa: F405 "USER": getenv("POSTGRES_USER"),
"PASSWORD": getenv("POSTGRES_PASSWORD"), # noqa: F405 "PASSWORD": getenv("POSTGRES_PASSWORD"),
"HOST": "database", "HOST": "database",
"PORT": 5432, "PORT": 5432,
} }

View file

@ -1,9 +1,9 @@
from evibes.settings import CONSTANCE_CONFIG from evibes.settings import CONSTANCE_CONFIG
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = CONSTANCE_CONFIG.get("EMAIL_HOST")[0] # type: ignore EMAIL_HOST = CONSTANCE_CONFIG.get("EMAIL_HOST")[0]
EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0] # type: ignore EMAIL_PORT = CONSTANCE_CONFIG.get("EMAIL_PORT")[0]
EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0] # type: ignore EMAIL_USE_TLS = CONSTANCE_CONFIG.get("EMAIL_USE_TLS")[0]
EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0] # type: ignore EMAIL_USE_SSL = CONSTANCE_CONFIG.get("EMAIL_USE_SSL")[0]
EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0] # type: ignore EMAIL_HOST_USER = CONSTANCE_CONFIG.get("EMAIL_HOST_USER")[0]
EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0] # type: ignore EMAIL_HOST_PASSWORD = CONSTANCE_CONFIG.get("EMAIL_HOST_PASSWORD")[0]

View file

@ -4,9 +4,9 @@ from evibes.settings.base import EVIBES_VERSION
from evibes.settings.constance import CONSTANCE_CONFIG from evibes.settings.constance import CONSTANCE_CONFIG
JAZZMIN_SETTINGS = { JAZZMIN_SETTINGS = {
"site_title": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} Admin", # type: ignore "site_title": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} Admin", # type: ignore [index]
"site_header": str(CONSTANCE_CONFIG.get("PROJECT_NAME")[0]), # type: ignore "site_header": str(CONSTANCE_CONFIG.get("PROJECT_NAME")[0]), # type: ignore [index]
"site_brand": str(CONSTANCE_CONFIG.get("PROJECT_NAME")[0]), # type: ignore "site_brand": str(CONSTANCE_CONFIG.get("PROJECT_NAME")[0]), # type: ignore [index]
"site_logo": "logo.png", "site_logo": "logo.png",
"login_logo": "logo.png", "login_logo": "logo.png",
"login_logo_dark": "logo.png", "login_logo_dark": "logo.png",
@ -18,21 +18,21 @@ JAZZMIN_SETTINGS = {
"user_avatar": "avatar", "user_avatar": "avatar",
"topmenu_links": [ "topmenu_links": [
{"name": _("Home"), "url": "admin:index"}, {"name": _("Home"), "url": "admin:index"},
{"name": _("Storefront"), "url": f"https://{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}", "new_window": True}, {"name": _("Storefront"), "url": f"https://{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}", "new_window": True}, # type: ignore [index]
{"name": "GitLab", "url": "https://gitlab.com/wiseless/evibes", "new_window": True}, {"name": "GitLab", "url": "https://gitlab.com/wiseless/evibes", "new_window": True},
{ {
"name": _("GraphQL Docs"), "name": _("GraphQL Docs"),
"url": f"https://api.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/graphql", # type: ignore "url": f"https://api.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/graphql", # type: ignore [index]
"new_window": True, "new_window": True,
}, },
{ {
"name": _("Platform REST Docs"), "name": _("Platform REST Docs"),
"url": f"https://api.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/docs/swagger", # type: ignore "url": f"https://api.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/docs/swagger", # type: ignore [index]
"new_window": True, "new_window": True,
}, },
{ {
"name": _("B2B REST Docs"), "name": _("B2B REST Docs"),
"url": f"https://b2b.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/docs/swagger", # type: ignore "url": f"https://b2b.{CONSTANCE_CONFIG.get('BASE_DOMAIN')[0]}/docs/swagger", # type: ignore [index]
"new_window": True, "new_window": True,
}, },
{"name": _("Support"), "url": "https://t.me/fureunoir", "new_window": True}, {"name": _("Support"), "url": "https://t.me/fureunoir", "new_window": True},

View file

@ -6,8 +6,11 @@ class SkipVariableDoesNotExistFilter(logging.Filter): # noqa: F405
if record.exc_info: if record.exc_info:
exc_type, exc_instance, _ = record.exc_info exc_type, exc_instance, _ = record.exc_info
try: try:
if exc_type.__name__ == "VariableDoesNotExist": # type: ignore if exc_type is not None:
if exc_type.__name__ == "VariableDoesNotExist":
return False return False
else:
return True
except AttributeError: except AttributeError:
return True return True
return "VariableDoesNotExist" not in record.getMessage() return "VariableDoesNotExist" not in record.getMessage()

View file

@ -11,6 +11,7 @@ class TransactionType(DjangoObjectType):
process = GenericScalar() process = GenericScalar()
def resolve_process(self: Transaction, info) -> dict: def resolve_process(self: Transaction, info) -> dict:
if self.balance is not None:
if info.context.user == self.balance.user: if info.context.user == self.balance.user:
return self.process return self.process
return {} return {}

View file

@ -9,55 +9,96 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Balance', name="Balance",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('amount', models.FloatField(default=0)), help_text="when the object first appeared on the database",
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
),
),
("amount", models.FloatField(default=0)),
], ],
options={ options={
'verbose_name': 'balance', "verbose_name": "balance",
'verbose_name_plural': 'balances', "verbose_name_plural": "balances",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Transaction', name="Transaction",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, (
help_text='unique id is used to surely identify any database object', "uuid",
primary_key=True, serialize=False, verbose_name='unique id')), models.UUIDField(
('is_active', models.BooleanField(default=True, default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="if set to false, this object can't be seen by users without needed permission", help_text="if set to false, this object can't be seen by users without needed permission",
verbose_name='is active')), verbose_name="is active",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, ),
help_text='when the object first appeared on the database', ),
verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, "created",
help_text='when the object was last modified', django_extensions.db.fields.CreationDateTimeField(
verbose_name='modified')), auto_now_add=True,
('amount', models.FloatField()), help_text="when the object first appeared on the database",
('currency', models.CharField(max_length=3)), verbose_name="created",
('payment_method', models.CharField(max_length=20)), ),
('process', models.JSONField(default=dict, verbose_name='processing details')), ),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
),
),
("amount", models.FloatField()),
("currency", models.CharField(max_length=3)),
("payment_method", models.CharField(max_length=20)),
("process", models.JSONField(default=dict, verbose_name="processing details")),
], ],
options={ options={
'verbose_name': 'transaction', "verbose_name": "transaction",
'verbose_name_plural': 'transactions', "verbose_name_plural": "transactions",
}, },
), ),
] ]

View file

@ -10,32 +10,42 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('core', '0002_initial'), ("core", "0002_initial"),
('payments', '0001_initial'), ("payments", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='balance', model_name="balance",
name='user', name="user",
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payments_balance', field=models.OneToOneField(
to=settings.AUTH_USER_MODEL, blank=True, null=True), on_delete=django.db.models.deletion.CASCADE,
related_name="payments_balance",
to=settings.AUTH_USER_MODEL,
blank=True,
null=True,
),
), ),
migrations.AddField( migrations.AddField(
model_name='transaction', model_name="transaction",
name='balance', name="balance",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='payments.balance'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="payments.balance"),
), ),
migrations.AddField( migrations.AddField(
model_name='transaction', model_name="transaction",
name='order', name="order",
field=models.ForeignKey(blank=True, help_text='order to process after paid', null=True, field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='payments_transactions', blank=True,
to='core.order'), help_text="order to process after paid",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="payments_transactions",
to="core.order",
),
), ),
migrations.AddIndex( migrations.AddIndex(
model_name='transaction', model_name="transaction",
index=django.contrib.postgres.indexes.GinIndex(fields=['process'], name='payments_tr_process_d5b008_gin'), index=django.contrib.postgres.indexes.GinIndex(fields=["process"], name="payments_tr_process_d5b008_gin"),
), ),
] ]

View file

@ -5,15 +5,20 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('payments', '0002_initial'), ("payments", "0002_initial"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='transaction', model_name="transaction",
name='balance', name="balance",
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to='payments.balance'), field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="transactions",
to="payments.balance",
),
), ),
] ]

View file

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('payments', '0003_alter_transaction_balance'), ("payments", "0003_alter_transaction_balance"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='transaction', model_name="transaction",
name='payment_method', name="payment_method",
field=models.CharField(blank=True, max_length=20, null=True), field=models.CharField(blank=True, max_length=20, null=True),
), ),
] ]

View file

@ -7,13 +7,11 @@ from core.abstract import NiceModel
class Transaction(NiceModel): class Transaction(NiceModel):
amount: float = FloatField(null=False, blank=False) # type: ignore amount = FloatField(null=False, blank=False)
balance: "Balance" = ForeignKey( balance = ForeignKey("payments.Balance", on_delete=CASCADE, blank=True, null=True, related_name="transactions")
"payments.Balance", on_delete=CASCADE, blank=True, null=True, related_name="transactions" currency = CharField(max_length=3, null=False, blank=False)
) # type: ignore payment_method = CharField(max_length=20, null=True, blank=True)
currency: str = CharField(max_length=3, null=False, blank=False) # type: ignore order = ForeignKey(
payment_method: str = CharField(max_length=20, null=True, blank=True) # type: ignore
order = ForeignKey( # type: ignore
"core.Order", "core.Order",
on_delete=CASCADE, on_delete=CASCADE,
blank=True, blank=True,
@ -21,7 +19,7 @@ class Transaction(NiceModel):
help_text=_("order to process after paid"), help_text=_("order to process after paid"),
related_name="payments_transactions", related_name="payments_transactions",
) )
process: dict = JSONField(verbose_name=_("processing details"), default=dict) # type: ignore process = JSONField(verbose_name=_("processing details"), default=dict)
def __str__(self): def __str__(self):
return f"{self.balance.user.email} | {self.amount}" return f"{self.balance.user.email} | {self.amount}"
@ -48,8 +46,8 @@ class Transaction(NiceModel):
class Balance(NiceModel): class Balance(NiceModel):
amount: float = FloatField(null=False, blank=False, default=0) # type: ignore amount = FloatField(null=False, blank=False, default=0)
user = OneToOneField( # type: ignore user = OneToOneField(
to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True, related_name="payments_balance" to="vibes_auth.User", on_delete=CASCADE, blank=True, null=True, related_name="payments_balance"
) )
transactions: QuerySet["Transaction"] transactions: QuerySet["Transaction"]

View file

@ -20,10 +20,10 @@ class TransactionProcessSerializer(ModelSerializer):
order_uuid = SerializerMethodField(read_only=True, required=False) order_uuid = SerializerMethodField(read_only=True, required=False)
def get_order_hr_id(self, obj: Transaction) -> str | None: def get_order_hr_id(self, obj: Transaction) -> str | None:
return obj.order.human_readable_id if obj.order else None # type: ignore return obj.order.human_readable_id if obj.order else None
def get_order_uuid(self, obj: Transaction) -> str | None: def get_order_uuid(self, obj: Transaction) -> str | None:
return str(obj.order.uuid) if obj.order else None # type: ignore return str(obj.order.uuid) if obj.order else None
class Meta: class Meta:
model = Transaction model = Transaction

View file

@ -25,7 +25,7 @@ def process_transaction_changes(instance, created, **_kwargs):
case _: case _:
gateway = AbstractGateway() gateway = AbstractGateway()
gateway.process_transaction(instance) gateway.process_transaction(instance)
except Exception as e: # noqa: except Exception as e:
instance.process = {"status": "NOGATEWAY", "error": str(e)} instance.process = {"status": "NOGATEWAY", "error": str(e)}
if not created: if not created:
status = instance.process.get("status", "").lower() status = instance.process.get("status", "").lower()

View file

@ -1,5 +1,4 @@
{% load tz static i18n filters conditions %} {% load tz static i18n filters conditions %}
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -61,12 +60,6 @@
color: #888; color: #888;
} }
.order-table {
width: 100%;
margin-top: 20px;
border-collapse: collapse;
}
.order-table th, .order-table td { .order-table th, .order-table td {
border: 1px solid #ddd; border: 1px solid #ddd;
padding: 8px; padding: 8px;

View file

@ -19,7 +19,10 @@ def balance_deposit_email(transaction_pk: str) -> tuple[bool, str]:
except Transaction.DoesNotExist: except Transaction.DoesNotExist:
return False, f"Transaction not found with the given pk: {transaction_pk}" return False, f"Transaction not found with the given pk: {transaction_pk}"
activate(transaction.balance.user.language) # type: ignore if not transaction.balance or not transaction.balance.user:
return False, f"Balance not found for the given transaction pk: {transaction_pk}"
activate(transaction.balance.user.language)
set_email_settings() set_email_settings()
connection = mail.get_connection() connection = mail.get_connection()
@ -31,13 +34,13 @@ def balance_deposit_email(transaction_pk: str) -> tuple[bool, str]:
context={ context={
"amount": transaction.amount, "amount": transaction.amount,
"balance": transaction.balance.amount, "balance": transaction.balance.amount,
"user_first_name": transaction.balance.user.first_name, # type: ignore "user_first_name": transaction.balance.user.first_name,
"project_name": config.PROJECT_NAME, "project_name": config.PROJECT_NAME,
"contact_email": config.EMAIL_FROM, "contact_email": config.EMAIL_FROM,
"today": datetime.today(), "today": datetime.today(),
}, },
), ),
to=[transaction.balance.user.email], # type: ignore to=[transaction.balance.user.email],
from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>",
connection=connection, connection=connection,
) )

View file

@ -103,18 +103,21 @@ testing = ["pytest", "pytest-django", "coverage"]
linting = ["black", "isort", "flake8", "bandit"] linting = ["black", "isort", "flake8", "bandit"]
[tool.mypy] [tool.mypy]
disable_error_code = ["import-untyped", "misc"] disable_error_code = ["no-redef", "import-untyped"]
exclude = ["*/migrations/*", "./evibes/settings/drf.py"] exclude = ["*/migrations/*"]
plugins = ["mypy_django_plugin.main", "mypy_drf_plugin.main"]
[tool.django-stubs]
django_settings_module = "evibes.settings"
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
target-version = "py312" target-version = "py312"
exclude = ["migrations", "media", "static", "storefront"] exclude = ["media", "static", "storefront"]
[tool.ruff.lint] [tool.ruff.lint]
select = ["E", "W", "F", "B", "I", "RUF", "UP", "N", "A", "COM", "C4", "DJ001", "RSE", "SIM", "ISC", "TID252", "PGH004"] select = ["E4", "E7", "E9", "F", "B", "Q"]
ignore = ["B904", "RUF001", "RUF002", "RUF003", "RUF005", "RUF012", "A003", "A002", "COM812", "S603"] ignore = ["RUF012", "A002", "A003"]
per-file-ignores = { "__init__.py" = ["E402", "F401"] }
[tool.ruff.format] [tool.ruff.format]
quote-style = "double" quote-style = "double"

View file

@ -102,9 +102,9 @@ class UpdateUser(BaseMutation):
try: try:
user = User.objects.get(uuid=uuid) user = User.objects.get(uuid=uuid)
except User.DoesNotExist: except User.DoesNotExist as dne:
name = "User" name = "User"
raise Http404(_(f"{name} does not exist: {uuid}")) raise Http404(_(f"{name} does not exist: {uuid}")) from dne
if not (info.context.user.has_perm("vibes_auth.change_user") or info.context.user == user): if not (info.context.user.has_perm("vibes_auth.change_user") or info.context.user == user):
raise PermissionDenied(permission_denied_message) raise PermissionDenied(permission_denied_message)
@ -176,8 +176,8 @@ class DeleteUser(BaseMutation):
else: else:
raise BadRequest("uuid or email must be specified") raise BadRequest("uuid or email must be specified")
return DeleteUser(success=True) return DeleteUser(success=True)
except User.DoesNotExist: except User.DoesNotExist as dne:
raise Http404(f"User with the given uuid: {uuid} or email: {email} does not exist.") raise Http404(f"User with the given uuid: {uuid} or email: {email} does not exist.") from dne
raise PermissionDenied(permission_denied_message) raise PermissionDenied(permission_denied_message)
@ -201,7 +201,7 @@ class ObtainJSONWebToken(BaseMutation):
access_token=serializer.validated_data["access"], access_token=serializer.validated_data["access"],
) )
except Exception as e: except Exception as e:
raise PermissionDenied(f"invalid credentials provided: {e!s}") raise PermissionDenied(f"invalid credentials provided: {e!s}") from e
class RefreshJSONWebToken(BaseMutation): class RefreshJSONWebToken(BaseMutation):
@ -222,7 +222,7 @@ class RefreshJSONWebToken(BaseMutation):
user=User.objects.get(uuid=serializer.validated_data["user"]["uuid"]), user=User.objects.get(uuid=serializer.validated_data["user"]["uuid"]),
) )
except Exception as e: except Exception as e:
raise PermissionDenied(f"invalid refresh token provided: {e!s}") raise PermissionDenied(f"invalid refresh token provided: {e!s}") from e
class VerifyJSONWebToken(BaseMutation): class VerifyJSONWebToken(BaseMutation):
@ -270,7 +270,7 @@ class ActivateUser(BaseMutation):
user.save() user.save()
except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e: except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e:
raise BadRequest(_(f"something went wrong: {e!s}")) raise BadRequest(_(f"something went wrong: {e!s}")) from e
return ActivateUser(success=True) return ActivateUser(success=True)
@ -322,7 +322,7 @@ class ConfirmResetPassword(BaseMutation):
return ConfirmResetPassword(success=True) return ConfirmResetPassword(success=True)
except (TypeError, ValueError, OverflowError, ValidationError, User.DoesNotExist) as e: except (TypeError, ValueError, OverflowError, ValidationError, User.DoesNotExist) as e:
raise BadRequest(_(f"something went wrong: {e!s}")) raise BadRequest(_(f"something went wrong: {e!s}")) from e
class UploadAvatar(BaseMutation): class UploadAvatar(BaseMutation):
@ -339,6 +339,6 @@ class UploadAvatar(BaseMutation):
info.context.user.avatar = avatar info.context.user.avatar = avatar
info.context.user.save() info.context.user.save()
except Exception as e: except Exception as e:
raise BadRequest(str(e)) raise BadRequest(str(e)) from e
return UploadAvatar(user=info.context.user) return UploadAvatar(user=info.context.user)

View file

@ -16,87 +16,179 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('auth', '0012_alter_user_first_name_max_length'), ("auth", "0012_alter_user_first_name_max_length"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Group', name="Group",
fields=[ fields=[],
],
options={ options={
'verbose_name': 'group', "verbose_name": "group",
'verbose_name_plural': 'groups', "verbose_name_plural": "groups",
'proxy': True, "proxy": True,
'indexes': [], "indexes": [],
'constraints': [], "constraints": [],
}, },
bases=('auth.group',), bases=("auth.group",),
managers=[ managers=[
('objects', django.contrib.auth.models.GroupManager()), ("objects", django.contrib.auth.models.GroupManager()),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='User', name="User",
fields=[ fields=[
('password', models.CharField(max_length=128, verbose_name='password')), ("password", models.CharField(max_length=128, verbose_name="password")),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ("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.', "is_superuser",
verbose_name='superuser status')), models.BooleanField(
('is_staff', models.BooleanField(default=False, default=False,
help_text='Designates whether the user can log into this admin site.', help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name='staff status')), verbose_name="superuser status",
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, ),
help_text='unique id is used to surely identify any database object', (
primary_key=True, serialize=False, verbose_name='unique id')), "is_staff",
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, models.BooleanField(
help_text='when the object first appeared on the database', default=False,
verbose_name='created')), help_text="Designates whether the user can log into this admin site.",
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name="staff status",
help_text='when the object was last modified', ),
verbose_name='modified')), ),
('email', ("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined")),
models.EmailField(help_text='user email address', max_length=254, unique=True, verbose_name='email')), (
('phone_number', "uuid",
models.CharField(blank=True, help_text='user phone number', max_length=20, null=True, unique=True, models.UUIDField(
default=uuid.uuid4,
editable=False,
help_text="unique id is used to surely identify any database object",
primary_key=True,
serialize=False,
verbose_name="unique id",
),
),
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True,
help_text="when the object first appeared on the database",
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
),
),
(
"email",
models.EmailField(
help_text="user email address", max_length=254, unique=True, verbose_name="email"
),
),
(
"phone_number",
models.CharField(
blank=True,
help_text="user phone number",
max_length=20,
null=True,
unique=True,
validators=[vibes_auth.validators.validate_phone_number], validators=[vibes_auth.validators.validate_phone_number],
verbose_name='phone_number')), verbose_name="phone_number",
('first_name', models.CharField(blank=True, max_length=150, null=True, verbose_name='first_name')), ),
('last_name', models.CharField(blank=True, max_length=150, null=True, verbose_name='last_name')), ),
('avatar', models.ImageField(blank=True, help_text='user profile image', null=True, ("first_name", models.CharField(blank=True, max_length=150, null=True, verbose_name="first_name")),
upload_to=vibes_auth.models.User.get_uuid_as_path, verbose_name='avatar')), ("last_name", models.CharField(blank=True, max_length=150, null=True, verbose_name="last_name")),
('is_verified', (
models.BooleanField(default=False, help_text='user verification status', verbose_name='is verified')), "avatar",
('is_active', models.BooleanField(default=False, help_text='unselect this instead of deleting accounts', models.ImageField(
verbose_name='is_active')), blank=True,
('is_subscribed', models.BooleanField(default=False, help_text="user's newsletter subscription status", help_text="user profile image",
verbose_name='is_subscribed')), null=True,
('activation_token', models.UUIDField(default=uuid.uuid4, verbose_name='activation token')), upload_to=vibes_auth.models.User.get_uuid_as_path,
('language', models.CharField( verbose_name="avatar",
choices=[('en-GB', 'English (British)'), ('ar-AR', 'العربية'), ('cs-CZ', 'Česky'), ),
('da-DK', 'Dansk'), ('de-DE', 'Deutsch'), ('en-US', 'English (American)'), ),
('es-ES', 'Español'), ('fr-FR', 'Français'), ('hi-IN', 'हिंदी'), ('it-IT', 'Italiano'), (
('ja-JP', '日本語'), ('kk-KZ', 'Қазақ'), ('nl-NL', 'Nederlands'), ('pl-PL', 'Polska'), "is_verified",
('pt-BR', 'Português'), ('ro-RO', 'Română'), ('ru-RU', 'Русский'), models.BooleanField(
('zh-hans', '简体中文')], default='en-GB', max_length=7)), default=False, help_text="user verification status", verbose_name="is verified"
('attributes', models.JSONField(blank=True, default=dict, null=True, verbose_name='attributes')), ),
('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', "is_active",
verbose_name='groups')), models.BooleanField(
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', default=False, help_text="unselect this instead of deleting accounts", verbose_name="is_active"
related_name='user_set', related_query_name='user', ),
to='auth.permission', verbose_name='user permissions')), ),
(
"is_subscribed",
models.BooleanField(
default=False, help_text="user's newsletter subscription status", verbose_name="is_subscribed"
),
),
("activation_token", models.UUIDField(default=uuid.uuid4, verbose_name="activation token")),
(
"language",
models.CharField(
choices=[
("en-GB", "English (British)"),
("ar-AR", "العربية"),
("cs-CZ", "Česky"),
("da-DK", "Dansk"),
("de-DE", "Deutsch"),
("en-US", "English (American)"),
("es-ES", "Español"),
("fr-FR", "Français"),
("hi-IN", "हिंदी"),
("it-IT", "Italiano"),
("ja-JP", "日本語"),
("kk-KZ", "Қазақ"),
("nl-NL", "Nederlands"),
("pl-PL", "Polska"),
("pt-BR", "Português"),
("ro-RO", "Română"),
("ru-RU", "Русский"),
("zh-hans", "简体中文"),
],
default="en-GB",
max_length=7,
),
),
("attributes", models.JSONField(blank=True, default=dict, null=True, verbose_name="attributes")),
(
"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={ options={
'verbose_name': 'user', "verbose_name": "user",
'verbose_name_plural': 'users', "verbose_name_plural": "users",
'swappable': 'AUTH_USER_MODEL', "swappable": "AUTH_USER_MODEL",
}, },
managers=[ managers=[
('objects', vibes_auth.managers.UserManager()), ("objects", vibes_auth.managers.UserManager()),
], ],
), ),
] ]

View file

@ -4,37 +4,34 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('token_blacklist', '0012_alter_outstandingtoken_user'), ("token_blacklist", "0012_alter_outstandingtoken_user"),
('vibes_auth', '0001_initial'), ("vibes_auth", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='BlacklistedToken', name="BlacklistedToken",
fields=[ fields=[],
],
options={ options={
'verbose_name': 'blacklisted token', "verbose_name": "blacklisted token",
'verbose_name_plural': 'blacklisted tokens', "verbose_name_plural": "blacklisted tokens",
'proxy': True, "proxy": True,
'indexes': [], "indexes": [],
'constraints': [], "constraints": [],
}, },
bases=('token_blacklist.blacklistedtoken',), bases=("token_blacklist.blacklistedtoken",),
), ),
migrations.CreateModel( migrations.CreateModel(
name='OutstandingToken', name="OutstandingToken",
fields=[ fields=[],
],
options={ options={
'verbose_name': 'outstanding token', "verbose_name": "outstanding token",
'verbose_name_plural': 'outstanding tokens', "verbose_name_plural": "outstanding tokens",
'proxy': True, "proxy": True,
'indexes': [], "indexes": [],
'constraints': [], "constraints": [],
}, },
bases=('token_blacklist.outstandingtoken',), bases=("token_blacklist.outstandingtoken",),
), ),
] ]

View file

@ -5,54 +5,53 @@ from django.db.models.functions import Lower
def forwards(apps, schema_editor): def forwards(apps, schema_editor):
if schema_editor: if schema_editor:
pass pass
User = apps.get_model('vibes_auth', 'User') User = apps.get_model("vibes_auth", "User")
User.objects.all().update(language=Lower('language')) User.objects.all().update(language=Lower("language"))
def backwards(apps, schema_editor): def backwards(apps, schema_editor):
if schema_editor: if schema_editor:
pass pass
User = apps.get_model('vibes_auth', 'User') User = apps.get_model("vibes_auth", "User")
for u in User.objects.all(): for u in User.objects.all():
parts = u.language.split('-', 1) parts = u.language.split("-", 1)
if len(parts) == 2: if len(parts) == 2:
u.language = f"{parts[0].lower()}-{parts[1].upper()}" u.language = f"{parts[0].lower()}-{parts[1].upper()}"
u.save(update_fields=['language']) u.save(update_fields=["language"])
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('vibes_auth', '0002_blacklistedtoken_outstandingtoken'), ("vibes_auth", "0002_blacklistedtoken_outstandingtoken"),
] ]
operations = [ operations = [
migrations.RunPython(forwards, backwards), migrations.RunPython(forwards, backwards),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name="user",
name='language', name="language",
field=models.CharField( field=models.CharField(
choices=[ choices=[
('en-gb', 'English (British)'), ("en-gb", "English (British)"),
('ar-ar', 'العربية'), ("ar-ar", "العربية"),
('cs-cz', 'Česky'), ("cs-cz", "Česky"),
('da-dk', 'Dansk'), ("da-dk", "Dansk"),
('de-de', 'Deutsch'), ("de-de", "Deutsch"),
('en-us', 'English (American)'), ("en-us", "English (American)"),
('es-es', 'Español'), ("es-es", "Español"),
('fr-fr', 'Français'), ("fr-fr", "Français"),
('hi-in', 'हिंदी'), ("hi-in", "हिंदी"),
('it-it', 'Italiano'), ("it-it", "Italiano"),
('ja-jp', '日本語'), ("ja-jp", "日本語"),
('kk-kz', 'Қазақ'), ("kk-kz", "Қазақ"),
('nl-nl', 'Nederlands'), ("nl-nl", "Nederlands"),
('pl-pl', 'Polska'), ("pl-pl", "Polska"),
('pt-br', 'Português'), ("pt-br", "Português"),
('ro-ro', 'Română'), ("ro-ro", "Română"),
('ru-ru', 'Русский'), ("ru-ru", "Русский"),
('zh-hans', '简体中文'), ("zh-hans", "简体中文"),
], ],
default='en-gb', default="en-gb",
max_length=7, max_length=7,
), ),
), ),

View file

@ -1,4 +1,3 @@
import uuid
from uuid import uuid4 from uuid import uuid4
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
@ -21,6 +20,7 @@ from rest_framework_simplejwt.token_blacklist.models import (
OutstandingToken as BaseOutstandingToken, OutstandingToken as BaseOutstandingToken,
) )
from blog.models import Post
from core.abstract import NiceModel from core.abstract import NiceModel
from core.models import Order, Wishlist from core.models import Order, Wishlist
from evibes.settings import LANGUAGE_CODE, LANGUAGES from evibes.settings import LANGUAGE_CODE, LANGUAGES
@ -83,8 +83,8 @@ class User(AbstractUser, NiceModel):
def get_uuid_as_path(self, *args): def get_uuid_as_path(self, *args):
return str(self.uuid) + "/" + args[0] return str(self.uuid) + "/" + args[0]
email: str = EmailField(_("email"), unique=True, help_text=_("user email address")) # type: ignore email = EmailField(_("email"), unique=True, help_text=_("user email address"))
phone_number: str = CharField( # type: ignore phone_number = CharField(
_("phone_number"), _("phone_number"),
max_length=20, max_length=20,
unique=True, unique=True,
@ -95,9 +95,10 @@ class User(AbstractUser, NiceModel):
validate_phone_number, validate_phone_number,
], ],
) )
username = None username: None = None # type: ignore [assignment]
first_name: str = CharField(_("first_name"), max_length=150, blank=True, null=True) # type: ignore posts: "Post"
last_name: str = CharField(_("last_name"), max_length=150, blank=True, null=True) # type: ignore first_name = CharField(_("first_name"), max_length=150, blank=True, null=True) # type: ignore [assignment]
last_name = CharField(_("last_name"), max_length=150, blank=True, null=True) # type: ignore [assignment]
avatar = ImageField( avatar = ImageField(
null=True, null=True,
verbose_name=_("avatar"), verbose_name=_("avatar"),
@ -106,28 +107,28 @@ class User(AbstractUser, NiceModel):
help_text=_("user profile image"), help_text=_("user profile image"),
) )
is_verified: bool = BooleanField( # type: ignore is_verified = BooleanField(
default=False, default=False,
verbose_name=_("is verified"), verbose_name=_("is verified"),
help_text=_("user verification status"), help_text=_("user verification status"),
) )
is_active: bool = BooleanField( # type: ignore is_active = BooleanField(
_("is_active"), _("is_active"),
default=False, default=False,
help_text=_("unselect this instead of deleting accounts"), help_text=_("unselect this instead of deleting accounts"),
) )
is_subscribed: bool = BooleanField( # type: ignore is_subscribed = BooleanField(
verbose_name=_("is_subscribed"), help_text=_("user's newsletter subscription status"), default=False verbose_name=_("is_subscribed"), help_text=_("user's newsletter subscription status"), default=False
) )
activation_token: uuid = UUIDField(default=uuid4, verbose_name=_("activation token")) # type: ignore activation_token = UUIDField(default=uuid4, verbose_name=_("activation token"))
language: str = CharField(choices=LANGUAGES, default=LANGUAGE_CODE, null=False, blank=False, max_length=7) # type: ignore language = CharField(choices=LANGUAGES, default=LANGUAGE_CODE, null=False, blank=False, max_length=7)
attributes: dict = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True) # type: ignore attributes = JSONField(verbose_name=_("attributes"), default=dict, blank=True, null=True)
USERNAME_FIELD = "email" USERNAME_FIELD = "email"
REQUIRED_FIELDS = [] REQUIRED_FIELDS = []
# noinspection PyClassVar # noinspection PyClassVar
objects = UserManager() # type: ignore objects = UserManager() # type: ignore [misc, assignment]
payments_balance: "Balance" payments_balance: "Balance"
user_related_wishlist: "Wishlist" user_related_wishlist: "Wishlist"
@ -190,11 +191,6 @@ class OutstandingToken(BaseOutstandingToken):
It does not add additional fields or logic to the base model but allows for It does not add additional fields or logic to the base model but allows for
overloading or extending its default functionality as required. overloading or extending its default functionality as required.
Attributes:
Meta (class): Contains metadata for the model, including options such as
whether the model is a proxy, the human-readable name for the model, and
the plural form of that name.
""" """
class Meta: class Meta:
@ -214,8 +210,6 @@ class BlacklistedToken(BaseBlacklistedToken):
meta options for the model, affecting its database and administrative meta options for the model, affecting its database and administrative
representation. representation.
Attributes:
None
""" """
class Meta: class Meta:

View file

@ -112,7 +112,7 @@ class TokenObtainSerializer(Serializer):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.user = None self.user: User | None = None
self.fields[self.username_field] = CharField(write_only=True) self.fields[self.username_field] = CharField(write_only=True)
self.fields["password"] = PasswordField() self.fields["password"] = PasswordField()
@ -124,19 +124,22 @@ class TokenObtainSerializer(Serializer):
with suppress(KeyError): with suppress(KeyError):
authenticate_kwargs["request"] = self.context["request"] authenticate_kwargs["request"] = self.context["request"]
self.user = authenticate(**authenticate_kwargs) # type: ignore self.user: User | None = authenticate(**authenticate_kwargs)
if not api_settings.USER_AUTHENTICATION_RULE(self.user): if not api_settings.USER_AUTHENTICATION_RULE(self.user):
raise AuthenticationFailed( raise AuthenticationFailed(
self.error_messages["no_active_account"], self.error_messages["no_active_account"],
_("no active account"), # type: ignore str(_("no active account")),
) )
return {} return {}
@classmethod @classmethod
def get_token(cls, user: AuthUser) -> Token: def get_token(cls, user: AuthUser) -> Token:
return cls.token_class.for_user(user) # type: ignore if cls.token_class is not None:
return cls.token_class.for_user(user)
else:
raise RuntimeError(_("must set token_class attribute on class."))
class TokenObtainPairSerializer(TokenObtainSerializer): class TokenObtainPairSerializer(TokenObtainSerializer):
@ -147,15 +150,19 @@ class TokenObtainPairSerializer(TokenObtainSerializer):
logger.debug("Data validated") logger.debug("Data validated")
refresh = self.get_token(self.user) # type: ignore if self.user is None:
raise ValidationError(_("no active account"))
refresh = self.get_token(self.user)
data["refresh"] = str(refresh) data["refresh"] = str(refresh)
data["access"] = str(refresh.access_token) # type: ignore # noinspection PyUnresolvedReferences
data["access"] = str(refresh.access_token) # type: ignore [attr-defined]
data["user"] = UserSerializer(self.user).data data["user"] = UserSerializer(self.user).data
logger.debug("Data formed") logger.debug("Data formed")
if api_settings.UPDATE_LAST_LOGIN: if api_settings.UPDATE_LAST_LOGIN:
update_last_login(self.user, self.user) # type: ignore update_last_login(self.user, self.user)
logger.debug("Updated last login") logger.debug("Updated last login")
logger.debug("Returning data") logger.debug("Returning data")
@ -183,7 +190,8 @@ class TokenRefreshSerializer(Serializer):
data["refresh"] = str(refresh) data["refresh"] = str(refresh)
user = User.objects.get(uuid=refresh.payload["user_uuid"]) user = User.objects.get(uuid=refresh.payload["user_uuid"])
data["user"] = UserSerializer(user).data # type: ignore # noinspection PyTypeChecker
data["user"] = UserSerializer(user).data
return data return data
@ -204,18 +212,19 @@ class TokenVerifySerializer(Serializer):
try: try:
payload = UntypedToken(attrs["token"]).payload payload = UntypedToken(attrs["token"]).payload
except TokenError: except TokenError as te:
raise ValidationError(_("invalid token")) raise ValidationError(_("invalid token")) from te
try: try:
user_uuid = payload["user_uuid"] user_uuid = payload["user_uuid"]
user = User.objects.get(uuid=user_uuid) user = User.objects.get(uuid=user_uuid)
except KeyError: except KeyError as ke:
raise ValidationError(_("no user uuid claim present in token")) raise ValidationError(_("no user uuid claim present in token")) from ke
except User.DoesNotExist: except User.DoesNotExist as dne:
raise ValidationError(_("user does not exist")) raise ValidationError(_("user does not exist")) from dne
attrs["user"] = UserSerializer(user).data # type: ignore # noinspection PyTypeChecker
attrs["user"] = UserSerializer(user).data
return attrs return attrs

View file

@ -52,7 +52,7 @@ def send_verification_email_task(user_pk: str) -> tuple[bool, str]:
return False, f"Something went wrong while sending an email: {e!s}" return False, f"Something went wrong while sending an email: {e!s}"
else: else:
return True, user.uuid return True, str(user.uuid)
@shared_task(queue="default") @shared_task(queue="default")
@ -95,4 +95,4 @@ def send_reset_password_email_task(user_pk: str) -> tuple[bool, str]:
return False, f"Something went wrong while sending an email: {e!s}" return False, f"Something went wrong while sending an email: {e!s}"
else: else:
return True, user.uuid return True, str(user.uuid)

View file

@ -47,8 +47,8 @@ class TokenObtainPairView(TokenViewBase):
subject to rate limiting depending on the global DEBUG setting. subject to rate limiting depending on the global DEBUG setting.
""" """
serializer_class = TokenObtainPairSerializer # type: ignore serializer_class = TokenObtainPairSerializer
_serializer_class = TokenObtainPairSerializer # type: ignore _serializer_class = TokenObtainPairSerializer
@method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h")) @method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h"))
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -82,8 +82,8 @@ class TokenRefreshView(TokenViewBase):
whether the application is in DEBUG mode or not. whether the application is in DEBUG mode or not.
""" """
serializer_class = TokenRefreshSerializer # type: ignore serializer_class = TokenRefreshSerializer
_serializer_class = TokenRefreshSerializer # type: ignore _serializer_class = TokenRefreshSerializer
@method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h")) @method_decorator(ratelimit(key="ip", rate="10/h" if not DEBUG else "888/h"))
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -104,8 +104,8 @@ class TokenVerifyView(TokenViewBase):
error response. error response.
""" """
serializer_class = TokenVerifySerializer # type: ignore serializer_class = TokenVerifySerializer
_serializer_class = TokenVerifySerializer # type: ignore _serializer_class = TokenVerifySerializer
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
try: try: