Fix Docker setup: add gunicorn, fix permissions, update docker-compose and entrypoint, add deployment instructions
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
# Все файлы хранятся в /Volume1/DockerAppsData/mixapp/
|
# Все файлы хранятся в /Volume1/DockerAppsData/mixapp/
|
||||||
# YAML файл хранится в /Volume1/DockerYAML/mix/
|
# YAML файл хранится в /Volume1/DockerYAML/mix/
|
||||||
|
|
||||||
|
|||||||
@@ -149,11 +149,18 @@ EOF
|
|||||||
|
|
||||||
# Если manage.py не в текущей директории, но есть в подпапке myproject
|
# Если manage.py не в текущей директории, но есть в подпапке myproject
|
||||||
if [ ! -f "manage.py" ] && [ -d "myproject" ]; then
|
if [ ! -f "manage.py" ] && [ -d "myproject" ]; then
|
||||||
echo "Changing directory to myproject..."
|
# Пытаемся войти в директорию, перенаправляя ошибки в /dev/null
|
||||||
cd myproject
|
if cd myproject 2>/dev/null; then
|
||||||
# Устанавливаем PYTHONPATH чтобы Python мог найти модуль myproject
|
echo "Changing directory to myproject..."
|
||||||
export PYTHONPATH=$(pwd):$PYTHONPATH
|
# Устанавливаем PYTHONPATH чтобы Python мог найти модуль myproject
|
||||||
echo "PYTHONPATH set to: $PYTHONPATH"
|
export PYTHONPATH=$(pwd):$PYTHONPATH
|
||||||
|
echo "PYTHONPATH set to: $PYTHONPATH"
|
||||||
|
else
|
||||||
|
# Если не можем войти в директорию (проблема с правами), устанавливаем PYTHONPATH из текущей директории
|
||||||
|
echo "Warning: Cannot access myproject directory (permission denied). Setting PYTHONPATH to include myproject..."
|
||||||
|
export PYTHONPATH=/app/myproject:$PYTHONPATH
|
||||||
|
echo "PYTHONPATH set to: $PYTHONPATH"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|||||||
@@ -187,7 +187,7 @@
|
|||||||
<th>Себестоимость:</th>
|
<th>Себестоимость:</th>
|
||||||
<td>
|
<td>
|
||||||
<strong class="fs-5">{{ product.cost_price }} руб.</strong>
|
<strong class="fs-5">{{ product.cost_price }} руб.</strong>
|
||||||
{% if product.cost_price_details.batches %}
|
{% if cost_price_details.batches %}
|
||||||
<button class="btn btn-sm btn-outline-info ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#costDetails" aria-expanded="false" aria-controls="costDetails">
|
<button class="btn btn-sm btn-outline-info ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#costDetails" aria-expanded="false" aria-controls="costDetails">
|
||||||
<i class="bi bi-info-circle"></i> Детали расчета
|
<i class="bi bi-info-circle"></i> Детали расчета
|
||||||
</button>
|
</button>
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if product.cost_price_details.batches %}
|
{% if cost_price_details.batches %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="collapse" id="costDetails">
|
<div class="collapse" id="costDetails">
|
||||||
@@ -206,13 +206,13 @@
|
|||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="alert alert-info mb-0">
|
<div class="alert alert-info mb-0">
|
||||||
<small><strong>Кешированная стоимость:</strong> {{ product.cost_price_details.cached_cost }} руб.</small>
|
<small><strong>Кешированная стоимость:</strong> {{ cost_price_details.cached_cost }} руб.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="alert alert-{% if product.cost_price_details.is_synced %}success{% else %}warning{% endif %} mb-0">
|
<div class="alert alert-{% if cost_price_details.is_synced %}success{% else %}warning{% endif %} mb-0">
|
||||||
<small><strong>Рассчитанная стоимость:</strong> {{ product.cost_price_details.calculated_cost }} руб.</small>
|
<small><strong>Рассчитанная стоимость:</strong> {{ cost_price_details.calculated_cost }} руб.</small>
|
||||||
{% if not product.cost_price_details.is_synced %}
|
{% if not cost_price_details.is_synced %}
|
||||||
<br><small class="text-danger">⚠ Требуется синхронизация!</small>
|
<br><small class="text-danger">⚠ Требуется синхронизация!</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -231,7 +231,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for batch in product.cost_price_details.batches %}
|
{% for batch in cost_price_details.batches %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ batch.warehouse_name }}</td>
|
<td>{{ batch.warehouse_name }}</td>
|
||||||
<td class="text-end">{{ batch.quantity }}</td>
|
<td class="text-end">{{ batch.quantity }}</td>
|
||||||
@@ -244,9 +244,9 @@
|
|||||||
<tfoot class="table-secondary">
|
<tfoot class="table-secondary">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Итого:</th>
|
<th>Итого:</th>
|
||||||
<th class="text-end">{{ product.cost_price_details.total_quantity }}</th>
|
<th class="text-end">{{ cost_price_details.total_quantity }}</th>
|
||||||
<th class="text-end" colspan="3">
|
<th class="text-end" colspan="3">
|
||||||
<strong>Средневзвешенная: {{ product.cost_price_details.calculated_cost }} руб.</strong>
|
<strong>Средневзвешенная: {{ cost_price_details.calculated_cost }} руб.</strong>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|||||||
@@ -161,10 +161,14 @@ class ProductDetailView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DetailVie
|
|||||||
context_object_name = 'product'
|
context_object_name = 'product'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Предзагрузка фотографий и аннотация остатков
|
# Предзагрузка фотографий, категорий, тегов и аннотация остатков
|
||||||
total_available = Coalesce(Sum('stocks__quantity_available'), Value(0), output_field=DecimalField())
|
total_available = Coalesce(Sum('stocks__quantity_available'), Value(0), output_field=DecimalField())
|
||||||
total_reserved = Coalesce(Sum('stocks__quantity_reserved'), Value(0), output_field=DecimalField())
|
total_reserved = Coalesce(Sum('stocks__quantity_reserved'), Value(0), output_field=DecimalField())
|
||||||
return super().get_queryset().prefetch_related('photos').annotate(
|
return super().get_queryset().prefetch_related(
|
||||||
|
'photos',
|
||||||
|
'categories',
|
||||||
|
'tags'
|
||||||
|
).annotate(
|
||||||
total_available=total_available,
|
total_available=total_available,
|
||||||
total_reserved=total_reserved,
|
total_reserved=total_reserved,
|
||||||
total_free=total_available - total_reserved,
|
total_free=total_available - total_reserved,
|
||||||
@@ -172,9 +176,16 @@ class ProductDetailView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DetailVie
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
# Добавляем фотографии товара в контекст
|
# Добавляем фотографии товара в контекст (используем уже загруженные через prefetch_related)
|
||||||
context['product_photos'] = self.object.photos.all().order_by('order', 'created_at')
|
product_photos = list(self.object.photos.all())
|
||||||
context['photos_count'] = self.object.photos.count()
|
product_photos.sort(key=lambda x: (x.order, x.created_at))
|
||||||
|
context['product_photos'] = product_photos
|
||||||
|
context['photos_count'] = len(product_photos)
|
||||||
|
|
||||||
|
# Кешируем cost_price_details, чтобы не делать множественные запросы к БД
|
||||||
|
from ..services.cost_calculator import ProductCostCalculator
|
||||||
|
context['cost_price_details'] = ProductCostCalculator.get_cost_details(self.object)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,3 +35,4 @@ tzdata==2025.2
|
|||||||
Unidecode==1.4.0
|
Unidecode==1.4.0
|
||||||
vine==5.1.0
|
vine==5.1.0
|
||||||
wcwidth==0.2.14
|
wcwidth==0.2.14
|
||||||
|
gunicorn==21.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user