diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index cedff70..024c109 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 8e53bb3..20c32de 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 1f4617c..93c31e0 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 1c83655..d8f0834 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/forms.py b/core/forms.py index ab9441c..60f9f9e 100644 --- a/core/forms.py +++ b/core/forms.py @@ -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 \ No newline at end of file diff --git a/core/migrations/0028_profile_profile_picture.py b/core/migrations/0028_profile_profile_picture.py new file mode 100644 index 0000000..0bfcfc3 --- /dev/null +++ b/core/migrations/0028_profile_profile_picture.py @@ -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'), + ), + ] diff --git a/core/migrations/__pycache__/0028_profile_profile_picture.cpython-311.pyc b/core/migrations/__pycache__/0028_profile_profile_picture.cpython-311.pyc new file mode 100644 index 0000000..2e1b54d Binary files /dev/null and b/core/migrations/__pycache__/0028_profile_profile_picture.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index b6f951e..340a79c 100644 --- a/core/models.py +++ b/core/models.py @@ -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 \ No newline at end of file + return self.content diff --git a/core/templates/base.html b/core/templates/base.html index 14e0674..4e468b6 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -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%; } @@ -99,19 +100,34 @@ {% if user.is_authenticated %}