diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 3a3aab8..cbc72b4 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 f3c7b55..1ec0e42 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 2b649dd..7bda0a8 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 cd8e56a..b98115b 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 f81aa07..7deba2d 100644 --- a/core/forms.py +++ b/core/forms.py @@ -84,6 +84,75 @@ class UserRegistrationForm(forms.ModelForm): profile.save() return user +class UserProfileForm(forms.ModelForm): + first_name = forms.CharField(label=_("First Name"), max_length=150, widget=forms.TextInput(attrs={'class': 'form-control'})) + last_name = forms.CharField(label=_("Last Name"), max_length=150, widget=forms.TextInput(attrs={'class': 'form-control'})) + email = forms.EmailField(label=_("Email"), widget=forms.EmailInput(attrs={'class': 'form-control'})) + + phone_number = forms.CharField(label=_("Phone Number"), max_length=20, widget=forms.TextInput(attrs={'class': 'form-control'})) + address = forms.CharField(label=_("Address"), required=False, widget=forms.TextInput(attrs={'class': 'form-control'})) + profile_picture = forms.ImageField(label=_("Profile Picture"), required=False, widget=forms.FileInput(attrs={'class': 'form-control'})) + + otp_method = forms.ChoiceField( + choices=[('email', _('Email')), ('whatsapp', _('WhatsApp'))], + label=_('Verify changes via'), + widget=forms.RadioSelect, + initial='email' + ) + + class Meta: + model = Profile + fields = ['profile_picture', 'phone_number', 'address', 'country', 'governate', 'city'] + widgets = { + 'country': forms.Select(attrs={'class': 'form-control'}), + 'governate': forms.Select(attrs={'class': 'form-control'}), + 'city': forms.Select(attrs={'class': 'form-control'}), + } + labels = { + 'country': _('Country'), + 'governate': _('Governate'), + 'city': _('City'), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance.user: + self.fields['first_name'].initial = self.instance.user.first_name + self.fields['last_name'].initial = self.instance.user.last_name + self.fields['email'].initial = self.instance.user.email + + lang = get_language() + name_field = 'name_ar' if lang == 'ar' else 'name_en' + + self.fields['country'].queryset = Country.objects.all().order_by(name_field) + + # Default Country logic (Oman) + oman = Country.objects.filter(name_en='Oman').first() + + # Initial QS setup + self.fields['governate'].queryset = Governate.objects.none() + self.fields['city'].queryset = City.objects.none() + + if 'country' in self.data: + try: + country_id = int(self.data.get('country')) + self.fields['governate'].queryset = Governate.objects.filter(country_id=country_id).order_by(name_field) + except (ValueError, TypeError): + pass + elif self.instance.pk and self.instance.country: + self.fields['governate'].queryset = self.instance.country.governate_set.order_by(name_field) + elif oman: + self.fields['governate'].queryset = Governate.objects.filter(country=oman).order_by(name_field) + + if 'governate' in self.data: + try: + gov_id = int(self.data.get('governate')) + self.fields['city'].queryset = City.objects.filter(governate_id=gov_id).order_by(name_field) + except (ValueError, TypeError): + pass + elif self.instance.pk and self.instance.governate: + self.fields['city'].queryset = self.instance.governate.city_set.order_by(name_field) + class ParcelForm(forms.ModelForm): class Meta: model = Parcel @@ -180,4 +249,4 @@ class ParcelForm(forms.ModelForm): gov_id = int(self.data.get('delivery_governate')) self.fields['delivery_city'].queryset = City.objects.filter(governate_id=gov_id).order_by(name_field) except (ValueError, TypeError): - pass \ No newline at end of file + pass diff --git a/core/migrations/0009_profile_address_profile_profile_picture_and_more.py b/core/migrations/0009_profile_address_profile_profile_picture_and_more.py new file mode 100644 index 0000000..f8664c1 --- /dev/null +++ b/core/migrations/0009_profile_address_profile_profile_picture_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 5.2.7 on 2026-01-25 11:40 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0008_platformprofile_privacy_policy_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='address', + field=models.CharField(blank=True, max_length=255, verbose_name='Address'), + ), + migrations.AddField( + model_name='profile', + name='profile_picture', + field=models.ImageField(blank=True, null=True, upload_to='profile_pics/', verbose_name='Profile Picture'), + ), + migrations.CreateModel( + name='OTPVerification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.CharField(max_length=6)), + ('purpose', models.CharField(choices=[('profile_update', 'Profile Update'), ('password_reset', 'Password Reset')], default='profile_update', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('is_verified', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/core/migrations/__pycache__/0009_profile_address_profile_profile_picture_and_more.cpython-311.pyc b/core/migrations/__pycache__/0009_profile_address_profile_profile_picture_and_more.cpython-311.pyc new file mode 100644 index 0000000..602a441 Binary files /dev/null and b/core/migrations/__pycache__/0009_profile_address_profile_profile_picture_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 8804f9f..13e3e99 100644 --- a/core/models.py +++ b/core/models.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _ from django.utils.translation import get_language from django.db.models.signals import post_save from django.dispatch import receiver +from django.utils import timezone import uuid class Country(models.Model): @@ -67,6 +68,8 @@ class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name=_('User')) role = models.CharField(_('Role'), max_length=20, choices=ROLE_CHOICES, default='shipper') phone_number = models.CharField(_('Phone Number'), max_length=20, blank=True) + profile_picture = models.ImageField(_('Profile Picture'), upload_to='profile_pics/', blank=True, null=True) + address = models.CharField(_('Address'), max_length=255, blank=True) country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Country')) governate = models.ForeignKey(Governate, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Governate')) @@ -162,4 +165,19 @@ class PlatformProfile(models.Model): class Meta: verbose_name = _('Platform Profile') - verbose_name_plural = _('Platform Profile') \ No newline at end of file + verbose_name_plural = _('Platform Profile') + +class OTPVerification(models.Model): + PURPOSE_CHOICES = ( + ('profile_update', _('Profile Update')), + ('password_reset', _('Password Reset')), + ) + user = models.ForeignKey(User, on_delete=models.CASCADE) + code = models.CharField(max_length=6) + purpose = models.CharField(max_length=20, choices=PURPOSE_CHOICES, default='profile_update') + created_at = models.DateTimeField(auto_now_add=True) + is_verified = models.BooleanField(default=False) + + def is_valid(self): + # OTP valid for 10 minutes + return self.created_at >= timezone.now() - timezone.timedelta(minutes=10) diff --git a/core/templates/base.html b/core/templates/base.html index 61d4e64..2841df6 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -87,7 +87,7 @@ {% endif %}