Features: 1) Add a management command to fix product stock prices; 2) Introduce 'products' field to CategoryType in GraphQL schema to fetch products associated with a category; 3) Enable GraphQL resolvers to utilize type hinting for better clarity.

Fixes: 1) Correct multiple unaligned code blocks in various Python scripts and GraphQL resolvers; 2) Improve condition formatting for readability in mutations and queries; 3) Resolve missing related_name in product model.

Extra: Simplify and refactor Windows scripts removing legacy spinner logic for clarity and better user feedback; adjust spacing, comments, and formatting across various files; update imports for unused QuerySet.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-17 11:13:11 +03:00
parent f66a6b0cb6
commit 6ee3870ab0
12 changed files with 135 additions and 205 deletions

View file

@ -106,7 +106,7 @@ class CategoryChildrenInline(admin.TabularInline):
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin): class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin):
mptt_indent_field = "name" mptt_indent_field = "name"
list_display = ("indented_title", "parent", "is_active", "modified") list_display = ("indented_title", "parent", "is_active", "modified")
list_filter = ("is_active", "level", "created", "modified") # noqa: DJ list_filter = ("is_active", "level", "created", "modified")
list_display_links = ("indented_title",) list_display_links = ("indented_title",)
search_fields = ( search_fields = (
"uuid", "uuid",

View file

@ -29,7 +29,7 @@ class CaseInsensitiveListFilter(BaseInFilter, CharFilter):
return qs return qs
if isinstance(value, str): if isinstance(value, str):
values = [v.strip() for v in value.split(',') if v.strip()] values = [v.strip() for v in value.split(",") if v.strip()]
else: else:
values = [v for v in value if v] values = [v for v in value if v]
@ -49,10 +49,7 @@ class ProductFilter(FilterSet):
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID")) uuid = UUIDFilter(field_name="uuid", lookup_expr="exact", label=_("UUID"))
name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name")) name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name"))
categories = CaseInsensitiveListFilter(field_name="category__name", label=_("Categories")) categories = CaseInsensitiveListFilter(field_name="category__name", label=_("Categories"))
category_uuid = CharFilter( category_uuid = CharFilter(method="filter_category", label="Category (UUID)")
method="filter_category",
label="Category (UUID)"
)
categories_slugs = CaseInsensitiveListFilter(field_name="category__slug", label=_("Categories Slugs")) categories_slugs = CaseInsensitiveListFilter(field_name="category__slug", label=_("Categories Slugs"))
tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags")) tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags"))
min_price = NumberFilter(field_name="stocks__price", lookup_expr="gte", label=_("Min Price")) min_price = NumberFilter(field_name="stocks__price", lookup_expr="gte", label=_("Min Price"))
@ -63,10 +60,7 @@ class ProductFilter(FilterSet):
quantity = NumberFilter(field_name="stocks__quantity", lookup_expr="gt", label=_("Quantity")) quantity = NumberFilter(field_name="stocks__quantity", lookup_expr="gt", label=_("Quantity"))
slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug")) slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug"))
is_digital = BooleanFilter(field_name="is_digital", label=_("Is Digital")) is_digital = BooleanFilter(field_name="is_digital", label=_("Is Digital"))
include_subcategories = BooleanFilter( include_subcategories = BooleanFilter(method="filter_include_flag", label=_("Include sub-categories"))
method="filter_include_flag",
label=_("Include sub-categories")
)
order_by = OrderingFilter( order_by = OrderingFilter(
fields=( fields=(
@ -322,7 +316,9 @@ class CategoryFilter(FilterSet):
name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name")) name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name"))
parent_uuid = CharFilter(method="filter_parent_uuid", label=_("Parent")) parent_uuid = CharFilter(method="filter_parent_uuid", label=_("Parent"))
slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug")) slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug"))
whole = BooleanFilter(field_name="whole", label=_("Whole category"), method="filter_whole_categories") whole = BooleanFilter(
field_name="whole", label=_("Whole category(has at least 1 product or not)"), method="filter_whole_categories"
)
tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags")) tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags"))
level = NumberFilter(field_name="level", lookup_expr="exact", label=_("Level")) level = NumberFilter(field_name="level", lookup_expr="exact", label=_("Level"))

