adding user profile
This commit is contained in:
parent
efa4c54412
commit
7bc32398ed
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -215,3 +215,44 @@ class CustomLoginForm(AuthenticationForm):
|
||||
if not isinstance(field.widget, (forms.RadioSelect, forms.CheckboxInput)):
|
||||
field.widget.attrs.update({"class": "form-control"})
|
||||
|
||||
class ProfileForm(forms.ModelForm):
|
||||
first_name = forms.CharField(max_length=150, required=False, widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
last_name = forms.CharField(max_length=150, required=False, widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
email = forms.EmailField(required=False, widget=forms.EmailInput(attrs={'class': 'form-control'}))
|
||||
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['profile_picture', 'phone_number', 'country_code']
|
||||
widgets = {
|
||||
'profile_picture': forms.FileInput(attrs={'class': 'form-control'}),
|
||||
'phone_number': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'country_code': forms.Select(attrs={'class': 'form-select'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop('user', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Dynamic country codes from the database
|
||||
countries = Country.objects.all()
|
||||
if countries.exists():
|
||||
self.fields['country_code'].widget = forms.Select(
|
||||
attrs={'class': 'form-select'},
|
||||
choices=[(c.code, str(c)) for c in countries]
|
||||
)
|
||||
|
||||
if user:
|
||||
self.fields['first_name'].initial = user.first_name
|
||||
self.fields['last_name'].initial = user.last_name
|
||||
self.fields['email'].initial = user.email
|
||||
|
||||
def save(self, commit=True):
|
||||
profile = super().save(commit=False)
|
||||
user = profile.user
|
||||
user.first_name = self.cleaned_data['first_name']
|
||||
user.last_name = self.cleaned_data['last_name']
|
||||
user.email = self.cleaned_data['email']
|
||||
if commit:
|
||||
user.save()
|
||||
profile.save()
|
||||
return profile
|
||||
18
core/migrations/0028_profile_profile_picture.py
Normal file
18
core/migrations/0028_profile_profile_picture.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-25 03:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0027_otpcode_email_alter_otpcode_phone_number'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='profile_picture',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='profiles/', verbose_name='Profile Picture'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -65,6 +65,10 @@ class Profile(models.Model):
|
||||
subscription_plan = models.CharField(max_length=20, choices=SUBSCRIPTION_CHOICES, default='NONE')
|
||||
subscription_expiry = models.DateField(null=True, blank=True)
|
||||
is_subscription_active = models.BooleanField(default=False)
|
||||
|
||||
# New Profile Picture field
|
||||
profile_picture = models.ImageField(_('Profile Picture'), upload_to='profiles/', blank=True, null=True)
|
||||
|
||||
def is_expired(self):
|
||||
if self.subscription_plan == "NONE":
|
||||
return False
|
||||
@ -73,10 +77,7 @@ class Profile(models.Model):
|
||||
if not self.subscription_expiry:
|
||||
return True
|
||||
return self.subscription_expiry < timezone.now().date()
|
||||
if not self.subscription_expiry:
|
||||
return 0
|
||||
delta = self.subscription_expiry - timezone.now().date()
|
||||
return delta.days
|
||||
|
||||
country_code = models.CharField(max_length=5, blank=True, default="966")
|
||||
phone_number = models.CharField(max_length=20, unique=True, null=True) # Changed to unique and nullable for migration safety
|
||||
|
||||
@ -91,6 +92,40 @@ class Profile(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username} - {self.role}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.profile_picture:
|
||||
self.profile_picture = self.compress_image(self.profile_picture)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def compress_image(self, image_field):
|
||||
if not image_field:
|
||||
return image_field
|
||||
|
||||
try:
|
||||
# Check file extension
|
||||
ext = os.path.splitext(image_field.name)[1].lower()
|
||||
if ext not in ['.jpg', '.jpeg', '.png', '.webp']:
|
||||
return image_field
|
||||
|
||||
img = Image.open(image_field)
|
||||
|
||||
if img.mode != 'RGB':
|
||||
img = img.convert('RGB')
|
||||
|
||||
# Resize if too large
|
||||
max_size = (500, 500)
|
||||
img.thumbnail(max_size, Image.LANCZOS)
|
||||
|
||||
output = BytesIO()
|
||||
img.save(output, format='JPEG', quality=80, optimize=True)
|
||||
output.seek(0)
|
||||
|
||||
new_name = os.path.splitext(image_field.name)[0] + '.jpg'
|
||||
return File(output, name=new_name)
|
||||
except Exception as e:
|
||||
# Not an image or other error, return as is
|
||||
return image_field
|
||||
|
||||
class OTPCode(models.Model):
|
||||
phone_number = models.CharField(max_length=20, null=True, blank=True)
|
||||
@ -531,4 +566,4 @@ class Testimonial(models.Model):
|
||||
def display_content(self):
|
||||
if get_language() == 'ar' and self.content_ar:
|
||||
return self.content_ar
|
||||
return self.content
|
||||
return self.content
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
.breadcrumb-container { background: #fff; border-bottom: 1px solid #eee; padding: 0.5rem 0; margin-bottom: 0; }
|
||||
.breadcrumb-item a { text-decoration: none; color: #6c757d; }
|
||||
.breadcrumb-item.active { color: #2196F3; font-weight: 600; }
|
||||
.nav-profile-pic { width: 30px; height: 30px; object-fit: cover; border-radius: 50%; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -99,19 +100,34 @@
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item dropdown ms-lg-3">
|
||||
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">
|
||||
<i class="fa-solid fa-user-circle me-1"></i> {{ user.username }}
|
||||
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" data-bs-toggle="dropdown">
|
||||
{% if user.profile.profile_picture %}
|
||||
<img src="{{ user.profile.profile_picture.url }}" alt="{{ user.username }}" class="nav-profile-pic me-2">
|
||||
{% else %}
|
||||
<i class="fa-solid fa-user-circle me-1"></i>
|
||||
{% endif %}
|
||||
{{ user.username }}
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'profile' %}">
|
||||
<i class="fa-solid fa-user-gear me-2"></i>{% trans "My Profile" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if user.is_staff %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}">{% trans "Admin Panel" %}</a>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}">
|
||||
<i class="fa-solid fa-lock me-2"></i>{% trans "Admin Panel" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{% url 'logout' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="dropdown-item">{% trans "Logout" %}</button>
|
||||
<button type="submit" class="dropdown-item">
|
||||
<i class="fa-solid fa-right-from-bracket me-2"></i>{% trans "Logout" %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
137
core/templates/core/profile.html
Normal file
137
core/templates/core/profile.html
Normal file
@ -0,0 +1,137 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "My Profile" %} - {{ app_settings.app_name|default:"MASAR CARGO" }}{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumb-container">
|
||||
<div class="container">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'home' %}">{% trans "Home" %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans "My Profile" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-primary text-white py-3">
|
||||
<h4 class="mb-0"><i class="fa-solid fa-user-edit me-2"></i>{% trans "Edit Profile" %}</h4>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="text-center mb-4">
|
||||
{% if profile.profile_picture %}
|
||||
<img src="{{ profile.profile_picture.url }}" alt="{{ user.username }}" class="rounded-circle img-thumbnail mb-3" style="width: 150px; height: 150px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-light d-inline-flex align-items-center justify-content-center mb-3" style="width: 150px; height: 150px; border: 2px dashed #ddd;">
|
||||
<i class="fa-solid fa-user fa-4x text-muted"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-muted small">{% trans "Update your profile picture" %}</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">{% trans "First Name" %}</label>
|
||||
{{ form.first_name }}
|
||||
{% if form.first_name.errors %}
|
||||
<div class="text-danger small">{{ form.first_name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">{% trans "Last Name" %}</label>
|
||||
{{ form.last_name }}
|
||||
{% if form.last_name.errors %}
|
||||
<div class="text-danger small">{{ form.last_name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">{% trans "Email" %}</label>
|
||||
{{ form.email }}
|
||||
{% if form.email.errors %}
|
||||
<div class="text-danger small">{{ form.email.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label fw-bold">{% trans "Country Code" %}</label>
|
||||
{{ form.country_code }}
|
||||
{% if form.country_code.errors %}
|
||||
<div class="text-danger small">{{ form.country_code.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-8 mb-3">
|
||||
<label class="form-label fw-bold">{% trans "Phone Number" %}</label>
|
||||
{{ form.phone_number }}
|
||||
{% if form.phone_number.errors %}
|
||||
<div class="text-danger small">{{ form.phone_number.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">{% trans "Profile Picture" %}</label>
|
||||
{{ form.profile_picture }}
|
||||
{% if form.profile_picture.errors %}
|
||||
<div class="text-danger small">{{ form.profile_picture.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{% url 'dashboard' %}" class="btn btn-outline-secondary">
|
||||
<i class="fa-solid fa-arrow-left me-1"></i> {% trans "Back to Dashboard" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary px-4">
|
||||
<i class="fa-solid fa-save me-1"></i> {% trans "Save Changes" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h5 class="fw-bold mb-3">{% trans "Account Information" %}</h5>
|
||||
<div class="row small">
|
||||
<div class="col-sm-4 text-muted">{% trans "Username" %}</div>
|
||||
<div class="col-sm-8 mb-2">{{ user.username }}</div>
|
||||
|
||||
<div class="col-sm-4 text-muted">{% trans "Role" %}</div>
|
||||
<div class="col-sm-8 mb-2">
|
||||
{% if profile.role == 'SHIPPER' %}
|
||||
<span class="badge bg-info">{% trans "Shipper" %}</span>
|
||||
{% elif profile.role == 'TRUCK_OWNER' %}
|
||||
<span class="badge bg-success">{% trans "Truck Owner" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ profile.role }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4 text-muted">{% trans "Subscription" %}</div>
|
||||
<div class="col-sm-8 mb-2">
|
||||
{{ profile.get_subscription_plan_display }}
|
||||
{% if profile.subscription_expiry %}
|
||||
<br><small class="text-muted">{% trans "Expires on:" %} {{ profile.subscription_expiry }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -10,6 +10,7 @@ urlpatterns = [
|
||||
path("verify-otp-login/", views.verify_otp_login, name="verify_otp_login"),
|
||||
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
|
||||
path("dashboard/", views.dashboard, name="dashboard"),
|
||||
path("profile/", views.profile_view, name="profile"),
|
||||
path("truck/register/", views.truck_register, name="truck_register"),
|
||||
path("truck/<int:truck_id>/edit/", views.edit_truck, name="edit_truck"),
|
||||
path("truck/<int:truck_id>/approve/", views.approve_truck, name="approve_truck"),
|
||||
|
||||
@ -13,7 +13,7 @@ from .forms import (
|
||||
CustomLoginForm,
|
||||
TruckForm, ShipmentForm, BidForm, UserRegistrationForm,
|
||||
OTPVerifyForm, ShipperOfferForm, RenewSubscriptionForm, AppSettingForm,
|
||||
ContactForm
|
||||
ContactForm, ProfileForm
|
||||
)
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext as _
|
||||
@ -239,6 +239,20 @@ def verify_otp_login(request):
|
||||
|
||||
return render(request, 'registration/verify_otp.html', {'form': form, 'purpose': 'login', 'otp_method': otp_method})
|
||||
|
||||
@login_required
|
||||
def profile_view(request):
|
||||
profile = request.user.profile
|
||||
if request.method == 'POST':
|
||||
form = ProfileForm(request.POST, request.FILES, instance=profile, user=request.user)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("Profile updated successfully!"))
|
||||
return redirect('profile')
|
||||
else:
|
||||
form = ProfileForm(instance=profile, user=request.user)
|
||||
|
||||
return render(request, 'core/profile.html', {'form': form, 'profile': profile})
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
profile, created = Profile.objects.get_or_create(user=request.user)
|
||||
@ -863,4 +877,4 @@ def chat_api(request):
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Chat API Error: {str(e)}")
|
||||
return JsonResponse({'success': False, 'error': str(e)})
|
||||
return JsonResponse({'success': False, 'error': str(e)})
|
||||
Binary file not shown.
@ -2126,3 +2126,31 @@ msgstr "لقد أرسلنا رمز تحقق إلى بريدك الإلكترون
|
||||
|
||||
msgid "We have sent a verification code to your email address. Please enter it below to log in."
|
||||
msgstr "لقد أرسلنا رمز تحقق إلى بريدك الإلكتروني. يرجى إدخاله أدناه لتسجيل الدخول."
|
||||
|
||||
msgid "My Profile"
|
||||
msgstr "ملفي الشخصي"
|
||||
|
||||
msgid "Edit Profile"
|
||||
msgstr "تعديل الملف الشخصي"
|
||||
|
||||
msgid "Update your profile picture"
|
||||
msgstr "تحديث صورة الملف الشخصي"
|
||||
|
||||
msgid "Profile Picture"
|
||||
msgstr "صورة الملف الشخصي"
|
||||
|
||||
msgid "Save Changes"
|
||||
msgstr "حفظ التغييرات"
|
||||
|
||||
msgid "Account Information"
|
||||
msgstr "معلومات الحساب"
|
||||
|
||||
msgid "Expires on:"
|
||||
msgstr "تنتهي في:"
|
||||
|
||||
msgid "First Name"
|
||||
msgstr "الاسم الأول"
|
||||
|
||||
msgid "Last Name"
|
||||
msgstr "اسم العائلة"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user