From 24355ecdf5fd5314c8e514bfe394c1c92ac78727 Mon Sep 17 00:00:00 2001 From: Vivek Date: Tue, 9 Dec 2025 03:59:57 +0530 Subject: [PATCH] The updates for the customer dashboard cum accounts --- .DS_Store | Bin 0 -> 8196 bytes accounts/customer_forms.py | 62 ++++++ accounts/customer_views.py | 130 +++++++++++ accounts/forms.py | 7 +- ...ry_user_district_user_latitude_and_more.py | 48 ++++ accounts/migrations/0004_alter_user_role.py | 18 ++ accounts/migrations/0005_alter_user_role.py | 18 ++ accounts/models.py | 17 +- accounts/urls.py | 3 + accounts/views.py | 10 +- bookings/__init__.py | 0 bookings/admin.py | 3 + bookings/apps.py | 6 + bookings/migrations/__init__.py | 0 bookings/models.py | 3 + bookings/tests.py | 3 + bookings/views.py | 3 + eventify/settings.py | 3 + eventify/urls.py | 9 +- events/views.py | 6 + .../0002_eventtype_event_type_icon.py | 18 ++ master_data/models.py | 1 + mobile_web_api/urls.py | 1 + mobile_web_api/views/events.py | 15 ++ templates/.DS_Store | Bin 0 -> 6148 bytes templates/base.html | 8 +- templates/customer/.DS_Store | Bin 0 -> 6148 bytes templates/customer/base_auth.html | 86 ++++++++ templates/customer/base_dashboard.html | 205 ++++++++++++++++++ .../customer/cusotmer_email_verification.html | 17 ++ templates/customer/customer_dashboard.html | 203 +++++++++++++++++ templates/customer/customer_login.html | 35 +++ .../customer_password_confimation.html | 30 +++ .../customer/customer_password_reset.html | 23 ++ templates/customer/customer_registration.html | 49 +++++ templates/customer/email_password_reset.txt | 7 + .../customer/email_verification_body.txt | 10 + utils/date_convertor.py | 16 ++ 38 files changed, 1057 insertions(+), 16 deletions(-) create mode 100644 .DS_Store create mode 100644 accounts/customer_forms.py create mode 100644 accounts/customer_views.py create mode 100644 accounts/migrations/0003_user_country_user_district_user_latitude_and_more.py create mode 100644 accounts/migrations/0004_alter_user_role.py create mode 100644 accounts/migrations/0005_alter_user_role.py create mode 100644 bookings/__init__.py create mode 100644 bookings/admin.py create mode 100644 bookings/apps.py create mode 100644 bookings/migrations/__init__.py create mode 100644 bookings/models.py create mode 100644 bookings/tests.py create mode 100644 bookings/views.py create mode 100644 master_data/migrations/0002_eventtype_event_type_icon.py create mode 100644 templates/.DS_Store create mode 100644 templates/customer/.DS_Store create mode 100644 templates/customer/base_auth.html create mode 100644 templates/customer/base_dashboard.html create mode 100644 templates/customer/cusotmer_email_verification.html create mode 100644 templates/customer/customer_dashboard.html create mode 100644 templates/customer/customer_login.html create mode 100644 templates/customer/customer_password_confimation.html create mode 100644 templates/customer/customer_password_reset.html create mode 100644 templates/customer/customer_registration.html create mode 100644 templates/customer/email_password_reset.txt create mode 100644 templates/customer/email_verification_body.txt create mode 100644 utils/date_convertor.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ec4ca450142d2ace073932bdfac0fd8a9d49e51d GIT binary patch literal 8196 zcmeHM%We}f6unMMN&0~43)%{#h-{E5RlK&SA+&-DRjt~pYI%jp#0f(u<8)@4K34e! z*u#PyTefTvD>g{{07BviV9N@Q?I}$rtyqFuJF?G>eb1fonH!Jn5D_bEm9G+w5m6dd zqSc4ykRr!r?I@|ZXAsE1C)y&PA`0g`;dFSL4x@lkz$jo8FbWt2{s#r{&Xy&8#Cu_6gZIz$n_zMDls2pQ=?is(5Ni{up8Z~z{Wm6ak4SyV{B?vRK(P~2NqLVyv1Oe zj`6H?2j*jJYSeUMnocafGK+U8OkEwkta2xo*JxU!fKi}R0l9ZilT8jjqILgX@TbvEQ%i{eFx8+k}5FmBE;zkT%H!Cn8SQNhh0i=((q}Z z%JT%5DzuyMf06L-Y4KO*4+pVRa;a#&{|wbIV;{8&))du~(NjT!(We-py7PdFn2!zH zZojA0w*>19SOPP5F>4OiHrf)<#ueRmZr4(Ro1rC|rCT&Z^S~)YDkm5p5)6w1#NWg- zbd^=ZH+32Vc0@ZAwe!1QC-_50@{4r!h#1!rF?#Tfp$n=@PW$y#7QJf8q71w^=2eF} zlEDwnqrWg*r%~5WntdwOJvWT5T={BQEY;P0s^@e%)7#fSaOSKvI5a#sGB`RqdhYz# z_{7A8qBUHyosDWBw*2yK;fbK&ZSe=yz%4J=ZO8SiYZ2dxmfbyGw1yI{OT7H}plfBf z4xf>O4i)ZBz`3vDEmSHYkJei3^`dn)bOPadiz0L*SNQ7>!>3|T)Pt=j@5Jd?@g7vrHcO(IF>X%&mX9zCa*^oHKiNBTnF z=_fnQ#@Qt{$!@TlY?|F=bL>8I*i+`RXPTdi-{y2Nju!(9VH}bmjpe}igkQy|WDazu z5E69{$yaYrKDE5Grc!?gQ>JalQpV&m)5^nFQ;Y(~LxC 1: + user.last_name = parts[1] + user.email = self.cleaned_data["email"] + user.is_active = True # create inactive until email verified + if commit: + user.save() + return user + + +class CustomerLoginForm(AuthenticationForm): + """ + Wrapper around Django's AuthenticationForm to customize widgets. + """ + username = forms.CharField( + label="Email or Username", + widget=forms.TextInput(attrs={ + "autofocus": True, + "placeholder": "Email or username", + "class": "input", + "autocomplete": "username", + }) + ) + + password = forms.CharField( + label="Password", + strip=False, + widget=forms.PasswordInput(attrs={ + "placeholder": "Enter your password", + "class": "input", + "autocomplete": "current-password", + }) + ) \ No newline at end of file diff --git a/accounts/customer_views.py b/accounts/customer_views.py new file mode 100644 index 0000000..8f7ac6f --- /dev/null +++ b/accounts/customer_views.py @@ -0,0 +1,130 @@ + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth import login as auth_login +from django.contrib.auth.models import User +from django.contrib.sites.shortcuts import get_current_site +from django.shortcuts import render, redirect +from django.template.loader import render_to_string +from django.urls import reverse_lazy +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode +from django.utils.encoding import force_bytes, force_str +from django.views import View +from django.views.generic import FormView +from django.contrib.auth.tokens import default_token_generator + +from django.contrib.auth import authenticate, login +from django.shortcuts import render + +from .customer_forms import RegisterForm, CustomerLoginForm + +from django.contrib.auth import logout +from django.shortcuts import redirect +from django.contrib import messages + +from django.contrib.auth.decorators import login_required + +from master_data.models import EventType +from events.models import Event + +from django.db.models import Prefetch +from events.models import EventImages + +from django.forms.models import model_to_dict +# from utils.date_convertor import convert_date_to_dd_mm_yyyy + +def send_verification_email(request, user): + """ + Renders and sends verification email with activation link. + Requires EMAIL_BACKEND configured. + """ + current_site = get_current_site(request) + subject = "Verify your email for {}".format(current_site.name) + token = default_token_generator.make_token(user) + uid = urlsafe_base64_encode(force_bytes(user.pk)) + activate_path = reverse_lazy('accounts:login', kwargs={'uidb64': uid, 'token': token}) + activate_url = request.build_absolute_uri(activate_path) + message = render_to_string('auth/email_verification_email.txt', { + 'user': user, + 'activate_url': activate_url, + 'domain': current_site.domain, + }) + user.email_user(subject, message) + + +class RegisterView(FormView): + template_name = "customer/customer_registration.html" + form_class = RegisterForm + success_url = reverse_lazy('login') + + def form_valid(self, form): + user = form.save() + # send_verification_email(self.request, user) + messages.success(self.request, "Account created. Kindly login to continue.") + return super().form_valid(form) + + +class EmailVerificationSentView(View): + template_name = "auth/email_verification.html" + + def get(self, request): + return render(request, self.template_name) + + +class ActivateAccountView(View): + """ + Activation link view: sets user.is_active=True if token valid. + """ + def get(self, request, uidb64, token): + try: + uid = force_str(urlsafe_base64_decode(uidb64)) + user = User.objects.get(pk=uid) + except (TypeError, ValueError, OverflowError, User.DoesNotExist): + user = None + + if user is not None and default_token_generator.check_token(user, token): + user.is_active = True + user.save() + messages.success(request, "Email verified — you can now sign in.") + return redirect('login') + else: + return render(request, 'auth/activation_invalid.html') + + +def login_view(request): + if request.method == "POST": + form = CustomerLoginForm(request, data=request.POST) + if form.is_valid(): + user = form.get_user() + login(request, user) + return redirect('customer_dashboard') + else: + form = CustomerLoginForm(request) + return render(request, 'customer/customer_login.html', {'form': form}) + +@login_required(login_url="login") +def customer_dashboard(request): + event_types = EventType.objects.all() + + events = Event.objects.all() + + events_dict = [model_to_dict(obj) for obj in events] + + for event in events_dict: + event['event_image'] = EventImages.objects.get(event=event['id'], is_primary=True).event_image.url + # event['start_date'] = convert_date_to_dd_mm_yyyy(event['start_date']) + + print('*' * 10) + print(events_dict) + print('*' * 10) + + context = { + 'event_types': event_types, + 'events': events_dict, + } + return render(request, "customer/customer_dashboard.html", context) + +def logout_view(request): + logout(request) + messages.success(request, "You have been logged out successfully.") + return redirect("login") \ No newline at end of file diff --git a/accounts/forms.py b/accounts/forms.py index d379e4b..05cb27b 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -10,6 +10,11 @@ User = get_user_model() class UserForm(forms.ModelForm): + full_name = forms.CharField( + max_length=150, + required=True, + label="Full Name" + ) password = forms.CharField( widget=forms.PasswordInput, label="Password" @@ -39,7 +44,7 @@ class UserForm(forms.ModelForm): class Meta: model = User - fields = ["username", "email", "phone_number", "role", "password", "confirm_password"] + fields = ["username","full_name", "email", "phone_number", "role", "password", "confirm_password"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/accounts/migrations/0003_user_country_user_district_user_latitude_and_more.py b/accounts/migrations/0003_user_country_user_district_user_latitude_and_more.py new file mode 100644 index 0000000..59bb473 --- /dev/null +++ b/accounts/migrations/0003_user_country_user_district_user_latitude_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 5.0 on 2025-12-08 04:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_alter_user_managers_alter_user_role'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='country', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='user', + name='district', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='user', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='user', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='user', + name='pincode', + field=models.CharField(blank=True, max_length=10, null=True), + ), + migrations.AddField( + model_name='user', + name='place', + field=models.CharField(blank=True, max_length=200, null=True), + ), + migrations.AddField( + model_name='user', + name='state', + field=models.CharField(blank=True, max_length=100, null=True), + ), + ] diff --git a/accounts/migrations/0004_alter_user_role.py b/accounts/migrations/0004_alter_user_role.py new file mode 100644 index 0000000..0d278f3 --- /dev/null +++ b/accounts/migrations/0004_alter_user_role.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-12-08 20:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0003_user_country_user_district_user_latitude_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField(choices=[('Admin', 'admin'), ('Manager', 'manager'), ('Staff', 'staff')], default='Staff', max_length=20), + ), + ] diff --git a/accounts/migrations/0005_alter_user_role.py b/accounts/migrations/0005_alter_user_role.py new file mode 100644 index 0000000..766940a --- /dev/null +++ b/accounts/migrations/0005_alter_user_role.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-12-08 21:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0004_alter_user_role'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField(choices=[('admin', 'Admin'), ('manager', 'Manager'), ('staff', 'Staff')], default='Staff', max_length=20), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index d8b16c1..9cc34c3 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -4,9 +4,9 @@ from django.db import models from accounts.manager import UserManager ROLE_CHOICES = ( - ('Admin', 'Admin'), - ('Manager', 'Manager'), - ('Staff', 'Staff'), + ('admin', 'Admin'), + ('manager', 'Manager'), + ('staff', 'Staff'), ) @@ -18,6 +18,17 @@ class User(AbstractUser): is_customer = models.BooleanField(default=False) is_user = models.BooleanField(default=False) + # Location fields + pincode = models.CharField(max_length=10, blank=True, null=True) + district = models.CharField(max_length=100, blank=True, null=True) + state = models.CharField(max_length=100, blank=True, null=True) + country = models.CharField(max_length=100, blank=True, null=True) + place = models.CharField(max_length=200, blank=True, null=True) + + # Location fields + latitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True) + longitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True) + objects = UserManager() def __str__(self): diff --git a/accounts/urls.py b/accounts/urls.py index b86e741..28dd2ed 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -4,6 +4,9 @@ from . import views app_name = 'accounts' urlpatterns = [ + path('login/', views.login_view, name='login'), + path('logout/', views.logout_view, name='logout'), + path('dashboard/', views.dashboard, name='dashboard'), path('users/', views.UserListView.as_view(), name='user_list'), path('users/add/', views.UserCreateView.as_view(), name='user_add'), path('users//edit/', views.UserUpdateView.as_view(), name='user_edit'), diff --git a/accounts/views.py b/accounts/views.py index 31c6dde..5cf344e 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -53,7 +53,7 @@ class UserDeleteView(LoginRequiredMixin, generic.DeleteView): def login_view(request): if request.user.is_authenticated: - return redirect("dashboard") # Redirect authenticated user + return redirect("accounts:dashboard") # Redirect authenticated user form = LoginForm(request, data=request.POST or None) @@ -61,7 +61,10 @@ def login_view(request): if form.is_valid(): user = form.get_user() login(request, user) - return redirect("dashboard") + if user.role == 'admin' or user.role == 'manager' or user.role == 'staff': + return redirect("accounts:dashboard") + else: + messages.error(request, "You are not authorized to access this page.") else: messages.error(request, "Invalid username or password") @@ -70,4 +73,5 @@ def login_view(request): def logout_view(request): logout(request) - return redirect("login") + messages.success(request, "You have been logged out successfully.") + return redirect("accounts:login") \ No newline at end of file diff --git a/bookings/__init__.py b/bookings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bookings/admin.py b/bookings/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/bookings/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/bookings/apps.py b/bookings/apps.py new file mode 100644 index 0000000..2b3d34c --- /dev/null +++ b/bookings/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BookingsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'bookings' diff --git a/bookings/migrations/__init__.py b/bookings/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bookings/models.py b/bookings/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/bookings/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/bookings/tests.py b/bookings/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/bookings/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/bookings/views.py b/bookings/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/bookings/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/eventify/settings.py b/eventify/settings.py index 4f7c707..b368aeb 100644 --- a/eventify/settings.py +++ b/eventify/settings.py @@ -104,3 +104,6 @@ AUTH_USER_MODEL = 'accounts.User' LOGIN_URL = 'login' LOGIN_REDIRECT_URL = 'dashboard' LOGOUT_REDIRECT_URL = 'login' + +# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +# DEFAULT_FROM_EMAIL = 'no-reply@example.com' \ No newline at end of file diff --git a/eventify/urls.py b/eventify/urls.py index 07f069f..926eb7c 100644 --- a/eventify/urls.py +++ b/eventify/urls.py @@ -2,18 +2,17 @@ from django.contrib import admin from django.urls import path, include from django.contrib.auth import views as auth_views from accounts.views import dashboard -from accounts.views import login_view -from accounts.views import logout_view +from accounts.customer_views import RegisterView +from accounts.customer_views import login_view, logout_view, customer_dashboard from django.conf.urls.static import static from django.conf import settings urlpatterns = [ path('admin/', admin.site.urls), - # path('', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'), - # path('logout/', auth_views.LogoutView.as_view(), name='logout'), path("", login_view, name="login"), path("logout/", logout_view, name="logout"), - path('dashboard/', dashboard, name='dashboard'), + path("register/", RegisterView.as_view(), name="register"), + path('dashboard/', customer_dashboard, name='customer_dashboard'), path('master-data/', include('master_data.urls')), path('events/', include('events.urls')), diff --git a/events/views.py b/events/views.py index 9b9b271..4a3711b 100644 --- a/events/views.py +++ b/events/views.py @@ -10,6 +10,10 @@ from django.shortcuts import get_object_or_404, redirect, render from django.contrib.auth.decorators import login_required +from django.http import JsonResponse +from .models import Event + + class EventListView(LoginRequiredMixin, generic.ListView): model = Event context_object_name = 'events' @@ -89,3 +93,5 @@ def delete_event_image(request, pk, img_id): image.delete() messages.success(request, "Image deleted!") return redirect("events:event_images", pk=pk) + + diff --git a/master_data/migrations/0002_eventtype_event_type_icon.py b/master_data/migrations/0002_eventtype_event_type_icon.py new file mode 100644 index 0000000..095bc46 --- /dev/null +++ b/master_data/migrations/0002_eventtype_event_type_icon.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-12-08 16:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('master_data', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='eventtype', + name='event_type_icon', + field=models.ImageField(blank=True, null=True, upload_to='event_type_icons/'), + ), + ] diff --git a/master_data/models.py b/master_data/models.py index 8fab712..78af328 100644 --- a/master_data/models.py +++ b/master_data/models.py @@ -2,6 +2,7 @@ from django.db import models class EventType(models.Model): event_type = models.CharField(max_length=50, null=False, blank=False) + event_type_icon = models.ImageField(upload_to='event_type_icons/', null=True, blank=True) def __str__(self): return self.event_type diff --git a/mobile_web_api/urls.py b/mobile_web_api/urls.py index 5419a7a..864b27d 100644 --- a/mobile_web_api/urls.py +++ b/mobile_web_api/urls.py @@ -17,4 +17,5 @@ urlpatterns += [ path('events/pincode-events/', EventListAPI.as_view()), path('events/event-details/', EventDetailAPI.as_view()), path('events/event-images/', EventImagesListAPI.as_view()), + path('events/events-by-category//', api_events_by_category, name='api_events_by_category'), ] diff --git a/mobile_web_api/views/events.py b/mobile_web_api/views/events.py index 37c35c2..fc7ce50 100644 --- a/mobile_web_api/views/events.py +++ b/mobile_web_api/views/events.py @@ -10,6 +10,8 @@ from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt import json +from django.contrib.auth.decorators import login_required + @method_decorator(csrf_exempt, name='dispatch') class EventTypeListAPIView(APIView): @@ -214,3 +216,16 @@ class EventImagesListAPI(APIView): return JsonResponse( {"status": "error", "message": str(e)}, ) + + +@login_required(login_url="login") +def api_events_by_category(request, slug): + events = Event.objects.filter(event_type=slug) + + events_dict = [model_to_dict(obj) for obj in events] + + for event in events_dict: + event['event_image'] = EventImages.objects.get(event=event['id'], is_primary=True).event_image.url + # event['start_date'] = convert_date_to_dd_mm_yyyy(event['start_date']) + + return JsonResponse({"events": events_dict}) \ No newline at end of file diff --git a/templates/.DS_Store b/templates/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c32632cd8d34b59d7fb287724507683681767a89 GIT binary patch literal 6148 zcmeHK&2G~`5S~c`O~3e;A< zD^ic2g(^4@fq)fg(s^%z&nLdm;>|IO40-R6MNDh7$Sxez$EDxtdv3R_{o{vf+xMb) zb@j7lVQM6O=Im%DJ2rmq{KTX+H9a#mJ2f{qcj4mv!s6nkiZxSp+}%bfJ3;NH^krD^ zJK|v@^lGKH<9b1(9E<01$vY4gYx8x}+7mp4i)HvnbjYVMI7&Q{HjH&aV1 zs>0t03Nnsmi#6GsD}&8Yu&1&|JntpH_Eo(o$lG@hnJb#HAg>v^(`OGkgP4C;|ETz_ zKn5b4&D)zsl!0Zq0ry}VcHsbCz$z&l7tAgDWNt+^NE4loaS>z&o