View file

@ -1,5 +1,5 @@
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Max, Min, QuerySet from django.db.models import Max, Min
from django.db.models.functions import Length from django.db.models.functions import Length
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from graphene import UUID, Field, Float, InputObjectType, Int, List, NonNull, ObjectType, String, relay from graphene import UUID, Field, Float, InputObjectType, Int, List, NonNull, ObjectType, String, relay
@ -63,8 +63,9 @@ class AttributeGroupType(DjangoObjectType):
filter_fields = ["uuid"] filter_fields = ["uuid"]
description = _("groups of attributes") description = _("groups of attributes")
def resolve_attributes(self, info): def resolve_attributes(self: AttributeGroup, info):
product_uuid = getattr(info.context, "_product_uuid", None) product_uuid = getattr(info.context, "_product_uuid", None)
qs = self.attributes.all() qs = self.attributes.all()
if product_uuid: if product_uuid:
@ -83,15 +84,15 @@ class BrandType(DjangoObjectType):
filter_fields = ["uuid", "name"] filter_fields = ["uuid", "name"]
description = _("brands") description = _("brands")
def resolve_categories(self, info): def resolve_categories(self: Brand, info):
if info.context.user.has_perm("core.view_category"): if info.context.user.has_perm("core.view_category"):
return self.categories.all() return self.categories.all()
return self.categories.filter(is_active=True) return self.categories.filter(is_active=True)
def resolve_big_logo(self, info): def resolve_big_logo(self: Brand, info):
return info.context.build_absolute_uri(self.big_logo.url) if self.big_logo else "" return info.context.build_absolute_uri(self.big_logo.url) if self.big_logo else ""
def resolve_small_logo(self, info): def resolve_small_logo(self: Brand, info):
return info.context.build_absolute_uri(self.small_logo.url) if self.small_logo else "" return info.context.build_absolute_uri(self.small_logo.url) if self.small_logo else ""
@ -121,6 +122,7 @@ class CategoryType(DjangoObjectType):
description=_("minimum and maximum prices for products in this category, if available."), description=_("minimum and maximum prices for products in this category, if available."),
) )
tags = DjangoFilterConnectionField(lambda: CategoryTagType, description=_("tags for this category")) tags = DjangoFilterConnectionField(lambda: CategoryTagType, description=_("tags for this category"))
products = DjangoFilterConnectionField(lambda: ProductType, description=_("products in this category"))
class Meta: class Meta:
model = Category model = Category
@ -145,15 +147,15 @@ class CategoryType(DjangoObjectType):
return categories return categories
return categories.filter(is_active=True) return categories.filter(is_active=True)
def resolve_image(self, info) -> str: def resolve_image(self: Category, info) -> str:
return info.context.build_absolute_uri(self.image.url) if self.image else "" return info.context.build_absolute_uri(self.image.url) if self.image else ""
def resolve_markup_percent(self, info) -> float: def resolve_markup_percent(self: Category, info) -> float:
if info.context.user.has_perm("core.view_category"): if info.context.user.has_perm("core.view_category"):
return float(self.markup_percent) return float(self.markup_percent)
return 0.0 return 0.0
def resolve_filterable_attributes(self, info): def resolve_filterable_attributes(self: Category, info):
filterable_results = cache.get(f"{self.uuid}_filterable_results", []) filterable_results = cache.get(f"{self.uuid}_filterable_results", [])
if len(filterable_results) > 0: if len(filterable_results) > 0:
@ -175,7 +177,10 @@ class CategoryType(DjangoObjectType):
if len(distinct_vals_list) <= 128: if len(distinct_vals_list) <= 128:
filterable_results.append( filterable_results.append(
FilterableAttributeType(attribute_name=attr.name, possible_values=distinct_vals_list) {
"attribute_name": attr.name,
"possible_values": distinct_vals_list,
}
) )
else: else:
pass pass
@ -184,7 +189,7 @@ class CategoryType(DjangoObjectType):
return filterable_results return filterable_results
def resolve_min_max_prices(self, info): def resolve_min_max_prices(self: Category, _info):
min_max_prices = cache.get(key=f"{self.name}_min_max_prices", default={}) min_max_prices = cache.get(key=f"{self.name}_min_max_prices", default={})
if not min_max_prices: if not min_max_prices:
@ -193,10 +198,9 @@ class CategoryType(DjangoObjectType):
) )
min_max_prices["min_price"] = price_aggregation.get("min_price", 0.0) min_max_prices["min_price"] = price_aggregation.get("min_price", 0.0)
min_max_prices["max_price"] = price_aggregation.get("max_price", 0.0) min_max_prices["max_price"] = price_aggregation.get("max_price", 0.0)
cache.set(key=f"{self.name}_min_max_prices", value=min_max_prices, timeout=86400) cache.set(key=f"{self.name}_min_max_prices", value=min_max_prices, timeout=86400)
return MinMaxPriceType(min_price=min_max_prices["min_price"], max_price=min_max_prices["max_price"]) return {"min_price": min_max_prices["min_price"], "max_price": min_max_prices["max_price"]}
class VendorType(DjangoObjectType): class VendorType(DjangoObjectType):
@ -230,12 +234,14 @@ class AddressType(DjangoObjectType):
) )
read_only_fields = ("api_response",) read_only_fields = ("api_response",)
def resolve_latitude(self, info): def resolve_latitude(self: Address, _info):
return self.location.y if self.location else None # noinspection PyUnresolvedReferences
def resolve_longitude(self, info):
return self.location.x if self.location else None return self.location.x if self.location else None
def resolve_longitude(self: Address, _info):
# noinspection PyUnresolvedReferences
return self.location.y if self.location else None
class FeedbackType(DjangoObjectType): class FeedbackType(DjangoObjectType):
comment = String(description=_("comment")) comment = String(description=_("comment"))
@ -269,13 +275,13 @@ class OrderProductType(DjangoObjectType):
filter_fields = ["uuid"] filter_fields = ["uuid"]
description = _("order products") description = _("order products")
def resolve_attributes(self, info): def resolve_attributes(self, _info):
return camelize(self.attributes) return camelize(self.attributes)
def resolve_notifications(self, info): def resolve_notifications(self, _info):
return camelize(self.notifications) return camelize(self.notifications)
def resolve_download_url(self, info) -> str | None: def resolve_download_url(self: OrderProduct, _info) -> str | None:
return self.download_url return self.download_url
@ -318,10 +324,10 @@ class OrderType(DjangoObjectType):
def resolve_total_quantity(self, _info): def resolve_total_quantity(self, _info):
return self.total_quantity return self.total_quantity
def resolve_notifications(self, info): def resolve_notifications(self, _info):
return camelize(self.notifications) return camelize(self.notifications)
def resolve_attributes(self, info): def resolve_attributes(self, _info):
return camelize(self.attributes) return camelize(self.attributes)
@ -335,7 +341,7 @@ class ProductImageType(DjangoObjectType):
filter_fields = ["uuid"] filter_fields = ["uuid"]
description = _("product's images") description = _("product's images")
def resolve_image(self, info): def resolve_image(self: ProductImage, info):
return info.context.build_absolute_uri(self.image.url) if self.image else "" return info.context.build_absolute_uri(self.image.url) if self.image else ""
@ -370,7 +376,7 @@ class ProductType(DjangoObjectType):
def resolve_price(self, _info) -> float: def resolve_price(self, _info) -> float:
return self.price or 0.0 return self.price or 0.0
def resolve_feedbacks(self, _info) -> QuerySet[Feedback]: def resolve_feedbacks(self: Product, _info):
if _info.context.user.has_perm("core.view_feedback"): if _info.context.user.has_perm("core.view_feedback"):
return Feedback.objects.filter(order_product__product=self) return Feedback.objects.filter(order_product__product=self)
return Feedback.objects.filter(order_product__product=self, is_active=True) return Feedback.objects.filter(order_product__product=self, is_active=True)
@ -378,7 +384,7 @@ class ProductType(DjangoObjectType):
def resolve_feedbacks_count(self, _info) -> int: def resolve_feedbacks_count(self, _info) -> int:
return self.feedbacks_count or 0 return self.feedbacks_count or 0
def resolve_attribute_groups(self, info): def resolve_attribute_groups(self: Product, info):
info.context._product_uuid = self.uuid info.context._product_uuid = self.uuid
return AttributeGroup.objects.filter(attributes__values__product=self).distinct() return AttributeGroup.objects.filter(attributes__values__product=self).distinct()
@ -415,10 +421,10 @@ class PromoCodeType(DjangoObjectType):
filter_fields = ["uuid"] filter_fields = ["uuid"]
description = _("promocodes") description = _("promocodes")
def resolve_discount(self, info) -> float: def resolve_discount(self: PromoCode, _info) -> float:
return self.discount_percent if self.discount_percent else self.discount_amount return self.discount_percent if self.discount_percent else self.discount_amount
def resolve_discount_type(self, 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

@ -0,0 +1,23 @@
import logging
from django.core.management.base import BaseCommand
from core.models import Product
from core.vendors import AbstractVendor
logger = logging.getLogger(__name__)
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.write(self.style.SUCCESS("Starting fixing stocks' prices..."))
for product in Product.objects.filter(stocks__isnull=False):
for stock in product.stocks:
try:
stock.price = AbstractVendor.round_price_marketologically(stock.price)
stock.save()
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"Error: {e}"))
self.stdout.write(self.style.SUCCESS("Successfully fixed stocks' prices!"))

