From 105da4a876ad395adb97e74708cf6773ce6edb3f Mon Sep 17 00:00:00 2001 From: Vivek Date: Wed, 17 Dec 2025 22:05:13 +0530 Subject: [PATCH] Reverting back to admin pages as login and updates in the mobile api --- .DS_Store | Bin 8196 -> 8196 bytes API_Documentation.md | 889 ++++++++++++++++++ accounts/customer_forms.py | 38 +- accounts/customer_views.py | 29 +- db_reset.py | 40 + eventify/settings.py | 40 +- eventify/urls.py | 30 +- {mobile_web_api => mobile_api}/__init__.py | 0 {mobile_web_api => mobile_api}/admin.py | 0 {mobile_web_api => mobile_api}/apps.py | 4 +- .../forms/__init__.py | 0 .../forms/event_forms.py | 0 mobile_api/forms/user_forms.py | 75 ++ .../migrations/__init__.py | 0 {mobile_web_api => mobile_api}/models.py | 0 {mobile_web_api => mobile_api}/tests.py | 0 {mobile_web_api => mobile_api}/urls.py | 4 +- mobile_api/utils.py | 90 ++ .../views/__init__.py | 0 mobile_api/views/events.py | 361 +++++++ mobile_api/views/user.py | 107 +++ mobile_web_api/forms/user_forms.py | 42 - mobile_web_api/views/events.py | 231 ----- mobile_web_api/views/user.py | 124 --- pg_to_sqlite_backup.py | 80 ++ templates/accounts/login.html | 2 + templates/customer/base_dashboard.html | 60 +- templates/customer/customer_calendar.html | 256 +++++ templates/customer/customer_profile.html | 33 + templates/customer/sampl.html | 0 utils/errors_json_convertor.py | 47 + web_api/__init__.py | 0 web_api/admin.py | 3 + web_api/apps.py | 6 + web_api/migrations/__init__.py | 0 web_api/models.py | 3 + web_api/tests.py | 3 + web_api/views/__init__.py | 2 + web_api/views/events.py | 0 39 files changed, 2147 insertions(+), 452 deletions(-) create mode 100644 API_Documentation.md create mode 100644 db_reset.py rename {mobile_web_api => mobile_api}/__init__.py (100%) rename {mobile_web_api => mobile_api}/admin.py (100%) rename {mobile_web_api => mobile_api}/apps.py (58%) rename {mobile_web_api => mobile_api}/forms/__init__.py (100%) rename {mobile_web_api => mobile_api}/forms/event_forms.py (100%) create mode 100644 mobile_api/forms/user_forms.py rename {mobile_web_api => mobile_api}/migrations/__init__.py (100%) rename {mobile_web_api => mobile_api}/models.py (100%) rename {mobile_web_api => mobile_api}/tests.py (100%) rename {mobile_web_api => mobile_api}/urls.py (68%) create mode 100644 mobile_api/utils.py rename {mobile_web_api => mobile_api}/views/__init__.py (100%) create mode 100644 mobile_api/views/events.py create mode 100644 mobile_api/views/user.py delete mode 100644 mobile_web_api/forms/user_forms.py delete mode 100644 mobile_web_api/views/events.py delete mode 100644 mobile_web_api/views/user.py create mode 100644 pg_to_sqlite_backup.py create mode 100644 templates/customer/customer_calendar.html create mode 100644 templates/customer/customer_profile.html create mode 100644 templates/customer/sampl.html create mode 100644 utils/errors_json_convertor.py create mode 100644 web_api/__init__.py create mode 100644 web_api/admin.py create mode 100644 web_api/apps.py create mode 100644 web_api/migrations/__init__.py create mode 100644 web_api/models.py create mode 100644 web_api/tests.py create mode 100644 web_api/views/__init__.py create mode 100644 web_api/views/events.py diff --git a/.DS_Store b/.DS_Store index ec4ca450142d2ace073932bdfac0fd8a9d49e51d..24c3ea4a87e204e4aabf0d4d210704a3669a7044 100644 GIT binary patch delta 36 scmZp1XmOa}&nU1lU^hRbz-AtSXvWQp1yk52HrQ-tm-x-Hv6hJ$0NK/edit/', views.UserUpdateView.as_view(), name='user_edit'), + path('users//delete/', views.UserDeleteView.as_view(), name='user_delete'), path('master-data/', include('master_data.urls')), path('events/', include('events.urls')), path('accounts/', include('accounts.urls')), - path('api/', include('mobile_web_api.urls')), + path('api/', include('mobile_api.urls')), + # path('web-api/', include('web_api.urls')), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/mobile_web_api/__init__.py b/mobile_api/__init__.py similarity index 100% rename from mobile_web_api/__init__.py rename to mobile_api/__init__.py diff --git a/mobile_web_api/admin.py b/mobile_api/admin.py similarity index 100% rename from mobile_web_api/admin.py rename to mobile_api/admin.py diff --git a/mobile_web_api/apps.py b/mobile_api/apps.py similarity index 58% rename from mobile_web_api/apps.py rename to mobile_api/apps.py index 5d3a13b..b902e8a 100644 --- a/mobile_web_api/apps.py +++ b/mobile_api/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class MobileWebApiConfig(AppConfig): +class MobileApiConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'mobile_web_api' + name = 'mobile_api' diff --git a/mobile_web_api/forms/__init__.py b/mobile_api/forms/__init__.py similarity index 100% rename from mobile_web_api/forms/__init__.py rename to mobile_api/forms/__init__.py diff --git a/mobile_web_api/forms/event_forms.py b/mobile_api/forms/event_forms.py similarity index 100% rename from mobile_web_api/forms/event_forms.py rename to mobile_api/forms/event_forms.py diff --git a/mobile_api/forms/user_forms.py b/mobile_api/forms/user_forms.py new file mode 100644 index 0000000..2b7c7ab --- /dev/null +++ b/mobile_api/forms/user_forms.py @@ -0,0 +1,75 @@ +# accounts/forms.py +from django import forms +from django.contrib.auth import get_user_model +from django.contrib.auth import authenticate + +User = get_user_model() + + +class RegisterForm(forms.ModelForm): + password = forms.CharField(widget=forms.PasswordInput) + + class Meta: + model = User + fields = ['email', 'phone_number', 'password'] + + def clean_email(self): + email = self.cleaned_data.get('email') + if User.objects.filter(email=email).exists(): + raise forms.ValidationError("Email is already registered.") + return email + + def clean_phone_number(self): + phone_number = self.cleaned_data.get('phone_number') + if User.objects.filter(phone_number=phone_number).exists(): + raise forms.ValidationError("Phone number is already registered.") + return phone_number + + def save(self, commit=True): + user = super().save(commit=False) + user.set_password(self.cleaned_data['password']) + if commit: + user.save() + return user + + +class LoginForm(forms.Form): + username = forms.CharField() + password = forms.CharField(widget=forms.PasswordInput) + + def clean(self): + cleaned_data = super().clean() + username = cleaned_data.get('username') + password = cleaned_data.get('password') + + print('*' * 100) + print(username, password) + print('*' * 100) + + if not username or not password: + raise forms.ValidationError("Username and password are required.") + + # Check if username contains '@' (email) or is a regular username + try: + if '@' in username: + print('1 **********************') + # Try to find user by email + user = User.objects.get(email=username) + print(user) + print('2 **********************') + username = user.username + print('3 **********************') + else: + print('4 **********************')# Use username as-is + user = User.objects.get(username=username) + except User.DoesNotExist: + print('5 **********************') + raise forms.ValidationError("Invalid credentials.") + + # Authenticate with the resolved username + user = authenticate(username=username, password=password) + if not user: + raise forms.ValidationError("Invalid credentials.") + + cleaned_data['user'] = user + return cleaned_data diff --git a/mobile_web_api/migrations/__init__.py b/mobile_api/migrations/__init__.py similarity index 100% rename from mobile_web_api/migrations/__init__.py rename to mobile_api/migrations/__init__.py diff --git a/mobile_web_api/models.py b/mobile_api/models.py similarity index 100% rename from mobile_web_api/models.py rename to mobile_api/models.py diff --git a/mobile_web_api/tests.py b/mobile_api/tests.py similarity index 100% rename from mobile_web_api/tests.py rename to mobile_api/tests.py diff --git a/mobile_web_api/urls.py b/mobile_api/urls.py similarity index 68% rename from mobile_web_api/urls.py rename to mobile_api/urls.py index 864b27d..0a71339 100644 --- a/mobile_web_api/urls.py +++ b/mobile_api/urls.py @@ -17,5 +17,7 @@ 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'), + path('events/events-by-category/', EventsByCategoryAPI.as_view(), name='api_events_by_category'), + path('events/events-by-month-year/', EventsByMonthYearAPI.as_view(), name='events_by_month_year'), + path('events/events-by-date/', EventsByDateAPI.as_view(), name='events_by_date'), ] diff --git a/mobile_api/utils.py b/mobile_api/utils.py new file mode 100644 index 0000000..7b06306 --- /dev/null +++ b/mobile_api/utils.py @@ -0,0 +1,90 @@ +""" +Utility functions for mobile API authentication and validation. +""" +import json +from django.http import JsonResponse +from rest_framework.authtoken.models import Token +from accounts.models import User + + +def validate_token_and_get_user(request, error_status_code=None): + """ + Validates token and username from request body. + + This function handles: + - JSON parsing from request body + - Token and username extraction + - Token validation + - Username verification against token user + + Args: + request: Django request object with JSON body containing 'token' and 'username' + error_status_code: Optional HTTP status code for error responses (default: None) + + Returns: + tuple: On success, returns (user, token, data, None) + tuple: On error, returns (None, None, None, JsonResponse) + + Error Responses: + - Invalid JSON: {"status": "error", "message": "Invalid JSON"} (400 if status_code provided) + - Missing credentials: {"status": "error", "message": "token and username required"} (400 if status_code provided) + - Invalid token: {"status": "invalid_token"} (401 if status_code provided) + - Username mismatch: {"status": "error", "message": "token does not match user"} (401 if status_code provided) + """ + try: + # Parse JSON from request body + data = json.loads(request.body) + except json.JSONDecodeError: + status = 400 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "error", "message": "Invalid JSON"}, + status=status + )) + + # Extract token and username + token_key = data.get("token") + username = data.get("username") + + # Validate both are present + if not token_key or not username: + status = 400 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "error", "message": "token and username required"}, + status=status + )) + + try: + # Get token object + token = Token.objects.get(key=token_key) + + if username: + if '@' in username: + user = User.objects.get(email=username) + else: + user = User.objects.get(username=username) + + if not user: + status = 401 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "error", "message": "user not found"}, + status=status + )) + + # Verify username matches token user + # if user.username != username: + # status = 401 if error_status_code else None + # return (None, None, None, JsonResponse( + # {"status": "error", "message": "token does not match user"}, + # status=status + # )) + + # Success - return user, token, data, and None for error_response + return (user, token, data, None) + + except Token.DoesNotExist: + status = 401 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "invalid_token"}, + status=status + )) + diff --git a/mobile_web_api/views/__init__.py b/mobile_api/views/__init__.py similarity index 100% rename from mobile_web_api/views/__init__.py rename to mobile_api/views/__init__.py diff --git a/mobile_api/views/events.py b/mobile_api/views/events.py new file mode 100644 index 0000000..5103f21 --- /dev/null +++ b/mobile_api/views/events.py @@ -0,0 +1,361 @@ +from django.http import JsonResponse +from rest_framework.views import APIView +from rest_framework.authentication import TokenAuthentication +from rest_framework.permissions import IsAuthenticated +from events.models import Event, EventImages +from master_data.models import EventType +from django.forms.models import model_to_dict +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt +from django.db.models import Q +from datetime import datetime, timedelta +import calendar +from mobile_api.utils import validate_token_and_get_user + + +@method_decorator(csrf_exempt, name='dispatch') +class EventTypeListAPIView(APIView): + + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + # Fetch event types manually without serializer + event_types_queryset = EventType.objects.all() + event_types = [] + + for event_type in event_types_queryset: + event_type_data = { + "id": event_type.id, + "event_type": event_type.event_type, + "event_type_icon": request.build_absolute_uri(event_type.event_type_icon.url) if event_type.event_type_icon else None + } + event_types.append(event_type_data) + + print(event_types) + + return JsonResponse({ + "status": "success", + "event_types": event_types + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +class EventListAPI(APIView): + + def post(self, request): + try: + print('*' * 100) + print(request.body) + print('*' * 100) + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + pincode = data.get("pincode") + print('*' * 100) + print(pincode) + print('*' * 100) + # pincode is optional - if not provided or 'all', return all events + if not pincode or pincode == 'all': + events = Event.objects.all().order_by('-created_date') + else: + events = Event.objects.filter(pincode=pincode).order_by('-created_date') + + event_list = [] + + for e in events: + data_dict = model_to_dict(e) + try: + thumb_img = EventImages.objects.get(event=e.id, is_primary=True) + data_dict['thumb_img'] = request.build_absolute_uri(thumb_img.event_image.url) + except EventImages.DoesNotExist: + data_dict['thumb_img'] = '' + + event_list.append(data_dict) + + print('*' * 100) + print(event_list) + print('*' * 100) + + return JsonResponse({ + "status": "success", + "events": event_list + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +class EventDetailAPI(APIView): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + event_id = data.get("event_id") + + events = Event.objects.get(id=event_id) + event_images = EventImages.objects.filter(event=event_id) + event_data = model_to_dict(events) + event_data["status"] = "success" + event_images_list = [] + for ei in event_images: + event_img = {} + event_img['is_primary'] = ei.is_primary + event_img['image'] = request.build_absolute_uri(ei.event_image.url) + event_images_list.append(event_img) + event_data["images"] = event_images_list + + print(event_data) + + return JsonResponse(event_data) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +class EventImagesListAPI(APIView): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + event_id = data.get("event_id") + + event_images = EventImages.objects.filter(event=event_id) + res_data = {} + res_data["status"] = "success" + event_images_list = [] + for ei in event_images: + event_images_list.append(request.build_absolute_uri(ei.event_image.url)) + + res_data["images"] = event_images_list + + print(res_data) + + return JsonResponse(res_data) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +@method_decorator(csrf_exempt, name='dispatch') +class EventsByCategoryAPI(APIView): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + category_id = data.get("category_id") + + if not category_id: + return JsonResponse( + {"status": "error", "message": "category_id is required"} + ) + + events = Event.objects.filter(event_type=category_id) + events_dict = [model_to_dict(obj) for obj in events] + + for event in events_dict: + try: + event['event_image'] = request.build_absolute_uri( + EventImages.objects.get(event=event['id'], is_primary=True).event_image.url + ) + except EventImages.DoesNotExist: + event['event_image'] = '' + # event['start_date'] = convert_date_to_dd_mm_yyyy(event['start_date']) + print(events_dict) + + return JsonResponse({ + "status": "success", + "events": events_dict + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +@method_decorator(csrf_exempt, name='dispatch') +class EventsByMonthYearAPI(APIView): + """ + API to get events by month and year. + Returns dates that have events, total count, and date-wise breakdown. + """ + + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + month_name = data.get("month") # e.g., "August", "august", "Aug" + year = data.get("year") # e.g., 2025 + + if not month_name or not year: + return JsonResponse( + {"status": "error", "message": "month and year are required"} + ) + + # Convert month name to month number + month_name_lower = month_name.lower().capitalize() + month_abbr = month_name_lower[:3] + + # Try full month name first, then abbreviation + month_number = None + for i in range(1, 13): + if calendar.month_name[i].lower() == month_name_lower or calendar.month_abbr[i].lower() == month_abbr.lower(): + month_number = i + break + + if not month_number: + return JsonResponse( + {"status": "error", "message": f"Invalid month name: {month_name}"} + ) + + # Convert year to integer + try: + year = int(year) + except (ValueError, TypeError): + return JsonResponse( + {"status": "error", "message": "Invalid year format"} + ) + + # Filter events where start_date or end_date falls in the given month/year + # An event is included if any part of it (start_date to end_date) overlaps with the month + events = Event.objects.filter( + Q(start_date__year=year, start_date__month=month_number) | + Q(end_date__year=year, end_date__month=month_number) | + Q(start_date__lte=datetime(year, month_number, 1).date(), + end_date__gte=datetime(year, month_number, calendar.monthrange(year, month_number)[1]).date()) + ).distinct() + + # Group events by date + date_events_dict = {} + all_dates = set() + + # Calculate month boundaries + month_start = datetime(year, month_number, 1).date() + month_end = datetime(year, month_number, calendar.monthrange(year, month_number)[1]).date() + + for event in events: + # Get all dates between start_date and end_date that fall in the target month + current_date = max(event.start_date, month_start) + end_date = min(event.end_date, month_end) + + # Iterate through each date in the event's date range that falls in the target month + while current_date <= end_date: + if current_date.year == year and current_date.month == month_number: + date_str = current_date.strftime('%Y-%m-%d') + all_dates.add(date_str) + if date_str not in date_events_dict: + date_events_dict[date_str] = 0 + date_events_dict[date_str] += 1 + + # Move to next day + current_date += timedelta(days=1) + + # Sort dates + sorted_dates = sorted(all_dates) + + # Build date_events list + date_events = [ + { + "date_of_event": date_str, + "events_of_date": date_events_dict[date_str] + } + for date_str in sorted_dates + ] + + # Calculate total number of events (unique events, not date occurrences) + total_events = events.count() + + print(sorted_dates) + print(total_events) + print(date_events) + + return JsonResponse({ + "status": "success", + "dates": sorted_dates, + "total_number_of_events": total_events, + "date_events": date_events + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) + + +@method_decorator(csrf_exempt, name='dispatch') +class EventsByDateAPI(APIView): + """ + API to get events occurring on a specific date. + Returns complete event information with primary images. + """ + + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request) + if error_response: + return error_response + + date_of_event = data.get("date_of_event") + + if not date_of_event: + return JsonResponse( + {"status": "error", "message": "date_of_event is required"} + ) + + # Parse date_of_event in YYYY-MM-DD format + try: + event_date = datetime.strptime(date_of_event, "%Y-%m-%d").date() + except ValueError: + return JsonResponse( + {"status": "error", "message": "Invalid date format. Expected YYYY-MM-DD"} + ) + + # Filter events where the provided date falls between start_date and end_date (inclusive) + events = Event.objects.filter( + start_date__lte=event_date, + end_date__gte=event_date + ).order_by('start_date', 'start_time') + + event_list = [] + + for e in events: + data_dict = model_to_dict(e) + try: + thumb_img = EventImages.objects.get(event=e.id, is_primary=True) + data_dict['thumb_img'] = request.build_absolute_uri(thumb_img.event_image.url) + except EventImages.DoesNotExist: + data_dict['thumb_img'] = '' + + event_list.append(data_dict) + + return JsonResponse({ + "status": "success", + "events": event_list + }) + + except Exception as e: + return JsonResponse( + {"status": "error", "message": str(e)}, + ) \ No newline at end of file diff --git a/mobile_api/views/user.py b/mobile_api/views/user.py new file mode 100644 index 0000000..5b3dab6 --- /dev/null +++ b/mobile_api/views/user.py @@ -0,0 +1,107 @@ +# accounts/views.py +import json +from django.views.decorators.csrf import csrf_exempt +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views import View +from rest_framework.authtoken.models import Token +from mobile_api.forms import RegisterForm, LoginForm +from rest_framework.authentication import TokenAuthentication +from django.contrib.auth import logout +from mobile_api.utils import validate_token_and_get_user +from utils.errors_json_convertor import simplify_form_errors + + +@method_decorator(csrf_exempt, name='dispatch') +class RegisterView(View): + def post(self, request): + try: + data = json.loads(request.body) + form = RegisterForm(data) + if form.is_valid(): + user = form.save() + token, _ = Token.objects.get_or_create(user=user) + return JsonResponse({'message': 'User registered successfully', 'token': token.key}, status=201) + return JsonResponse({'errors': form.errors}, status=400) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + + +@method_decorator(csrf_exempt, name='dispatch') +class LoginView(View): + def post(self, request): + print('0') + try: + data = json.loads(request.body) + form = LoginForm(data) + print('1') + if form.is_valid(): + print('2') + user = form.cleaned_data['user'] + token, _ = Token.objects.get_or_create(user=user) + print('3') + response = { + 'message': 'Login successful', + 'token': token.key, + 'username': user.username, + 'email': user.email, + 'phone_number': user.phone_number, + 'first_name': user.first_name, + 'last_name': user.last_name, + 'role': user.role, + 'pincode': user.pincode, + 'district': user.district, + 'state': user.state, + 'country': user.country, + 'place': user.place, + 'latitude': user.latitude, + 'longitude': user.longitude, + } + print('4') + print(response) + return JsonResponse(response, status=200) + + return JsonResponse(simplify_form_errors(form), status=401) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + + +@method_decorator(csrf_exempt, name='dispatch') +class StatusView(View): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) + if error_response: + return error_response + + return JsonResponse({ + "status": "logged_in", + "username": user.username, + "email": user.email + }) + + except Exception as e: + return JsonResponse({"status": "error", "message": str(e)}, status=500) + + +@method_decorator(csrf_exempt, name='dispatch') +class LogoutView(View): + def post(self, request): + try: + user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) + if error_response: + return error_response + + # πŸ” Call Django's built-in logout + logout(request) + + # πŸ—‘ Delete the token to invalidate future access + token.delete() + + return JsonResponse({ + "status": "logged_out", + "message": "Logout successful" + }) + + except Exception as e: + return JsonResponse({"status": "error", "message": str(e)}, status=500) diff --git a/mobile_web_api/forms/user_forms.py b/mobile_web_api/forms/user_forms.py deleted file mode 100644 index 0bab4a5..0000000 --- a/mobile_web_api/forms/user_forms.py +++ /dev/null @@ -1,42 +0,0 @@ -# accounts/forms.py -from django import forms -from django.contrib.auth import get_user_model -from django.contrib.auth import authenticate - -User = get_user_model() - - -class RegisterForm(forms.ModelForm): - password = forms.CharField(widget=forms.PasswordInput) - - class Meta: - model = User - fields = ['email', 'phone_number', 'password'] - - def clean_email(self): - phone_number = self.cleaned_data.get('phone_number') - if User.objects.filter(phone_number=phone_number).exists(): - raise forms.ValidationError("phone_number is already registered.") - return phone_number - - def save(self, commit=True): - user = super().save(commit=False) - user.set_password(self.cleaned_data['password']) - if commit: - user.save() - return user - - -class LoginForm(forms.Form): - username = forms.CharField() - password = forms.CharField(widget=forms.PasswordInput) - - def clean(self): - cleaned_data = super().clean() - username = cleaned_data.get('username') - password = cleaned_data.get('password') - user = authenticate(username=username, password=password) - if not user: - raise forms.ValidationError("Invalid credentials.") - cleaned_data['user'] = user - return cleaned_data diff --git a/mobile_web_api/views/events.py b/mobile_web_api/views/events.py deleted file mode 100644 index fc7ce50..0000000 --- a/mobile_web_api/views/events.py +++ /dev/null @@ -1,231 +0,0 @@ -from django.http import JsonResponse -from rest_framework.views import APIView -from rest_framework.authentication import TokenAuthentication -from rest_framework.permissions import IsAuthenticated -from events.models import Event, EventImages -from rest_framework.authtoken.models import Token -from master_data.models import EventType -from django.forms.models import model_to_dict -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): - - def post(self, request): - try: - # Manually load JSON because we are not using parsers - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - # Fetch event types manually without serializer - event_types = list(EventType.objects.values("id", "event_type")) - - return JsonResponse({ - "status": "success", - "event_types": event_types - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - return JsonResponse( - {"status": "error", "message": str(e)}, - ) - - -class EventListAPI(APIView): - - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - pincode = data.get("pincode") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - events = Event.objects.filter(pincode=pincode).order_by('-created_date') - event_list = [] - - for e in events: - data_dict = model_to_dict(e) - print('*' * 10) - print(e.id) - print('*' * 10) - try: - thumb_img = EventImages.objects.get(event=e.id, is_primary=True) - data_dict['thumb_img'] = request.build_absolute_uri(thumb_img.event_image.url) - except EventImages.DoesNotExist: - data_dict['thumb_img'] = '' - - event_list.append(data_dict) - - return JsonResponse({ - "status": "success", - "events": event_list - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - return JsonResponse( - {"status": "error", "message": str(e)}, - ) - - -class EventDetailAPI(APIView): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - event_id = data.get("event_id") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - events = Event.objects.get(id=event_id) - event_images = EventImages.objects.filter(event=event_id) - data = model_to_dict(events) - data["status"] = "success" - event_images_list = [] - for ei in event_images: - event_img = {} - event_img['is_primary'] = ei.is_primary - event_img['image'] = request.build_absolute_uri(ei.event_image.url) - event_images_list.append(event_img) - data["images"] = event_images_list - - return JsonResponse(data) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - return JsonResponse( - {"status": "error", "message": str(e)}, - ) - - -class EventImagesListAPI(APIView): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - event_id = data.get("event_id") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"} - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"} - ) - - event_images = EventImages.objects.filter(event=event_id) - res_data = {} - res_data["status"] = "success" - event_images_list = [] - for ei in event_images: - event_images_list.append(request.build_absolute_uri(ei.event_image.url)) - - res_data["images"] = event_images_list - - return JsonResponse(res_data) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}) - - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON"} - ) - - except Exception as e: - 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/mobile_web_api/views/user.py b/mobile_web_api/views/user.py deleted file mode 100644 index d094896..0000000 --- a/mobile_web_api/views/user.py +++ /dev/null @@ -1,124 +0,0 @@ -# accounts/views.py -import json -from django.views.decorators.csrf import csrf_exempt -from django.http import JsonResponse -from django.utils.decorators import method_decorator -from django.views import View -from rest_framework.authtoken.models import Token -from mobile_web_api.forms import RegisterForm, LoginForm -from rest_framework.authentication import TokenAuthentication -from django.contrib.auth import logout - - -@method_decorator(csrf_exempt, name='dispatch') -class RegisterView(View): - def post(self, request): - try: - data = json.loads(request.body) - form = RegisterForm(data) - if form.is_valid(): - user = form.save() - token, _ = Token.objects.get_or_create(user=user) - return JsonResponse({'message': 'User registered successfully', 'token': token.key}, status=201) - return JsonResponse({'errors': form.errors}, status=400) - except Exception as e: - return JsonResponse({'error': str(e)}, status=500) - - -@method_decorator(csrf_exempt, name='dispatch') -class LoginView(View): - def post(self, request): - try: - data = json.loads(request.body) - form = LoginForm(data) - if form.is_valid(): - user = form.cleaned_data['user'] - token, _ = Token.objects.get_or_create(user=user) - return JsonResponse({'message': 'Login successful', 'token': token.key}) - return JsonResponse({'errors': form.errors}, status=401) - except Exception as e: - return JsonResponse({'error': str(e)}, status=500) - - -@method_decorator(csrf_exempt, name='dispatch') -class StatusView(View): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"}, - status=400 - ) - - try: - token = Token.objects.get(key=token_key) - - if token.user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"}, - status=401 - ) - - return JsonResponse({ - "status": "logged_in", - "username": token.user.username, - "email": token.user.email - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}, status=401) - - except json.JSONDecodeError: - return JsonResponse({"status": "error", "message": "Invalid JSON"}, status=400) - except Exception as e: - return JsonResponse({"status": "error", "message": str(e)}, status=500) - - -@method_decorator(csrf_exempt, name='dispatch') -class LogoutView(View): - def post(self, request): - try: - data = json.loads(request.body) - - token_key = data.get("token") - username = data.get("username") - - if not token_key or not username: - return JsonResponse( - {"status": "error", "message": "token and username required"}, - status=400 - ) - - try: - token = Token.objects.get(key=token_key) - user = token.user - - if user.username != username: - return JsonResponse( - {"status": "error", "message": "token does not match user"}, - status=401 - ) - - # πŸ” Call Django's built-in logout - logout(request) - - # πŸ—‘ Delete the token to invalidate future access - token.delete() - - return JsonResponse({ - "status": "logged_out", - "message": "Logout successful" - }) - - except Token.DoesNotExist: - return JsonResponse({"status": "invalid_token"}, status=401) - - except json.JSONDecodeError: - return JsonResponse({"status": "error", "message": "Invalid JSON"}, status=400) - except Exception as e: - return JsonResponse({"status": "error", "message": str(e)}, status=500) diff --git a/pg_to_sqlite_backup.py b/pg_to_sqlite_backup.py new file mode 100644 index 0000000..a8fca89 --- /dev/null +++ b/pg_to_sqlite_backup.py @@ -0,0 +1,80 @@ +# pg_to_sqlite_backup.py +import os +import sys +import django +from django.conf import settings +from django.core.management import call_command +from django.db import connections, DEFAULT_DB_ALIAS + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eventify.settings") +django.setup() + +SQLITE_PATH = "backup.sqlite3" +BACKUP_ALIAS = "backup" + + +def ensure_backup_db(): + # Add a SQLite backup database alias + settings.DATABASES[BACKUP_ALIAS] = { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.abspath(SQLITE_PATH), + } + # Remove existing file so migrate creates a fresh DB + if os.path.exists(SQLITE_PATH): + os.remove(SQLITE_PATH) + + # Run migrations on the backup DB + print("Running migrations on backup (SQLite)…") + call_command("migrate", database=BACKUP_ALIAS, interactive=False, verbosity=0) + print("SQLite schema created at", SQLITE_PATH) + + +def copy_all_tables(): + pg_conn = connections[DEFAULT_DB_ALIAS] # default = PostgreSQL + sqlite_conn = connections[BACKUP_ALIAS] # new SQLite + + pg_cursor = pg_conn.cursor() + sqlite_cursor = sqlite_conn.cursor() + + tables = pg_conn.introspection.table_names() + + for table in tables: + # Fetch column names from PostgreSQL + cols = [c.name for c in pg_conn.introspection.get_table_description(pg_cursor, table)] + col_list = ", ".join(f'"{c}"' for c in cols) + placeholders = ", ".join(["?"] * len(cols)) + + # Fetch all rows + pg_cursor.execute(f'SELECT {col_list} FROM "{table}"') + rows = pg_cursor.fetchall() + + # Insert into SQLite + if rows: + insert_sql = f'INSERT INTO "{table}" ({col_list}) VALUES ({placeholders})' + sqlite_cursor.executemany(insert_sql, rows) + + print(f"Copied {len(rows)} rows from {table}") + + sqlite_conn.commit() + pg_cursor.close() + sqlite_cursor.close() + + +def main(): + if "--force" not in sys.argv: + print("Refusing to run without --force (destructive for existing backup file).") + sys.exit(1) + + # Safety: ensure default DB is PostgreSQL + engine = settings.DATABASES[DEFAULT_DB_ALIAS]["ENGINE"] + if "postgresql" not in engine: + print("Default database is not PostgreSQL. Aborting.") + sys.exit(1) + + ensure_backup_db() + copy_all_tables() + print("Done. SQLite backup at", SQLITE_PATH) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/templates/accounts/login.html b/templates/accounts/login.html index b19eedb..551ea81 100644 --- a/templates/accounts/login.html +++ b/templates/accounts/login.html @@ -15,6 +15,8 @@
+

