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 9ec34c7..b7ec571 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ 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 870921f..cc24b2e 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ 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 @@ + + +
+ +| Tracking # | +Shipper | +Carrier | +Receiver | +From | +To | +Status | +Price | +Created 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" }} | +