editing registeration

This commit is contained in:
Flatlogic Bot 2026-01-28 00:24:24 +00:00
parent 5f2219fc0f
commit 9d123496e2
13 changed files with 305 additions and 119 deletions

View File

@ -1,7 +1,7 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from .models import Profile, Parcel, Country, Governate, City, PlatformProfile, Testimonial
from .models import Profile, Parcel, Country, Governate, City, PlatformProfile, Testimonial, DriverRating
from django.utils.translation import gettext_lazy as _
from django.urls import path, reverse
from django.shortcuts import render
@ -18,9 +18,25 @@ class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = _('Profiles')
fieldsets = (
(None, {'fields': ('role', 'is_approved', 'phone_number', 'profile_picture', 'address')}),
(_('Driver Info'), {'fields': ('license_front_image', 'license_back_image', 'car_plate_number'), 'classes': ('collapse',)}),
(_('Location'), {'fields': ('country', 'governate', 'city'), 'classes': ('collapse',)}),
)
class CustomUserAdmin(UserAdmin):
inlines = (ProfileInline,)
list_display = ('username', 'email', 'get_role', 'get_approval_status', 'is_active', 'is_staff')
list_filter = ('is_active', 'is_staff', 'profile__role', 'profile__is_approved')
def get_role(self, obj):
return obj.profile.get_role_display()
get_role.short_description = _('Role')
def get_approval_status(self, obj):
return obj.profile.is_approved
get_approval_status.short_description = _('Approved')
get_approval_status.boolean = True
def get_inline_instances(self, request, obj=None):
if not obj:
@ -171,6 +187,4 @@ admin.site.register(Governate)
admin.site.register(City)
admin.site.register(PlatformProfile, PlatformProfileAdmin)
admin.site.register(Testimonial, TestimonialAdmin)
# Set custom admin index template - using default 'admin/index.html' which we have overridden
# admin.site.index_template = 'admin/dashboard.html'
admin.site.register(DriverRating)

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-01-28 00:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0019_profile_car_plate_number_profile_license_back_image_and_more'),
]
operations = [
migrations.AddField(
model_name='profile',
name='is_approved',
field=models.BooleanField(default=False, help_text='Designates whether this user is approved to use the platform (mainly for drivers).', verbose_name='Approved'),
),
]

View File

@ -82,6 +82,9 @@ class Profile(models.Model):
governate = models.ForeignKey(Governate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Governate'))
city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('City'))
# Approval Status
is_approved = models.BooleanField(_('Approved'), default=False, help_text=_("Designates whether this user is approved to use the platform (mainly for drivers)."))
def __str__(self):
return f"{self.user.username} - {self.get_role_display()}"
@ -275,4 +278,4 @@ class DriverRating(models.Model):
class Meta:
verbose_name = _('Driver Rating')
verbose_name_plural = _('Driver Ratings')
verbose_name_plural = _('Driver Ratings')

View File

