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',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'drf_yasg',
|
'drf_yasg',
|
||||||
|
'rangefilter',
|
||||||
'core',
|
'core',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -242,6 +243,7 @@ JAZZMIN_SETTINGS = {
|
|||||||
"user_avatar": None,
|
"user_avatar": None,
|
||||||
"topmenu_links": [
|
"topmenu_links": [
|
||||||
{"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]},
|
{"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]},
|
||||||
|
{"name": "View Website", "url": "index", "new_window": True},
|
||||||
{"model": "auth.User"},
|
{"model": "auth.User"},
|
||||||
{"app": "core"},
|
{"app": "core"},
|
||||||
],
|
],
|
||||||
|
|||||||
Binary file not shown.
@ -13,6 +13,9 @@ from .mail import send_html_email
|
|||||||
import logging
|
import logging
|
||||||
import csv
|
import csv
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
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):
|
class ProfileInline(admin.StackedInline):
|
||||||
model = Profile
|
model = Profile
|
||||||
@ -92,9 +95,13 @@ class CustomUserAdmin(UserAdmin):
|
|||||||
|
|
||||||
class ParcelAdmin(admin.ModelAdmin):
|
class ParcelAdmin(admin.ModelAdmin):
|
||||||
list_display = ('tracking_number', 'shipper', 'carrier', 'price', 'status', 'payment_status', 'created_at')
|
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')
|
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):
|
def export_as_csv(self, request, queryset):
|
||||||
response = HttpResponse(content_type='text/csv')
|
response = HttpResponse(content_type='text/csv')
|
||||||
@ -118,6 +125,21 @@ class ParcelAdmin(admin.ModelAdmin):
|
|||||||
return response
|
return response
|
||||||
export_as_csv.short_description = _("Export Selected to CSV")
|
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):
|
class PlatformProfileAdmin(admin.ModelAdmin):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('General Info'), {
|
(_('General Info'), {
|
||||||
|
|||||||
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
|
django-jazzmin==3.0.1
|
||||||
djangorestframework==3.15.1
|
djangorestframework==3.15.1
|
||||||
drf-yasg
|
drf-yasg
|
||||||
|
gunicorn==22.0.0
|
||||||
|
django-cors-headers
|
||||||
|
django-admin-rangefilter
|
||||||
Loading…
x
Reference in New Issue
Block a user