From 0beefaf8a83438ac876043066e053717f4a880b6 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 28 Jan 2026 04:47:18 +0000 Subject: [PATCH] fffuin --- .dockerignore | 15 +++++ Dockerfile | 42 ++++++++++++ config/__pycache__/settings.cpython-311.pyc | Bin 9584 -> 9658 bytes config/settings.py | 4 +- core/__pycache__/admin.cpython-311.pyc | Bin 16409 -> 17788 bytes core/admin.py | 28 +++++++- .../admin/core/parcel/parcel_list_print.html | 63 ++++++++++++++++++ entrypoint.sh | 16 +++++ requirements.txt | 3 + 9 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 core/templates/admin/core/parcel/parcel_list_print.html create mode 100644 entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..995ca0a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.gitignore +.env +.venv +venv/ +__pycache__ +*.pyc +*.pyo +*.pyd +.DS_Store +db.sqlite3 +staticfiles/ +media/ +.idea +.vscode diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9af1520 --- /dev/null +++ b/Dockerfile @@ -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"] + diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 9ec34c718448923cc4fd4ed27b9909da2ddf289b..b7ec5715518d8c81a7c2ada548423de54aaaaf94 100644 GIT binary patch delta 874 zcmXw#OH30{6o%(^I(^a7PAN2MhldcM@-|($5DFNf2%_>xY79Y7sgu+~+F}L8;v2{y`9$YaojLtV&)_-R^FmMZ@RIo|@QknEHLSrKSceUG3-59! zg@n7{J$!(VQG-mv-M}B^Isc|=vn=z(uB?`Px0fU);?c;Y97{zK>WqD!H`TI#mS|D` zI4gPbRrQt``M{igRF0&p$08?^aw_8I6H(lAilJzWN7CbIISy0lqHuZ&BC#SrF+H7- zRDXq;kfhS?-9b*O1Ky}0@556ac#4`Ug=AJQNZ9c}~Vwy;o2E7d@i z#%CHm2F>Uyxa%p;^runGc2I#CHF1KNZKY;(3n#d;mDGxE!@Px-pxZg2tl$Yy2NtEc zsfs$$U96Ekp6@Ss+Ng+un-kpG04+oJaDqn@y?Lq7IixJr+A?C*hWT@X@B9M2@S@?u_yN2)AHnl9S=0Rbd;b5^oYVg3Vd=m|AmAhX8c}Dp zwdKGL`saK7KdCULQJ97qjphV{!X=mjp5!R^%OG>kI=qr2+^;&k1~V|1bMg5)+<*ei z!%esax8V-lg?n%x7K%RJJ%B|>!$S~2=Fdk(&pOSdGy+SI=IvvLPas|3gYFE=4xfU- zlV|XpbHzzsI9zr3(%~!TNUx#D*Sv9(xA2a83Gfr%!y0^mkFXA(U;{oEC4**;!58=n z-*PUEW{!g}NE$Xrnq%CD?!&y5RigWe7_p-5 zEPz807uB#3dYBjb*;0=c-OpqU6e22mJBy%4i5Ruzcqw5W?JrmzCoS&)zwL^!-eh@` zc12K^7DCM9u98>`*C&Y>E4NH=#U)mQaUJf_$@ZY{CF0K2c2iDp~fZ7a359JAG3c3fqH6kAe;MZ)Zi JxmD+<{{dp^^0xp0 diff --git a/config/settings.py b/config/settings.py index d858f0c..5e1cce8 100644 --- a/config/settings.py +++ b/config/settings.py @@ -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', ], -} \ No newline at end of file +} diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 870921fe83414f0ac4d50f972c0be96f6482ae5e..cc24b2e1e9e57812338acfbcc6433769b3da101a 100644 GIT binary patch delta 4745 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 Q>s5ATukU$}+31k|3t`gx*Z=?k diff --git a/core/admin.py b/core/admin.py index e34444c..d0ce2d0 100644 --- a/core/admin.py +++ b/core/admin.py @@ -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) \ No newline at end of file diff --git a/core/templates/admin/core/parcel/parcel_list_print.html b/core/templates/admin/core/parcel/parcel_list_print.html new file mode 100644 index 0000000..9cf7c5b --- /dev/null +++ b/core/templates/admin/core/parcel/parcel_list_print.html @@ -0,0 +1,63 @@ + + + + + Parcel List + + + +
+

Parcel List

+

Generated on: {% now "Y-m-d H:i" %}

+
+ + {% if not is_pdf %} +
+ + +
+ {% endif %} + + + + + + + + + + + + + + + + + {% for parcel in parcels %} + + + + + + + + + + + + {% endfor %} + +
Tracking #ShipperCarrierReceiverFromToStatusPriceCreated At
{{ parcel.tracking_number }}{{ parcel.shipper.username|default:"-" }}{{ parcel.carrier.username|default:"-" }}{{ parcel.receiver_name }}{{ parcel.pickup_city.name_en|default:"-" }}{{ parcel.delivery_city.name_en|default:"-" }}{{ parcel.get_status_display }}{{ parcel.price }}{{ parcel.created_at|date:"Y-m-d H:i" }}
+ + \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..b1b9940 --- /dev/null +++ b/entrypoint.sh @@ -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 diff --git a/requirements.txt b/requirements.txt index e59ab91..47ee07c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 \ No newline at end of file