From a3174399c84967df0e52a36fc71fde6b3d798b36 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 26 Jan 2026 04:33:09 +0000 Subject: [PATCH] Autosave: 20260126-043309 --- core/__pycache__/admin.cpython-311.pyc | Bin 10232 -> 10508 bytes core/__pycache__/forms.cpython-311.pyc | Bin 20182 -> 25331 bytes core/__pycache__/models.cpython-311.pyc | Bin 19991 -> 20264 bytes core/__pycache__/urls.cpython-311.pyc | Bin 2568 -> 3711 bytes core/admin.py | 6 +- core/forms.py | 79 ++++++++++++++- core/migrations/0016_country_phone_code.py | 18 ++++ .../0016_country_phone_code.cpython-311.pyc | Bin 0 -> 873 bytes core/models.py | 5 +- core/templates/core/emails/base_email.html | 93 ++++++++++++++++++ .../core/emails/password_reset_email.html | 26 +++++ .../core/emails/password_reset_subject.txt | 1 + core/templates/core/login.html | 74 ++++++++++---- .../core/password_reset_complete.html | 44 +++++++++ .../core/password_reset_confirm.html | 64 ++++++++++++ core/templates/core/password_reset_done.html | 32 ++++++ core/templates/core/password_reset_form.html | 72 ++++++++++++++ core/templates/core/shipment_request.html | 12 ++- .../__pycache__/core_tags.cpython-311.pyc | Bin 0 -> 631 bytes core/templatetags/core_tags.py | 8 ++ core/urls.py | 23 ++++- 21 files changed, 527 insertions(+), 30 deletions(-) create mode 100644 core/migrations/0016_country_phone_code.py create mode 100644 core/migrations/__pycache__/0016_country_phone_code.cpython-311.pyc create mode 100644 core/templates/core/emails/base_email.html create mode 100644 core/templates/core/emails/password_reset_email.html create mode 100644 core/templates/core/emails/password_reset_subject.txt create mode 100644 core/templates/core/password_reset_complete.html create mode 100644 core/templates/core/password_reset_confirm.html create mode 100644 core/templates/core/password_reset_done.html create mode 100644 core/templates/core/password_reset_form.html create mode 100644 core/templatetags/__pycache__/core_tags.cpython-311.pyc create mode 100644 core/templatetags/core_tags.py diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a145697c9bedf365ef189404695e7e208ad26bcb..39feadc3cd2cd2335ddb279ac3d84c9db9562797 100644 GIT binary patch delta 604 zcmez2-xI{QoR^o20SImdmSu`aPvnzejN7Q*%gDvV;LeaD-NKL}GuekxnpG#2F-v#y z0Ves$*BM2)bwN5nV1fSRg(AX}IhiCd#OyJ}lo?VvQ?-{dF)*wKVhCVl0GSNqLCssq zB!*$$T`XdP%oz4cGoY&?^!XM0_DZ4p|d7oUp7|^om45XGy$l1a-x!q;WUsThCo7-1uR%J5y-y9m7JeflA2cnaweReSft5VG;Q)GC1Iq9Fy798@Ol0Tx8I?!k~45L2GiqdL{sYeu@A9 delta 322 zcmeAP`r*&FoR^o20SLPN%QEw%Ch|!znr&3?W#mg`%+djggTMma$&3Oblm9S^GU`me z$EYybhzY`ypPbGl1{RYS54rPm|TkLs>xvBA~dAHb$@^iop&dlQY#N?99veY6& zpf*jWqDeps!e%L&0AxcoB^KS{O3u$KNzE$(831P|7HKjTO`1GO*?IClCBDgzm37$q yK?dn>7FFqGoV;AsjC&Q35Asj(JEqMqRGFD3OK3E3USyED!XR^jL1yw|jZ6UMHdhh= diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 3def7a285ab8b5add9a1941296b8f892f0965e0a..e6a472da42693b7b8c30ac59035d94bd5be99589 100644 GIT binary patch delta 8718 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 GI{ypbYsX;# delta 4717 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 delta 3389 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 delta 394 zcmew_(;=dB$jh9?1CvS-nYcleUo@3D zix(~=1`-kn3h}{(BtSxvKp}p(kQ7jeRT?NHF!=_fBG61hxRe}7wLDNt2ri@m5>f;T z3B!eyKtjquArZKc3dl@VppfX~X-vwDVv~0PN%6_|n3TCC;EHrWigbaBBqyseD>F(> z_5za9lk=FRxY9+VWKxWS88l5MUt`(5xtv{&iBWd)G|n95A_1WOB9Ie{K#nQ`Iim<< zagiv95CamE6S?FBZt)Zp<)>xlq{f$Jrj{4UP2R+n$gKmEWd!2l1g6Py+}D^NuyAbt Uz*Eo2Zz##n)W8jbMOr|40E=Zwg8%>k diff --git a/core/admin.py b/core/admin.py index bde313b..b16cdd3 100644 --- a/core/admin.py +++ b/core/admin.py @@ -149,6 +149,10 @@ class PlatformProfileAdmin(admin.ModelAdmin): fieldsets += ((_('Tools'), {'fields': ('test_connection_link',)}),) return fieldsets +class CountryAdmin(admin.ModelAdmin): + list_display = ('name_en', 'name_ar', 'phone_code') + search_fields = ('name_en', 'name_ar', 'phone_code') + class TestimonialAdmin(admin.ModelAdmin): list_display = ('name_en', 'role_en', 'is_active', 'created_at') list_filter = ('is_active', 'created_at') @@ -158,7 +162,7 @@ class TestimonialAdmin(admin.ModelAdmin): admin.site.unregister(User) admin.site.register(User, CustomUserAdmin) admin.site.register(Parcel, ParcelAdmin) -admin.site.register(Country) +admin.site.register(Country, CountryAdmin) admin.site.register(Governate) admin.site.register(City) admin.site.register(PlatformProfile, PlatformProfileAdmin) diff --git a/core/forms.py b/core/forms.py index 2500420..dfaccd8 100644 --- a/core/forms.py +++ b/core/forms.py @@ -14,7 +14,10 @@ class UserRegistrationForm(forms.ModelForm): password = forms.CharField(widget=forms.PasswordInput, label=_("Password")) password_confirm = forms.CharField(widget=forms.PasswordInput, label=_("Confirm Password")) role = forms.ChoiceField(choices=Profile.ROLE_CHOICES, label=_("Register as")) + + phone_code = forms.ModelChoiceField(queryset=Country.objects.none(), label=_("Code"), required=False, widget=forms.Select(attrs={'class': 'form-control'})) 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")) @@ -36,12 +39,17 @@ class UserRegistrationForm(forms.ModelForm): lang = get_language() name_field = 'name_ar' if lang == 'ar' else 'name_en' + # Phone Code setup + self.fields['phone_code'].queryset = Country.objects.exclude(phone_code='').order_by(name_field) + self.fields['phone_code'].label_from_instance = lambda obj: f"{obj.phone_code} ({obj.name})" + self.fields['country'].queryset = Country.objects.all().order_by(name_field) # Default Country logic oman = Country.objects.filter(name_en='Oman').first() if oman: self.fields['country'].initial = oman + self.fields['phone_code'].initial = oman if 'country' in self.data: try: @@ -70,6 +78,17 @@ class UserRegistrationForm(forms.ModelForm): raise forms.ValidationError(_("Passwords don't match")) return password_confirm + def clean(self): + cleaned_data = super().clean() + phone_code = cleaned_data.get('phone_code') + phone_number = cleaned_data.get('phone_number') + + if phone_code and phone_number: + # If user didn't type the code in the phone number input, prepend it + if not phone_number.startswith(phone_code.phone_code): + cleaned_data['phone_number'] = f"{phone_code.phone_code}{phone_number}" + return cleaned_data + def save(self, commit=True): user = super().save(commit=False) user.set_password(self.cleaned_data['password']) @@ -90,7 +109,9 @@ class UserProfileForm(forms.ModelForm): 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_code = forms.ModelChoiceField(queryset=Country.objects.none(), label=_("Code"), required=False, widget=forms.Select(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'})) @@ -125,10 +146,25 @@ class UserProfileForm(forms.ModelForm): lang = get_language() name_field = 'name_ar' if lang == 'ar' else 'name_en' - self.fields['country'].queryset = Country.objects.all().order_by(name_field) + # Phone Code setup + self.fields['phone_code'].queryset = Country.objects.exclude(phone_code='').order_by(name_field) + self.fields['phone_code'].label_from_instance = lambda obj: f"{obj.phone_code} ({obj.name})" # Default Country logic (Oman) oman = Country.objects.filter(name_en='Oman').first() + if oman: + self.fields['phone_code'].initial = oman + + # Initial splitting of phone number + if self.instance.pk and self.instance.phone_number: + for country in Country.objects.exclude(phone_code=''): + if self.instance.phone_number.startswith(country.phone_code): + self.fields['phone_code'].initial = country + # Strip code from display + self.fields['phone_number'].initial = self.instance.phone_number[len(country.phone_code):] + break + + self.fields['country'].queryset = Country.objects.all().order_by(name_field) # Initial QS setup self.fields['governate'].queryset = Governate.objects.none() @@ -154,7 +190,19 @@ class UserProfileForm(forms.ModelForm): elif self.instance.pk and self.instance.governate: self.fields['city'].queryset = self.instance.governate.city_set.order_by(name_field) + def clean(self): + cleaned_data = super().clean() + phone_code = cleaned_data.get('phone_code') + phone_number = cleaned_data.get('phone_number') + + if phone_code and phone_number: + if not phone_number.startswith(phone_code.phone_code): + cleaned_data['phone_number'] = f"{phone_code.phone_code}{phone_number}" + return cleaned_data + class ParcelForm(forms.ModelForm): + receiver_phone_code = forms.ModelChoiceField(queryset=Country.objects.none(), label=_("Receiver Code"), required=False, widget=forms.Select(attrs={'class': 'form-control'})) + class Meta: model = Parcel fields = [ @@ -202,16 +250,29 @@ class ParcelForm(forms.ModelForm): lang = get_language() name_field = 'name_ar' if lang == 'ar' else 'name_en' - # Set querysets for countries - self.fields['pickup_country'].queryset = Country.objects.all().order_by(name_field) - self.fields['delivery_country'].queryset = Country.objects.all().order_by(name_field) + # Phone Code setup + self.fields['receiver_phone_code'].queryset = Country.objects.exclude(phone_code='').order_by(name_field) + self.fields['receiver_phone_code'].label_from_instance = lambda obj: f"{obj.phone_code} ({obj.name})" # Default Country logic oman = Country.objects.filter(name_en='Oman').first() if oman: + self.fields['receiver_phone_code'].initial = oman self.fields['pickup_country'].initial = oman self.fields['delivery_country'].initial = oman + # Initial splitting of phone number (if editing) + if self.instance.pk and self.instance.receiver_phone: + for country in Country.objects.exclude(phone_code=''): + if self.instance.receiver_phone.startswith(country.phone_code): + self.fields['receiver_phone_code'].initial = country + self.fields['receiver_phone'].initial = self.instance.receiver_phone[len(country.phone_code):] + break + + # Set querysets for countries + self.fields['pickup_country'].queryset = Country.objects.all().order_by(name_field) + self.fields['delivery_country'].queryset = Country.objects.all().order_by(name_field) + # Pickup self.fields['pickup_governate'].queryset = Governate.objects.none() self.fields['pickup_city'].queryset = City.objects.none() @@ -251,3 +312,13 @@ class ParcelForm(forms.ModelForm): self.fields['delivery_city'].queryset = City.objects.filter(governate_id=gov_id).order_by(name_field) except (ValueError, TypeError): pass + + def clean(self): + cleaned_data = super().clean() + phone_code = cleaned_data.get('receiver_phone_code') + phone_number = cleaned_data.get('receiver_phone') + + if phone_code and phone_number: + if not phone_number.startswith(phone_code.phone_code): + cleaned_data['receiver_phone'] = f"{phone_code.phone_code}{phone_number}" + return cleaned_data \ No newline at end of file diff --git a/core/migrations/0016_country_phone_code.py b/core/migrations/0016_country_phone_code.py new file mode 100644 index 0000000..6fe1b2e --- /dev/null +++ b/core/migrations/0016_country_phone_code.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-01-25 17:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0015_testimonial'), + ] + + operations = [ + migrations.AddField( + model_name='country', + name='phone_code', + field=models.CharField(blank=True, help_text='e.g. +968', max_length=10, verbose_name='Phone Code'), + ), + ] diff --git a/core/migrations/__pycache__/0016_country_phone_code.cpython-311.pyc b/core/migrations/__pycache__/0016_country_phone_code.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7193d89de047243d3cc99fd25e4b2ac689f542fd GIT binary patch literal 873 zcmZuv&ui2`6rM@4*(7eEhJxT$S&E8_u&D=&LJ_2(P;9YOJj6rHW~RH@CYhKYb=%V( zJb3J_|3OvkAE5um3VSGX_0(I+_SBPavfUP`Z;~(X&3p6an{V=UX2w8ZYa2WMFO1M1 z5lpRe;f$BTIY0yv9H0=}IFxJ&BMH4iMEZ<~OmR=1E;_9?ZAz;x!$Eg49)r(9Nq0Z0gXCY zm)CY^(q<`jA~&Q>8FE63GUAGuo!LK>a$J`A>t%wMX zX+&t``7}+0`doWK$V<^95jt>vrRyeT%ncUPi#zy^#zLa=RjbQFYUOP>XKv9x_414* z)+d&1r?Kl%ODJPq-JJy*ErAZDEY6iGt#~(4p!jyK9o8c4QiJm36F#>^Sy?*|-nq6s5%PI*7ee^&QTh&`kTJ%<5jThE;q|9b WYgjGNb=WsXv-eI#;_N@!a^Jr(A?5f0 literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index fb8a8a6..7f57570 100644 --- a/core/models.py +++ b/core/models.py @@ -11,6 +11,7 @@ import uuid class Country(models.Model): name_en = models.CharField(_('Name (English)'), max_length=100) name_ar = models.CharField(_('Name (Arabic)'), max_length=100) + phone_code = models.CharField(_('Phone Code'), max_length=10, blank=True, help_text=_("e.g. +968")) @property def name(self): @@ -19,7 +20,7 @@ class Country(models.Model): return self.name_en def __str__(self): - return self.name + return f"{self.name} ({self.phone_code})" if self.phone_code else self.name class Meta: verbose_name = _('Country') @@ -240,4 +241,4 @@ class Testimonial(models.Model): class Meta: verbose_name = _('Testimonial') verbose_name_plural = _('Testimonials') - ordering = ['-created_at'] + ordering = ['-created_at'] \ No newline at end of file diff --git a/core/templates/core/emails/base_email.html b/core/templates/core/emails/base_email.html new file mode 100644 index 0000000..28e6343 --- /dev/null +++ b/core/templates/core/emails/base_email.html @@ -0,0 +1,93 @@ +{% load i18n core_tags %} +{% get_platform_profile as platform %} + + + + + + + + +
+
+ {% if platform and platform.logo %} + + {% else %} +

{{ site_name }}

+ {% endif %} +
+
+ {% block content %}{% endblock %} +
+ +
+ + diff --git a/core/templates/core/emails/password_reset_email.html b/core/templates/core/emails/password_reset_email.html new file mode 100644 index 0000000..65958b7 --- /dev/null +++ b/core/templates/core/emails/password_reset_email.html @@ -0,0 +1,26 @@ +{% extends "core/emails/base_email.html" %} +{% load i18n %} + +{% block content %} +

{% trans "Reset Your Password" %}

+ +

{% trans "Hello," %}

+ +

{% trans "You are receiving this email because you requested a password reset for your account at" %} {{ site_name }}.

+ +

{% trans "Please click the button below to choose a new password:" %}

+ + + +

{% trans "If the button above doesn't work, verify that you entered your username correctly and try pasting this link into your browser:" %}

+ +

{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}

+ +

{% trans "Your username is:" %} {{ user.get_username }}

+ +

{% trans "If you did not request this, please ignore this email." %}

+ +

{% trans "Thanks," %}
{% trans "The" %} {{ site_name }} {% trans "Team" %}

+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/emails/password_reset_subject.txt b/core/templates/core/emails/password_reset_subject.txt new file mode 100644 index 0000000..fb089be --- /dev/null +++ b/core/templates/core/emails/password_reset_subject.txt @@ -0,0 +1 @@ +{% trans "Password reset on" %} {{ site_name }} \ No newline at end of file diff --git a/core/templates/core/login.html b/core/templates/core/login.html index e00f40f..765521e 100644 --- a/core/templates/core/login.html +++ b/core/templates/core/login.html @@ -4,36 +4,52 @@ {% block title %}{% trans "Login" %} | masarX{% endblock %} {% block content %} -
-
+
+
-
- - +
+
+
+ + +
+ {% if platform_profile.logo %} + {{ platform_profile.name }} + {% else %} +

{{ platform_profile.name|default:"masarX" }}

+ {% endif %} +

{% trans "Welcome Back" %}

+

{% trans "Please login to your account" %}

+
-
-
-

{% trans "Login to masarX" %}

{% csrf_token %} {% for field in form %}
- + {{ field }} {% if field.errors %} -
{{ field.errors }}
+
{{ field.errors }}
{% endif %}
{% endfor %} - + + + + + +
+ {% trans "Don't have an account?" %} + {% trans "Register" %} +
-
-

{% trans "Don't have an account?" %} {% trans "Register here" %}

-
@@ -44,14 +60,30 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/core/templates/core/password_reset_complete.html b/core/templates/core/password_reset_complete.html new file mode 100644 index 0000000..2630ff2 --- /dev/null +++ b/core/templates/core/password_reset_complete.html @@ -0,0 +1,44 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Password Reset Complete" %} | masarX{% endblock %} + +{% block content %} +
+
+
+
+
+
+
+ +
+

{% trans "Password Changed!" %}

+

+ {% trans "Your password has been set. You may go ahead and log in now." %} +

+ + {% trans "Log In" %} + +
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/password_reset_confirm.html b/core/templates/core/password_reset_confirm.html new file mode 100644 index 0000000..b17e003 --- /dev/null +++ b/core/templates/core/password_reset_confirm.html @@ -0,0 +1,64 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "New Password" %} | masarX{% endblock %} + +{% block content %} +
+
+
+
+
+
+
+

{% trans "Set New Password" %}

+

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

+
+ +
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
{{ field.errors }}
+ {% endif %} +
+ {% endfor %} + +
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/password_reset_done.html b/core/templates/core/password_reset_done.html new file mode 100644 index 0000000..f330199 --- /dev/null +++ b/core/templates/core/password_reset_done.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Email Sent" %} | masarX{% endblock %} + +{% block content %} +
+
+
+
+
+
+
+ +
+

{% trans "Check Your Email" %}

+

+ {% trans "We've sent you instructions on how to reset your password. If an account exists with the email you entered, you will receive them shortly." %} +

+

+ {% trans "If you don't receive an email, please check your spam folder." %} +

+ + {% trans "Return to Login" %} + +
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/password_reset_form.html b/core/templates/core/password_reset_form.html new file mode 100644 index 0000000..beb531f --- /dev/null +++ b/core/templates/core/password_reset_form.html @@ -0,0 +1,72 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Reset Password" %} | masarX{% endblock %} + +{% block content %} +
+
+
+
+
+
+
+ {% if platform_profile.logo %} + {{ platform_profile.name }} + {% else %} +

{{ platform_profile.name|default:"masarX" }}

+ {% endif %} +

{% trans "Reset Password" %}

+

{% trans "Enter your email address and we'll send you a link to reset your password." %}

+
+ +
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.errors %} +
{{ field.errors }}
+ {% endif %} +
+ {% endfor %} + +
+ + +
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/shipment_request.html b/core/templates/core/shipment_request.html index d86aa5b..93884bd 100644 --- a/core/templates/core/shipment_request.html +++ b/core/templates/core/shipment_request.html @@ -120,7 +120,17 @@
- {{ form.receiver_phone }} +
+
+ {{ form.receiver_phone_code }} +
+
+ {{ form.receiver_phone }} +
+
+ {% if form.receiver_phone_code.errors %} +
{{ form.receiver_phone_code.errors }}
+ {% endif %} {% if form.receiver_phone.errors %}
{{ form.receiver_phone.errors }}
{% endif %} diff --git a/core/templatetags/__pycache__/core_tags.cpython-311.pyc b/core/templatetags/__pycache__/core_tags.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa341480481c4d47d49bf53bafe634667a53e05e GIT binary patch literal 631 zcmZ8e%}V4z5bjQ9G}?#=%EAs_yy%Qzz>|k%o3 zCq?!p_7O%2tmGt!2QM-*H&0ea`!E)rs;{b3KVS9L!ooZw*?#^J|0DZg94pQTm>du| zXAz5d$}-;J++ZhlGPmP$<}lz%%_dzE_-h^3sY5-Pv2Nu4W}oh7quO`YtUsAwn!NNh zWWzMjKzMPVKwo0EkFp=95ZW@vn%P|XCT&vZOp~^faEmT10~@I9^aRwM?=@XC1ju4Fi=&bYtYv4 zv|Jv5?iyN#O?UWC2L+Wjt5IJ_ov}ypwGeCn#m24JxDl;Fv_`cS&e7do(<^79BsdvJ zG@MIe`fX)0f>eDBz8m*2z|+!07{p2gmh&p6Re~9!YK0!9R`t+4`K$Mh3gjuYafw{B zGj&L6>~hZUSYtGO?%4Wh`rM1gpPfQ9Zp3OKR?ob9K3DMNvBUZ1n2~yMnX@t#slwF+ EZzF1wMgRZ+ literal 0 HcmV?d00001 diff --git a/core/templatetags/core_tags.py b/core/templatetags/core_tags.py new file mode 100644 index 0000000..2dae0de --- /dev/null +++ b/core/templatetags/core_tags.py @@ -0,0 +1,8 @@ +from django import template +from core.models import PlatformProfile + +register = template.Library() + +@register.simple_tag +def get_platform_profile(): + return PlatformProfile.objects.first() diff --git a/core/urls.py b/core/urls.py index f5f57ce..b551e47 100644 --- a/core/urls.py +++ b/core/urls.py @@ -9,6 +9,27 @@ urlpatterns = [ path('register/', views.register, name='register'), path('register/verify/', views.verify_registration, name='verify_registration'), + # Password Reset URLs + path('password-reset/', auth_views.PasswordResetView.as_view( + template_name='core/password_reset_form.html', + email_template_name='core/emails/password_reset_email.html', + subject_template_name='core/emails/password_reset_subject.txt', + success_url='/password-reset/done/' + ), name='password_reset'), + + path('password-reset/done/', auth_views.PasswordResetDoneView.as_view( + template_name='core/password_reset_done.html' + ), name='password_reset_done'), + + path('reset///', auth_views.PasswordResetConfirmView.as_view( + template_name='core/password_reset_confirm.html', + success_url='/reset/done/' + ), name='password_reset_confirm'), + + path('reset/done/', auth_views.PasswordResetCompleteView.as_view( + template_name='core/password_reset_complete.html' + ), name='password_reset_complete'), + path('dashboard/', views.dashboard, name='dashboard'), path('shipment-request/', views.shipment_request, name='shipment_request'), path('accept-parcel//', views.accept_parcel, name='accept_parcel'), @@ -27,4 +48,4 @@ urlpatterns = [ path('profile/', views.profile_view, name='profile'), path('profile/edit/', views.edit_profile, name='edit_profile'), path('profile/verify-otp/', views.verify_otp_view, name='verify_otp'), -] +] \ No newline at end of file