View file

@ -281,6 +281,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
on_delete=CASCADE, on_delete=CASCADE,
help_text=_("category this product belongs to"), help_text=_("category this product belongs to"),
verbose_name=_("category"), verbose_name=_("category"),
related_name="products",
) )
brand = ForeignKey( brand = ForeignKey(
"core.Brand", "core.Brand",
@ -1292,8 +1293,7 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
if self.order_product.status != "FINISHED": if self.order_product.status != "FINISHED":
raise ValueError(_("you can not download a digital asset for a non-finished order")) raise ValueError(_("you can not download a digital asset for a non-finished order"))
return (f"https://api.{config.BASE_URL}/" # noqa: DJ return f"https://api.{config.BASE_URL}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}"
f"download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}")
class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel): class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel):

View file

@ -96,9 +96,7 @@ class EvibesPermission(permissions.BasePermission):
return queryset.none() return queryset.none()
base = queryset.filter(is_active=True, user=request.user) base = queryset.filter(is_active=True, user=request.user)
if request.user.has_perm( if request.user.has_perm(f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}"):
f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}"
):
return queryset.filter(is_active=True) return queryset.filter(is_active=True)
return base return base

View file

@ -11,7 +11,7 @@ from core.views import CustomGraphQLView, CustomRedocView, CustomSwaggerView, fa
from evibes.settings import SPECTACULAR_PLATFORM_SETTINGS from evibes.settings import SPECTACULAR_PLATFORM_SETTINGS
urlpatterns = [ urlpatterns = [
path(r'health/', include('health_check.urls')), path(r"health/", include("health_check.urls")),
path("prometheus/", include("django_prometheus.urls")), path("prometheus/", include("django_prometheus.urls")),
path(r"graphql/", csrf_exempt(CustomGraphQLView.as_view(graphiql=True, schema=schema))), path(r"graphql/", csrf_exempt(CustomGraphQLView.as_view(graphiql=True, schema=schema))),
path( path(

View file

@ -5,10 +5,13 @@ from evibes.settings.base import getenv
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" # Or "constance.backends.redis.RedisBackend" CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" # Or "constance.backends.redis.RedisBackend"
CONSTANCE_ADDITIONAL_FIELDS = { CONSTANCE_ADDITIONAL_FIELDS = {
"json": ["django.forms.fields.JSONField", { "json": [
"required": False, "django.forms.fields.JSONField",
"widget": "core.widgets.JSONTableWidget", {
}], "required": False,
"widget": "core.widgets.JSONTableWidget",
},
],
} }
CONSTANCE_CONFIG = { CONSTANCE_CONFIG = {
@ -18,8 +21,10 @@ CONSTANCE_CONFIG = {
"COMPANY_NAME": (getenv("COMPANY_NAME"), _("Name of the company")), "COMPANY_NAME": (getenv("COMPANY_NAME"), _("Name of the company")),
"COMPANY_ADDRESS": (getenv("COMPANY_ADDRESS"), _("Address of the company")), "COMPANY_ADDRESS": (getenv("COMPANY_ADDRESS"), _("Address of the company")),
"COMPANY_PHONE_NUMBER": (getenv("COMPANY_PHONE_NUMBER"), _("Phone number of the company")), "COMPANY_PHONE_NUMBER": (getenv("COMPANY_PHONE_NUMBER"), _("Phone number of the company")),
"STOCKS_ARE_SINGLE": (getenv("STOCKS_ARE_SINGLE", True), "STOCKS_ARE_SINGLE": (
_("Designates whether every product has one stock or not")), getenv("STOCKS_ARE_SINGLE", True),
_("Designates whether every product has one stock or not"),
),
"EMAIL_HOST": (getenv("EMAIL_HOST", "smtp.404.org"), _("SMTP host")), "EMAIL_HOST": (getenv("EMAIL_HOST", "smtp.404.org"), _("SMTP host")),
"EMAIL_PORT": (int(getenv("EMAIL_PORT", "465")), _("SMTP port")), "EMAIL_PORT": (int(getenv("EMAIL_PORT", "465")), _("SMTP port")),
"EMAIL_USE_TLS": (bool(int(getenv("EMAIL_USE_TLS", 0))), _("Use TLS (Specify 0 for No and 1 for Yes)")), "EMAIL_USE_TLS": (bool(int(getenv("EMAIL_USE_TLS", 0))), _("Use TLS (Specify 0 for No and 1 for Yes)")),

View file

@ -10,19 +10,15 @@ if (-not (Test-Path -Path ".\evibes" -PathType Container))
$purple = "`e[38;2;121;101;209m" $purple = "`e[38;2;121;101;209m"
$reset = "`e[0m" $reset = "`e[0m"
$artPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) '..\ASCII_ART_EVIBES' $artPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) '..\ASCII_ART_EVIBES'
if (-not (Test-Path $artPath)) if (-not (Test-Path $artPath))
{ {
Write-Host "❌ Could not find ASCII art at $artPath" -ForegroundColor Red Write-Host "❌ Could not find ASCII art at $artPath" -ForegroundColor Red
exit 1 exit 1
} }
$art = Get-Content -Raw -Path $artPath Get-Content -Raw -Path $artPath | ForEach-Object { Write-Host "$purple$_$reset" }
$art -split "`r?`n" | ForEach-Object {
Write-Host "$purple$_$reset"
}
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
if (-not (Test-Path '.env')) if (-not (Test-Path '.env'))
@ -34,7 +30,6 @@ if (-not (Test-Path '.env'))
$cpuCount = [Environment]::ProcessorCount $cpuCount = [Environment]::ProcessorCount
if ($cpuCount -lt 4) if ($cpuCount -lt 4)
{ {
Write-Error "Insufficient CPU cores: $cpuCount detected. Minimum 4 required."
exit 1 exit 1
} }
@ -42,54 +37,25 @@ $totalMemBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory
$totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2) $totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2)
if ($totalMemGB -lt 6) if ($totalMemGB -lt 6)
{ {
Write-Error "Insufficient RAM: $totalMemGB GB detected. Minimum 6 GB required."
exit 1 exit 1
} }
$currentDrive = Split-Path -Qualifier $PWD $currentDrive = Split-Path -Qualifier $PWD
$driveLetter = $currentDrive.Substring(0, 1) $driveLetter = $currentDrive.Substring(0, 1)
$driveInfo = Get-PSDrive -Name $driveLetter $freeGB = [Math]::Round((Get-PSDrive -Name $driveLetter).Free / 1GB, 2)
$freeGB = [Math]::Round($driveInfo.Free / 1GB, 2)
if ($freeGB -lt 20) if ($freeGB -lt 20)
{ {
Write-Error "Insufficient free disk space on drive $driveLetter`: $freeGB GB available. Minimum 20 GB required."
exit 1 exit 1
} }
Write-Host "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB" -ForegroundColor Green Write-Host "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB" -ForegroundColor Green
$Spinner = @('|', '/', '-', '\') Write-Host "Pulling images" -ForegroundColor Magenta
$Colors = @('White', 'Gray') docker compose pull
$Delay = 100 Write-Host "Images pulled successfully" -ForegroundColor Green
function Invoke-Spinner Write-Host "Building images" -ForegroundColor Magenta
{ docker compose build
[CmdletBinding()] Write-Host "Images built successfully" -ForegroundColor Green
param( Write-Host ""
[Parameter(Mandatory)][string]$Arguments, Write-Host "You can now use run.ps1 script." -ForegroundColor Cyan
[Parameter(Mandatory)][string]$Message
)
Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message)
$si = New-Object System.Diagnostics.ProcessStartInfo
$si.FileName = "docker"
$si.Arguments = "$Arguments --ansi never"
$si.RedirectStandardOutput = $true
$si.RedirectStandardError = $true
$si.UseShellExecute = $false
$p = [System.Diagnostics.Process]::Start($si)
$i = 0
while (-not $p.HasExited)
{
$frame = $Spinner[$i % $Spinner.Length]
$col = $Colors[$i % $Colors.Length]
Write-Host -NoNewLine -ForegroundColor $col $frame
Start-Sleep -Milliseconds $Delay
Write-Host -NoNewline "`b"
$i++
}
$p.WaitForExit()
Write-Host -ForegroundColor Green ""
}
Invoke-Spinner -Arguments "compose pull" -Message "Pulling related images"
Invoke-Spinner -Arguments "compose build" -Message "Building the project's images"

View file

@ -23,45 +23,29 @@ $art -split "`r?`n" | ForEach-Object {
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
$Spinner = @('|', '/', '-', '\')
$Colors = @('White', 'Gray')
$Delay = 100
function Invoke-Spinner Write-Host "Shutting down..." -ForegroundColor Magenta
{ docker compose down
[CmdletBinding()] Write-Host "Services were shut down successfully!" -ForegroundColor Green
param(
[Parameter(Mandatory)][string]$Arguments,
[Parameter(Mandatory)][string]$Message
)
Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message)
$si = New-Object System.Diagnostics.ProcessStartInfo
$si.FileName = "docker"
$si.Arguments = "$Arguments --ansi never"
$si.RedirectStandardOutput = $true
$si.RedirectStandardError = $true
$si.UseShellExecute = $false
$p = [System.Diagnostics.Process]::Start($si)
$i = 0
while (-not $p.HasExited)
{
$frame = $Spinner[$i % $Spinner.Length]
$col = $Colors[$i % $Colors.Length]
Write-Host -NoNewLine -ForegroundColor $col $frame
Start-Sleep -Milliseconds $Delay
Write-Host -NoNewline "`b"
$i++
}
$p.WaitForExit()
Write-Host -ForegroundColor Green ""
}
Invoke-Spinner -Arguments "compose down" -Message "Stopping services" Write-Host "Spinning services up..." -ForegroundColor Magenta
Invoke-Spinner -Arguments "compose build" -Message "Rebuilding services" docker compose up -d --build --wait
Invoke-Spinner -Arguments "compose up -d" -Message "Starting services" Write-Host "Services are up and healthy!" -ForegroundColor Green
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py migrate --no-input" -Message "Applying database migrations"
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py collectstatic --no-input" -Message "Collecting static files"
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py set_default_caches" -Message "Setting default caches"
Invoke-Spinner -Arguments "system prune -f" -Message "Cleaning up unused Docker data"
Write-Host "`nAll done! eVibes is up and running." -ForegroundColor White Write-Host "Applying migrations..." -ForegroundColor Magenta
docker compose exec app poetry run python manage.py migrate --no-input
Write-Host "Migrations applied successfully!" -ForegroundColor Green
Write-Host "Collecting static files..." -ForegroundColor Magenta
docker compose exec app poetry run python manage.py collectstatic --no-input
Write-Host "Static files collected successfully!" -ForegroundColor Green
Write-Host "Setting default caches..." -ForegroundColor Magenta
docker compose exec app poetry run python manage.py set_default_caches
Write-Host "Default caches set successfully!" -ForegroundColor Green
Write-Host "Cleaning up unused Docker data..." -ForegroundColor Magenta
docker system prune -f
Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green
Write-Host "All done! eVibes is up and running!" -ForegroundColor Cyan

View file

@ -49,43 +49,20 @@ foreach ($prop in $config.services.PSObject.Properties)
Write-Host " • Found image: $image" Write-Host " • Found image: $image"
} }
$Spinner = @('|', '/', '-', '\') Write-Host "Spinning services up..." -ForegroundColor Magenta
$Colors = @('White', 'Gray') docker compose up --no-build --detach --wait
$Delay = 100 Write-Host "Services are up and healthy!" -ForegroundColor Green
function Invoke-Spinner Write-Host "Applying migrations..." -ForegroundColor Magenta
{ docker compose exec app poetry run python manage.py migrate --no-input
[CmdletBinding()] Write-Host "Migrations applied successfully!" -ForegroundColor Green
param(
[Parameter(Mandatory)][string]$Arguments,
[Parameter(Mandatory)][string]$Message
)
Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message)
$si = New-Object System.Diagnostics.ProcessStartInfo
$si.FileName = "docker"
$si.Arguments = "$Arguments --ansi never"
$si.RedirectStandardOutput = $true
$si.RedirectStandardError = $true
$si.UseShellExecute = $false
$p = [System.Diagnostics.Process]::Start($si)
$i = 0
while (-not $p.HasExited)
{
$frame = $Spinner[$i % $Spinner.Length]
$col = $Colors[$i % $Colors.Length]
Write-Host -NoNewLine -ForegroundColor $col $frame
Start-Sleep -Milliseconds $Delay
Write-Host -NoNewLine "`b"
$i++
}
$p.WaitForExit()
Write-Host -ForegroundColor Green ""
}
Invoke-Spinner -Arguments "compose up --no-build --detach --wait" -Message "Starting services" Write-Host "Collecting static files..." -ForegroundColor Magenta
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py migrate --no-input" -Message "Applying migrations" docker compose exec app poetry run python manage.py collectstatic --no-input
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py collectstatic --no-input" -Message "Collecting static files" Write-Host "Static files collected successfully!" -ForegroundColor Green
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py set_default_caches" -Message "Setting default caches"
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py set_default_caches" -Message "Cleaning up"
Write-Host "All done! eVibes is up and running." -ForegroundColor Green Write-Host "Setting default caches..." -ForegroundColor Magenta
docker compose exec app poetry run python manage.py set_default_caches
Write-Host "Default caches set successfully!" -ForegroundColor Green
Write-Host "All done! eVibes is up and running!" -ForegroundColor Cyan

View file

@ -25,43 +25,18 @@ $art -split "`r?`n" | ForEach-Object {
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
$Spinner = @('|', '/', '-', '\') Write-Host "Shutting down..." -ForegroundColor Magenta
$Colors = @('White', 'Gray') docker compose down
$Delay = 100 Write-Host "Services were shut down successfully!" -ForegroundColor Green
function Invoke-Spinner Write-Host "Cleaning up unused Docker data..." -ForegroundColor Magenta
{ docker system prune -a -f --volumes
[CmdletBinding()] Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green
param(
[Parameter(Mandatory)][string]$Arguments,
[Parameter(Mandatory)][string]$Message
)
Write-Host -NoNewLine -ForegroundColor Cyan ("{0}… " -f $Message)
$si = New-Object System.Diagnostics.ProcessStartInfo
$si.FileName = "docker"
$si.Arguments = "$Arguments --ansi never"
$si.RedirectStandardOutput = $true
$si.RedirectStandardError = $true
$si.UseShellExecute = $false
$p = [System.Diagnostics.Process]::Start($si)
$i = 0
while (-not $p.HasExited)
{
$frame = $Spinner[$i % $Spinner.Length]
$col = $Colors[$i % $Colors.Length]
Write-Host -NoNewLine -ForegroundColor $col $frame
Start-Sleep -Milliseconds $Delay
Write-Host -NoNewline "`b"
$i++
}
$p.WaitForExit()
Write-Host -ForegroundColor Green ""
}
Invoke-Spinner -Arguments "compose down" -Message "Killing services" Write-Host "Removing related files..." -ForegroundColor Magenta
Invoke-Spinner -Arguments "system prune -f" -Message "Cleaning up Docker data"
Write-Host -ForegroundColor Cyan "Removing related files..."
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./services_data Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./services_data
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./media Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./media
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./static Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./static
Write-Host -ForegroundColor Red "Bye-bye, hope you return later!" Write-Host "Related files removed successfully!" -ForegroundColor Magenta
Write-Host "Bye-bye, hope you return later!" -ForegroundColor Red