Welcome To Eventify Admin Panel

+

Login

{% if messages %} diff --git a/templates/customer/base_dashboard.html b/templates/customer/base_dashboard.html index 442b5cb..449f3c1 100644 --- a/templates/customer/base_dashboard.html +++ b/templates/customer/base_dashboard.html @@ -1,9 +1,8 @@ - + - @@ -26,36 +25,44 @@ .sidebar { width: 260px; background: linear-gradient(180deg, #1e3cfa, #1436c7); - color: white; + color: #fff; padding: 30px 20px; display: flex; flex-direction: column; } + .nav-item { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 30px; + border-radius: 12px; + margin-bottom: 8px; + font-size: 15px; + line-height: 1.5; + text-decoration: none; + color: #000000; + /* force white text */ + background: transparent; + } + + .nav-item:hover, + .nav-item.active { + background: #fff; + color: #000000; + padding: 16px 30px; + } + + .nav-item:visited { + color: #000000; + } + .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; } @@ -146,9 +153,14 @@
diff --git a/templates/customer/customer_calendar.html b/templates/customer/customer_calendar.html new file mode 100644 index 0000000..562b0da --- /dev/null +++ b/templates/customer/customer_calendar.html @@ -0,0 +1,256 @@ +{% extends "customer/base_dashboard.html" %} +{% load static %} + +{% block content %} + + + + + +
+ + +
+ +
+ + +
+

{{ month_name }}

+

{{ year }}

+
+ + +
+ + +
+
M
T
W
T
F
+
S
+
S
+
+ + +
+ {% for week in calendar_weeks %} + {% for day in week %} + {% if day.month == current_month %} +
+ {% if day.day == selected_day %} +
+ {% endif %} + + {{ day.day }} + + {% if day.events %} +
+ {% for ev in day.events %} + + {% endfor %} +
+ {% endif %} +
+ {% else %} +
{{ day.day }}
+ {% endif %} + {% endfor %} + {% endfor %} +
+
+ + +
+ +
+
{{ selected_day }} {{ selected_month_short }}
+
+

+ {{ selected_date_verbose }} +

+

+ {{ events|length }} Events +

+
+
+ + {% for event in events %} +
+ +
+

{{ event.title }}

+
+ πŸ“… {{ event.date }} + πŸ“ {{ event.location }} +
+
+
+ {% endfor %} + +
+
+ + +{% endblock %} diff --git a/templates/customer/customer_profile.html b/templates/customer/customer_profile.html new file mode 100644 index 0000000..3fae7f5 --- /dev/null +++ b/templates/customer/customer_profile.html @@ -0,0 +1,33 @@ +{% extends "customer/base_dashboard.html" %} +{% load static %} + +{% block content %} + +
+

Profile

+
+ {% csrf_token %} + {% if form.non_field_errors %} +
{{ form.non_field_errors }}
+ {% endif %} + +
+ {% for field in form %} +
+
+ + {{ field }} + {% if field.errors %} +
{{ field.errors|striptags }}
+ {% endif %} +
+
+ {% endfor %} +
+ +
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/customer/sampl.html b/templates/customer/sampl.html new file mode 100644 index 0000000..e69de29 diff --git a/utils/errors_json_convertor.py b/utils/errors_json_convertor.py new file mode 100644 index 0000000..166f582 --- /dev/null +++ b/utils/errors_json_convertor.py @@ -0,0 +1,47 @@ +def simplify_errors(error_response): + """ + Convert nested 'errors' dict like: + { + "errors": { + "__all__": ["Invalid credentials."] + } + } + into: + { + "errors": "Invalid credentials." + } + """ + errors = error_response.get("errors", {}) + + # Collect all messages into a flat list + messages = [] + if isinstance(errors, dict): + for field_errors in errors.values(): + if isinstance(field_errors, (list, tuple)): + messages.extend(str(msg) for msg in field_errors) + else: + messages.append(str(field_errors)) + else: + # If 'errors' is not a dict, just stringify it + messages.append(str(errors)) + + # Join multiple messages if needed + combined = " ".join(messages).strip() + return {"errors": combined} + + + +def simplify_form_errors(form): + """ + Flatten Django form.errors into a single 'errors' string. + """ + errors = form.errors # ErrorDict + messages = [] + + for field_errors in errors.values(): + # field_errors is usually an ErrorList (list-like) + for msg in field_errors: + messages.append(str(msg)) + + combined = " ".join(messages).strip() + return {"errors": combined} \ No newline at end of file diff --git a/web_api/__init__.py b/web_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_api/admin.py b/web_api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/web_api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/web_api/apps.py b/web_api/apps.py new file mode 100644 index 0000000..4d0aa3d --- /dev/null +++ b/web_api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WebApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'web_api' diff --git a/web_api/migrations/__init__.py b/web_api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_api/models.py b/web_api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/web_api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/web_api/tests.py b/web_api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/web_api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/web_api/views/__init__.py b/web_api/views/__init__.py new file mode 100644 index 0000000..2158289 --- /dev/null +++ b/web_api/views/__init__.py @@ -0,0 +1,2 @@ +from .user import * +from .events import * \ No newline at end of file diff --git a/web_api/views/events.py b/web_api/views/events.py new file mode 100644 index 0000000..e69de29