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:
parent
f66a6b0cb6
commit
6ee3870ab0
12 changed files with 135 additions and 205 deletions
|
|
@ -106,7 +106,7 @@ class CategoryChildrenInline(admin.TabularInline):
|
|||
class CategoryAdmin(DraggableMPTTAdmin, BasicModelAdmin, TabbedTranslationAdmin):
|
||||
mptt_indent_field = "name"
|
||||
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",)
|
||||
search_fields = (
|
||||
"uuid",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class CaseInsensitiveListFilter(BaseInFilter, CharFilter):
|
|||
return qs
|
||||
|
||||
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:
|
||||
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"))
|
||||
name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name"))
|
||||
categories = CaseInsensitiveListFilter(field_name="category__name", label=_("Categories"))
|
||||
category_uuid = CharFilter(
|
||||
method="filter_category",
|
||||
label="Category (UUID)"
|
||||
)
|
||||
category_uuid = CharFilter(method="filter_category", label="Category (UUID)")
|
||||
categories_slugs = CaseInsensitiveListFilter(field_name="category__slug", label=_("Categories Slugs"))
|
||||
tags = CaseInsensitiveListFilter(field_name="tags__tag_name", label=_("Tags"))
|
||||
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"))
|
||||
slug = CharFilter(field_name="slug", lookup_expr="exact", label=_("Slug"))
|
||||
is_digital = BooleanFilter(field_name="is_digital", label=_("Is Digital"))
|
||||
include_subcategories = BooleanFilter(
|
||||
method="filter_include_flag",
|
||||
label=_("Include sub-categories")
|
||||
)
|
||||
include_subcategories = BooleanFilter(method="filter_include_flag", label=_("Include sub-categories"))
|
||||
|
||||
order_by = OrderingFilter(
|
||||
fields=(
|
||||
|
|
@ -322,7 +316,9 @@ class CategoryFilter(FilterSet):
|
|||
name = CharFilter(field_name="name", lookup_expr="icontains", label=_("Name"))
|
||||
parent_uuid = CharFilter(method="filter_parent_uuid", label=_("Parent"))
|
||||
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"))
|
||||
level = NumberFilter(field_name="level", lookup_expr="exact", label=_("Level"))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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.utils.translation import gettext_lazy as _
|
||||
from graphene import UUID, Field, Float, InputObjectType, Int, List, NonNull, ObjectType, String, relay
|
||||
|
|
@ -63,8 +63,9 @@ class AttributeGroupType(DjangoObjectType):
|
|||
filter_fields = ["uuid"]
|
||||
description = _("groups of attributes")
|
||||
|
||||
def resolve_attributes(self, info):
|
||||
def resolve_attributes(self: AttributeGroup, info):
|
||||
product_uuid = getattr(info.context, "_product_uuid", None)
|
||||
|
||||
qs = self.attributes.all()
|
||||
|
||||
if product_uuid:
|
||||
|
|
@ -83,15 +84,15 @@ class BrandType(DjangoObjectType):
|
|||
filter_fields = ["uuid", "name"]
|
||||
description = _("brands")
|
||||
|
||||
def resolve_categories(self, info):
|
||||
def resolve_categories(self: Brand, info):
|
||||
if info.context.user.has_perm("core.view_category"):
|
||||
return self.categories.all()
|
||||
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 ""
|
||||
|
||||
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 ""
|
||||
|
||||
|
||||
|
|
@ -121,6 +122,7 @@ class CategoryType(DjangoObjectType):
|
|||
description=_("minimum and maximum prices for products in this category, if available."),
|
||||
)
|
||||
tags = DjangoFilterConnectionField(lambda: CategoryTagType, description=_("tags for this category"))
|
||||
products = DjangoFilterConnectionField(lambda: ProductType, description=_("products in this category"))
|
||||
|
||||
class Meta:
|
||||
model = Category
|
||||
|
|
@ -145,15 +147,15 @@ class CategoryType(DjangoObjectType):
|
|||
return categories
|
||||
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 ""
|
||||
|
||||
def resolve_markup_percent(self, info) -> float:
|
||||
def resolve_markup_percent(self: Category, info) -> float:
|
||||
if info.context.user.has_perm("core.view_category"):
|
||||
return float(self.markup_percent)
|
||||
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", [])
|
||||
|
||||
if len(filterable_results) > 0:
|
||||
|
|
@ -175,7 +177,10 @@ class CategoryType(DjangoObjectType):
|
|||
|
||||
if len(distinct_vals_list) <= 128:
|
||||
filterable_results.append(
|
||||
FilterableAttributeType(attribute_name=attr.name, possible_values=distinct_vals_list)
|
||||
{
|
||||
"attribute_name": attr.name,
|
||||
"possible_values": distinct_vals_list,
|
||||
}
|
||||
)
|
||||
else:
|
||||
pass
|
||||
|
|
@ -184,7 +189,7 @@ class CategoryType(DjangoObjectType):
|
|||
|
||||
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={})
|
||||
|
||||
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["max_price"] = price_aggregation.get("max_price", 0.0)
|
||||
|
||||
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):
|
||||
|
|
@ -230,12 +234,14 @@ class AddressType(DjangoObjectType):
|
|||
)
|
||||
read_only_fields = ("api_response",)
|
||||
|
||||
def resolve_latitude(self, info):
|
||||
return self.location.y if self.location else None
|
||||
|
||||
def resolve_longitude(self, info):
|
||||
def resolve_latitude(self: Address, _info):
|
||||
# noinspection PyUnresolvedReferences
|
||||
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):
|
||||
comment = String(description=_("comment"))
|
||||
|
|
@ -269,13 +275,13 @@ class OrderProductType(DjangoObjectType):
|
|||
filter_fields = ["uuid"]
|
||||
description = _("order products")
|
||||
|
||||
def resolve_attributes(self, info):
|
||||
def resolve_attributes(self, _info):
|
||||
return camelize(self.attributes)
|
||||
|
||||
def resolve_notifications(self, info):
|
||||
def resolve_notifications(self, _info):
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -318,10 +324,10 @@ class OrderType(DjangoObjectType):
|
|||
def resolve_total_quantity(self, _info):
|
||||
return self.total_quantity
|
||||
|
||||
def resolve_notifications(self, info):
|
||||
def resolve_notifications(self, _info):
|
||||
return camelize(self.notifications)
|
||||
|
||||
def resolve_attributes(self, info):
|
||||
def resolve_attributes(self, _info):
|
||||
return camelize(self.attributes)
|
||||
|
||||
|
||||
|
|
@ -335,7 +341,7 @@ class ProductImageType(DjangoObjectType):
|
|||
filter_fields = ["uuid"]
|
||||
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 ""
|
||||
|
||||
|
||||
|
|
@ -370,7 +376,7 @@ class ProductType(DjangoObjectType):
|
|||
def resolve_price(self, _info) -> float:
|
||||
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"):
|
||||
return Feedback.objects.filter(order_product__product=self)
|
||||
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:
|
||||
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
|
||||
|
||||
return AttributeGroup.objects.filter(attributes__values__product=self).distinct()
|
||||
|
|
@ -415,10 +421,10 @@ class PromoCodeType(DjangoObjectType):
|
|||
filter_fields = ["uuid"]
|
||||
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
|
||||
|
||||
def resolve_discount_type(self, info) -> str:
|
||||
def resolve_discount_type(self: PromoCode, _info) -> str:
|
||||
return "percent" if self.discount_percent else "amount"
|
||||
|
||||
|
||||
|
|
|
|||
23
core/management/commands/fix_prices.py
Normal file
23
core/management/commands/fix_prices.py
Normal 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!"))
|
||||
|
|
@ -281,6 +281,7 @@ class Product(ExportModelOperationsMixin("product"), NiceModel):
|
|||
on_delete=CASCADE,
|
||||
help_text=_("category this product belongs to"),
|
||||
verbose_name=_("category"),
|
||||
related_name="products",
|
||||
)
|
||||
brand = ForeignKey(
|
||||
"core.Brand",
|
||||
|
|
@ -1292,8 +1293,7 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
|
|||
if self.order_product.status != "FINISHED":
|
||||
raise ValueError(_("you can not download a digital asset for a non-finished order"))
|
||||
|
||||
return (f"https://api.{config.BASE_URL}/" # noqa: DJ
|
||||
f"download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}")
|
||||
return f"https://api.{config.BASE_URL}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}"
|
||||
|
||||
|
||||
class Documentary(ExportModelOperationsMixin("attribute_group"), NiceModel):
|
||||
|
|
|
|||
|
|
@ -96,9 +96,7 @@ class EvibesPermission(permissions.BasePermission):
|
|||
return queryset.none()
|
||||
|
||||
base = queryset.filter(is_active=True, user=request.user)
|
||||
if request.user.has_perm(
|
||||
f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}"
|
||||
):
|
||||
if request.user.has_perm(f"{app_label}.{self.ACTION_PERM_MAP.get(view.action)}_{model_name}"):
|
||||
return queryset.filter(is_active=True)
|
||||
return base
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from core.views import CustomGraphQLView, CustomRedocView, CustomSwaggerView, fa
|
|||
from evibes.settings import SPECTACULAR_PLATFORM_SETTINGS
|
||||
|
||||
urlpatterns = [
|
||||
path(r'health/', include('health_check.urls')),
|
||||
path(r"health/", include("health_check.urls")),
|
||||
path("prometheus/", include("django_prometheus.urls")),
|
||||
path(r"graphql/", csrf_exempt(CustomGraphQLView.as_view(graphiql=True, schema=schema))),
|
||||
path(
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ from evibes.settings.base import getenv
|
|||
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" # Or "constance.backends.redis.RedisBackend"
|
||||
|
||||
CONSTANCE_ADDITIONAL_FIELDS = {
|
||||
"json": ["django.forms.fields.JSONField", {
|
||||
"required": False,
|
||||
"widget": "core.widgets.JSONTableWidget",
|
||||
}],
|
||||
"json": [
|
||||
"django.forms.fields.JSONField",
|
||||
{
|
||||
"required": False,
|
||||
"widget": "core.widgets.JSONTableWidget",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
CONSTANCE_CONFIG = {
|
||||
|
|
@ -18,8 +21,10 @@ CONSTANCE_CONFIG = {
|
|||
"COMPANY_NAME": (getenv("COMPANY_NAME"), _("Name of the company")),
|
||||
"COMPANY_ADDRESS": (getenv("COMPANY_ADDRESS"), _("Address of the company")),
|
||||
"COMPANY_PHONE_NUMBER": (getenv("COMPANY_PHONE_NUMBER"), _("Phone number of the company")),
|
||||
"STOCKS_ARE_SINGLE": (getenv("STOCKS_ARE_SINGLE", True),
|
||||
_("Designates whether every product has one stock or not")),
|
||||
"STOCKS_ARE_SINGLE": (
|
||||
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_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)")),
|
||||
|
|
|
|||
|
|
@ -10,19 +10,15 @@ if (-not (Test-Path -Path ".\evibes" -PathType Container))
|
|||
|
||||
$purple = "`e[38;2;121;101;209m"
|
||||
$reset = "`e[0m"
|
||||
|
||||
$artPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) '..\ASCII_ART_EVIBES'
|
||||
|
||||
if (-not (Test-Path $artPath))
|
||||
{
|
||||
Write-Host "❌ Could not find ASCII art at $artPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$art = Get-Content -Raw -Path $artPath
|
||||
$art -split "`r?`n" | ForEach-Object {
|
||||
Write-Host "$purple$_$reset"
|
||||
}
|
||||
|
||||
Get-Content -Raw -Path $artPath | ForEach-Object { Write-Host "$purple$_$reset" }
|
||||
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
|
||||
|
||||
if (-not (Test-Path '.env'))
|
||||
|
|
@ -34,7 +30,6 @@ if (-not (Test-Path '.env'))
|
|||
$cpuCount = [Environment]::ProcessorCount
|
||||
if ($cpuCount -lt 4)
|
||||
{
|
||||
Write-Error "Insufficient CPU cores: $cpuCount detected. Minimum 4 required."
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
|
@ -42,54 +37,25 @@ $totalMemBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory
|
|||
$totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2)
|
||||
if ($totalMemGB -lt 6)
|
||||
{
|
||||
Write-Error "Insufficient RAM: $totalMemGB GB detected. Minimum 6 GB required."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$currentDrive = Split-Path -Qualifier $PWD
|
||||
$driveLetter = $currentDrive.Substring(0, 1)
|
||||
$driveInfo = Get-PSDrive -Name $driveLetter
|
||||
$freeGB = [Math]::Round($driveInfo.Free / 1GB, 2)
|
||||
$freeGB = [Math]::Round((Get-PSDrive -Name $driveLetter).Free / 1GB, 2)
|
||||
if ($freeGB -lt 20)
|
||||
{
|
||||
Write-Error "Insufficient free disk space on drive $driveLetter`: $freeGB GB available. Minimum 20 GB required."
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB" -ForegroundColor Green
|
||||
|
||||
$Spinner = @('|', '/', '-', '\')
|
||||
$Colors = @('White', 'Gray')
|
||||
$Delay = 100
|
||||
Write-Host "Pulling images" -ForegroundColor Magenta
|
||||
docker compose pull
|
||||
Write-Host "Images pulled successfully" -ForegroundColor Green
|
||||
|
||||
function Invoke-Spinner
|
||||
{
|
||||
[CmdletBinding()]
|
||||
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 pull" -Message "Pulling related images"
|
||||
Invoke-Spinner -Arguments "compose build" -Message "Building the project's images"
|
||||
Write-Host "Building images" -ForegroundColor Magenta
|
||||
docker compose build
|
||||
Write-Host "Images built successfully" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "You can now use run.ps1 script." -ForegroundColor Cyan
|
||||
|
|
|
|||
|
|
@ -23,45 +23,29 @@ $art -split "`r?`n" | ForEach-Object {
|
|||
|
||||
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
|
||||
|
||||
$Spinner = @('|', '/', '-', '\')
|
||||
$Colors = @('White', 'Gray')
|
||||
$Delay = 100
|
||||
|
||||
function Invoke-Spinner
|
||||
{
|
||||
[CmdletBinding()]
|
||||
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 "✔"
|
||||
}
|
||||
Write-Host "Shutting down..." -ForegroundColor Magenta
|
||||
docker compose down
|
||||
Write-Host "Services were shut down successfully!" -ForegroundColor Green
|
||||
|
||||
Invoke-Spinner -Arguments "compose down" -Message "Stopping services"
|
||||
Invoke-Spinner -Arguments "compose build" -Message "Rebuilding services"
|
||||
Invoke-Spinner -Arguments "compose up -d" -Message "Starting services"
|
||||
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 "Spinning services up..." -ForegroundColor Magenta
|
||||
docker compose up -d --build --wait
|
||||
Write-Host "Services are up and healthy!" -ForegroundColor Green
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -49,43 +49,20 @@ foreach ($prop in $config.services.PSObject.Properties)
|
|||
Write-Host " • Found image: $image"
|
||||
}
|
||||
|
||||
$Spinner = @('|', '/', '-', '\')
|
||||
$Colors = @('White', 'Gray')
|
||||
$Delay = 100
|
||||
Write-Host "Spinning services up..." -ForegroundColor Magenta
|
||||
docker compose up --no-build --detach --wait
|
||||
Write-Host "Services are up and healthy!" -ForegroundColor Green
|
||||
|
||||
function Invoke-Spinner
|
||||
{
|
||||
[CmdletBinding()]
|
||||
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 "✔"
|
||||
}
|
||||
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
|
||||
|
||||
Invoke-Spinner -Arguments "compose up --no-build --detach --wait" -Message "Starting services"
|
||||
Invoke-Spinner -Arguments "compose exec app poetry run python manage.py migrate --no-input" -Message "Applying 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 "compose exec app poetry run python manage.py set_default_caches" -Message "Cleaning up"
|
||||
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 "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
|
||||
|
|
|
|||
|
|
@ -25,43 +25,18 @@ $art -split "`r?`n" | ForEach-Object {
|
|||
|
||||
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
|
||||
|
||||
$Spinner = @('|', '/', '-', '\')
|
||||
$Colors = @('White', 'Gray')
|
||||
$Delay = 100
|
||||
Write-Host "Shutting down..." -ForegroundColor Magenta
|
||||
docker compose down
|
||||
Write-Host "Services were shut down successfully!" -ForegroundColor Green
|
||||
|
||||
function Invoke-Spinner
|
||||
{
|
||||
[CmdletBinding()]
|
||||
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 "✔"
|
||||
}
|
||||
Write-Host "Cleaning up unused Docker data..." -ForegroundColor Magenta
|
||||
docker system prune -a -f --volumes
|
||||
Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green
|
||||
|
||||
Invoke-Spinner -Arguments "compose down" -Message "Killing services"
|
||||
Invoke-Spinner -Arguments "system prune -f" -Message "Cleaning up Docker data"
|
||||
Write-Host -ForegroundColor Cyan "Removing related files..."
|
||||
Write-Host "Removing related files..." -ForegroundColor Magenta
|
||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./services_data
|
||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./media
|
||||
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
|
||||
Loading…
Reference in a new issue