adding user profile

This commit is contained in:
Flatlogic Bot 2026-01-25 03:02:12 +00:00
parent efa4c54412
commit 7bc32398ed
14 changed files with 301 additions and 11 deletions

View File

@ -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

View 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'),
),
]

View File

@ -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

View File

@ -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>

View 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 %}

View File

@ -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"),

View File

@ -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.

View File

@ -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 "اسم العائلة"