From d109df39735f10a3ec506fc89341cd445bcdb7bc Mon Sep 17 00:00:00 2001 From: Vivek Date: Fri, 19 Dec 2025 19:35:38 +0530 Subject: [PATCH] Updates for the api and bug fixes --- .../migrations/0006_user_profile_picture.py | 18 ++ accounts/models.py | 2 + db_1.sqlite3 | Bin 0 -> 167936 bytes eventify/settings.py | 26 +-- eventify/urls.py | 3 +- mobile_api/forms/user_forms.py | 48 ++++- mobile_api/urls.py | 1 + mobile_api/utils.py | 38 ++-- mobile_api/views/events.py | 30 ++- mobile_api/views/user.py | 173 +++++++++++++++++- 10 files changed, 302 insertions(+), 37 deletions(-) create mode 100644 accounts/migrations/0006_user_profile_picture.py create mode 100644 db_1.sqlite3 diff --git a/accounts/migrations/0006_user_profile_picture.py b/accounts/migrations/0006_user_profile_picture.py new file mode 100644 index 0000000..2a9c04e --- /dev/null +++ b/accounts/migrations/0006_user_profile_picture.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-12-19 13:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0005_alter_user_role'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='profile_picture', + field=models.ImageField(blank=True, null=True, upload_to='profile_pictures/'), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 9cc34c3..f40fb2f 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -29,6 +29,8 @@ class User(AbstractUser): 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) + profile_picture = models.ImageField(upload_to='profile_pictures/', blank=True, null=True, default='default.png') + objects = UserManager() def __str__(self): diff --git a/db_1.sqlite3 b/db_1.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..948fdc94f001fbb11b3371b7fb14e5fc00b3d8be GIT binary patch literal 167936 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 literal 0 HcmV?d00001 diff --git a/eventify/settings.py b/eventify/settings.py index c5e6eeb..492b976 100644 --- a/eventify/settings.py +++ b/eventify/settings.py @@ -71,24 +71,24 @@ TEMPLATES = [ WSGI_APPLICATION = 'eventify.wsgi.application' -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} - # DATABASES = { # 'default': { -# 'ENGINE': 'django.db.backends.postgresql', -# 'NAME': 'eventify_uat_db', # your DB name -# 'USER': 'eventify_uat', # your DB user -# 'PASSWORD': 'eventifyplus@!@#$', # your DB password -# 'HOST': '0.0.0.0', # or IP/domain -# 'PORT': '5440', # default PostgreSQL port +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': BASE_DIR / 'db.sqlite3', # } # } +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'eventify_uat_db', # your DB name + 'USER': 'eventify_uat', # your DB user + 'PASSWORD': 'eventifyplus@!@#$', # your DB password + 'HOST': '0.0.0.0', # or IP/domain + 'PORT': '5440', # default PostgreSQL port + } +} + AUTH_PASSWORD_VALIDATORS = [ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, diff --git a/eventify/urls.py b/eventify/urls.py index 31e173b..115d720 100644 --- a/eventify/urls.py +++ b/eventify/urls.py @@ -6,6 +6,7 @@ from django.contrib.auth import views as auth_views # from accounts.customer_views import login_view, logout_view, customer_dashboard, customer_calendar # from accounts.customer_views import customer_profile from accounts import views +from mobile_api.views.user import WebRegisterView from django.conf.urls.static import static from django.conf import settings @@ -18,8 +19,8 @@ urlpatterns = [ # path('calendar/', customer_calendar, name='customer_calendar'), # path('profile/', customer_profile, name='customer_profile'), - path('', views.login_view, name='login'), + path('register/', WebRegisterView.as_view(), name='register'), path('logout/', views.logout_view, name='logout'), path('dashboard/', views.dashboard, name='dashboard'), path('users/', views.UserListView.as_view(), name='user_list'), diff --git a/mobile_api/forms/user_forms.py b/mobile_api/forms/user_forms.py index 2b7c7ab..2ec5cca 100644 --- a/mobile_api/forms/user_forms.py +++ b/mobile_api/forms/user_forms.py @@ -15,7 +15,8 @@ class RegisterForm(forms.ModelForm): def clean_email(self): email = self.cleaned_data.get('email') - if User.objects.filter(email=email).exists(): + # Ensure both email and username do not clash, since we set username = email + if User.objects.filter(email=email).exists() or User.objects.filter(username=email).exists(): raise forms.ValidationError("Email is already registered.") return email @@ -27,12 +28,57 @@ class RegisterForm(forms.ModelForm): def save(self, commit=True): user = super().save(commit=False) + # Set username equal to email to avoid separate username errors + user.username = self.cleaned_data['email'] user.set_password(self.cleaned_data['password']) if commit: user.save() return user +class WebRegisterForm(forms.ModelForm): + password = forms.CharField(widget=forms.PasswordInput) + confirm_password = forms.CharField(widget=forms.PasswordInput) + + class Meta: + model = User + fields = ['first_name', 'last_name', 'email', 'phone_number', 'password', 'confirm_password'] + + def clean_email(self): + email = self.cleaned_data.get('email') + # Ensure both email and username do not clash, since we set username = email + if User.objects.filter(email=email).exists() or User.objects.filter(username=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 clean(self): + cleaned_data = super().clean() + password = cleaned_data.get('password') + confirm_password = cleaned_data.get('confirm_password') + if password != confirm_password: + raise forms.ValidationError("Passwords do not match.") + return cleaned_data + + def save(self, commit=True): + user = super().save(commit=False) + # Set username equal to email to avoid separate username errors + user.username = self.cleaned_data['email'] + user.set_password(self.cleaned_data['password']) + print('*' * 100) + print(user.username) + print('*' * 100) + if commit: + user.save() + return user + + + class LoginForm(forms.Form): username = forms.CharField() password = forms.CharField(widget=forms.PasswordInput) diff --git a/mobile_api/urls.py b/mobile_api/urls.py index 0a71339..c6e917f 100644 --- a/mobile_api/urls.py +++ b/mobile_api/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ path('user/login/', LoginView.as_view(), name='json_login'), path('user/status/', StatusView.as_view(), name='user_status'), path('user/logout/', LogoutView.as_view(), name='user_logout'), + path('user/update-profile/', UpdateProfileView.as_view(), name='update_profile'), ] # Event URLS diff --git a/mobile_api/utils.py b/mobile_api/utils.py index 7b06306..95b18df 100644 --- a/mobile_api/utils.py +++ b/mobile_api/utils.py @@ -12,13 +12,14 @@ 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 + - JSON parsing from request body (for application/json requests) + - Form data parsing (for multipart/form-data requests) - Token and username extraction - Token validation - Username verification against token user Args: - request: Django request object with JSON body containing 'token' and 'username' + request: Django request object with JSON body or form data containing 'token' and 'username' error_status_code: Optional HTTP status code for error responses (default: None) Returns: @@ -31,15 +32,22 @@ def validate_token_and_get_user(request, error_status_code=None): - 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 - )) + # Check if it's multipart/form-data + is_multipart = request.content_type and 'multipart/form-data' in request.content_type + + if is_multipart: + # For multipart/form-data, get data from POST + data = request.POST.dict() + else: + # For JSON requests, parse from body + try: + 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") @@ -62,6 +70,8 @@ def validate_token_and_get_user(request, error_status_code=None): user = User.objects.get(email=username) else: user = User.objects.get(username=username) + else: + user = None if not user: status = 401 if error_status_code else None @@ -87,4 +97,10 @@ def validate_token_and_get_user(request, error_status_code=None): {"status": "invalid_token"}, status=status )) + except User.DoesNotExist: + status = 401 if error_status_code else None + return (None, None, None, JsonResponse( + {"status": "error", "message": "user not found"}, + status=status + )) diff --git a/mobile_api/views/events.py b/mobile_api/views/events.py index 5103f21..360ea83 100644 --- a/mobile_api/views/events.py +++ b/mobile_api/views/events.py @@ -240,12 +240,23 @@ class EventsByMonthYearAPI(APIView): # 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() + # 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() + + events = Event.objects.filter(start_date__year=year, start_date__month=month_number).distinct() + print('*' * 100) + print(f'Total events: {events.count()}') + print('*' * 100) + unique_start_dates = events.values_list('start_date', flat=True).distinct() + date_strings = [d.strftime('%Y-%m-%d') for d in unique_start_dates] + print('*' * 100) + print(f'Unique start dates: {date_strings}') + print('*' * 100) + # Group events by date date_events_dict = {} @@ -293,7 +304,7 @@ class EventsByMonthYearAPI(APIView): return JsonResponse({ "status": "success", - "dates": sorted_dates, + "dates": date_strings, "total_number_of_events": total_events, "date_events": date_events }) @@ -334,9 +345,8 @@ class EventsByDateAPI(APIView): # 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') + start_date=event_date + ).order_by('start_date') event_list = [] diff --git a/mobile_api/views/user.py b/mobile_api/views/user.py index 5b3dab6..2ce5c92 100644 --- a/mobile_api/views/user.py +++ b/mobile_api/views/user.py @@ -5,11 +5,12 @@ 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 mobile_api.forms import RegisterForm, LoginForm, WebRegisterForm 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 +from accounts.models import User @method_decorator(csrf_exempt, name='dispatch') @@ -27,6 +28,38 @@ class RegisterView(View): return JsonResponse({'error': str(e)}, status=500) +@method_decorator(csrf_exempt, name='dispatch') +class WebRegisterView(View): + def post(self, request): + print('0') + print('*' * 100) + print(request.body) + print('*' * 100) + try: + data = json.loads(request.body) + form = WebRegisterForm(data) + print('1') + print('*' * 100) + print(form.errors) + print('*' * 100) + if form.is_valid(): + print('2') + user = form.save() + token, _ = Token.objects.get_or_create(user=user) + print('3') + response = { + 'message': 'User registered successfully', + 'token': token.key, + 'username': user.username, + 'email': user.email, + 'phone_number': user.phone_number, + } + return JsonResponse(response, 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): @@ -56,6 +89,7 @@ class LoginView(View): 'place': user.place, 'latitude': user.latitude, 'longitude': user.longitude, + 'profile_photo': request.build_absolute_uri(user.profile_picture.url) if user.profile_picture else '' } print('4') print(response) @@ -105,3 +139,140 @@ class LogoutView(View): except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) + + +@method_decorator(csrf_exempt, name='dispatch') +class UpdateProfileView(View): + def post(self, request): + try: + # Authenticate user using validate_token_and_get_user + user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True) + if error_response: + # Convert error response format to match our API response format + error_data = json.loads(error_response.content) + return JsonResponse({ + 'success': False, + 'error': error_data.get('message', error_data.get('status', 'Authentication failed')) + }, status=error_response.status_code) + + errors = {} + updated_fields = [] + + # Get update data - handle both JSON and multipart/form-data + is_multipart = request.content_type and 'multipart/form-data' in request.content_type + if is_multipart: + # For multipart, get data from POST (data already contains token/username from validation) + json_data = request.POST.dict() + else: + # For JSON, use data from validate_token_and_get_user + json_data = data if data else {} + + # Update first_name + if 'first_name' in json_data: + first_name = json_data.get('first_name', '').strip() + if first_name: + user.first_name = first_name + updated_fields.append('first_name') + elif first_name == '': + user.first_name = '' + updated_fields.append('first_name') + + # Update last_name + if 'last_name' in json_data: + last_name = json_data.get('last_name', '').strip() + if last_name: + user.last_name = last_name + updated_fields.append('last_name') + elif last_name == '': + user.last_name = '' + updated_fields.append('last_name') + + # Update phone_number + if 'phone_number' in json_data: + phone_number = json_data.get('phone_number', '').strip() + if phone_number: + # Check if phone number is already taken by another user + if User.objects.filter(phone_number=phone_number).exclude(id=user.id).exists(): + errors['phone_number'] = 'Phone number is already registered.' + else: + user.phone_number = phone_number + updated_fields.append('phone_number') + elif phone_number == '': + user.phone_number = None + updated_fields.append('phone_number') + + # Update email + if 'email' in json_data: + email = json_data.get('email', '').strip().lower() + if email: + # Validate email format + if '@' not in email: + errors['email'] = 'Invalid email format.' + # Check if email is already taken by another user + elif User.objects.filter(email=email).exclude(id=user.id).exists(): + errors['email'] = 'Email is already registered.' + else: + user.email = email + # Also update username if it was set to email + if user.username == user.email or not user.username: + user.username = email + updated_fields.append('email') + elif email == '': + errors['email'] = 'Email cannot be empty.' + + # Update pincode + if 'pincode' in json_data: + pincode = json_data.get('pincode', '').strip() + if pincode: + user.pincode = pincode + updated_fields.append('pincode') + elif pincode == '': + user.pincode = None + updated_fields.append('pincode') + + # Handle profile_picture (multipart form-data only) + if 'profile_photo' in request.FILES: + # Handle file upload from multipart/form-data + profile_photo = request.FILES['profile_photo'] + # Validate file type + if not profile_photo.content_type.startswith('image/'): + errors['profile_photo'] = 'File must be an image.' + else: + user.profile_picture = profile_photo + updated_fields.append('profile_photo') + + # Return errors if any + if errors: + return JsonResponse({ + 'success': False, + 'errors': errors + }, status=400) + + # Save user if any fields were updated + if updated_fields: + user.save() + return JsonResponse({ + 'success': True, + 'message': 'Profile updated successfully', + 'updated_fields': updated_fields, + 'user': { + 'username': user.username, + 'email': user.email, + 'first_name': user.first_name, + 'last_name': user.last_name, + 'phone_number': user.phone_number, + 'pincode': user.pincode, + 'profile_picture': user.profile_picture.url if user.profile_picture else None, + } + }, status=200) + else: + return JsonResponse({ + 'success': False, + 'error': 'No fields provided for update' + }, status=400) + + except Exception as e: + return JsonResponse({ + 'success': False, + 'error': str(e) + }, status=500)