diff --git a/.gitignore b/.gitignore index 3020e7d..dac1739 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ media *.py[cod] *$py.class +meetings + # C extensions *.so diff --git a/booking_system/settings.py b/booking_system/settings.py index c2400c9..e4b877e 100644 --- a/booking_system/settings.py +++ b/booking_system/settings.py @@ -22,14 +22,12 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', - # Third-party apps 'rest_framework', 'rest_framework_simplejwt', 'corsheaders', - # Local apps 'users', - # 'meetings', + 'meetings', ] MIDDLEWARE = [ @@ -63,9 +61,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'booking_system.wsgi.application' -# Database -# https://docs.djangoproject.com/en/5.2/ref/settings/#databases - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -74,9 +69,6 @@ DATABASES = { } -# Password validation -# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators - AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -93,13 +85,14 @@ AUTH_PASSWORD_VALIDATORS = [ ] -# JWT Configuration SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(hours=24), # 24h as specified + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=24), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, 'SIGNING_KEY': os.getenv('JWT_SECRET', SECRET_KEY), + 'AUTH_HEADER_TYPES': ('Bearer',), + } # Stripe Configuration @@ -116,13 +109,12 @@ JITSI_BASE_URL = os.getenv('JITSI_BASE_URL', 'https://meet.jit.si') EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = os.getenv('SMTP_HOST', 'smtp.hostinger.com') EMAIL_PORT = int(os.getenv('SMTP_PORT', 465)) -EMAIL_USE_SSL = True # Since you're using port 465 +EMAIL_USE_SSL = True EMAIL_HOST_USER = os.getenv('SMTP_USERNAME', 'hello@attunehearttherapy.com') EMAIL_HOST_PASSWORD = os.getenv('SMTP_PASSWORD', 'G&n2S;ffTc8f') DEFAULT_FROM_EMAIL = os.getenv('SMTP_FROM', 'hello@attunehearttherapy.com') -# Django REST Framework REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', @@ -135,7 +127,6 @@ REST_FRAMEWORK = { ), } -# CORS Configuration CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:3000", @@ -149,45 +140,29 @@ CORS_ALLOW_CREDENTIALS = True ROOT_URLCONF = 'booking_system.urls' -# Custom User Model AUTH_USER_MODEL = 'users.CustomUser' -# Authentication backends AUTHENTICATION_BACKENDS = [ 'users.backends.EmailBackend', 'django.contrib.auth.backends.ModelBackend', ] -# Email templates -EMAIL_TEMPLATES = { - 'MEETING_BOOKED': 'emails/meeting_booked.html', - 'MEETING_INVITATION': 'emails/meeting_invitation.html', - 'MEETING_REMINDER': 'emails/meeting_reminder.html', - 'MEETING_CANCELLED': 'emails/meeting_cancelled.html', - 'PAYMENT_SUCCESS': 'emails/payment_success.html', -} - -# Internationalization LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_TZ = True -# Static files (CSS, JavaScript, Images) STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') -# Default primary key field type DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -# Celery Configuration (if using Redis) CELERY_BROKER_URL = 'redis://localhost:6379/0' CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' -# Logging LOGGING = { 'version': 1, 'disable_existing_loggers': False, diff --git a/booking_system/urls.py b/booking_system/urls.py index a452274..8de9543 100644 --- a/booking_system/urls.py +++ b/booking_system/urls.py @@ -4,5 +4,5 @@ from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/auth/', include('users.urls')), - # path('api/', include('meetings.urls')), + path('api/', include('meetings.urls')), ] \ No newline at end of file diff --git a/users/admin.py b/users/admin.py index d68cce1..0c3609f 100644 --- a/users/admin.py +++ b/users/admin.py @@ -5,15 +5,15 @@ from .models import CustomUser, UserProfile @admin.register(CustomUser) class UserAdmin(admin.ModelAdmin): - list_display = ('email', 'username', 'first_name', 'last_name', 'is_staff') - search_fields = ('email', 'username', 'first_name', 'last_name') + list_display = ('email', 'first_name', 'last_name', 'is_staff') + search_fields = ('email', 'first_name', 'last_name') ordering = ('email',) @admin.register(UserProfile) class UserProfileAdmin(admin.ModelAdmin): list_display = ('user', 'timezone', 'created_at', 'updated_at') - search_fields = ('user__email', 'user__username') + search_fields = ('user__email', 'user__first_name', 'user__last_name') ordering = ('user__email',) diff --git a/users/managers.py b/users/managers.py new file mode 100644 index 0000000..34708f5 --- /dev/null +++ b/users/managers.py @@ -0,0 +1,32 @@ +from django.contrib.auth.models import BaseUserManager +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ +from django.core.validators import validate_email + +class CustomUserManager(BaseUserManager): + def valide_email(self, email): + if not email: + raise ValidationError(_('The Email field must be set')) + try: + validate_email(email) + except ValidationError: + raise ValidationError(_('Enter a valid email address.')) + return self.normalize_email(email) + + def create_user(self, email, first_name, last_name, password=None, **extra_fields): + email = self.valide_email(email) + user = self.model(email=email, first_name=first_name, last_name=last_name, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, first_name, last_name, password=None, **extra_fields): + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + extra_fields.setdefault('is_active', True) + if extra_fields.get('is_staff') is not True: + raise ValueError(_('Superuser must have is_staff=True.')) + if extra_fields.get('is_superuser') is not True: + raise ValueError(_('Superuser must have is_superuser=True.')) + + return self.create_user(email, first_name, last_name, password, **extra_fields) \ No newline at end of file diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py index 90e4546..98d3e3f 100644 --- a/users/migrations/0001_initial.py +++ b/users/migrations/0001_initial.py @@ -1,9 +1,6 @@ -# Generated by Django 5.2.8 on 2025-11-12 06:32 +# Generated by Django 5.2.8 on 2025-11-13 00:35 -import django.contrib.auth.models -import django.contrib.auth.validators import django.db.models.deletion -import django.utils.timezone from django.conf import settings from django.db import migrations, models @@ -22,27 +19,21 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('email', models.EmailField(max_length=254, unique=True)), + ('first_name', models.CharField(max_length=50)), + ('last_name', models.CharField(max_length=50)), + ('is_staff', models.BooleanField(default=False)), + ('is_superuser', models.BooleanField(default=False)), + ('is_active', models.BooleanField(default=True)), ('phone_number', models.CharField(blank=True, max_length=20)), + ('last_login', models.DateTimeField(auto_now=True)), + ('date_joined', models.DateTimeField(auto_now_add=True)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', 'abstract': False, }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], ), migrations.CreateModel( name='UserProfile', diff --git a/users/models.py b/users/models.py index 54626d6..33c68e0 100644 --- a/users/models.py +++ b/users/models.py @@ -1,12 +1,22 @@ -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import PermissionsMixin, AbstractBaseUser from django.db import models +from .managers import CustomUserManager -class CustomUser(AbstractUser): +class CustomUser(AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True) + first_name = models.CharField(max_length=50) + last_name = models.CharField(max_length=50) + is_staff = models.BooleanField(default=False) + is_superuser = models.BooleanField(default=False) + is_active = models.BooleanField(default=True) phone_number = models.CharField(max_length=20, blank=True) + last_login = models.DateTimeField(auto_now=True) + date_joined = models.DateTimeField(auto_now_add=True) + + objects = CustomUserManager() USERNAME_FIELD = 'email' - REQUIRED_FIELDS = ['username'] + REQUIRED_FIELDS = ['first_name', 'last_name'] def __str__(self): return self.email diff --git a/users/serializers.py b/users/serializers.py index 77c46ca..47f1901 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -14,7 +14,7 @@ class UserRegistrationSerializer(serializers.ModelSerializer): class Meta: model = CustomUser - fields = ['email', 'username', 'password', 'password2', 'first_name', 'last_name', 'profile'] + fields = ['email', 'password', 'password2', 'first_name', 'last_name', 'profile'] extra_kwargs = { 'first_name': {'required': True}, 'last_name': {'required': True} @@ -29,7 +29,6 @@ class UserRegistrationSerializer(serializers.ModelSerializer): validated_data.pop('password2') user = CustomUser.objects.create_user( email=validated_data['email'], - username=validated_data['username'], password=validated_data['password'], first_name=validated_data['first_name'], last_name=validated_data['last_name'], @@ -41,4 +40,4 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser - fields = ['id', 'email', 'username', 'first_name', 'last_name', 'phone_number', 'profile'] \ No newline at end of file + fields = ['id', 'email', 'first_name', 'last_name', 'phone_number', 'profile'] \ No newline at end of file