The updates for the customer dashboard cum accounts
This commit is contained in:
62
accounts/customer_forms.py
Normal file
62
accounts/customer_forms.py
Normal file
@@ -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",
|
||||||
|
})
|
||||||
|
)
|
||||||
130
accounts/customer_views.py
Normal file
130
accounts/customer_views.py
Normal file
@@ -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")
|
||||||
@@ -10,6 +10,11 @@ User = get_user_model()
|
|||||||
|
|
||||||
|
|
||||||
class UserForm(forms.ModelForm):
|
class UserForm(forms.ModelForm):
|
||||||
|
full_name = forms.CharField(
|
||||||
|
max_length=150,
|
||||||
|
required=True,
|
||||||
|
label="Full Name"
|
||||||
|
)
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput,
|
||||||
label="Password"
|
label="Password"
|
||||||
@@ -39,7 +44,7 @@ class UserForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
accounts/migrations/0004_alter_user_role.py
Normal file
18
accounts/migrations/0004_alter_user_role.py
Normal file
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
accounts/migrations/0005_alter_user_role.py
Normal file
18
accounts/migrations/0005_alter_user_role.py
Normal file
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -4,9 +4,9 @@ from django.db import models
|
|||||||
from accounts.manager import UserManager
|
from accounts.manager import UserManager
|
||||||
|
|
||||||
ROLE_CHOICES = (
|
ROLE_CHOICES = (
|
||||||
('Admin', 'Admin'),
|
('admin', 'Admin'),
|
||||||
('Manager', 'Manager'),
|
('manager', 'Manager'),
|
||||||
('Staff', 'Staff'),
|
('staff', 'Staff'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -18,6 +18,17 @@ class User(AbstractUser):
|
|||||||
is_customer = models.BooleanField(default=False)
|
is_customer = models.BooleanField(default=False)
|
||||||
is_user = 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()
|
objects = UserManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ from . import views
|
|||||||
app_name = 'accounts'
|
app_name = 'accounts'
|
||||||
|
|
||||||
urlpatterns = [
|
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/', views.UserListView.as_view(), name='user_list'),
|
||||||
path('users/add/', views.UserCreateView.as_view(), name='user_add'),
|
path('users/add/', views.UserCreateView.as_view(), name='user_add'),
|
||||||
path('users/<int:pk>/edit/', views.UserUpdateView.as_view(), name='user_edit'),
|
path('users/<int:pk>/edit/', views.UserUpdateView.as_view(), name='user_edit'),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class UserDeleteView(LoginRequiredMixin, generic.DeleteView):
|
|||||||
|
|
||||||
def login_view(request):
|
def login_view(request):
|
||||||
if request.user.is_authenticated:
|
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)
|
form = LoginForm(request, data=request.POST or None)
|
||||||
|
|
||||||
@@ -61,7 +61,10 @@ def login_view(request):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.get_user()
|
user = form.get_user()
|
||||||
login(request, 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:
|
else:
|
||||||
messages.error(request, "Invalid username or password")
|
messages.error(request, "Invalid username or password")
|
||||||
|
|
||||||
@@ -70,4 +73,5 @@ def login_view(request):
|
|||||||
|
|
||||||
def logout_view(request):
|
def logout_view(request):
|
||||||
logout(request)
|
logout(request)
|
||||||
return redirect("login")
|
messages.success(request, "You have been logged out successfully.")
|
||||||
|
return redirect("accounts:login")
|
||||||
0
bookings/__init__.py
Normal file
0
bookings/__init__.py
Normal file
3
bookings/admin.py
Normal file
3
bookings/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
bookings/apps.py
Normal file
6
bookings/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BookingsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'bookings'
|
||||||
0
bookings/migrations/__init__.py
Normal file
0
bookings/migrations/__init__.py
Normal file
3
bookings/models.py
Normal file
3
bookings/models.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
bookings/tests.py
Normal file
3
bookings/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
bookings/views.py
Normal file
3
bookings/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -104,3 +104,6 @@ AUTH_USER_MODEL = 'accounts.User'
|
|||||||
LOGIN_URL = 'login'
|
LOGIN_URL = 'login'
|
||||||
LOGIN_REDIRECT_URL = 'dashboard'
|
LOGIN_REDIRECT_URL = 'dashboard'
|
||||||
LOGOUT_REDIRECT_URL = 'login'
|
LOGOUT_REDIRECT_URL = 'login'
|
||||||
|
|
||||||
|
# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
# DEFAULT_FROM_EMAIL = 'no-reply@example.com'
|
||||||
@@ -2,18 +2,17 @@ from django.contrib import admin
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from accounts.views import dashboard
|
from accounts.views import dashboard
|
||||||
from accounts.views import login_view
|
from accounts.customer_views import RegisterView
|
||||||
from accounts.views import logout_view
|
from accounts.customer_views import login_view, logout_view, customer_dashboard
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
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("", login_view, name="login"),
|
||||||
path("logout/", logout_view, name="logout"),
|
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('master-data/', include('master_data.urls')),
|
||||||
path('events/', include('events.urls')),
|
path('events/', include('events.urls')),
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ from django.shortcuts import get_object_or_404, redirect, render
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from .models import Event
|
||||||
|
|
||||||
|
|
||||||
class EventListView(LoginRequiredMixin, generic.ListView):
|
class EventListView(LoginRequiredMixin, generic.ListView):
|
||||||
model = Event
|
model = Event
|
||||||
context_object_name = 'events'
|
context_object_name = 'events'
|
||||||
@@ -89,3 +93,5 @@ def delete_event_image(request, pk, img_id):
|
|||||||
image.delete()
|
image.delete()
|
||||||
messages.success(request, "Image deleted!")
|
messages.success(request, "Image deleted!")
|
||||||
return redirect("events:event_images", pk=pk)
|
return redirect("events:event_images", pk=pk)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
18
master_data/migrations/0002_eventtype_event_type_icon.py
Normal file
18
master_data/migrations/0002_eventtype_event_type_icon.py
Normal file
@@ -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/'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -2,6 +2,7 @@ from django.db import models
|
|||||||
|
|
||||||
class EventType(models.Model):
|
class EventType(models.Model):
|
||||||
event_type = models.CharField(max_length=50, null=False, blank=False)
|
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):
|
def __str__(self):
|
||||||
return self.event_type
|
return self.event_type
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ urlpatterns += [
|
|||||||
path('events/pincode-events/', EventListAPI.as_view()),
|
path('events/pincode-events/', EventListAPI.as_view()),
|
||||||
path('events/event-details/', EventDetailAPI.as_view()),
|
path('events/event-details/', EventDetailAPI.as_view()),
|
||||||
path('events/event-images/', EventImagesListAPI.as_view()),
|
path('events/event-images/', EventImagesListAPI.as_view()),
|
||||||
|
path('events/events-by-category/<int:slug>/', api_events_by_category, name='api_events_by_category'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
class EventTypeListAPIView(APIView):
|
class EventTypeListAPIView(APIView):
|
||||||
@@ -214,3 +216,16 @@ class EventImagesListAPI(APIView):
|
|||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{"status": "error", "message": str(e)},
|
{"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})
|
||||||
BIN
templates/.DS_Store
vendored
Normal file
BIN
templates/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -9,7 +9,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{% url 'dashboard' %}">Eventify</a>
|
<a class="navbar-brand" href="{% url 'accounts:dashboard' %}">Eventify</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navmenu">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navmenu">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<!-- Accessible by Admin, Manager, Staff -->
|
<!-- Accessible by Admin, Manager, Staff -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'dashboard' %}">Dashboard</a>
|
<a class="nav-link" href="{% url 'accounts:dashboard' %}">Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if user.role == "admin" or user.role == "manager" %}
|
{% if user.role == "admin" or user.role == "manager" %}
|
||||||
@@ -46,9 +46,9 @@
|
|||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li class="nav-item"><a class="nav-link" href="#">{{ user.username }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="#">{{ user.username }}</a></li>
|
||||||
<li class="nav-item"><a class="nav-link text-danger" href="{% url 'logout' %}">Logout</a></li>
|
<li class="nav-item"><a class="nav-link text-danger" href="{% url 'accounts:logout' %}">Logout</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Login</a></li>
|
<li class="nav-item"><a class="nav-link" href="{% url 'accounts:login' %}">Login</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
templates/customer/.DS_Store
vendored
Normal file
BIN
templates/customer/.DS_Store
vendored
Normal file
Binary file not shown.
86
templates/customer/base_auth.html
Normal file
86
templates/customer/base_auth.html
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||||
|
<title>{% block title %}Auth{% endblock %}</title>
|
||||||
|
<style>
|
||||||
|
/* GLOBAL */
|
||||||
|
:root{
|
||||||
|
--blue1:#1e3cfa;
|
||||||
|
--blue2:#3c63ff;
|
||||||
|
--muted:#f4f6ff;
|
||||||
|
--card-bg:#fff;
|
||||||
|
--accent: #3c63ff;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;font-family:Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;background:var(--muted);color:#111}
|
||||||
|
.auth-wrapper{display:flex;min-height:100vh}
|
||||||
|
.auth-left{
|
||||||
|
width:40%;
|
||||||
|
min-width:320px;
|
||||||
|
background:linear-gradient(180deg,var(--blue1),var(--blue2));
|
||||||
|
color:#fff;padding:48px;display:flex;flex-direction:column;justify-content:center;gap:10px;
|
||||||
|
}
|
||||||
|
.brand{font-weight:700;font-size:28px}
|
||||||
|
.auth-left h1{font-size:36px;margin:0}
|
||||||
|
.auth-left p{opacity:.92;margin:0;font-size:16px}
|
||||||
|
|
||||||
|
.auth-right{flex:1;display:flex;align-items:center;justify-content:center;padding:30px}
|
||||||
|
.auth-card{width:100%;max-width:480px;background:var(--card-bg);border-radius:16px;padding:36px;box-shadow:0 10px 30px rgba(16,24,40,0.08)}
|
||||||
|
|
||||||
|
h2{margin:0 0 6px 0;font-size:24px}
|
||||||
|
.subtitle{color:#666;font-size:14px;margin-bottom:18px}
|
||||||
|
|
||||||
|
.input-group{margin-bottom:14px}
|
||||||
|
.input-group label{display:block;font-size:13px;margin-bottom:8px;color:#333}
|
||||||
|
.input-group input{width:100%;padding:12px;border-radius:10px;border:1.5px solid #e6eefc;background:#f7f9ff;outline:none;transition:.15s}
|
||||||
|
.input-group input:focus{border-color:var(--accent);background:#fff;box-shadow:0 0 0 4px rgba(60,99,255,0.06)}
|
||||||
|
|
||||||
|
.btn-primary{display:inline-block;width:100%;padding:12px;border-radius:10px;border:none;background:var(--accent);color:#fff;font-weight:600;cursor:pointer}
|
||||||
|
.btn-primary:hover{filter:brightness(.95)}
|
||||||
|
|
||||||
|
.form-links, .redirect{text-align:right;margin-top:10px;font-size:13px}
|
||||||
|
a.link{color:var(--accent);text-decoration:none}
|
||||||
|
.redirect{text-align:center;font-size:14px;margin-top:18px}
|
||||||
|
|
||||||
|
/* messages */
|
||||||
|
.messages{margin-bottom:12px}
|
||||||
|
.message{padding:10px;border-radius:8px;margin-bottom:8px;font-size:14px}
|
||||||
|
.message.info{background:#eef5ff;color:#024}
|
||||||
|
.message.success{background:#ecfdf5;color:#064}
|
||||||
|
.message.warning{background:#fff7e6;color:#7a4b00}
|
||||||
|
.errorlist{color:#b00020;margin:6px 0 0 0;font-size:13px}
|
||||||
|
|
||||||
|
/* responsive */
|
||||||
|
@media (max-width:900px){
|
||||||
|
.auth-wrapper{flex-direction:column}
|
||||||
|
.auth-left{width:100%;min-height:180px;padding:28px;text-align:center}
|
||||||
|
.auth-right{padding:20px}
|
||||||
|
.auth-card{border-radius:14px;padding:28px}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="auth-wrapper">
|
||||||
|
<div class="auth-left">
|
||||||
|
<div class="brand">Eventify</div>
|
||||||
|
<p>{% block left_subtext %}Your events at your fingertips.{% endblock %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="auth-right">
|
||||||
|
<div class="auth-card">
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for m in messages %}
|
||||||
|
<div class="message {{ m.tags }}">{{ m }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
205
templates/customer/base_dashboard.html
Normal file
205
templates/customer/base_dashboard.html
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
||||||
|
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}Dashboard{% endblock %}</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
background: #f5f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SIDEBAR */
|
||||||
|
.sidebar {
|
||||||
|
width: 260px;
|
||||||
|
background: linear-gradient(180deg, #1e3cfa, #1436c7);
|
||||||
|
color: white;
|
||||||
|
padding: 30px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover,
|
||||||
|
.nav-item.active {
|
||||||
|
background: white;
|
||||||
|
color: #1436c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-nav {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAIN CONTENT AREA */
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
background: #f5f7ff;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOPBAR */
|
||||||
|
.topbar {
|
||||||
|
background: white;
|
||||||
|
padding: 20px 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid #e6e9f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
width: 350px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1.5px solid #dfe3f8;
|
||||||
|
border-radius: 12px;
|
||||||
|
outline: none;
|
||||||
|
background: #f8faff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-section {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e6e6e6;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DROPDOWN MENU */
|
||||||
|
.dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 55px;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
width: 160px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 12px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover {
|
||||||
|
background: #f1f4ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="layout">
|
||||||
|
|
||||||
|
<!-- SIDEBAR -->
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="logo">EVENTIFY</div>
|
||||||
|
|
||||||
|
<div class="nav">
|
||||||
|
<div class="nav-item active">🏠 Home</div>
|
||||||
|
<div class="nav-item">📅 Calendar</div>
|
||||||
|
<div class="nav-item">👤 Profile</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-nav">
|
||||||
|
<div class="nav-item">❓ Help</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MAIN CONTENT -->
|
||||||
|
<div class="content">
|
||||||
|
<div class="topbar">
|
||||||
|
<div class="search-box">
|
||||||
|
<input type="text" placeholder="Search">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-section" id="profileDropdownBtn">
|
||||||
|
<div class="profile-avatar">
|
||||||
|
<i class="fa fa-user"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DROPDOWN -->
|
||||||
|
<div class="dropdown-menu" id="dropdownMenu">
|
||||||
|
<div class="dropdown-item" onclick="window.location.href='{% url 'logout' %}'">Logout</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const dropdownBtn = document.getElementById("profileDropdownBtn");
|
||||||
|
const dropdownMenu = document.getElementById("dropdownMenu");
|
||||||
|
|
||||||
|
dropdownBtn.addEventListener("click", () => {
|
||||||
|
dropdownMenu.style.display =
|
||||||
|
dropdownMenu.style.display === "flex" ? "none" : "flex";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close dropdown when clicking outside
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (!dropdownBtn.contains(e.target)) {
|
||||||
|
dropdownMenu.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
17
templates/customer/cusotmer_email_verification.html
Normal file
17
templates/customer/cusotmer_email_verification.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "base_auth.html" %}
|
||||||
|
{% block title %}Verify Email{% endblock %}
|
||||||
|
{% block left_heading %}Verify your email{% endblock %}
|
||||||
|
{% block left_subtext %}A verification link has been sent to your email{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Verify Your Email</h2>
|
||||||
|
<p class="subtitle">Please check your inbox and click the verification link</p>
|
||||||
|
|
||||||
|
<div style="font-size:14px;color:#444;margin-top:12px;">
|
||||||
|
If you don't receive the email within a few minutes, check your spam folder.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="redirect">
|
||||||
|
<a class="link" href="{% url 'myapp:login' %}">Back to Login</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
203
templates/customer/customer_dashboard.html
Normal file
203
templates/customer/customer_dashboard.html
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
{% extends "customer/base_dashboard.html" %}
|
||||||
|
{% block title %}Dashboard{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.welcome-box {
|
||||||
|
background: #3c63ff;
|
||||||
|
color: white;
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 25px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-cards-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-card {
|
||||||
|
background: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 14px;
|
||||||
|
width: 280px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background: gray;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categories {
|
||||||
|
margin: 30px 0 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat {
|
||||||
|
background: #f1f3ff;
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat.active {
|
||||||
|
background: #3c63ff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 22px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
height: 170px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 8px 0 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-details {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-right: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-container {
|
||||||
|
padding: 30px 40px;
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="dashboard-container">
|
||||||
|
<!-- WELCOME BANNER -->
|
||||||
|
<div class="welcome-box">
|
||||||
|
<div class="welcome-text">
|
||||||
|
<p>Welcome Back,</p>
|
||||||
|
<h2>{{ request.user.first_name|default:"Jane Doe" }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Events Around You</h3>
|
||||||
|
|
||||||
|
<!-- CATEGORY LIST -->
|
||||||
|
<div class="categories">
|
||||||
|
{% for category in event_types %}
|
||||||
|
<div class="cat" data-slug="{{ category.id }}">
|
||||||
|
{% if category.event_type_icon %}
|
||||||
|
<img src="{{ category.event_type_icon.url }}" alt="{{ category.event_type }} icon" class="cat-icon">
|
||||||
|
{% endif %}
|
||||||
|
{{ category.event_type }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EVENT GRID -->
|
||||||
|
<div id="event-grid" class="event-grid">
|
||||||
|
{% for event in events %}
|
||||||
|
<div class="card">
|
||||||
|
|
||||||
|
{% if event.event_image %}
|
||||||
|
<img src="{{ event.event_image }}" alt="">
|
||||||
|
{% else %}
|
||||||
|
<img src="/static/default.jpg" alt="">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="card-title">{{ event.title }}</div>
|
||||||
|
<div class="card-details">
|
||||||
|
📅 {{ mydate|date:"d-m-Y" }}<br>
|
||||||
|
📍 {{ event.place }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
|
||||||
|
const categories = document.querySelectorAll(".cat");
|
||||||
|
const eventGrid = document.getElementById("event-grid");
|
||||||
|
|
||||||
|
categories.forEach(cat => {
|
||||||
|
cat.addEventListener("click", function () {
|
||||||
|
|
||||||
|
categories.forEach(c => c.classList.remove("active"));
|
||||||
|
this.classList.add("active");
|
||||||
|
|
||||||
|
const slug = this.getAttribute("data-slug");
|
||||||
|
const url = `/api/events/events-by-category/${slug}/`;
|
||||||
|
|
||||||
|
eventGrid.innerHTML = "<p>Loading...</p>";
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
eventGrid.innerHTML = "";
|
||||||
|
|
||||||
|
if (data.events.length === 0) {
|
||||||
|
eventGrid.innerHTML = "<p>No events found for this category.</p>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.events.forEach(event => {
|
||||||
|
const card = `
|
||||||
|
<div class="card">
|
||||||
|
<img src="${event.event_image}" alt="">
|
||||||
|
<div class="card-title">${event.title}</div>
|
||||||
|
<div class="card-details">
|
||||||
|
📅 ${event.start_date}<br>
|
||||||
|
📍 ${event.place}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
eventGrid.innerHTML += card;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
35
templates/customer/customer_login.html
Normal file
35
templates/customer/customer_login.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{% extends "customer/base_auth.html" %}
|
||||||
|
{% block title %}Login{% endblock %}
|
||||||
|
{% block left_heading %}Welcome Back{% endblock %}
|
||||||
|
{% block left_subtext %}Login to access your events dashboard{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Sign In</h2>
|
||||||
|
<p class="subtitle">Enter your credentials to continue</p>
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="id_username">Email or Username</label>
|
||||||
|
<input id="id_username" name="username" type="text" required>
|
||||||
|
{% if form.username.errors %}
|
||||||
|
<div class="errorlist">{{ form.username.errors }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="id_password">Password</label>
|
||||||
|
<input id="id_password" name="password" type="password" required>
|
||||||
|
{% if form.password.errors %}
|
||||||
|
<div class="errorlist">{{ form.password.errors }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button class="btn-primary" type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="redirect">
|
||||||
|
Don't have an account? <a class="link" href="{% url 'register' %}">Sign Up</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
30
templates/customer/customer_password_confimation.html
Normal file
30
templates/customer/customer_password_confimation.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{% extends "base_auth.html" %}
|
||||||
|
{% block title %}Choose New Password{% endblock %}
|
||||||
|
{% block left_heading %}Choose new password{% endblock %}
|
||||||
|
{% block left_subtext %}Create a new secure password for your account{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Reset Password</h2>
|
||||||
|
<p class="subtitle">Enter your new password</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.new_password1.label_tag }}
|
||||||
|
{{ form.new_password1 }}
|
||||||
|
{% if form.new_password1.errors %}<div class="errorlist">{{ form.new_password1.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.new_password2.label_tag }}
|
||||||
|
{{ form.new_password2 }}
|
||||||
|
{% if form.new_password2.errors %}<div class="errorlist">{{ form.new_password2.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn-primary" type="submit">Reset Password</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="redirect">
|
||||||
|
Back to <a class="link" href="{% url 'login' %}">Login</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
23
templates/customer/customer_password_reset.html
Normal file
23
templates/customer/customer_password_reset.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{% extends "auth/base_auth.html" %}
|
||||||
|
{% block title %}Forgot Password{% endblock %}
|
||||||
|
{% block left_heading %}Reset Password{% endblock %}
|
||||||
|
{% block left_subtext %}Enter your email to receive a reset link{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Forgot Password</h2>
|
||||||
|
<p class="subtitle">Enter your email to receive a reset link</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="id_email">Email</label>
|
||||||
|
<input id="id_email" name="email" type="email" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn-primary" type="submit">Send Reset Link</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="redirect">
|
||||||
|
Remember your password? <a class="link" href="{% url 'login' %}">Login</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
49
templates/customer/customer_registration.html
Normal file
49
templates/customer/customer_registration.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% extends "customer/base_auth.html" %}
|
||||||
|
{% block title %}Register{% endblock %}
|
||||||
|
{% block left_heading %}Create Account{% endblock %}
|
||||||
|
{% block left_subtext %}Join and explore exciting events{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Create Account</h2>
|
||||||
|
<p class="subtitle">Join and explore exciting events</p>
|
||||||
|
|
||||||
|
<form method="post" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.full_name.label_tag }}
|
||||||
|
{{ form.full_name }}
|
||||||
|
{% if form.full_name.errors %}<div class="errorlist">{{ form.full_name.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.email.label_tag }}
|
||||||
|
{{ form.email }}
|
||||||
|
{% if form.email.errors %}<div class="errorlist">{{ form.email.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.username.label_tag }}
|
||||||
|
{{ form.username }}
|
||||||
|
{% if form.username.errors %}<div class="errorlist">{{ form.username.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.password1.label_tag }}
|
||||||
|
{{ form.password1 }}
|
||||||
|
{% if form.password1.errors %}<div class="errorlist">{{ form.password1.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
{{ form.password2.label_tag }}
|
||||||
|
{{ form.password2 }}
|
||||||
|
{% if form.password2.errors %}<div class="errorlist">{{ form.password2.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn-primary" type="submit">Create Account</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="redirect">
|
||||||
|
Already have an account? <a class="link" href="{% url 'login' %}">Login</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
7
templates/customer/email_password_reset.txt
Normal file
7
templates/customer/email_password_reset.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
You're receiving this email because a password reset was requested for your account.
|
||||||
|
|
||||||
|
Please go to the following page and choose a new password:
|
||||||
|
|
||||||
|
{{ protocol }}://{{ domain }}{% url 'myapp:password_reset_confirm' uidb64=uid token=token %}
|
||||||
|
|
||||||
|
If you didn't request a password reset, please ignore this message.
|
||||||
10
templates/customer/email_verification_body.txt
Normal file
10
templates/customer/email_verification_body.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Hi {{ user.get_full_name|default: user.username }},
|
||||||
|
|
||||||
|
Thanks for signing up. Please verify your email address by clicking the link below:
|
||||||
|
|
||||||
|
{{ activate_url }}
|
||||||
|
|
||||||
|
If you did not sign up, ignore this email.
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
{{ domain }}
|
||||||
16
utils/date_convertor.py
Normal file
16
utils/date_convertor.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def convert_date_to_dd_mm_yyyy(date_str):
|
||||||
|
if isinstance(date_str, datetime.date):
|
||||||
|
return date_str.strftime("%d-%m-%Y")
|
||||||
|
|
||||||
|
# If the input is a string → parse it as YYYY-MM-DD
|
||||||
|
if isinstance(date_str, str):
|
||||||
|
try:
|
||||||
|
dt = datetime.strptime(date_str, "%Y-%m-%d")
|
||||||
|
return dt.strftime("%d-%m-%Y")
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("String date must be in 'YYYY-MM-DD' format")
|
||||||
|
|
||||||
|
# Unsupported type
|
||||||
|
raise TypeError("Input must be a string or datetime.date object")
|
||||||
Reference in New Issue
Block a user