adding variviction
This commit is contained in:
parent
241aa3abd2
commit
b77b8b1619
Binary file not shown.
@ -211,5 +211,6 @@ WHATSAPP_ENABLED = os.getenv("WHATSAPP_ENABLED", "true").lower() == "true"
|
|||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
LOGIN_URL = 'login'
|
||||||
LOGIN_REDIRECT_URL = 'dashboard'
|
LOGIN_REDIRECT_URL = 'dashboard'
|
||||||
LOGOUT_REDIRECT_URL = 'index'
|
LOGOUT_REDIRECT_URL = 'index'
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -15,6 +15,7 @@ class UserRegistrationForm(forms.ModelForm):
|
|||||||
password_confirm = forms.CharField(widget=forms.PasswordInput, label=_("Confirm Password"))
|
password_confirm = forms.CharField(widget=forms.PasswordInput, label=_("Confirm Password"))
|
||||||
role = forms.ChoiceField(choices=Profile.ROLE_CHOICES, label=_("Register as"))
|
role = forms.ChoiceField(choices=Profile.ROLE_CHOICES, label=_("Register as"))
|
||||||
phone_number = forms.CharField(max_length=20, label=_("Phone Number"))
|
phone_number = forms.CharField(max_length=20, label=_("Phone Number"))
|
||||||
|
verification_method = forms.ChoiceField(choices=[('email', _('Email')), ('whatsapp', _('WhatsApp'))], label=_("Verify via"), widget=forms.RadioSelect, initial='email')
|
||||||
|
|
||||||
country = forms.ModelChoiceField(queryset=Country.objects.all(), required=False, label=_("Country"))
|
country = forms.ModelChoiceField(queryset=Country.objects.all(), required=False, label=_("Country"))
|
||||||
governate = forms.ModelChoiceField(queryset=Governate.objects.none(), required=False, label=_("Governate"))
|
governate = forms.ModelChoiceField(queryset=Governate.objects.none(), required=False, label=_("Governate"))
|
||||||
|
|||||||
18
core/migrations/0014_alter_otpverification_purpose.py
Normal file
18
core/migrations/0014_alter_otpverification_purpose.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-25 15:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0013_platformprofile_enable_payment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='otpverification',
|
||||||
|
name='purpose',
|
||||||
|
field=models.CharField(choices=[('profile_update', 'Profile Update'), ('password_reset', 'Password Reset'), ('registration', 'Registration')], default='profile_update', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -198,6 +198,7 @@ class OTPVerification(models.Model):
|
|||||||
PURPOSE_CHOICES = (
|
PURPOSE_CHOICES = (
|
||||||
('profile_update', _('Profile Update')),
|
('profile_update', _('Profile Update')),
|
||||||
('password_reset', _('Password Reset')),
|
('password_reset', _('Password Reset')),
|
||||||
|
('registration', _('Registration')),
|
||||||
)
|
)
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
code = models.CharField(max_length=6)
|
code = models.CharField(max_length=6)
|
||||||
|
|||||||
@ -8,6 +8,13 @@
|
|||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
<!-- Back to Profile -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'profile' %}" class="btn btn-link text-decoration-none text-muted ps-0">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Profile" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm border-0">
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card-body p-5">
|
<div class="card-body p-5">
|
||||||
<h2 class="fw-bold mb-4 text-center">{% trans "Edit Profile" %}</h2>
|
<h2 class="fw-bold mb-4 text-center">{% trans "Edit Profile" %}</h2>
|
||||||
@ -91,4 +98,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -8,6 +8,13 @@
|
|||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
|
<!-- Back to Home -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'index' %}" class="btn btn-link text-decoration-none text-muted ps-0">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Home" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm border-0">
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card-body p-5">
|
<div class="card-body p-5">
|
||||||
<h2 class="fw-bold mb-4 text-center">{% trans "Login to masarX" %}</h2>
|
<h2 class="fw-bold mb-4 text-center">{% trans "Login to masarX" %}</h2>
|
||||||
@ -47,4 +54,4 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -7,24 +7,33 @@
|
|||||||
<section class="py-5 bg-light" style="min-height: 80vh;">
|
<section class="py-5 bg-light" style="min-height: 80vh;">
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-10 col-lg-8">
|
||||||
|
<!-- Back to Home -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'index' %}" class="btn btn-link text-decoration-none text-muted ps-0">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Home" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm border-0">
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card-body p-5">
|
<div class="card-body p-5">
|
||||||
<h2 class="fw-bold mb-4 text-center">{% trans "Join masarX" %}</h2>
|
<h2 class="fw-bold mb-4 text-center">{% trans "Join masarX" %}</h2>
|
||||||
<form method="post" id="registrationForm">
|
<form method="post" id="registrationForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in form %}
|
<div class="row">
|
||||||
<div class="mb-3">
|
{% for field in form %}
|
||||||
<label class="form-label fw-semibold" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
<div class="col-md-6 mb-3">
|
||||||
{{ field }}
|
<label class="form-label fw-semibold" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||||
{% if field.help_text %}
|
{{ field }}
|
||||||
<div class="form-text small">{{ field.help_text }}</div>
|
{% if field.help_text %}
|
||||||
{% endif %}
|
<div class="form-text small">{{ field.help_text }}</div>
|
||||||
{% if field.errors %}
|
{% endif %}
|
||||||
<div class="text-danger small">{{ field.errors }}</div>
|
{% if field.errors %}
|
||||||
{% endif %}
|
<div class="text-danger small">{{ field.errors }}</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endfor %}
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<button type="submit" class="btn btn-masarx-primary w-100 py-2 mt-3">{% trans "Create Account" %}</button>
|
<button type="submit" class="btn btn-masarx-primary w-100 py-2 mt-3">{% trans "Create Account" %}</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
@ -43,47 +52,50 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const governateSelect = document.getElementById('id_governate');
|
const governateSelect = document.getElementById('id_governate');
|
||||||
const citySelect = document.getElementById('id_city');
|
const citySelect = document.getElementById('id_city');
|
||||||
|
|
||||||
countrySelect.addEventListener('change', function() {
|
// Only attach listeners if elements exist (safety check)
|
||||||
const countryId = this.value;
|
if (countrySelect && governateSelect && citySelect) {
|
||||||
governateSelect.innerHTML = '<option value="">{% trans "Select Governate" %}</option>';
|
countrySelect.addEventListener('change', function() {
|
||||||
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
|
const countryId = this.value;
|
||||||
|
governateSelect.innerHTML = '<option value="">{% trans "Select Governate" %}</option>';
|
||||||
if (countryId) {
|
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
|
||||||
fetch(`{% url 'get_governates' %}?country_id=${countryId}`)
|
|
||||||
.then(response => response.json())
|
if (countryId) {
|
||||||
.then(data => {
|
fetch(`{% url 'get_governates' %}?country_id=${countryId}`)
|
||||||
data.forEach(gov => {
|
.then(response => response.json())
|
||||||
const option = document.createElement('option');
|
.then(data => {
|
||||||
option.value = gov.id;
|
data.forEach(gov => {
|
||||||
option.textContent = gov.name;
|
const option = document.createElement('option');
|
||||||
governateSelect.appendChild(option);
|
option.value = gov.id;
|
||||||
|
option.textContent = gov.name;
|
||||||
|
governateSelect.appendChild(option);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
governateSelect.addEventListener('change', function() {
|
governateSelect.addEventListener('change', function() {
|
||||||
const governateId = this.value;
|
const governateId = this.value;
|
||||||
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
|
citySelect.innerHTML = '<option value="">{% trans "Select City" %}</option>';
|
||||||
|
|
||||||
if (governateId) {
|
if (governateId) {
|
||||||
fetch(`{% url 'get_cities' %}?governate_id=${governateId}`)
|
fetch(`{% url 'get_cities' %}?governate_id=${governateId}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
data.forEach(city => {
|
data.forEach(city => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = city.id;
|
option.value = city.id;
|
||||||
option.textContent = city.name;
|
option.textContent = city.name;
|
||||||
citySelect.appendChild(option);
|
citySelect.appendChild(option);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
input, select {
|
input:not([type=radio]):not([type=checkbox]), select {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
@ -98,6 +110,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
#id_verification_method {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
#id_verification_method li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
.btn-masarx-primary {
|
.btn-masarx-primary {
|
||||||
background-color: var(--accent-orange);
|
background-color: var(--accent-orange);
|
||||||
border-color: var(--accent-orange);
|
border-color: var(--accent-orange);
|
||||||
@ -106,4 +129,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -6,6 +6,13 @@
|
|||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
<!-- Back to Dashboard -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'dashboard' %}" class="btn btn-link text-decoration-none text-muted ps-0">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Dashboard" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm p-4" style="border-radius: 20px;">
|
<div class="card border-0 shadow-sm p-4" style="border-radius: 20px;">
|
||||||
<h2 class="mb-4">{% trans "Request a Shipment" %}</h2>
|
<h2 class="mb-4">{% trans "Request a Shipment" %}</h2>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
|||||||
@ -8,6 +8,13 @@
|
|||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
<!-- Back to Edit Profile -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'edit_profile' %}" class="btn btn-link text-decoration-none text-muted ps-0">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Edit Profile" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm border-0">
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card-body p-5">
|
<div class="card-body p-5">
|
||||||
<h2 class="fw-bold mb-4 text-center">{% trans "Verification Required" %}</h2>
|
<h2 class="fw-bold mb-4 text-center">{% trans "Verification Required" %}</h2>
|
||||||
@ -44,4 +51,4 @@
|
|||||||
letter-spacing: 0.5em;
|
letter-spacing: 0.5em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
53
core/templates/core/verify_registration.html
Normal file
53
core/templates/core/verify_registration.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Verify Account" %} | masarX{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="py-5 bg-light" style="min-height: 80vh;">
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<!-- Back to Register -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'register' %}" class="btn btn-link text-decoration-none text-muted ps-0">
|
||||||
|
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Register" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<h2 class="fw-bold mb-4 text-center">{% trans "Account Verification" %}</h2>
|
||||||
|
<p class="text-center text-muted mb-4">
|
||||||
|
{% trans "We have sent a verification code to verify your account. Please enter it below to complete registration." %}
|
||||||
|
</p>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold" for="code">{% trans "Verification Code" %}</label>
|
||||||
|
<input type="text" name="code" id="code" class="form-control text-center fs-3 tracking-widest" maxlength="6" required placeholder="000000">
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-masarx-primary py-2">{% trans "Verify Account" %}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn-masarx-primary {
|
||||||
|
background-color: var(--accent-orange);
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.tracking-widest {
|
||||||
|
letter-spacing: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
@ -7,6 +7,7 @@ urlpatterns = [
|
|||||||
path('login/', auth_views.LoginView.as_view(template_name='core/login.html'), name='login'),
|
path('login/', auth_views.LoginView.as_view(template_name='core/login.html'), name='login'),
|
||||||
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
|
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
|
||||||
path('register/', views.register, name='register'),
|
path('register/', views.register, name='register'),
|
||||||
|
path('register/verify/', views.verify_registration, name='verify_registration'),
|
||||||
path('dashboard/', views.dashboard, name='dashboard'),
|
path('dashboard/', views.dashboard, name='dashboard'),
|
||||||
path('shipment-request/', views.shipment_request, name='shipment_request'),
|
path('shipment-request/', views.shipment_request, name='shipment_request'),
|
||||||
path('accept-parcel/<int:parcel_id>/', views.accept_parcel, name='accept_parcel'),
|
path('accept-parcel/<int:parcel_id>/', views.accept_parcel, name='accept_parcel'),
|
||||||
|
|||||||
@ -43,13 +43,76 @@ def register(request):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = UserRegistrationForm(request.POST)
|
form = UserRegistrationForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.save()
|
# Save user but inactive
|
||||||
login(request, user)
|
user = form.save(commit=True)
|
||||||
return redirect('dashboard')
|
user.is_active = False
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
# Generate OTP
|
||||||
|
code = ''.join(random.choices(string.digits, k=6))
|
||||||
|
OTPVerification.objects.create(user=user, code=code, purpose='registration')
|
||||||
|
|
||||||
|
# Send OTP
|
||||||
|
method = form.cleaned_data.get('verification_method', 'email')
|
||||||
|
if method == 'whatsapp':
|
||||||
|
phone = user.profile.phone_number
|
||||||
|
send_whatsapp_message(phone, f"Your verification code is: {code}")
|
||||||
|
messages.info(request, _("Verification code sent to WhatsApp."))
|
||||||
|
else:
|
||||||
|
send_mail(
|
||||||
|
_('Verification Code'),
|
||||||
|
f'Your verification code is: {code}',
|
||||||
|
settings.DEFAULT_FROM_EMAIL,
|
||||||
|
[user.email],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
messages.info(request, _("Verification code sent to email."))
|
||||||
|
|
||||||
|
request.session['registration_user_id'] = user.id
|
||||||
|
return redirect('verify_registration')
|
||||||
else:
|
else:
|
||||||
form = UserRegistrationForm()
|
form = UserRegistrationForm()
|
||||||
return render(request, 'core/register.html', {'form': form})
|
return render(request, 'core/register.html', {'form': form})
|
||||||
|
|
||||||
|
def verify_registration(request):
|
||||||
|
if 'registration_user_id' not in request.session:
|
||||||
|
messages.error(request, _("Session expired or invalid."))
|
||||||
|
return redirect('register')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
code = request.POST.get('code')
|
||||||
|
user_id = request.session['registration_user_id']
|
||||||
|
try:
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
otp = OTPVerification.objects.filter(
|
||||||
|
user=user,
|
||||||
|
code=code,
|
||||||
|
purpose='registration',
|
||||||
|
is_verified=False
|
||||||
|
).latest('created_at')
|
||||||
|
|
||||||
|
if otp.is_valid():
|
||||||
|
# Activate User
|
||||||
|
user.is_active = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
otp.is_verified = True
|
||||||
|
otp.save()
|
||||||
|
del request.session['registration_user_id']
|
||||||
|
|
||||||
|
# Login
|
||||||
|
login(request, user)
|
||||||
|
|
||||||
|
messages.success(request, _("Account verified successfully!"))
|
||||||
|
return redirect('dashboard')
|
||||||
|
else:
|
||||||
|
messages.error(request, _("Invalid or expired code."))
|
||||||
|
except (User.DoesNotExist, OTPVerification.DoesNotExist):
|
||||||
|
messages.error(request, _("Invalid code."))
|
||||||
|
|
||||||
|
return render(request, 'core/verify_registration.html')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
# Ensure profile exists
|
# Ensure profile exists
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user