@ -10,6 +10,16 @@
</a>
</div>
{% if not is_approved %}
<div class="alert alert-warning shadow-sm border-0 d-flex align-items-center mb-4" role="alert" style="border-radius: 12px;">
<i class="bi bi-hourglass-split fs-3 me-3 text-warning"></i>
<div>
<h5 class="alert-heading mb-1">{% trans "Account Pending Approval" %}</h5>
<p class="mb-0 text-muted">{% trans "Your driver account is currently under review by our administrators. You will be notified once your documents are verified and your account is approved to accept shipments." %}</p>
</div>
</div>
{% endif %}
<ul class="nav nav-pills mb-4" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-available-tab" data-bs-toggle="pill" data-bs-target="#pills-available" type="button" role="tab">{% trans "Available Shipments" %}</button>
@ -29,132 +39,143 @@
<!-- Available Shipments -->
<div class="tab-pane fade show active" id="pills-available" role="tabpanel">
<!-- Toolbar -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="mb-0 text-muted">{% trans "Browse Shipments" %}</h5>
<div class="btn-group" role="group" aria-label="View toggle">
<button type="button" class="btn btn-outline-primary active" id="btn-grid-view">
<i class="bi bi-grid"></i> {% trans "Grid" %}
</button>
<button type="button" class="btn btn-outline-primary" id="btn-list-view">
<i class="bi bi-list"></i> {% trans "List" %}
</button>
</div>
</div>
{% if available_parcels %}
<!-- Grid View -->
<div id="view-grid" class="row g-4">
{% for parcel in available_parcels %}
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100" style="border-radius: 15px;">
<div class="card-body">
<h5 class="card-title">{{ parcel.description|truncatechars:30 }}</h5>
<p class="card-text mb-1 small"><strong>{% trans "Pickup" %}:</strong> {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}</p>
<p class="card-text mb-3 small"><strong>{% trans "Delivery" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}</p>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted small"><strong>{% trans "Weight" %}:</strong> {{ parcel.weight }} kg</span>
</div>
<!-- Bid/Price Highlight -->
<div class="bg-light p-2 rounded text-center mb-3 border border-primary border-opacity-25">
<small class="text-uppercase text-muted fw-bold" style="font-size: 0.7rem;">{% trans "Shipper's Offer (Bid)" %}</small>
<div class="text-primary fw-bold fs-4">{{ parcel.price }} <span class="fs-6">OMR</span></div>
</div>
<form action="{% url 'accept_parcel' parcel.id %}" method="POST">
{% csrf_token %}
<button type="submit" class="btn btn-masarx-primary w-100">{% trans "Accept Shipment" %}</button>
</form>
</div>
</div>
{% if is_approved %}
<!-- Toolbar -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="mb-0 text-muted">{% trans "Browse Shipments" %}</h5>
<div class="btn-group" role="group" aria-label="View toggle">
<button type="button" class="btn btn-outline-primary active" id="btn-grid-view">
<i class="bi bi-grid"></i> {% trans "Grid" %}
</button>
<button type="button" class="btn btn-outline-primary" id="btn-list-view">
<i class="bi bi-list"></i> {% trans "List" %}
</button>
</div>
{% endfor %}
</div>
<!-- List View (Initially Hidden) -->
<div id="view-list" class="d-none">
<div class="d-flex flex-column gap-3">
{% if available_parcels %}
<!-- Grid View -->
<div id="view-grid" class="row g-4">
{% for parcel in available_parcels %}
<div class="card border-0 shadow-sm rounded-3">
<div class="card-body p-3">
<div class="row align-items-center">
<div class="col-md-8 mb-3 mb-md-0">
<h5 class="card-title mb-2">{{ parcel.description|truncatechars:80 }}</h5>
<div class="d-flex flex-wrap gap-3 text-muted small">
<span class="d-flex align-items-center gap-1">
<i class="bi bi-geo-alt-fill text-primary"></i>
<strong>{% trans "From" %}:</strong> {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-geo-alt-fill text-danger"></i>
<strong>{% trans "To" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-box-seam"></i> {{ parcel.weight }} kg
</span>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100" style="border-radius: 15px;">
<div class="card-body">
<h5 class="card-title">{{ parcel.description|truncatechars:30 }}</h5>
<p class="card-text mb-1 small"><strong>{% trans "Pickup" %}:</strong> {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}</p>
<p class="card-text mb-3 small"><strong>{% trans "Delivery" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}</p>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted small"><strong>{% trans "Weight" %}:</strong> {{ parcel.weight }} kg</span>
</div>
<div class="col-md-4 text-md-end">
<div class="d-flex flex-column align-items-md-end gap-2">
<div class="text-primary fw-bold fs-5">{{ parcel.price }} OMR</div>
<form action="{% url 'accept_parcel' parcel.id %}" method="POST" class="w-100 w-md-auto">
{% csrf_token %}
<button type="submit" class="btn btn-masarx-primary btn-sm w-100">{% trans "Accept" %}</button>
</form>
</div>
<!-- Bid/Price Highlight -->
<div class="bg-light p-2 rounded text-center mb-3 border border-primary border-opacity-25">
<small class="text-uppercase text-muted fw-bold" style="font-size: 0.7rem;">{% trans "Shipper's Offer (Bid)" %}</small>
<div class="text-primary fw-bold fs-4">{{ parcel.price }} <span class="fs-6">OMR</span></div>
</div>
<form action="{% url 'accept_parcel' parcel.id %}" method="POST">
{% csrf_token %}
<button type="submit" class="btn btn-masarx-primary w-100">{% trans "Accept Shipment" %}</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Pagination -->
{% if available_parcels.has_other_pages %}
<nav aria-label="Page navigation" class="mt-5">
<ul class="pagination justify-content-center">
{% if available_parcels.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ available_parcels.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&laquo;</span>
</li>
{% endif %}
<!-- List View (Initially Hidden) -->
<div id="view-list" class="d-none">
<div class="d-flex flex-column gap-3">
{% for parcel in available_parcels %}
<div class="card border-0 shadow-sm rounded-3">
<div class="card-body p-3">
<div class="row align-items-center">
<div class="col-md-8 mb-3 mb-md-0">
<h5 class="card-title mb-2">{{ parcel.description|truncatechars:80 }}</h5>
<div class="d-flex flex-wrap gap-3 text-muted small">
<span class="d-flex align-items-center gap-1">
<i class="bi bi-geo-alt-fill text-primary"></i>
<strong>{% trans "From" %}:</strong> {{ parcel.pickup_governate.name }} / {{ parcel.pickup_city.name }}
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-geo-alt-fill text-danger"></i>
<strong>{% trans "To" %}:</strong> {{ parcel.delivery_governate.name }} / {{ parcel.delivery_city.name }}
</span>
<span class="d-flex align-items-center gap-1">
<i class="bi bi-box-seam"></i> {{ parcel.weight }} kg
</span>
</div>
</div>
<div class="col-md-4 text-md-end">
<div class="d-flex flex-column align-items-md-end gap-2">
<div class="text-primary fw-bold fs-5">{{ parcel.price }} OMR</div>
<form action="{% url 'accept_parcel' parcel.id %}" method="POST" class="w-100 w-md-auto">
{% csrf_token %}
<button type="submit" class="btn btn-masarx-primary btn-sm w-100">{% trans "Accept" %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% for i in available_parcels.paginator.page_range %}
{% if available_parcels.number == i %}
<li class="page-item active"><span class="page-link">{{ i }}</span></li>
<!-- Pagination -->
{% if available_parcels.has_other_pages %}
<nav aria-label="Page navigation" class="mt-5">
<ul class="pagination justify-content-center">
{% if available_parcels.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ available_parcels.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li>
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&laquo;</span>
</li>
{% endif %}
{% endfor %}
{% if available_parcels.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ available_parcels.next_page_number }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&raquo;</span>
</li>
{% endif %}
</ul>
</nav>
{% for i in available_parcels.paginator.page_range %}
{% if available_parcels.number == i %}
<li class="page-item active"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% if available_parcels.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ available_parcels.next_page_number }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&raquo;</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<p class="text-center py-5">{% trans "No shipments available at the moment." %}</p>
{% endif %}
{% else %}
<p class="text-center py-5">{% trans "No shipments available at the moment." %}</p>
<!-- Not Approved State for Tab Pane -->
<div class="text-center py-5">
<i class="bi bi-lock-fill display-4 text-muted mb-3"></i>
<h4 class="text-muted">{% trans "Access Restricted" %}</h4>
<p class="text-muted">{% trans "Please wait for administrator approval to view available shipments." %}</p>
</div>
{% endif %}
</div>
<!-- My Deliveries (Active) -->
@ -318,4 +339,4 @@
if (listViewBtn) listViewBtn.addEventListener('click', () => setView('list'));
});
</script>
{% endblock %}
{% endblock %}

View File

@ -173,6 +173,20 @@
<div class="value">
<strong>{{ parcel.shipper.first_name }} {{ parcel.shipper.last_name }}</strong><br>
{{ parcel.shipper.profile.address }}<br>
{% if parcel.shipper.profile.city %}
{{ parcel.shipper.profile.city.name_en }} / <span class="ar-text">{{ parcel.shipper.profile.city.name_ar }}</span><br>
{% endif %}
{% if parcel.shipper.profile.governate %}
{{ parcel.shipper.profile.governate.name_en }} / <span class="ar-text">{{ parcel.shipper.profile.governate.name_ar }}</span><br>
{% endif %}
{% if parcel.shipper.profile.country %}
{{ parcel.shipper.profile.country.name_en }} / <span class="ar-text">{{ parcel.shipper.profile.country.name_ar }}</span><br>
{% endif %}
<br>
{{ parcel.shipper.email }}<br>
{{ parcel.shipper.profile.phone_number }}
</div>
@ -195,7 +209,7 @@
<span class="ar ar-text">الوزن</span>
</th>
<th class="text-right">
Amount (OMR)
Amount
<span class="ar ar-text">المبلغ</span>
</th>
</tr>
@ -208,7 +222,7 @@
</td>
<td>{{ parcel.tracking_number }}</td>
<td>{{ parcel.weight }} kg</td>
<td class="text-right">{{ parcel.price }}</td>
<td class="text-right">{{ parcel.price }} OMR / <span class="ar-text">ر.ع.</span></td>
</tr>
</tbody>
<tfoot>
@ -216,7 +230,7 @@
<td colspan="3" class="text-right">
TOTAL / الإجمالي
</td>
<td class="text-right">{{ parcel.price }} OMR</td>
<td class="text-right">{{ parcel.price }} OMR / <span class="ar-text">ر.ع.</span></td>
</tr>
</tfoot>
</table>

View File

@ -208,7 +208,7 @@
</div>
<div class="section">
<h3>COD / الدفع</h3>
<p class="bold">{{ parcel.price }} OMR</p>
<p class="bold">{{ parcel.price }} OMR / <span class="ar-text">ر.ع.</span></p>
</div>
</div>

View File

@ -88,6 +88,11 @@ def register_shipper(request):
user.is_active = False
user.save()
# Auto-approve Shippers
if hasattr(user, 'profile'):
user.profile.is_approved = True
user.profile.save()
# Generate OTP
code = ''.join(random.choices(string.digits, k=6))
OTPVerification.objects.create(user=user, code=code, purpose='registration')
@ -124,6 +129,12 @@ def register_driver(request):
user = form.save(commit=True)
user.is_active = False
user.save()
# Drivers are NOT approved by default (model default is False)
# We can rely on the default, but explicit is fine too if we want to be sure
if hasattr(user, 'profile'):
user.profile.is_approved = False
user.profile.save()
# Generate OTP
code = ''.join(random.choices(string.digits, k=6))
@ -236,6 +247,12 @@ def dashboard(request):
else:
available_parcels_list = Parcel.objects.filter(status='pending').order_by('-created_at')
# Check Approval Status
if not profile.is_approved:
messages.warning(request, _("Your account is pending approval. You cannot accept shipments yet."))
# Empty list if not approved
available_parcels_list = Parcel.objects.none()
# Pagination for Available Shipments
page = request.GET.get('page', 1)
paginator = Paginator(available_parcels_list, 9) # Show 9 parcels per page
@ -260,7 +277,8 @@ def dashboard(request):
'available_parcels': available_parcels,
'my_parcels': my_parcels,
'completed_parcels': completed_parcels,
'cancelled_parcels': cancelled_parcels
'cancelled_parcels': cancelled_parcels,
'is_approved': profile.is_approved # Pass to template
})
@login_required
@ -292,6 +310,11 @@ def accept_parcel(request, parcel_id):
if profile.role != 'car_owner':
messages.error(request, _("Only car owners can accept shipments."))
return redirect('dashboard')
# Check Approval
if not profile.is_approved:
messages.error(request, _("Your account is pending approval. You cannot accept shipments yet."))
return redirect('dashboard')
platform_profile = PlatformProfile.objects.first()
payments_enabled = platform_profile.enable_payment if platform_profile else True
@ -877,6 +900,10 @@ def update_parcel_status_ajax(request):
if payments_enabled and parcel.payment_status != 'paid':
return JsonResponse({'success': False, 'error': _('Payment pending')})
# Check Approval for Driver via AJAX
if not request.user.profile.is_approved:
return JsonResponse({'success': False, 'error': _('Account pending approval')})
parcel.carrier = request.user
parcel.status = 'picked_up' # Or 'assigned'? Logic says 'picked_up' in accept_parcel
parcel.save()
@ -928,4 +955,4 @@ def cancel_parcel(request, parcel_id):
parcel.save()
messages.success(request, _("Shipment cancelled successfully."))
return redirect('dashboard')
return redirect('dashboard')

