diff --git a/dta_service/core/migrations/0001_initial.py b/dta_service/core/migrations/0001_initial.py
new file mode 100644
index 0000000..c4fd570
--- /dev/null
+++ b/dta_service/core/migrations/0001_initial.py
@@ -0,0 +1,166 @@
+# Generated by Django 5.2.4 on 2025-07-07 14:51
+
+import core.models
+import django.db.models.deletion
+import django.utils.timezone
+import uuid
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('auth', '0012_alter_user_first_name_max_length'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ 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')),
+ ('email', models.EmailField(max_length=254, unique=True)),
+ ('first_name', models.CharField(max_length=30)),
+ ('last_name', models.CharField(max_length=30)),
+ ('user_type', models.CharField(choices=[('property_owner', 'Property Owner'), ('vendor', 'Vendor'), ('admin', 'Admin')], max_length=20)),
+ ('is_active', models.BooleanField(default=True)),
+ ('is_staff', models.BooleanField(default=False)),
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now)),
+ ('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={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Conversation',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Property',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('address', models.CharField(max_length=200)),
+ ('city', models.CharField(max_length=100)),
+ ('state', models.CharField(max_length=2)),
+ ('zip_code', models.CharField(max_length=10)),
+ ('market_value', models.DecimalField(decimal_places=2, max_digits=12)),
+ ('loan_amount', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)),
+ ('loan_interest_rate', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True)),
+ ('loan_term', models.IntegerField(blank=True, null=True)),
+ ('loan_start_date', models.DateField(blank=True, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='VideoCategory',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('description', models.TextField(blank=True, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PropertyOwner',
+ fields=[
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
+ ('phone_number', models.CharField(blank=True, max_length=20, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Vendor',
+ fields=[
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
+ ('business_name', models.CharField(max_length=100)),
+ ('business_type', models.CharField(choices=[('contractor', 'Contractor'), ('inspector', 'Inspector'), ('lender', 'Lender'), ('other', 'Other')], max_length=20)),
+ ('phone_number', models.CharField(blank=True, max_length=20, null=True)),
+ ('address', models.CharField(max_length=200)),
+ ('city', models.CharField(max_length=100)),
+ ('state', models.CharField(max_length=2)),
+ ('zip_code', models.CharField(max_length=10)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Message',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('text', models.TextField()),
+ ('attachment', models.FileField(blank=True, null=True, upload_to=core.models.message_file_path)),
+ ('timestamp', models.DateTimeField(auto_now_add=True)),
+ ('read', models.BooleanField(default=False)),
+ ('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='core.conversation')),
+ ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PasswordResetToken',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('expires_at', models.DateTimeField()),
+ ('used', models.BooleanField(default=False)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='conversation',
+ name='property',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to='core.property'),
+ ),
+ migrations.CreateModel(
+ name='Video',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=200)),
+ ('description', models.TextField(blank=True, null=True)),
+ ('link', models.URLField()),
+ ('duration', models.IntegerField(help_text='Duration in seconds')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='core.videocategory')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='property',
+ name='owner',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='properties', to='core.propertyowner'),
+ ),
+ migrations.AddField(
+ model_name='conversation',
+ name='property_owner',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to='core.propertyowner'),
+ ),
+ migrations.AddField(
+ model_name='conversation',
+ name='vendor',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to='core.vendor'),
+ ),
+ migrations.CreateModel(
+ name='UserVideoProgress',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('progress', models.IntegerField(default=0, help_text='Progress in seconds')),
+ ('status', models.CharField(choices=[('not_started', 'Not Started'), ('in_progress', 'In Progress'), ('completed', 'Completed')], default='not_started', max_length=20)),
+ ('last_watched', models.DateTimeField(auto_now=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='video_progress', to=settings.AUTH_USER_MODEL)),
+ ('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_progress', to='core.video')),
+ ],
+ options={
+ 'unique_together': {('user', 'video')},
+ },
+ ),
+ migrations.AlterUniqueTogether(
+ name='conversation',
+ unique_together={('property_owner', 'vendor', 'property')},
+ ),
+ ]
diff --git a/dta_service/core/migrations/0002_alter_conversation_unique_together.py b/dta_service/core/migrations/0002_alter_conversation_unique_together.py
new file mode 100644
index 0000000..a7d7328
--- /dev/null
+++ b/dta_service/core/migrations/0002_alter_conversation_unique_together.py
@@ -0,0 +1,17 @@
+# Generated by Django 5.2.4 on 2025-07-09 19:42
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterUniqueTogether(
+ name='conversation',
+ unique_together=set(),
+ ),
+ ]
diff --git a/dta_service/core/models.py b/dta_service/core/models.py
index ab0aeb8..3fc7c94 100644
--- a/dta_service/core/models.py
+++ b/dta_service/core/models.py
@@ -151,8 +151,8 @@ class Conversation(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
- class Meta:
- unique_together = ('property_owner', 'vendor', 'property')
+ # class Meta:
+ # unique_together = ('property_owner', 'vendor', 'property')
def __str__(self):
return f"Conversation between {self.property_owner} and {self.vendor} about {self.property}"
diff --git a/dta_service/core/serializers.py b/dta_service/core/serializers.py
index 07cd434..37f01e8 100644
--- a/dta_service/core/serializers.py
+++ b/dta_service/core/serializers.py
@@ -106,25 +106,44 @@ class VendorSerializer(serializers.ModelSerializer):
class Meta:
model = Vendor
fields = ['user', 'business_name', 'business_type', 'phone_number',
- 'address', 'city', 'state', 'zip_code']
+ 'address', 'city', 'state', 'zip_code']
+
def create(self, validated_data):
+ # Extract user data
user_data = validated_data.pop('user')
- user = User.objects.create_user(**user_data)
+
+ # Get or create category
+ user, _ = User.objects.get_or_create(**user_data)
+
+ # Create video with the category
vendor = Vendor.objects.create(user=user, **validated_data)
return vendor
-
+
def update(self, instance, validated_data):
user_data = validated_data.pop('user', None)
+
+ # Update Vendor fields
+ for attr, value in validated_data.items():
+ setattr(instance, attr, value)
+ instance.save()
+
+ # Update nested User fields if provided
if user_data:
user = instance.user
+ email = user_data.get('email', None)
+
+ # Only validate email uniqueness if it's being changed
+ if email and email != user.email:
+ if User.objects.filter(email=email).exists():
+ raise serializers.ValidationError({
+ 'email': 'A user with this email already exists.'
+ })
+
for attr, value in user_data.items():
setattr(user, attr, value)
user.save()
- for attr, value in validated_data.items():
- setattr(instance, attr, value)
- instance.save()
return instance
class PropertySerializer(serializers.ModelSerializer):
@@ -148,24 +167,73 @@ class VideoSerializer(serializers.ModelSerializer):
model = Video
fields = ['id', 'category', 'title', 'description', 'link', 'duration', 'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at']
+
+ def create(self, validated_data):
+ # Extract category data
+ category_data = validated_data.pop('category')
+
+ # Get or create category
+ category, _ = VideoCategory.objects.get_or_create(**category_data)
+
+ # Create video with the category
+ video = Video.objects.create(category=category, **validated_data)
+ return video
+
+ def update(self, instance, validated_data):
+ # Handle category update if provided
+ category_data = validated_data.pop('category', None)
+ if category_data:
+ category, _ = VideoCategory.objects.get_or_create(**category_data)
+ instance.category = category
+
+ # Update other fields
+ for attr, value in validated_data.items():
+ setattr(instance, attr, value)
+
+ instance.save()
+ return instance
class UserVideoProgressSerializer(serializers.ModelSerializer):
video = VideoSerializer()
class Meta:
model = UserVideoProgress
- fields = ['video', 'progress', 'status', 'last_watched']
+ fields = ['video', 'progress', 'status', 'last_watched', 'user']
read_only_fields = ['status', 'last_watched']
+ def create(self, validated_data):
+ # Extract video data
+ video_data = validated_data.pop('video')
+
+ # Get or create video
+ video_serializer = VideoSerializer(data=video_data)
+ video_serializer.is_valid(raise_exception=True)
+ video = video_serializer.save()
+ user = validated_data.pop('user')
+ # Create progress record
+ progress = UserVideoProgress.objects.create(
+ user=user,
+ video=video,
+ **validated_data
+ )
+ return progress
+
def update(self, instance, validated_data):
+ # Handle video update if provided
+ video_data = validated_data.pop('video', None)
+ if video_data:
+ video_serializer = VideoSerializer(instance.video, data=video_data, partial=True)
+ video_serializer.is_valid(raise_exception=True)
+ video_serializer.save()
+
+ # Update progress
instance.progress = validated_data.get('progress', instance.progress)
instance.save()
+
return instance
class ConversationSerializer(serializers.ModelSerializer):
- property_owner = PropertyOwnerSerializer()
- vendor = VendorSerializer()
- property = PropertySerializer()
+
class Meta:
model = Conversation
@@ -173,7 +241,7 @@ class ConversationSerializer(serializers.ModelSerializer):
read_only_fields = ['id', 'created_at', 'updated_at']
class MessageSerializer(serializers.ModelSerializer):
- sender = UserSerializer(read_only=True)
+
class Meta:
model = Message
@@ -181,8 +249,34 @@ class MessageSerializer(serializers.ModelSerializer):
read_only_fields = ['id', 'timestamp']
def create(self, validated_data):
- message = Message.objects.create(**validated_data)
+ # Extract user data
+ sender = validated_data.pop('sender')
+
+ message = Message.objects.create(sender=sender, **validated_data)
return message
+
+
+ def update(self, instance, validated_data):
+ """
+ Handle updates to message fields.
+ Note: sender and conversation are typically read-only in updates
+ """
+ # Update text if provided
+ instance.text = validated_data.get('text', instance.text)
+
+ # Update read status if provided
+ if 'read' in validated_data:
+ instance.read = validated_data['read']
+
+ # Handle attachment updates (if needed)
+ if 'attachment' in validated_data:
+ # Delete old attachment if exists
+ if instance.attachment:
+ instance.attachment.delete()
+ instance.attachment = validated_data['attachment']
+
+ instance.save()
+ return instance
class PasswordResetRequestSerializer(serializers.Serializer):
email = serializers.EmailField()
@@ -210,6 +304,7 @@ class PasswordResetConfirmSerializer(serializers.Serializer):
new_password2 = serializers.CharField(write_only=True)
def validate(self, attrs):
+
try:
token = PasswordResetToken.objects.get(token=attrs['token'])
except PasswordResetToken.DoesNotExist:
diff --git a/dta_service/core/templates/password_reset_email.html b/dta_service/core/templates/password_reset_email.html
new file mode 100644
index 0000000..a084d3a
--- /dev/null
+++ b/dta_service/core/templates/password_reset_email.html
@@ -0,0 +1,20 @@
+
+
+
+ Password Reset
+
+
+ Hello {{ user.first_name }},
+
+ You're receiving this email because you requested a password reset for your account.
+
+ Please click the following link to reset your password:
+
+ {{ reset_url }}
+
+ If you didn't request this, please ignore this email.
+
+ Thanks,
+ The Real Estate App Team
+
+
\ No newline at end of file
diff --git a/dta_service/core/urls/__init__.py b/dta_service/core/urls/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/dta_service/core/urls/conversation.py b/dta_service/core/urls/conversation.py
new file mode 100644
index 0000000..e7030ee
--- /dev/null
+++ b/dta_service/core/urls/conversation.py
@@ -0,0 +1,21 @@
+from django.urls import path
+from rest_framework.routers import DefaultRouter
+from core.views import ConversationViewSet, MessageViewSet
+
+router = DefaultRouter()
+router.register(r'', ConversationViewSet, basename='conversation')
+
+urlpatterns = [
+ path('/messages/', MessageViewSet.as_view({
+ 'get': 'list',
+ 'post': 'create'
+ }), name='conversation-messages'),
+ path('/messages//', MessageViewSet.as_view({
+ 'get': 'retrieve',
+ 'put': 'update',
+ 'patch': 'partial_update',
+ 'delete': 'destroy'
+ }), name='message-detail'),
+]
+
+urlpatterns += router.urls
\ No newline at end of file
diff --git a/dta_service/core/urls/property.py b/dta_service/core/urls/property.py
new file mode 100644
index 0000000..dd3c2f7
--- /dev/null
+++ b/dta_service/core/urls/property.py
@@ -0,0 +1,8 @@
+from django.urls import path
+from rest_framework.routers import DefaultRouter
+from core.views import PropertyViewSet
+
+router = DefaultRouter()
+router.register(r'', PropertyViewSet, basename='property')
+
+urlpatterns = router.urls
\ No newline at end of file
diff --git a/dta_service/core/urls/property_owner.py b/dta_service/core/urls/property_owner.py
new file mode 100644
index 0000000..6610f08
--- /dev/null
+++ b/dta_service/core/urls/property_owner.py
@@ -0,0 +1,8 @@
+from django.urls import path
+from rest_framework.routers import DefaultRouter
+from core.views import PropertyOwnerViewSet
+
+router = DefaultRouter()
+router.register(r'', PropertyOwnerViewSet, basename='property-owner')
+
+urlpatterns = router.urls
\ No newline at end of file
diff --git a/dta_service/core/urls/vendor.py b/dta_service/core/urls/vendor.py
new file mode 100644
index 0000000..61709a2
--- /dev/null
+++ b/dta_service/core/urls/vendor.py
@@ -0,0 +1,8 @@
+from django.urls import path
+from rest_framework.routers import DefaultRouter
+from core.views import VendorViewSet
+
+router = DefaultRouter()
+router.register(r'', VendorViewSet, basename='vendor')
+
+urlpatterns = router.urls
\ No newline at end of file
diff --git a/dta_service/core/urls/video.py b/dta_service/core/urls/video.py
new file mode 100644
index 0000000..7f2385c
--- /dev/null
+++ b/dta_service/core/urls/video.py
@@ -0,0 +1,10 @@
+from django.urls import path
+from rest_framework.routers import DefaultRouter
+from core.views import VideoCategoryViewSet, VideoViewSet, UserVideoProgressViewSet
+
+router = DefaultRouter()
+router.register(r'categories', VideoCategoryViewSet, basename='video-category')
+router.register(r'', VideoViewSet, basename='video')
+router.register(r'progress', UserVideoProgressViewSet, basename='video-progress')
+
+urlpatterns = router.urls
\ No newline at end of file
diff --git a/dta_service/core/views.py b/dta_service/core/views.py
index aaefd23..c0d20af 100644
--- a/dta_service/core/views.py
+++ b/dta_service/core/views.py
@@ -32,7 +32,8 @@ class UserRegisterView(generics.CreateAPIView):
permission_classes = [permissions.AllowAny]
class LogoutView(APIView):
- permission_classes = [IsAuthenticated]
+ permission_classes = (permissions.AllowAny,)
+ authentication_classes = ()
def post(self, request):
try:
@@ -118,10 +119,11 @@ class VideoViewSet(viewsets.ModelViewSet):
filterset_fields = ['category']
class UserVideoProgressViewSet(viewsets.ModelViewSet):
+ queryset = UserVideoProgress.objects.all()
serializer_class = UserVideoProgressSerializer
permission_classes = [IsAuthenticated]
- def get_queryset(self):
+ def get_queryset(self):
return UserVideoProgress.objects.filter(user=self.request.user)
def perform_create(self, serializer):
diff --git a/dta_service/dta_service/settings.py b/dta_service/dta_service/settings.py
index feaaa12..d9a24e9 100644
--- a/dta_service/dta_service/settings.py
+++ b/dta_service/dta_service/settings.py
@@ -11,6 +11,10 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
+import os
+
+from datetime import timedelta
+from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -25,7 +29,7 @@ SECRET_KEY = 'django-insecure-d!3*0+eki$pqv1n^)_v6&t^o@+-x2-i+8lzf5f%itsnngm3@y7
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = []
+ALLOWED_HOSTS = ['*']
# Application definition
@@ -37,6 +41,16 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+
+ # Third-party apps
+ 'rest_framework',
+ 'rest_framework_simplejwt',
+ 'rest_framework_simplejwt.token_blacklist',
+ 'channels',
+ 'django_filters',
+
+ # Local apps
+ 'core',
]
MIDDLEWARE = [
@@ -58,6 +72,7 @@ TEMPLATES = [
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
+ 'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
@@ -67,6 +82,7 @@ TEMPLATES = [
]
WSGI_APPLICATION = 'dta_service.wsgi.application'
+ASGI_APPLICATION = 'config.asgi.application'
# Database
@@ -108,15 +124,89 @@ TIME_ZONE = 'UTC'
USE_I18N = True
+USE_L10N = True
USE_TZ = True
+FRONTEND_URL = config("FRONTEND_URL")
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/5.2/howto/static-files/
-STATIC_URL = 'static/'
+# Static files
+STATIC_URL = '/static/'
+STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
+
+# Media files
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+# Custom user model
+AUTH_USER_MODEL = 'core.User'
+
+# REST Framework
+REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
+ ),
+ 'DEFAULT_PERMISSION_CLASSES': (
+ 'rest_framework.permissions.IsAuthenticated',
+ ),
+ 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
+}
+
+# JWT Settings
+SIMPLE_JWT = {
+ 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
+ 'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
+ 'ROTATE_REFRESH_TOKENS': True,
+ 'BLACKLIST_AFTER_ROTATION': True,
+ 'UPDATE_LAST_LOGIN': False,
+
+ 'ALGORITHM': 'HS256',
+ 'SIGNING_KEY': SECRET_KEY,
+ 'VERIFYING_KEY': None,
+ 'AUDIENCE': None,
+ 'ISSUER': None,
+ 'JWK_URL': None,
+ 'LEEWAY': 0,
+
+ 'AUTH_HEADER_TYPES': ('Bearer',),
+ 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
+ 'USER_ID_FIELD': 'id',
+ 'USER_ID_CLAIM': 'user_id',
+ 'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
+
+ 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
+ 'TOKEN_TYPE_CLAIM': 'token_type',
+ 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
+
+ 'JTI_CLAIM': 'jti',
+
+ 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
+ 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
+ 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
+}
+
+# Email settings
+# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+# EMAIL_HOST = config('SMTP2GO_HOST')
+# EMAIL_PORT = config('SMTP2GO_PORT', cast=int)
+# EMAIL_USE_TLS = True
+# EMAIL_HOST_USER = config('SMTP2GO_USERNAME')
+# EMAIL_HOST_PASSWORD = config('SMTP2GO_PASSWORD')
+# DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL')
+
+
+# CHANNEL_LAYERS = {
+# 'default': {
+# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
+# 'CONFIG': {
+# "hosts": [(config('REDIS_HOST'), config('REDIS_PORT', cast=int))],
+# },
+# },
+# }
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+TEST_DISCOVER_PATTERN = "test_*.py"
\ No newline at end of file
diff --git a/dta_service/dta_service/urls.py b/dta_service/dta_service/urls.py
index 6094d68..83068c8 100644
--- a/dta_service/dta_service/urls.py
+++ b/dta_service/dta_service/urls.py
@@ -21,8 +21,17 @@ from rest_framework_simplejwt.views import (
)
from core.views import (
CustomTokenObtainPairView, LogoutView,
- PasswordResetRequestView, PasswordResetConfirmView
+ PasswordResetRequestView, PasswordResetConfirmView,
+ UserRegisterView
)
+from rest_framework.routers import DefaultRouter
+from core.views import VideoCategoryViewSet, VideoViewSet, UserVideoProgressViewSet, VendorViewSet
+
+router = DefaultRouter()
+router.register(r'categories', VideoCategoryViewSet, basename='video-category')
+router.register(r'', VideoViewSet, basename='video')
+router.register(r'progress', UserVideoProgressViewSet, basename='video-progress')
+router.register(r'', VendorViewSet, basename='vendor')
urlpatterns = [
path('admin/', admin.site.urls),
diff --git a/dta_service/tests/__init__.py b/dta_service/tests/__init__.py
new file mode 100644
index 0000000..f1488e3
--- /dev/null
+++ b/dta_service/tests/__init__.py
@@ -0,0 +1,4 @@
+# core/tests/__init__.py
+from .test_models import *
+from .test_serializers import *
+from .test_views import *
\ No newline at end of file
diff --git a/dta_service/tests/test_models.py b/dta_service/tests/test_models.py
new file mode 100644
index 0000000..9e38437
--- /dev/null
+++ b/dta_service/tests/test_models.py
@@ -0,0 +1,328 @@
+from django.test import TestCase
+from django.contrib.auth import get_user_model
+from core.models import (
+ PropertyOwner, Vendor, Property, VideoCategory, Video,
+ UserVideoProgress, Conversation, Message, PasswordResetToken
+)
+from datetime import datetime, timedelta
+from django.utils import timezone
+
+User = get_user_model()
+
+class UserModelTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='test@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+
+ def test_user_creation(self):
+ self.assertEqual(self.user.email, 'test@example.com')
+ self.assertEqual(self.user.first_name, 'Test')
+ self.assertEqual(self.user.last_name, 'User')
+ self.assertEqual(self.user.user_type, 'property_owner')
+ self.assertTrue(self.user.check_password('testpass123'))
+
+ def test_user_str(self):
+ self.assertEqual(str(self.user), 'test@example.com')
+
+class PropertyOwnerModelTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(
+ user=self.user,
+ phone_number='1234567890'
+ )
+
+ def test_property_owner_creation(self):
+ self.assertEqual(self.owner.user, self.user)
+ self.assertEqual(self.owner.phone_number, '1234567890')
+
+ def test_property_owner_str(self):
+ self.assertEqual(str(self.owner), 'Property Owner')
+
+class VendorModelTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.user,
+ business_name='Test Vendor',
+ business_type='contractor',
+ phone_number='1234567890',
+ address='123 Test St',
+ city='Testville',
+ state='TS',
+ zip_code='12345'
+ )
+
+ def test_vendor_creation(self):
+ self.assertEqual(self.vendor.user, self.user)
+ self.assertEqual(self.vendor.business_name, 'Test Vendor')
+ self.assertEqual(self.vendor.business_type, 'contractor')
+ self.assertEqual(self.vendor.phone_number, '1234567890')
+ self.assertEqual(self.vendor.address, '123 Test St')
+ self.assertEqual(self.vendor.city, 'Testville')
+ self.assertEqual(self.vendor.state, 'TS')
+ self.assertEqual(self.vendor.zip_code, '12345')
+
+ def test_vendor_str(self):
+ self.assertEqual(str(self.vendor), 'Test Vendor')
+
+class PropertyModelTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.user)
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00,
+ loan_amount=400000.00,
+ loan_interest_rate=3.5,
+ loan_term=30,
+ loan_start_date='2020-01-01'
+ )
+
+ def test_property_creation(self):
+ self.assertEqual(self.property.owner, self.owner)
+ self.assertEqual(self.property.address, '123 Main St')
+ self.assertEqual(self.property.city, 'Anytown')
+ self.assertEqual(self.property.state, 'CA')
+ self.assertEqual(self.property.zip_code, '90210')
+ self.assertEqual(self.property.market_value, 500000.00)
+ self.assertEqual(self.property.loan_amount, 400000.00)
+ self.assertEqual(self.property.loan_interest_rate, 3.5)
+ self.assertEqual(self.property.loan_term, 30)
+ self.assertEqual(str(self.property.loan_start_date), '2020-01-01')
+
+ def test_property_str(self):
+ self.assertEqual(str(self.property), '123 Main St, Anytown, CA 90210')
+
+class VideoModelTest(TestCase):
+ def setUp(self):
+ self.category = VideoCategory.objects.create(
+ name='Test Category',
+ description='Test Description'
+ )
+ self.video = Video.objects.create(
+ category=self.category,
+ title='Test Video',
+ description='Test Video Description',
+ link='https://example.com/video',
+ duration=300
+ )
+
+ def test_video_creation(self):
+ self.assertEqual(self.video.category, self.category)
+ self.assertEqual(self.video.title, 'Test Video')
+ self.assertEqual(self.video.description, 'Test Video Description')
+ self.assertEqual(self.video.link, 'https://example.com/video')
+ self.assertEqual(self.video.duration, 300)
+
+ def test_video_str(self):
+ self.assertEqual(str(self.video), 'Test Video')
+
+class UserVideoProgressModelTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='user@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.category = VideoCategory.objects.create(name='Test Category')
+ self.video = Video.objects.create(
+ category=self.category,
+ title='Test Video',
+ link='https://example.com/video',
+ duration=300
+ )
+ self.progress = UserVideoProgress.objects.create(
+ user=self.user,
+ video=self.video,
+ progress=150
+ )
+
+ def test_progress_creation(self):
+ self.assertEqual(self.progress.user, self.user)
+ self.assertEqual(self.progress.video, self.video)
+ self.assertEqual(self.progress.progress, 150)
+ self.assertEqual(self.progress.status, 'in_progress')
+
+ def test_status_update(self):
+ # Test not started
+ self.progress.progress = 0
+ self.progress.save()
+ self.assertEqual(self.progress.status, 'not_started')
+
+ # Test completed
+ self.progress.progress = 300
+ self.progress.save()
+ self.assertEqual(self.progress.status, 'completed')
+
+ # Test in progress
+ self.progress.progress = 150
+ self.progress.save()
+ self.assertEqual(self.progress.status, 'in_progress')
+
+ def test_progress_str(self):
+ self.assertEqual(str(self.progress), 'user@example.com - Test Video - in_progress')
+
+class ConversationModelTest(TestCase):
+ def setUp(self):
+ self.owner_user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.owner_user)
+
+ self.vendor_user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.vendor_user,
+ business_name='Test Vendor',
+ business_type='contractor'
+ )
+
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+
+ self.conversation = Conversation.objects.create(
+ property_owner=self.owner,
+ vendor=self.vendor,
+ property=self.property
+ )
+
+ def test_conversation_creation(self):
+ self.assertEqual(self.conversation.property_owner, self.owner)
+ self.assertEqual(self.conversation.vendor, self.vendor)
+ self.assertEqual(self.conversation.property, self.property)
+
+ def test_conversation_str(self):
+ expected_str = f"Conversation between {self.owner} and {self.vendor} about {self.property}"
+ self.assertEqual(str(self.conversation), expected_str)
+
+class MessageModelTest(TestCase):
+ def setUp(self):
+ self.owner_user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.owner_user)
+
+ self.vendor_user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.vendor_user,
+ business_name='Test Vendor',
+ business_type='contractor'
+ )
+
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+
+ self.conversation = Conversation.objects.create(
+ property_owner=self.owner,
+ vendor=self.vendor,
+ property=self.property
+ )
+
+ self.message = Message.objects.create(
+ conversation=self.conversation,
+ sender=self.owner_user,
+ text='Test message'
+ )
+
+ def test_message_creation(self):
+ self.assertEqual(self.message.conversation, self.conversation)
+ self.assertEqual(self.message.sender, self.owner_user)
+ self.assertEqual(self.message.text, 'Test message')
+ self.assertFalse(self.message.read)
+
+ def test_message_str(self):
+ expected_str = f"Message from {self.owner_user} in {self.conversation}"
+ self.assertEqual(str(self.message), expected_str)
+
+class PasswordResetTokenModelTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='user@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.token = PasswordResetToken.objects.create(
+ user=self.user,
+ expires_at=timezone.now() + timedelta(hours=24)
+ )
+
+ def test_token_creation(self):
+ self.assertEqual(self.token.user, self.user)
+ self.assertFalse(self.token.used)
+ self.assertTrue(self.token.is_valid())
+
+ def test_token_invalid_after_use(self):
+ self.token.used = True
+ self.token.save()
+ self.assertFalse(self.token.is_valid())
+
+ def test_token_invalid_after_expiry(self):
+ self.token.expires_at = timezone.now() - timedelta(hours=1)
+ self.token.save()
+ self.assertFalse(self.token.is_valid())
+
+ def test_token_str(self):
+ self.assertEqual(str(self.token), f"Password reset token for {self.user.email}")
\ No newline at end of file
diff --git a/dta_service/tests/test_serializers.py b/dta_service/tests/test_serializers.py
new file mode 100644
index 0000000..69df07d
--- /dev/null
+++ b/dta_service/tests/test_serializers.py
@@ -0,0 +1,511 @@
+from django.test import TestCase
+from django.contrib.auth import get_user_model
+from rest_framework.exceptions import ValidationError
+from core.serializers import (
+ UserRegisterSerializer, PropertyOwnerSerializer, VendorSerializer,
+ PropertySerializer, VideoSerializer, UserVideoProgressSerializer,
+ ConversationSerializer, MessageSerializer, PasswordResetRequestSerializer,
+ PasswordResetConfirmSerializer
+)
+from core.models import (
+ PropertyOwner, Vendor, Property, VideoCategory, Video,
+ UserVideoProgress, Conversation, Message, PasswordResetToken
+)
+import uuid
+from datetime import datetime, timedelta
+
+User = get_user_model()
+
+class UserRegisterSerializerTest(TestCase):
+ def setUp(self):
+ self.valid_data = {
+ 'email': 'test@example.com',
+ 'first_name': 'Test',
+ 'last_name': 'User',
+ 'user_type': 'property_owner',
+ 'password': 'testpass123',
+ 'password2': 'testpass123'
+ }
+
+ def test_valid_serializer(self):
+ serializer = UserRegisterSerializer(data=self.valid_data)
+ self.assertTrue(serializer.is_valid())
+ user = serializer.save()
+ self.assertEqual(user.email, 'test@example.com')
+ self.assertEqual(user.first_name, 'Test')
+ self.assertEqual(user.last_name, 'User')
+ self.assertEqual(user.user_type, 'property_owner')
+
+ def test_password_mismatch(self):
+ invalid_data = self.valid_data.copy()
+ invalid_data['password2'] = 'differentpass'
+ serializer = UserRegisterSerializer(data=invalid_data)
+ with self.assertRaises(ValidationError):
+ serializer.is_valid(raise_exception=True)
+
+class PropertyOwnerSerializerTest(TestCase):
+ def setUp(self):
+ self.user_data = {
+ 'email': 'owner@example.com',
+ 'first_name': 'Property',
+ 'last_name': 'Owner',
+ 'user_type': 'property_owner',
+ 'password': 'testpass123'
+ }
+ self.owner_data = {
+ 'user': self.user_data,
+ 'phone_number': '1234567890'
+ }
+
+ def test_create_property_owner(self):
+ serializer = PropertyOwnerSerializer(data=self.owner_data)
+ self.assertTrue(serializer.is_valid())
+ owner = serializer.save()
+ self.assertEqual(owner.user.email, 'owner@example.com')
+ self.assertEqual(owner.phone_number, '1234567890')
+
+ def test_update_property_owner(self):
+ user = User.objects.create_user(**self.user_data)
+ owner = PropertyOwner.objects.create(user=user, phone_number='1234567890')
+
+ update_data = {
+ 'user': {
+ 'first_name': 'NewName',
+ 'last_name': 'NewLast',
+ 'email': 'newowner@example.com'
+ },
+ 'phone_number': '9876543210'
+ }
+
+ serializer = PropertyOwnerSerializer(instance=owner, data=update_data, partial=True)
+ self.assertTrue(serializer.is_valid())
+ updated_owner = serializer.save()
+
+ self.assertEqual(updated_owner.user.first_name, 'NewName')
+ self.assertEqual(updated_owner.user.last_name, 'NewLast')
+ self.assertEqual(updated_owner.phone_number, '9876543210')
+
+class VendorSerializerTest(TestCase):
+ def setUp(self):
+ self.user_data = {
+ 'email': 'vendor@example.com',
+ 'first_name': 'Vendor',
+ 'last_name': 'User',
+ 'user_type': 'vendor',
+ 'password': 'testpass123'
+ }
+ self.vendor_data = {
+ 'user': self.user_data,
+ 'business_name': 'Test Vendor',
+ 'business_type': 'contractor',
+ 'phone_number': '1234567890',
+ 'address': '123 Test St',
+ 'city': 'Testville',
+ 'state': 'TS',
+ 'zip_code': '12345'
+ }
+
+ def test_create_vendor(self):
+ serializer = VendorSerializer(data=self.vendor_data)
+ self.assertTrue(serializer.is_valid())
+ vendor = serializer.save()
+ self.assertEqual(vendor.user.email, 'vendor@example.com')
+ self.assertEqual(vendor.business_name, 'Test Vendor')
+ self.assertEqual(vendor.business_type, 'contractor')
+ self.assertEqual(vendor.phone_number, '1234567890')
+ self.assertEqual(vendor.address, '123 Test St')
+ self.assertEqual(vendor.city, 'Testville')
+ self.assertEqual(vendor.state, 'TS')
+ self.assertEqual(vendor.zip_code, '12345')
+
+ def test_update_vendor(self):
+ user = User.objects.create_user(**self.user_data)
+ vendor = Vendor.objects.create(
+ user=user,
+ business_name='Test Vendor',
+ business_type='contractor',
+ phone_number='1234567890',
+ address='123 Test St',
+ city='Testville',
+ state='TS',
+ zip_code='12345'
+ )
+
+ update_data = {
+ 'user': {
+ 'first_name': 'NewVendor',
+ 'last_name': 'NewUser',
+ 'email': 'newvendor@example.com'
+ },
+ 'business_name': 'Updated Vendor',
+ 'phone_number': '9876543210'
+ }
+
+ serializer = VendorSerializer(instance=vendor, data=update_data, partial=True)
+ self.assertTrue(serializer.is_valid())
+ updated_vendor = serializer.save()
+
+ self.assertEqual(updated_vendor.user.first_name, 'NewVendor')
+ self.assertEqual(updated_vendor.user.last_name, 'NewUser')
+ self.assertEqual(updated_vendor.business_name, 'Updated Vendor')
+ self.assertEqual(updated_vendor.phone_number, '9876543210')
+
+class PropertySerializerTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.user)
+ self.property_data = {
+ 'owner': self.owner.pk,
+ 'address': '123 Main St',
+ 'city': 'Anytown',
+ 'state': 'CA',
+ 'zip_code': '90210',
+ 'market_value': '500000.00',
+ 'loan_amount': '400000.00',
+ 'loan_interest_rate': '3.50',
+ 'loan_term': 30,
+ 'loan_start_date': '2020-01-01'
+ }
+
+ def test_create_property(self):
+ serializer = PropertySerializer(data=self.property_data)
+ self.assertTrue(serializer.is_valid())
+ property = serializer.save()
+ self.assertEqual(property.owner, self.owner)
+ self.assertEqual(property.address, '123 Main St')
+ self.assertEqual(property.city, 'Anytown')
+ self.assertEqual(property.state, 'CA')
+ self.assertEqual(property.zip_code, '90210')
+ self.assertEqual(str(property.market_value), '500000.00')
+ self.assertEqual(str(property.loan_amount), '400000.00')
+ self.assertEqual(str(property.loan_interest_rate), '3.50')
+ self.assertEqual(property.loan_term, 30)
+ self.assertEqual(str(property.loan_start_date), '2020-01-01')
+
+ def test_update_property(self):
+ property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+
+ update_data = {
+ 'address': '456 New St',
+ 'city': 'Newtown',
+ 'state': 'NY',
+ 'zip_code': '10001',
+ 'market_value': '600000.00'
+ }
+
+ serializer = PropertySerializer(instance=property, data=update_data, partial=True)
+ self.assertTrue(serializer.is_valid())
+ updated_property = serializer.save()
+
+ self.assertEqual(updated_property.address, '456 New St')
+ self.assertEqual(updated_property.city, 'Newtown')
+ self.assertEqual(updated_property.state, 'NY')
+ self.assertEqual(updated_property.zip_code, '10001')
+ self.assertEqual(str(updated_property.market_value), '600000.00')
+
+class VideoSerializerTest(TestCase):
+ def setUp(self):
+ self.category = VideoCategory.objects.create(
+ name='Test Category',
+ description='Test Description'
+ )
+ self.video_data = {
+ 'category': {
+ 'id': self.category.id,
+ 'name': self.category.name,
+ 'description': self.category.description
+ },
+ 'title': 'Test Video',
+ 'description': 'Test Video Description',
+ 'link': 'https://example.com/video',
+ 'duration': 300
+ }
+
+ def test_video_serializer(self):
+ serializer = VideoSerializer(data=self.video_data)
+ self.assertTrue(serializer.is_valid())
+ video = serializer.save()
+ self.assertEqual(video.category, self.category)
+ self.assertEqual(video.title, 'Test Video')
+ self.assertEqual(video.description, 'Test Video Description')
+ self.assertEqual(video.link, 'https://example.com/video')
+ self.assertEqual(video.duration, 300)
+
+class UserVideoProgressSerializerTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='user@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.category = VideoCategory.objects.create(name='Test Category')
+ self.video = Video.objects.create(
+ category=self.category,
+ title='Test Video',
+ link='https://example.com/video',
+ duration=300
+ )
+ self.progress_data = {
+ 'video': {
+ 'id': self.video.id,
+ 'title': self.video.title,
+ 'link': self.video.link,
+ 'duration': self.video.duration,
+ 'category': {
+ 'id': self.category.id,
+ 'name': self.category.name
+ }
+ },
+ 'user': self.user.pk,
+ 'progress': 150
+ }
+
+ def test_progress_serializer(self):
+ serializer = UserVideoProgressSerializer(data=self.progress_data)
+
+ self.assertTrue(serializer.is_valid())
+ progress = serializer.save(user=self.user)
+ self.assertEqual(progress.user, self.user)
+ self.assertEqual(progress.video.title, self.video.title)
+ self.assertEqual(progress.video.category.name, self.video.category.name)
+ self.assertEqual(progress.progress, 150)
+ self.assertEqual(progress.status, 'in_progress')
+
+ def test_many_progress_serializer(self):
+ categories = [VideoCategory.objects.create(name='Category One'),
+ VideoCategory.objects.create(name='Category Two')]
+
+ videos = [
+ Video.objects.create(
+ category=categories[0],
+ title='Test Video 1',
+ link='https://example.com/video1',
+ duration=300
+ ),
+ Video.objects.create(
+ category=categories[0],
+ title='Test Video 2',
+ link='https://example.com/video2',
+ duration=300
+ ),
+ Video.objects.create(
+ category=categories[0],
+ title='Test Video 3',
+ link='https://example.com/video3',
+ duration=300
+ ),
+ Video.objects.create(
+ category=categories[1],
+ title='Test Video 4',
+ link='https://example.com/video4',
+ duration=300
+ )
+ ]
+
+ progress_data = [
+ UserVideoProgress.objects.create(
+ video=videos[0],
+ user=self.user,
+ progress=100
+ ),
+ UserVideoProgress.objects.create(
+ video=videos[1],
+ user=self.user,
+ progress=30
+ ),
+ UserVideoProgress.objects.create(
+ video=videos[2],
+ user=self.user,
+ progress=0
+ ),
+ UserVideoProgress.objects.create(
+ video=videos[3],
+ user=self.user,
+ progress=0
+ ),
+ ]
+
+ serializer = UserVideoProgressSerializer(UserVideoProgress.objects.filter(user=self.user), many=True)
+ self.assertEqual(len(serializer.data), len(progress_data))
+ for i in range(len(serializer.data)):
+ self.assertEqual(serializer.data[i]['video']['title'], progress_data[i].video.title)
+ self.assertEqual(serializer.data[i]['video']['description'], progress_data[i].video.description)
+ self.assertEqual(serializer.data[i]['video']['link'], progress_data[i].video.link)
+ self.assertEqual(serializer.data[i]['video']['duration'], progress_data[i].video.duration)
+
+
+
+class ConversationSerializerTest(TestCase):
+ def setUp(self):
+ self.owner_user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.owner_user)
+
+ self.vendor_user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.vendor_user,
+ business_name='Test Vendor',
+ business_type='contractor'
+ )
+
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+
+ self.conversation_data = {
+ 'property_owner': self.owner.pk,
+ 'vendor': self.vendor.pk,
+ 'property': self.property.pk
+ }
+
+ def test_conversation_serializer(self):
+ serializer = ConversationSerializer(data=self.conversation_data)
+ self.assertTrue(serializer.is_valid(), serializer.errors)
+ conversation = serializer.save()
+ self.assertEqual(conversation.property_owner, self.owner)
+ self.assertEqual(conversation.vendor, self.vendor)
+ self.assertEqual(conversation.property, self.property)
+
+class MessageSerializerTest(TestCase):
+ def setUp(self):
+ self.owner_user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.owner_user)
+
+ self.vendor_user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.vendor_user,
+ business_name='Test Vendor',
+ business_type='contractor'
+ )
+
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+
+ self.conversation = Conversation.objects.create(
+ property_owner=self.owner,
+ vendor=self.vendor,
+ property=self.property
+ )
+
+ self.message_data = {
+ 'text': 'Test message'
+ }
+
+ def test_message_serializer(self):
+
+ context = {'sender': self.owner_user.pk, 'conversation': self.conversation.pk, 'text': 'Test message'}
+ serializer = MessageSerializer(data=context)
+ self.assertTrue(serializer.is_valid(), serializer.errors)
+
+ message = serializer.save()
+ self.assertEqual(message.conversation, self.conversation)
+ self.assertEqual(message.sender, self.owner_user)
+ self.assertEqual(message.text, 'Test message')
+ self.assertFalse(message.read)
+
+class PasswordResetRequestSerializerTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='user@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.valid_data = {'email': 'user@example.com'}
+ self.invalid_data = {'email': 'nonexistent@example.com'}
+
+ def test_valid_email(self):
+ serializer = PasswordResetRequestSerializer(data=self.valid_data)
+ self.assertTrue(serializer.is_valid())
+
+ def test_invalid_email(self):
+ serializer = PasswordResetRequestSerializer(data=self.invalid_data)
+ with self.assertRaises(ValidationError):
+ serializer.is_valid(raise_exception=True)
+
+class PasswordResetConfirmSerializerTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(
+ email='user@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.token = uuid.uuid4()
+ self.valid_data = {
+ 'token': self.token,
+ 'new_password': 'newpass123',
+ 'new_password2': 'newpass123'
+ }
+ self.mismatch_data = {
+ 'token': self.token,
+ 'new_password': 'newpass123',
+ 'new_password2': 'differentpass'
+ }
+
+ def test_valid_password_reset(self):
+ PasswordResetToken.objects.create(
+ user=self.user,
+ token=self.token,
+ expires_at=datetime.now() + timedelta(hours=24)
+ )
+ serializer = PasswordResetConfirmSerializer(data=self.valid_data)
+
+ self.assertTrue(serializer.is_valid())
+
+ def test_password_mismatch(self):
+ serializer = PasswordResetConfirmSerializer(data=self.mismatch_data)
+ with self.assertRaises(ValidationError):
+ serializer.is_valid(raise_exception=True)
+
\ No newline at end of file
diff --git a/dta_service/tests/test_views.py b/dta_service/tests/test_views.py
new file mode 100644
index 0000000..4ec3907
--- /dev/null
+++ b/dta_service/tests/test_views.py
@@ -0,0 +1,588 @@
+from django.test import TestCase
+from django.urls import reverse
+from rest_framework.test import APIClient
+from rest_framework import status
+from django.contrib.auth import get_user_model
+from core.models import (
+ PropertyOwner, Vendor, Property, VideoCategory, Video,
+ UserVideoProgress, Conversation, Message, PasswordResetToken
+)
+from datetime import datetime, timedelta
+from django.utils import timezone
+
+User = get_user_model()
+
+class AuthenticationTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+ self.user = User.objects.create_user(
+ email='test@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.login_url = reverse('token_obtain_pair')
+ self.refresh_url = reverse('token_refresh')
+ self.logout_url = reverse('logout')
+ self.register_url = reverse('register')
+ self.password_reset_url = reverse('password_reset')
+ self.password_reset_confirm_url = reverse('password_reset_confirm')
+
+ def test_user_login(self):
+ data = {
+ 'email': 'test@example.com',
+ 'password': 'testpass123'
+ }
+ response = self.client.post(self.login_url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertIn('access', response.data)
+ self.assertIn('refresh', response.data)
+ self.assertIn('user', response.data)
+
+ def test_user_login_invalid_credentials(self):
+ data = {
+ 'email': 'test@example.com',
+ 'password': 'wrongpassword'
+ }
+ response = self.client.post(self.login_url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_token_refresh(self):
+ # First login to get refresh token
+ login_data = {
+ 'email': 'test@example.com',
+ 'password': 'testpass123'
+ }
+ login_response = self.client.post(self.login_url, login_data, format='json')
+ refresh_token = login_response.data['refresh']
+
+ # Now refresh the token
+ refresh_data = {
+ 'refresh': refresh_token
+ }
+ response = self.client.post(self.refresh_url, refresh_data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertIn('access', response.data)
+
+ def test_user_logout(self):
+ # First login to get tokens
+ login_data = {
+ 'email': 'test@example.com',
+ 'password': 'testpass123'
+ }
+ login_response = self.client.post(self.login_url, login_data, format='json')
+ self.assertEqual(login_response.status_code, status.HTTP_200_OK, login_response.text)
+
+ refresh_token = login_response.data['refresh']
+
+ # Now logout
+ logout_data = {
+ 'refresh_token': refresh_token
+ }
+ response = self.client.post(self.logout_url, logout_data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_205_RESET_CONTENT, response.text)
+
+ def test_user_registration(self):
+ data = {
+ 'email': 'newuser@example.com',
+ 'first_name': 'New',
+ 'last_name': 'User',
+ 'user_type': 'vendor',
+ 'password': 'newpass123',
+ 'password2': 'newpass123'
+ }
+ response = self.client.post(self.register_url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertTrue(User.objects.filter(email='newuser@example.com').exists())
+
+ def test_password_reset_request(self):
+ data = {
+ 'email': 'test@example.com'
+ }
+ response = self.client.post(self.password_reset_url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(PasswordResetToken.objects.filter(user=self.user).exists())
+
+ def test_password_reset_confirm(self):
+ # First create a reset token
+ expires_at = timezone.now() + timedelta(hours=24)
+ token = PasswordResetToken.objects.create(
+ user=self.user,
+ expires_at=expires_at
+ )
+
+ data = {
+ 'token': str(token.token),
+ 'new_password': 'newpass123',
+ 'new_password2': 'newpass123'
+ }
+ response = self.client.post(self.password_reset_confirm_url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ # Verify password was changed
+ self.user.refresh_from_db()
+ self.assertTrue(self.user.check_password('newpass123'))
+
+ # Verify token was marked as used
+ token.refresh_from_db()
+ self.assertTrue(token.used)
+
+class PropertyOwnerViewTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+ self.user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.user, phone_number='1234567890')
+ self.other_user = User.objects.create_user(
+ email='other@example.com',
+ first_name='Other',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.other_owner = PropertyOwner.objects.create(user=self.other_user)
+ self.url = reverse('property-owner-list')
+
+ # Authenticate
+ self.client.force_authenticate(user=self.user)
+
+ def test_get_property_owners(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 2) # Both owners should be visible
+
+ def test_create_property_owner(self):
+ data = {
+ 'user': {
+ 'email': 'newowner@example.com',
+ 'first_name': 'New',
+ 'last_name': 'Owner',
+ 'user_type': 'property_owner',
+ 'password': 'testpass123'
+ },
+ 'phone_number': '9876543210'
+ }
+ response = self.client.post(self.url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
+ self.assertTrue(PropertyOwner.objects.filter(user__email='newowner@example.com').exists())
+
+ def test_update_property_owner(self):
+ url = f"{self.url}{self.owner.pk}/"
+ data = {
+ 'user': {
+ 'first_name': 'Updated',
+ 'last_name': 'Owner',
+ 'email': 'new_email@email.com',
+ 'user_type': 'property_owner'
+ },
+ 'phone_number': '9999999999'
+ }
+ response = self.client.put(url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
+ self.owner.refresh_from_db()
+ self.assertEqual(self.owner.user.first_name, 'Updated')
+ self.assertEqual(self.owner.phone_number, '9999999999')
+
+class VendorViewTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+ self.user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.user,
+ business_name='Test Vendor',
+ business_type='contractor',
+ phone_number='1234567890',
+ address='123 Test St',
+ city='Testville',
+ state='TS',
+ zip_code='12345'
+ )
+ self.url = reverse('vendor-list')
+
+ # Authenticate
+ self.client.force_authenticate(user=self.user)
+
+ def test_get_vendors(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
+
+ def test_create_vendor(self):
+ data = {
+ 'user': {
+ 'email': 'newvendor@example.com',
+ 'first_name': 'New',
+ 'last_name': 'Vendor',
+ 'user_type': 'vendor',
+ 'password': 'testpass123'
+ },
+ 'business_name': 'New Vendor',
+ 'business_type': 'inspector',
+ 'phone_number': '9876543210',
+ 'address': '456 New St',
+ 'city': 'Newtown',
+ 'state': 'NS',
+ 'zip_code': '54321'
+ }
+ response = self.client.post(self.url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertTrue(Vendor.objects.filter(business_name='New Vendor').exists())
+
+ def test_update_vendor(self):
+ url = f"{self.url}{self.vendor.pk}/"
+ data = {
+ 'user': {
+ 'first_name': 'Updated',
+ 'last_name': 'Vendor',
+ },
+ 'business_name': 'Updated Vendor',
+ 'phone_number': '9999999999'
+ }
+ response = self.client.patch(url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
+ self.vendor.refresh_from_db()
+ self.assertEqual(self.vendor.user.first_name, 'Updated')
+ self.assertEqual(self.vendor.business_name, 'Updated Vendor')
+ self.assertEqual(self.vendor.phone_number, '9999999999')
+
+class PropertyViewTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+ self.owner_user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.owner_user)
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+ self.url = reverse('property-list')
+
+ # Authenticate as property owner
+ self.client.force_authenticate(user=self.owner_user)
+
+ def test_get_properties(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
+
+ def test_create_property(self):
+ data = {
+ 'owner': self.owner.pk,
+ 'address': '456 New St',
+ 'city': 'Newtown',
+ 'state': 'NY',
+ 'zip_code': '10001',
+ 'market_value': '600000.00'
+ }
+ response = self.client.post(self.url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED,response.text)
+ self.assertTrue(Property.objects.filter(address='456 New St').exists())
+
+ def test_update_property(self):
+ url = f"{self.url}{self.property.pk}/"
+ data = {
+ 'address': '789 Updated St',
+ 'market_value': '550000.00'
+ }
+ response = self.client.patch(url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.property.refresh_from_db()
+ self.assertEqual(self.property.address, '789 Updated St')
+ self.assertEqual(str(self.property.market_value), '550000.00')
+
+class VideoViewTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+ self.user = User.objects.create_user(
+ email='user@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.category = VideoCategory.objects.create(
+ name='Test Category',
+ description='Test Description'
+ )
+ self.video = Video.objects.create(
+ category=self.category,
+ title='Test Video',
+ description='Test Video Description',
+ link='https://example.com/video',
+ duration=300
+ )
+ self.url = reverse('video-list')
+
+ # Authenticate
+ self.client.force_authenticate(user=self.user)
+
+ def test_get_videos(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
+
+ def test_create_video(self):
+ data = {
+ 'category': {
+ "name": "New Category",
+ "description": "a description"
+ },
+ 'title': 'New Video',
+ 'description': 'New Video Description',
+ 'link': 'https://example.com/new-video',
+ 'duration': 240
+ }
+ response = self.client.post(self.url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
+ self.assertTrue(Video.objects.filter(title='New Video').exists())
+
+ def test_update_video(self):
+ url = f"{self.url}{self.video.pk}/"
+ data = {
+ 'title': 'Updated Video',
+ 'duration': 360
+ }
+ response = self.client.patch(url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
+ self.video.refresh_from_db()
+ self.assertEqual(self.video.title, 'Updated Video')
+ self.assertEqual(self.video.duration, 360)
+
+class UserVideoProgressViewTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+ self.user = User.objects.create_user(
+ email='user@example.com',
+ first_name='Test',
+ last_name='User',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.category = VideoCategory.objects.create(name='Test Category')
+ self.video = Video.objects.create(
+ category=self.category,
+ title='Test Video',
+ link='https://example.com/video',
+ duration=300
+ )
+ self.video_1 = Video.objects.create(
+ category=self.category,
+ title='Test Video 1',
+ link='https://example.com/video_1',
+ duration=300
+ )
+ self.video_2 = Video.objects.create(
+ category=self.category,
+ title='Test Video 2',
+ link='https://example.com/video_2',
+ duration=300
+ )
+ self.progress = UserVideoProgress.objects.create(
+ user=self.user,
+ video=self.video,
+ progress=150
+ )
+ UserVideoProgress.objects.create(
+ user=self.user,
+ video=self.video_1,
+ progress=150
+ )
+ UserVideoProgress.objects.create(
+ user=self.user,
+ video=self.video_2,
+ progress=150
+ )
+ self.url = reverse('video-progress-list')
+
+ # Authenticate
+ self.client.force_authenticate(user=self.user)
+
+ def test_get_video_progress(self):
+ response = self.client.get(f"{self.url}", kwargs={'format','json'})
+ breakpoint()
+ self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
+ breakpoint()
+ self.assertEqual(len(response.data), 1)
+
+ def test_update_video_progress(self):
+ url = f"{self.url}{self.progress.pk}/"
+ data = {
+ 'progress': 200
+ }
+ response = self.client.patch(url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.progress.refresh_from_db()
+ self.assertEqual(self.progress.progress, 200)
+ self.assertEqual(self.progress.status, 'in_progress')
+
+class ConversationViewTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+
+ # Create owner user
+ self.owner_user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.owner_user)
+
+ # Create vendor user
+ self.vendor_user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.vendor_user,
+ business_name='Test Vendor',
+ business_type='contractor'
+ )
+
+ # Create property
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+
+ # Create conversation
+ self.conversation = Conversation.objects.create(
+ property_owner=self.owner,
+ vendor=self.vendor,
+ property=self.property
+ )
+
+ self.url = reverse('conversation-list')
+
+ # Authenticate as owner
+ self.client.force_authenticate(user=self.owner_user)
+
+ def test_get_conversations_as_owner(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
+
+ def test_create_conversation(self):
+ data = {
+ 'property_owner': self.owner.pk,
+ 'vendor': self.vendor.pk,
+ 'property': self.property.pk
+ }
+ response = self.client.post(self.url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
+ self.assertTrue(Conversation.objects.filter(property_owner=self.owner, vendor=self.vendor, property=self.property).exists())
+
+ def test_get_conversations_as_vendor(self):
+ # Switch to vendor user
+ self.client.force_authenticate(user=self.vendor_user)
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
+
+class MessageViewTests(TestCase):
+ def setUp(self):
+ self.client = APIClient()
+
+ # Create owner user
+ self.owner_user = User.objects.create_user(
+ email='owner@example.com',
+ first_name='Property',
+ last_name='Owner',
+ user_type='property_owner',
+ password='testpass123'
+ )
+ self.owner = PropertyOwner.objects.create(user=self.owner_user)
+
+ # Create vendor user
+ self.vendor_user = User.objects.create_user(
+ email='vendor@example.com',
+ first_name='Vendor',
+ last_name='User',
+ user_type='vendor',
+ password='testpass123'
+ )
+ self.vendor = Vendor.objects.create(
+ user=self.vendor_user,
+ business_name='Test Vendor',
+ business_type='contractor'
+ )
+
+ # Create property
+ self.property = Property.objects.create(
+ owner=self.owner,
+ address='123 Main St',
+ city='Anytown',
+ state='CA',
+ zip_code='90210',
+ market_value=500000.00
+ )
+
+ # Create conversation
+ self.conversation = Conversation.objects.create(
+ property_owner=self.owner,
+ vendor=self.vendor,
+ property=self.property
+ )
+
+ # Create message
+ self.message = Message.objects.create(
+ conversation=self.conversation,
+ sender=self.owner_user,
+ text='Test message'
+ )
+
+ self.url = reverse('conversation-messages', kwargs={'conversation_id': self.conversation.pk})
+
+ # Authenticate as owner
+ self.client.force_authenticate(user=self.owner_user)
+
+ def test_get_messages(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
+
+ def test_create_message(self):
+ data = {
+ 'sender': self.owner.pk,
+ 'text': 'New message',
+ 'conversation': self.conversation.pk
+ }
+ response = self.client.post(self.url, data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
+ self.assertTrue(Message.objects.filter(text='New message').exists())
+
+ def test_get_messages_as_vendor(self):
+ # Switch to vendor user
+ self.client.force_authenticate(user=self.vendor_user)
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
\ No newline at end of file