editing registeration
This commit is contained in:
parent
5f2219fc0f
commit
9d123496e2
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.contrib.auth.models import User
|
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.utils.translation import gettext_lazy as _
|
||||||
from django.urls import path, reverse
|
from django.urls import path, reverse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
@ -18,9 +18,25 @@ class ProfileInline(admin.StackedInline):
|
|||||||
model = Profile
|
model = Profile
|
||||||
can_delete = False
|
can_delete = False
|
||||||
verbose_name_plural = _('Profiles')
|
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):
|
class CustomUserAdmin(UserAdmin):
|
||||||
inlines = (ProfileInline,)
|
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):
|
def get_inline_instances(self, request, obj=None):
|
||||||
if not obj:
|
if not obj:
|
||||||
@ -171,6 +187,4 @@ admin.site.register(Governate)
|
|||||||
admin.site.register(City)
|
admin.site.register(City)
|
||||||
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
admin.site.register(PlatformProfile, PlatformProfileAdmin)
|
||||||
admin.site.register(Testimonial, TestimonialAdmin)
|
admin.site.register(Testimonial, TestimonialAdmin)
|
||||||
|
admin.site.register(DriverRating)
|
||||||
# Set custom admin index template - using default 'admin/index.html' which we have overridden
|
|
||||||
# admin.site.index_template = 'admin/dashboard.html'
|
|
||||||
18
core/migrations/0020_profile_is_approved.py
Normal file
18
core/migrations/0020_profile_is_approved.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -82,6 +82,9 @@ class Profile(models.Model):
|
|||||||
governate = models.ForeignKey(Governate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Governate'))
|
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'))
|
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):
|
def __str__(self):
|
||||||
return f"{self.user.username} - {self.get_role_display()}"
|
return f"{self.user.username} - {self.get_role_display()}"
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,16 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</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">
|
<ul class="nav nav-pills mb-4" id="pills-tab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<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>
|
<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,6 +39,7 @@
|
|||||||
<!-- Available Shipments -->
|
<!-- Available Shipments -->
|
||||||
<div class="tab-pane fade show active" id="pills-available" role="tabpanel">
|
<div class="tab-pane fade show active" id="pills-available" role="tabpanel">
|
||||||
|
|
||||||
|
{% if is_approved %}
|
||||||
<!-- Toolbar -->
|
<!-- Toolbar -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h5 class="mb-0 text-muted">{% trans "Browse Shipments" %}</h5>
|
<h5 class="mb-0 text-muted">{% trans "Browse Shipments" %}</h5>
|
||||||
@ -155,6 +166,16 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-center py-5">{% trans "No shipments available at the moment." %}</p>
|
<p class="text-center py-5">{% trans "No shipments available at the moment." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<!-- 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>
|
</div>
|
||||||
|
|
||||||
<!-- My Deliveries (Active) -->
|
<!-- My Deliveries (Active) -->
|
||||||
|
|||||||
@ -173,6 +173,20 @@
|
|||||||
<div class="value">
|
<div class="value">
|
||||||
<strong>{{ parcel.shipper.first_name }} {{ parcel.shipper.last_name }}</strong><br>
|
<strong>{{ parcel.shipper.first_name }} {{ parcel.shipper.last_name }}</strong><br>
|
||||||
{{ parcel.shipper.profile.address }}<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.email }}<br>
|
||||||
{{ parcel.shipper.profile.phone_number }}
|
{{ parcel.shipper.profile.phone_number }}
|
||||||
</div>
|
</div>
|
||||||
@ -195,7 +209,7 @@
|
|||||||
<span class="ar ar-text">الوزن</span>
|
<span class="ar ar-text">الوزن</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-right">
|
<th class="text-right">
|
||||||
Amount (OMR)
|
Amount
|
||||||
<span class="ar ar-text">المبلغ</span>
|
<span class="ar ar-text">المبلغ</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -208,7 +222,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{ parcel.tracking_number }}</td>
|
<td>{{ parcel.tracking_number }}</td>
|
||||||
<td>{{ parcel.weight }} kg</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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
@ -216,7 +230,7 @@
|
|||||||
<td colspan="3" class="text-right">
|
<td colspan="3" class="text-right">
|
||||||
TOTAL / الإجمالي
|
TOTAL / الإجمالي
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">{{ parcel.price }} OMR</td>
|
<td class="text-right">{{ parcel.price }} OMR / <span class="ar-text">ر.ع.</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -208,7 +208,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>COD / الدفع</h3>
|
<h3>COD / الدفع</h3>
|
||||||
<p class="bold">{{ parcel.price }} OMR</p>
|
<p class="bold">{{ parcel.price }} OMR / <span class="ar-text">ر.ع.</span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -88,6 +88,11 @@ def register_shipper(request):
|
|||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
# Auto-approve Shippers
|
||||||
|
if hasattr(user, 'profile'):
|
||||||
|
user.profile.is_approved = True
|
||||||
|
user.profile.save()
|
||||||
|
|
||||||
# Generate OTP
|
# Generate OTP
|
||||||
code = ''.join(random.choices(string.digits, k=6))
|
code = ''.join(random.choices(string.digits, k=6))
|
||||||
OTPVerification.objects.create(user=user, code=code, purpose='registration')
|
OTPVerification.objects.create(user=user, code=code, purpose='registration')
|
||||||
@ -125,6 +130,12 @@ def register_driver(request):
|
|||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.save()
|
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
|
# Generate OTP
|
||||||
code = ''.join(random.choices(string.digits, k=6))
|
code = ''.join(random.choices(string.digits, k=6))
|
||||||
OTPVerification.objects.create(user=user, code=code, purpose='registration')
|
OTPVerification.objects.create(user=user, code=code, purpose='registration')
|
||||||
@ -236,6 +247,12 @@ def dashboard(request):
|
|||||||
else:
|
else:
|
||||||
available_parcels_list = Parcel.objects.filter(status='pending').order_by('-created_at')
|
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
|
# Pagination for Available Shipments
|
||||||
page = request.GET.get('page', 1)
|
page = request.GET.get('page', 1)
|
||||||
paginator = Paginator(available_parcels_list, 9) # Show 9 parcels per page
|
paginator = Paginator(available_parcels_list, 9) # Show 9 parcels per page
|
||||||
@ -260,7 +277,8 @@ def dashboard(request):
|
|||||||
'available_parcels': available_parcels,
|
'available_parcels': available_parcels,
|
||||||
'my_parcels': my_parcels,
|
'my_parcels': my_parcels,
|
||||||
'completed_parcels': completed_parcels,
|
'completed_parcels': completed_parcels,
|
||||||
'cancelled_parcels': cancelled_parcels
|
'cancelled_parcels': cancelled_parcels,
|
||||||
|
'is_approved': profile.is_approved # Pass to template
|
||||||
})
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -293,6 +311,11 @@ def accept_parcel(request, parcel_id):
|
|||||||
messages.error(request, _("Only car owners can accept shipments."))
|
messages.error(request, _("Only car owners can accept shipments."))
|
||||||
return redirect('dashboard')
|
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()
|
platform_profile = PlatformProfile.objects.first()
|
||||||
payments_enabled = platform_profile.enable_payment if platform_profile else True
|
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':
|
if payments_enabled and parcel.payment_status != 'paid':
|
||||||
return JsonResponse({'success': False, 'error': _('Payment pending')})
|
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.carrier = request.user
|
||||||
parcel.status = 'picked_up' # Or 'assigned'? Logic says 'picked_up' in accept_parcel
|
parcel.status = 'picked_up' # Or 'assigned'? Logic says 'picked_up' in accept_parcel
|
||||||
parcel.save()
|
parcel.save()
|
||||||
|
|||||||
Binary file not shown.
@ -1393,3 +1393,92 @@ msgstr "رقم التتبع"
|
|||||||
|
|
||||||
msgid "Cancelled Shipments"
|
msgid "Cancelled Shipments"
|
||||||
msgstr "الشحنات الملغاة"
|
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 "يحدد ما إذا كان هذا المستخدم معتمداً لاستخدام المنصة (بشكل رئيسي للسائقين)."
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user