Binary file not shown.

View File

@ -1393,3 +1393,92 @@ msgstr "رقم التتبع"
msgid "Cancelled Shipments"
msgstr "الشحنات الملغاة"
msgid "Grid"
msgstr "شبكة"
msgid "List"
msgstr "قائمة"
msgid "Scan Parcel"
msgstr "مسح الطرد"
msgid "Account Pending Approval"
msgstr "الحساب قيد الانتظار"
msgid ""
"Your driver account is currently under review by our administrators. You "
"will be notified once your documents are verified and your account is "
"approved to accept shipments."
msgstr ""
"حساب السائق الخاص بك قيد المراجعة حالياً من قبل المشرفين. سيتم إشعارك "
"بمجرد التحقق من مستنداتك والموافقة على حسابك لقبول الشحنات."
msgid "Welcome to masarX"
msgstr "مرحباً بك في مسارX"
msgid "Choose how you want to join us today."
msgstr "اختر كيف تريد الانضمام إلينا اليوم."
msgid "I am a Shipper"
msgstr "أنا شاحن"
msgid "I want to send parcels and track my shipments easily."
msgstr "أريد إرسال الطرود وتتبع شحناتي بسهولة."
msgid "Register as Shipper"
msgstr "التسجيل كشاحن"
msgid "I am a Car Owner"
msgstr "أنا صاحب سيارة"
msgid "I want to deliver parcels and earn money on my own schedule."
msgstr "أريد توصيل الطرود وكسب المال وفق جدولي الخاص."
msgid "Register as Car Owner"
msgstr "التسجيل كصاحب سيارة"
msgid "Car Owner Registration"
msgstr "تسجيل صاحب سيارة"
msgid "Take photo with Webcam"
msgstr "التقاط صورة بالكاميرا"
msgid ""
"Please provide clear photos of your license (front and back) and car plate "
"number for verification."
msgstr ""
"يرجى تقديم صور واضحة لرخصتك (الأمام والخلف) ورقم لوحة السيارة للتحقق."
msgid "Submit Application"
msgstr "تقديم الطلب"
msgid "Take Photo"
msgstr "التقاط صورة"
msgid "Could not access webcam. Please check permissions."
msgstr "تعذر الوصول إلى الكاميرا. يرجى التحقق من الأذونات."
msgid "Photo captured!"
msgstr "تم التقاط الصورة!"
msgid "Back"
msgstr "عودة"
msgid "License Front Image"
msgstr "صورة الرخصة (الأمام)"
msgid "License Back Image"
msgstr "صورة الرخصة (الخلف)"
msgid "Car Plate Number"
msgstr "رقم لوحة السيارة"
msgid "Approved"
msgstr "موافق عليه"
msgid ""
"Designates whether this user is approved to use the platform (mainly for "
"drivers)."
msgstr "يحدد ما إذا كان هذا المستخدم معتمداً لاستخدام المنصة (بشكل رئيسي للسائقين)."