diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ec4ca45 Binary files /dev/null and b/.DS_Store differ diff --git a/accounts/customer_forms.py b/accounts/customer_forms.py new file mode 100644 index 0000000..6a8bb79 --- /dev/null +++ b/accounts/customer_forms.py @@ -0,0 +1,62 @@ +from django import forms +from django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.forms import AuthenticationForm + +from django.contrib.auth import get_user_model + + +class RegisterForm(UserCreationForm): + full_name = forms.CharField(max_length=150, required=False, label="Full name") + email = forms.EmailField(required=True, label="Email") + + class Meta: + model = get_user_model() + fields = ("username", "full_name", "email", "password1", "password2") + + def clean_email(self): + email = self.cleaned_data.get("email") + user = get_user_model() + if user.objects.filter(email__iexact=email).exists(): + raise forms.ValidationError("A user with that email already exists.") + return email + + def save(self, commit=True): + user = super().save(commit=False) + # Save full name to first_name/last_name if possible + full_name = self.cleaned_data.get("full_name", "").strip() + if full_name: + parts = full_name.split(None, 1) + user.first_name = parts[0] + if len(parts) > 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 0000000..c32632c Binary files /dev/null and b/templates/.DS_Store differ diff --git a/templates/base.html b/templates/base.html index 7495244..77f4e9a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -9,7 +9,7 @@