diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 82e7cc0..f7f591a 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 1c5c1d2..c9053e4 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 0d02fbb..9951dda 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/forms.py b/core/forms.py index d48204e..c9de2b1 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,5 +1,5 @@ from django import forms -from .models import Truck, Shipment, Bid, Profile, Country, OTPCode +from .models import Truck, Shipment, Bid, Profile, Country, OTPCode, City from django.utils.translation import gettext_lazy as _ from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User @@ -89,12 +89,19 @@ class TruckForm(forms.ModelForm): class ShipmentForm(forms.ModelForm): class Meta: model = Shipment - fields = ['description', 'weight', 'origin', 'destination', 'delivery_date'] + fields = [ + 'description', 'weight', + 'origin_country', 'origin_city', + 'destination_country', 'destination_city', + 'delivery_date' + ] widgets = { 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'weight': forms.TextInput(attrs={'class': 'form-control'}), - 'origin': forms.TextInput(attrs={'class': 'form-control'}), - 'destination': forms.TextInput(attrs={'class': 'form-control'}), + 'origin_country': forms.Select(attrs={'class': 'form-select location-selector', 'data-type': 'origin'}), + 'origin_city': forms.Select(attrs={'class': 'form-select'}), + 'destination_country': forms.Select(attrs={'class': 'form-select location-selector', 'data-type': 'destination'}), + 'destination_city': forms.Select(attrs={'class': 'form-select'}), 'delivery_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), } @@ -120,8 +127,12 @@ class BidForm(forms.ModelForm): class ShipperOfferForm(forms.Form): description = forms.CharField(label=_('Goods Description'), widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})) weight = forms.CharField(label=_('Weight/Volume'), widget=forms.TextInput(attrs={'class': 'form-control'})) - origin = forms.CharField(label=_('Origin'), widget=forms.TextInput(attrs={'class': 'form-control'})) - destination = forms.CharField(label=_('Destination'), widget=forms.TextInput(attrs={'class': 'form-control'})) + + origin_country = forms.ModelChoiceField(queryset=Country.objects.all(), widget=forms.Select(attrs={'class': 'form-select location-selector', 'data-type': 'origin'})) + origin_city = forms.ModelChoiceField(queryset=City.objects.all(), widget=forms.Select(attrs={'class': 'form-select'})) + destination_country = forms.ModelChoiceField(queryset=Country.objects.all(), widget=forms.Select(attrs={'class': 'form-select location-selector', 'data-type': 'destination'})) + destination_city = forms.ModelChoiceField(queryset=City.objects.all(), widget=forms.Select(attrs={'class': 'form-select'})) + delivery_date = forms.DateField(label=_('Requested Delivery Date'), widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})) amount = forms.DecimalField(label=_('Offer Amount'), max_digits=10, decimal_places=2, widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})) - comments = forms.CharField(label=_('Comments'), required=False, widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 2})) + comments = forms.CharField(label=_('Comments'), required=False, widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 2})) \ No newline at end of file diff --git a/core/migrations/0010_shipment_destination_country_shipment_origin_country_and_more.py b/core/migrations/0010_shipment_destination_country_shipment_origin_country_and_more.py new file mode 100644 index 0000000..eede752 --- /dev/null +++ b/core/migrations/0010_shipment_destination_country_shipment_origin_country_and_more.py @@ -0,0 +1,57 @@ +# Generated by Django 5.2.7 on 2026-01-23 14:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_otpcode_alter_profile_phone_number'), + ] + + operations = [ + migrations.AddField( + model_name='shipment', + name='destination_country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='shipments_destination', to='core.country'), + ), + migrations.AddField( + model_name='shipment', + name='origin_country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='shipments_origin', to='core.country'), + ), + migrations.AlterField( + model_name='shipment', + name='destination', + field=models.CharField(blank=True, max_length=255, verbose_name='Destination (Legacy)'), + ), + migrations.AlterField( + model_name='shipment', + name='origin', + field=models.CharField(blank=True, max_length=255, verbose_name='Origin (Legacy)'), + ), + migrations.CreateModel( + name='City', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='City Name')), + ('country', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cities', to='core.country')), + ], + options={ + 'verbose_name': 'City', + 'verbose_name_plural': 'Cities', + 'ordering': ['name'], + }, + ), + migrations.AddField( + model_name='shipment', + name='destination_city', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='shipments_destination', to='core.city'), + ), + migrations.AddField( + model_name='shipment', + name='origin_city', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='shipments_origin', to='core.city'), + ), + ] diff --git a/core/migrations/__pycache__/0010_shipment_destination_country_shipment_origin_country_and_more.cpython-311.pyc b/core/migrations/__pycache__/0010_shipment_destination_country_shipment_origin_country_and_more.cpython-311.pyc new file mode 100644 index 0000000..b086e3e Binary files /dev/null and b/core/migrations/__pycache__/0010_shipment_destination_country_shipment_origin_country_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index f1331d6..db726e5 100644 --- a/core/models.py +++ b/core/models.py @@ -20,6 +20,18 @@ class Country(models.Model): def __str__(self): return f"{self.name} (+{self.code})" +class City(models.Model): + country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='cities') + name = models.CharField(_('City Name'), max_length=100) + + class Meta: + verbose_name = _('City') + verbose_name_plural = _('Cities') + ordering = ['name'] + + def __str__(self): + return f"{self.name} ({self.country.name})" + class Profile(models.Model): ROLE_CHOICES = ( ('SHIPPER', _('Shipper (Need Goods Moved)')), @@ -123,8 +135,15 @@ class Shipment(models.Model): shipper = models.ForeignKey(User, on_delete=models.CASCADE, related_name='shipments') description = models.TextField(_('Goods Description')) weight = models.CharField(_('Weight/Volume'), max_length=100) - origin = models.CharField(_('Origin'), max_length=255) - destination = models.CharField(_('Destination'), max_length=255) + + origin_country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, related_name='shipments_origin') + origin_city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, related_name='shipments_origin') + destination_country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, related_name='shipments_destination') + destination_city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, related_name='shipments_destination') + + origin = models.CharField(_('Origin (Legacy)'), max_length=255, blank=True) + destination = models.CharField(_('Destination (Legacy)'), max_length=255, blank=True) + delivery_date = models.DateField(_('Requested Delivery Date')) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='OPEN') @@ -133,7 +152,19 @@ class Shipment(models.Model): created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"{self.origin} to {self.destination} - {self.status}" + return f"{self.display_origin} to {self.display_destination} - {self.status}" + + @property + def display_origin(self): + if self.origin_city and self.origin_country: + return f"{self.origin_city.name}, {self.origin_country.name}" + return self.origin + + @property + def display_destination(self): + if self.destination_city and self.destination_country: + return f"{self.destination_city.name}, {self.destination_country.name}" + return self.destination class Bid(models.Model): STATUS_CHOICES = ( @@ -202,4 +233,4 @@ def sync_user_groups(sender, instance, **kwargs): instance.user.groups.remove(*other_groups) # Add user to the correct group - instance.user.groups.add(group) + instance.user.groups.add(group) \ No newline at end of file diff --git a/core/templates/core/place_bid.html b/core/templates/core/place_bid.html index 6abaa7d..b3805c3 100644 --- a/core/templates/core/place_bid.html +++ b/core/templates/core/place_bid.html @@ -4,7 +4,7 @@ {% block content %}
{% trans "Enter shipment details to receive bids or send as an offer." %}
- {% if form.errors %} -{{ shipment.description }}
-{{ shipment.description }}
+| {% trans "Truck" %} | {% trans "Amount" %} | @@ -48,8 +48,11 @@||||||
|---|---|---|---|---|---|---|---|
| {{ bid.truck.display_truck_type }} ({{ bid.truck.plate_no }}) | -${{ bid.amount }} | +
+ {{ bid.truck.display_truck_type }}
+ {{ bid.truck.plate_no }}
+ |
+ ${{ bid.amount }} | {{ bid.get_status_display }} @@ -68,7 +71,10 @@ | |||
| {% trans "No offers for this shipment." %} | +
+
+ {% trans "No offers for this shipment yet." %} + |
- {{ bid.shipment.origin }} + {{ bid.shipment.display_origin }} - {{ bid.shipment.destination }} + {{ bid.shipment.display_destination }} | ${{ bid.amount }} | {{ bid.created_at|date:"d M Y" }} | @@ -93,10 +98,9 @@ {% for shipment in shipments %} - {% if shipment.status != 'OPEN' or not shipment.bids.exists %}|||
| {{ shipment.description|truncatechars:30 }} | -{{ shipment.origin }} {{ shipment.destination }} | +{{ shipment.display_origin }} {{ shipment.display_destination }} | {{ shipment.delivery_date }} | @@ -114,7 +118,6 @@ {% trans "Track" %} | |||
| {% trans "No active shipments." %} | @@ -126,4 +129,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/core/templates/core/truck_owner_dashboard.html b/core/templates/core/truck_owner_dashboard.html index 8c1013d..850f182 100644 --- a/core/templates/core/truck_owner_dashboard.html +++ b/core/templates/core/truck_owner_dashboard.html @@ -38,7 +38,7 @@ {% for bid in bids %}|||||||
|
- {{ bid.shipment.origin }} {{ bid.shipment.destination }}
+ {{ bid.shipment.display_origin }} {{ bid.shipment.display_destination }}
{{ bid.shipment.delivery_date }}
|
{{ bid.truck.plate_no }} | @@ -122,4 +122,4 @@ {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/core/views.py b/core/views.py index ee86512..435c180 100644 --- a/core/views.py +++ b/core/views.py @@ -2,7 +2,7 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth import login, authenticate, logout from django.utils import timezone -from .models import Profile, Truck, Shipment, Bid, Message, OTPCode +from .models import Profile, Truck, Shipment, Bid, Message, OTPCode, Country, City from .forms import TruckForm, ShipmentForm, BidForm, UserRegistrationForm, OTPVerifyForm, ShipperOfferForm from django.contrib import messages from django.utils.translation import gettext as _ @@ -77,7 +77,8 @@ def verify_otp_registration(request): profile.save() login(request, user) - del request.session['registration_data'] + if 'registration_data' in request.session: + del request.session['registration_data'] messages.success(request, _("Registration successful. Welcome!")) return redirect('dashboard') else: @@ -137,7 +138,8 @@ def verify_otp_login(request): otp_record.save() login(request, user) - del request.session['pre_otp_user_id'] + if 'pre_otp_user_id' in request.session: + del request.session['pre_otp_user_id'] messages.success(request, _("Logged in successfully!")) return redirect('dashboard') else: @@ -253,7 +255,7 @@ def suspend_truck(request, truck_id): @login_required def post_shipment(request): - """Note: This is now largely redundant but kept for compatibility or direct posting.""" + """Note: This is used as the 'Add A Bid' / 'Add Post' action for Shippers.""" if request.user.profile.role != 'SHIPPER': return redirect('dashboard') @@ -263,7 +265,7 @@ def post_shipment(request): shipment = form.save(commit=False) shipment.shipper = request.user shipment.save() - messages.success(request, _("Shipment posted successfully!")) + messages.success(request, _("Shipment posted successfully! It is now open for bids or you can browse trucks to send it as an offer.")) return redirect('dashboard') else: messages.error(request, _("Please correct the errors in the form.")) @@ -296,8 +298,10 @@ def place_bid(request, truck_id): shipper=request.user, description=form.cleaned_data['description'], weight=form.cleaned_data['weight'], - origin=form.cleaned_data['origin'], - destination=form.cleaned_data['destination'], + origin_country=form.cleaned_data['origin_country'], + origin_city=form.cleaned_data['origin_city'], + destination_country=form.cleaned_data['destination_country'], + destination_city=form.cleaned_data['destination_city'], delivery_date=form.cleaned_data['delivery_date'], status='OPEN' ) @@ -314,7 +318,7 @@ def place_bid(request, truck_id): # Notify Truck Owner via WhatsApp owner_phone = getattr(truck.owner.profile, 'full_phone_number', None) if owner_phone: - msg = f"New offer received for your truck ({truck.plate_no})! Route: {shipment.origin} to {shipment.destination}. Amount: {bid.amount}" + msg = f"New offer received for your truck ({truck.plate_no})! Route: {shipment.display_origin} to {shipment.display_destination}. Amount: {bid.amount}" send_whatsapp_message(owner_phone, msg) messages.success(request, _("Offer sent successfully!")) @@ -357,7 +361,7 @@ def accept_bid(request, bid_id): # Notify Shipper via WhatsApp shipper_phone = getattr(bid.shipment.shipper.profile, 'full_phone_number', None) if shipper_phone: - msg = f"Your offer for truck {bid.truck.plate_no} ({bid.shipment.origin} to {bid.shipment.destination}) has been accepted!" + msg = f"Your offer for truck {bid.truck.plate_no} ({bid.shipment.display_origin} to {bid.shipment.display_destination}) has been accepted!" send_whatsapp_message(shipper_phone, msg) messages.success(request, _("Offer accepted! Shipment is now in progress.")) @@ -373,4 +377,4 @@ def reject_bid(request, bid_id): bid.status = 'REJECTED' bid.save() messages.info(request, _("Offer rejected.")) - return redirect('dashboard') + return redirect('dashboard') \ No newline at end of file||||||