fffuin
This commit is contained in:
parent
e6c45971eb
commit
0beefaf8a8
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@ -0,0 +1,15 @@
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
.venv
|
||||
venv/
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.DS_Store
|
||||
db.sqlite3
|
||||
staticfiles/
|
||||
media/
|
||||
.idea
|
||||
.vscode
|
||||
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@ -0,0 +1,42 @@
|
||||
FROM python:3.11-slim-bookworm
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
# WeasyPrint needs: libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 libjpeg-dev libopenjp2-7-dev libxcb1
|
||||
# MySQLclient needs: default-libmysqlclient-dev gcc pkg-config
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
pkg-config \
|
||||
default-libmysqlclient-dev \
|
||||
libpango-1.0-0 \
|
||||
libpangoft2-1.0-0 \
|
||||
libharfbuzz-subset0 \
|
||||
libjpeg-dev \
|
||||
libopenjp2-7-dev \
|
||||
libxcb1 \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install python dependencies
|
||||
COPY requirements.txt /app/
|
||||
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy project
|
||||
COPY . /app/
|
||||
|
||||
# Copy entrypoint script and make it executable
|
||||
COPY entrypoint.sh /app/entrypoint.sh
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Entrypoint
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
|
||||
Binary file not shown.
@ -59,6 +59,7 @@ INSTALLED_APPS = [
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'drf_yasg',
|
||||
'rangefilter',
|
||||
'core',
|
||||
]
|
||||
|
||||
@ -242,6 +243,7 @@ JAZZMIN_SETTINGS = {
|
||||
"user_avatar": None,
|
||||
"topmenu_links": [
|
||||
{"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]},
|
||||
{"name": "View Website", "url": "index", "new_window": True},
|
||||
{"model": "auth.User"},
|
||||
{"app": "core"},
|
||||
],
|
||||
@ -306,4 +308,4 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -13,6 +13,9 @@ from .mail import send_html_email
|
||||
import logging
|
||||
import csv
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from rangefilter.filters import DateRangeFilter
|
||||
from django.template.loader import render_to_string
|
||||
import weasyprint
|
||||
|
||||
class ProfileInline(admin.StackedInline):
|
||||
model = Profile
|
||||
@ -92,9 +95,13 @@ class CustomUserAdmin(UserAdmin):
|
||||
|
||||
class ParcelAdmin(admin.ModelAdmin):
|
||||
list_display = ('tracking_number', 'shipper', 'carrier', 'price', 'status', 'payment_status', 'created_at')
|
||||
list_filter = ('status', 'payment_status', 'created_at')
|
||||
list_filter = (
|
||||
'status',
|
||||
'payment_status',
|
||||
('created_at', DateRangeFilter),
|
||||
)
|
||||
search_fields = ('tracking_number', 'shipper__username', 'receiver_name', 'carrier__username')
|
||||
actions = ['export_as_csv']
|
||||
actions = ['export_as_csv', 'print_parcels', 'export_pdf']
|
||||
|
||||
def export_as_csv(self, request, queryset):
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
@ -118,6 +125,21 @@ class ParcelAdmin(admin.ModelAdmin):
|
||||
return response
|
||||
export_as_csv.short_description = _("Export Selected to CSV")
|
||||
|
||||
def print_parcels(self, request, queryset):
|
||||
return render(request, 'admin/core/parcel/parcel_list_print.html', {'parcels': queryset, 'is_pdf': False})
|
||||
print_parcels.short_description = _("Print Selected Parcels")
|
||||
|
||||
def export_pdf(self, request, queryset):
|
||||
html_string = render_to_string('admin/core/parcel/parcel_list_print.html', {'parcels': queryset, 'is_pdf': True})
|
||||
html = weasyprint.HTML(string=html_string, base_url=request.build_absolute_uri())
|
||||
result = html.write_pdf()
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = 'attachment; filename="parcels_list.pdf"'
|
||||
response.write(result)
|
||||
return response
|
||||
export_pdf.short_description = _("Download Selected as PDF")
|
||||
|
||||
class PlatformProfileAdmin(admin.ModelAdmin):
|
||||
fieldsets = (
|
||||
(_('General Info'), {
|
||||
@ -260,4 +282,4 @@ class NotificationTemplateAdmin(admin.ModelAdmin):
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
admin.site.register(NotificationTemplate, NotificationTemplateAdmin)
|
||||
admin.site.register(NotificationTemplate, NotificationTemplateAdmin)
|
||||
63
core/templates/admin/core/parcel/parcel_list_print.html
Normal file
63
core/templates/admin/core/parcel/parcel_list_print.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Parcel List</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; font-size: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { border: 1px solid #ddd; padding: 6px; text-align: left; }
|
||||
th { background-color: #f2f2f2; }
|
||||
.header { text-align: center; margin-bottom: 20px; }
|
||||
.meta { margin-bottom: 10px; font-size: 10px; color: #666; }
|
||||
@media print {
|
||||
.no-print { display: none; }
|
||||
table { font-size: 10px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Parcel List</h1>
|
||||
<p class="meta">Generated on: {% now "Y-m-d H:i" %}</p>
|
||||
</div>
|
||||
|
||||
{% if not is_pdf %}
|
||||
<div class="no-print" style="margin-bottom: 20px; text-align: center;">
|
||||
<button onclick="window.print()" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">Print List</button>
|
||||
<button onclick="window.history.back()" style="padding: 10px 20px; font-size: 16px; cursor: pointer; margin-left: 10px;">Back</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tracking #</th>
|
||||
<th>Shipper</th>
|
||||
<th>Carrier</th>
|
||||
<th>Receiver</th>
|
||||
<th>From</th>
|
||||
<th>To</th>
|
||||
<th>Status</th>
|
||||
<th>Price</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for parcel in parcels %}
|
||||
<tr>
|
||||
<td>{{ parcel.tracking_number }}</td>
|
||||
<td>{{ parcel.shipper.username|default:"-" }}</td>
|
||||
<td>{{ parcel.carrier.username|default:"-" }}</td>
|
||||
<td>{{ parcel.receiver_name }}</td>
|
||||
<td>{{ parcel.pickup_city.name_en|default:"-" }}</td>
|
||||
<td>{{ parcel.delivery_city.name_en|default:"-" }}</td>
|
||||
<td>{{ parcel.get_status_display }}</td>
|
||||
<td>{{ parcel.price }}</td>
|
||||
<td>{{ parcel.created_at|date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
16
entrypoint.sh
Normal file
16
entrypoint.sh
Normal file
@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status
|
||||
set -e
|
||||
|
||||
# Apply database migrations
|
||||
echo "Applying database migrations..."
|
||||
python manage.py migrate
|
||||
|
||||
# Collect static files
|
||||
echo "Collecting static files..."
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
# Start Gunicorn
|
||||
echo "Starting Gunicorn..."
|
||||
exec gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3
|
||||
@ -7,3 +7,6 @@ qrcode
|
||||
django-jazzmin==3.0.1
|
||||
djangorestframework==3.15.1
|
||||
drf-yasg
|
||||
gunicorn==22.0.0
|
||||
django-cors-headers
|
||||
django-admin-rangefilter
|
||||
Loading…
x
Reference in New Issue
Block a user