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 0000000..948fdc9 Binary files /dev/null and b/db_1.sqlite3 differ 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)