big update
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -257,3 +257,4 @@ local_settings.py
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
|
media/
|
||||||
|
|||||||
@@ -1,3 +1,210 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from core.models import (
|
||||||
|
User,
|
||||||
|
PropertyOwner,
|
||||||
|
Message,
|
||||||
|
Vendor,
|
||||||
|
Property,
|
||||||
|
VideoCategory,
|
||||||
|
Video,
|
||||||
|
UserVideoProgress,
|
||||||
|
Conversation,
|
||||||
|
Offer,
|
||||||
|
PropertyPictures,
|
||||||
|
OpenHouse,
|
||||||
|
PropertySaleInfo,
|
||||||
|
PropertyTaxInfo,
|
||||||
|
PropertyWalkScoreInfo,
|
||||||
|
SchoolInfo,
|
||||||
|
Attorney, RealEstateAgent, UserViewModel, PropertySave
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
class UserAdmin(admin.ModelAdmin):
|
||||||
|
model = User
|
||||||
|
list_display = (
|
||||||
|
"email",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"is_active",
|
||||||
|
"is_staff",
|
||||||
|
"has_usable_password",
|
||||||
|
"user_type",
|
||||||
|
# "has_signed_tos",
|
||||||
|
"last_login",
|
||||||
|
)
|
||||||
|
search_fields = ("fields", "email", "first_name", "last_name", "user_type")
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyOwnerAdmin(admin.ModelAdmin):
|
||||||
|
model = PropertyOwner
|
||||||
|
list_display = (
|
||||||
|
"pk",
|
||||||
|
"user",
|
||||||
|
"phone_number",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VendorAdmin(admin.ModelAdmin):
|
||||||
|
model = Vendor
|
||||||
|
list_display = ("business_name", "business_type", "address")
|
||||||
|
search_fields = (
|
||||||
|
"business_name",
|
||||||
|
"business_type",
|
||||||
|
"phone_number",
|
||||||
|
"state",
|
||||||
|
"zip_code",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class VideoCategoryAdmin(admin.ModelAdmin):
|
||||||
|
model = VideoCategory
|
||||||
|
list_display = ("name", "description")
|
||||||
|
search_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
class VideoAdmin(admin.ModelAdmin):
|
||||||
|
model = Video
|
||||||
|
list_display = ("title", "description", "category__name", "duration", "link")
|
||||||
|
search_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
class UserVideoProgressAdmin(admin.ModelAdmin):
|
||||||
|
model = UserVideoProgress
|
||||||
|
|
||||||
|
|
||||||
|
class MessageStackedInline(admin.StackedInline):
|
||||||
|
model = Message
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationAdmin(admin.ModelAdmin):
|
||||||
|
model = Conversation
|
||||||
|
inlines = [MessageStackedInline]
|
||||||
|
|
||||||
|
|
||||||
|
class OfferAdmin(admin.ModelAdmin):
|
||||||
|
model = Offer
|
||||||
|
list_display = ("pk", "user", "property", "status")
|
||||||
|
search_fields = ("user", "status")
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyPicturesAdmin(admin.ModelAdmin):
|
||||||
|
model = PropertyPictures
|
||||||
|
list_display = ("id", "image")
|
||||||
|
|
||||||
|
|
||||||
|
class OpenHouseAdmin(admin.ModelAdmin):
|
||||||
|
model = OpenHouse
|
||||||
|
|
||||||
|
|
||||||
|
class PropertySaleInfoAdmin(admin.ModelAdmin):
|
||||||
|
model = PropertySaleInfo
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyTaxInfoAdmin(admin.ModelAdmin):
|
||||||
|
model = PropertyTaxInfo
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyWalkScoreInfoAdmin(admin.ModelAdmin):
|
||||||
|
model = PropertyWalkScoreInfo
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolInfoAdmin(admin.ModelAdmin):
|
||||||
|
model = SchoolInfo
|
||||||
|
|
||||||
|
class PropertyWalkScoreInfoStackedInline(admin.StackedInline):
|
||||||
|
model = PropertyWalkScoreInfo
|
||||||
|
|
||||||
|
class SchoolInfoStackedInline(admin.StackedInline):
|
||||||
|
model = SchoolInfo
|
||||||
|
|
||||||
|
class PropertyAdmin(admin.ModelAdmin):
|
||||||
|
model = Property
|
||||||
|
list_display = ("pk", "owner", "address", "city", "state", "zip_code")
|
||||||
|
search_fields = ("address", "city", "state", "zip_code", "owner")
|
||||||
|
inlines = [PropertyWalkScoreInfoStackedInline, SchoolInfoStackedInline]
|
||||||
|
|
||||||
|
|
||||||
|
# Registering the new Attorney model
|
||||||
|
@admin.register(Attorney)
|
||||||
|
class AttorneyAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'firm_name', 'bar_number', 'phone_number', 'city', 'state', 'years_experience')
|
||||||
|
list_filter = ('state', 'years_experience', 'specialties') # You might need custom list_filter for JSONField
|
||||||
|
search_fields = ('user__email', 'user__first_name', 'user__last_name', 'firm_name', 'bar_number', 'city')
|
||||||
|
# Use filter_horizontal for JSONField if you want a nice interface for many-to-many like selection
|
||||||
|
# filter_horizontal = ('specialties', 'licensed_states')
|
||||||
|
fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'fields': ('user', 'firm_name', 'bar_number', 'phone_number', 'address', 'city', 'state', 'zip_code', 'bio', 'profile_picture', 'website')
|
||||||
|
}),
|
||||||
|
('Professional Details', {
|
||||||
|
'fields': ('specialties', 'years_experience', 'licensed_states')
|
||||||
|
}),
|
||||||
|
('Location', {
|
||||||
|
'fields': ('latitude', 'longitude'),
|
||||||
|
}),
|
||||||
|
('Timestamps', {
|
||||||
|
'fields': ('created_at', 'updated_at'),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
readonly_fields = ('created_at', 'updated_at')
|
||||||
|
|
||||||
|
|
||||||
|
# Registering the new RealEstateAgent model
|
||||||
|
@admin.register(RealEstateAgent)
|
||||||
|
class RealEstateAgentAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'brokerage_name', 'license_number', 'phone_number', 'city', 'state', 'agent_type', 'years_experience')
|
||||||
|
list_filter = ('agent_type', 'state', 'years_experience', 'specialties')
|
||||||
|
search_fields = ('user__email', 'user__first_name', 'user__last_name', 'brokerage_name', 'license_number', 'city')
|
||||||
|
#filter_horizontal = ('specialties', 'licensed_states')
|
||||||
|
fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'fields': ('user', 'brokerage_name', 'license_number', 'phone_number', 'address', 'city', 'state', 'zip_code', 'bio', 'profile_picture', 'website', 'agent_type')
|
||||||
|
}),
|
||||||
|
('Professional Details', {
|
||||||
|
'fields': ('specialties', 'years_experience', 'licensed_states')
|
||||||
|
}),
|
||||||
|
('Location', {
|
||||||
|
'fields': ('latitude', 'longitude'),
|
||||||
|
}),
|
||||||
|
|
||||||
|
('Timestamps', {
|
||||||
|
'fields': ('created_at', 'updated_at'),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
readonly_fields = ('created_at', 'updated_at')
|
||||||
|
|
||||||
|
@admin.register(UserViewModel)
|
||||||
|
class UserViewModelAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('pk','user','created_at')
|
||||||
|
readonly_fields = ('created_at',)
|
||||||
|
|
||||||
|
@admin.register(PropertySave)
|
||||||
|
class PropertySaveAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('pk', 'user','property')
|
||||||
|
readonly_fields = ('created_at',)
|
||||||
|
|
||||||
|
admin.site.register(User, UserAdmin)
|
||||||
|
admin.site.register(PropertyOwner, PropertyOwnerAdmin)
|
||||||
|
admin.site.register(Vendor, VendorAdmin)
|
||||||
|
admin.site.register(Property, PropertyAdmin)
|
||||||
|
admin.site.register(VideoCategory, VideoCategoryAdmin)
|
||||||
|
admin.site.register(Video, VideoAdmin)
|
||||||
|
admin.site.register(UserVideoProgress, UserVideoProgressAdmin)
|
||||||
|
admin.site.register(Conversation, ConversationAdmin)
|
||||||
|
admin.site.register(Offer, OfferAdmin)
|
||||||
|
admin.site.register(PropertyPictures, PropertyPicturesAdmin)
|
||||||
|
|
||||||
|
admin.site.register(OpenHouse, OpenHouseAdmin)
|
||||||
|
admin.site.register(PropertySaleInfo, PropertySaleInfoAdmin)
|
||||||
|
admin.site.register(PropertyTaxInfo, PropertyTaxInfoAdmin)
|
||||||
|
admin.site.register(PropertyWalkScoreInfo, PropertyWalkScoreInfoAdmin)
|
||||||
|
admin.site.register(SchoolInfo, SchoolInfoAdmin)
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
|
|
||||||
class CoreConfig(AppConfig):
|
class CoreConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = 'core'
|
name = "core"
|
||||||
|
|||||||
@@ -5,32 +5,38 @@ from django.contrib.auth import get_user_model
|
|||||||
from rest_framework_simplejwt.tokens import AccessToken
|
from rest_framework_simplejwt.tokens import AccessToken
|
||||||
from .models import Conversation, Message
|
from .models import Conversation, Message
|
||||||
from .serializers import MessageSerializer
|
from .serializers import MessageSerializer
|
||||||
|
from .services.moderation_classifier import moderation_classifier, ModerationLabel
|
||||||
|
from .services.llm_service import AsyncLLMService
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class ChatConsumer(AsyncWebsocketConsumer):
|
class ChatConsumer(AsyncWebsocketConsumer):
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
self.conversation_id = self.scope['url_route']['kwargs']['conversation_id']
|
print(self.scope)
|
||||||
self.conversation_group_name = f'chat_{self.conversation_id}'
|
|
||||||
|
self.account_id = self.scope["url_route"]["kwargs"]["account_id"]
|
||||||
|
self.account_group_name = f"chat_{self.account_id}"
|
||||||
|
|
||||||
# Authenticate user via JWT
|
# Authenticate user via JWT
|
||||||
token = self.scope.get('query_string').decode('utf-8').split('=')[1]
|
token = self.scope.get("query_string").decode("utf-8").split("=")[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
access_token = AccessToken(token)
|
access_token = AccessToken(token)
|
||||||
user_id = access_token['user_id']
|
user_id = access_token["user_id"]
|
||||||
self.user = await self.get_user(user_id)
|
self.user = await self.get_user(user_id)
|
||||||
|
|
||||||
# Check if user is part of the conversation
|
# # Check if user is part of the conversation
|
||||||
conversation = await self.get_conversation(self.conversation_id)
|
# conversation = await self.get_conversation(self.conversation_id)
|
||||||
if not await self.is_participant(conversation, self.user):
|
# if not await self.is_participant(conversation, self.user):
|
||||||
await self.close()
|
# await self.close()
|
||||||
return
|
# return
|
||||||
|
#
|
||||||
|
|
||||||
await self.channel_layer.group_add(
|
|
||||||
self.conversation_group_name,
|
# await self.channel_layer.group_add(
|
||||||
self.channel_name
|
# self.account_group_name, self.account_id
|
||||||
)
|
# )
|
||||||
|
|
||||||
await self.accept()
|
await self.accept()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -38,38 +44,63 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
|||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
async def disconnect(self, close_code):
|
async def disconnect(self, close_code):
|
||||||
|
if (self.channel_layer):
|
||||||
await self.channel_layer.group_discard(
|
await self.channel_layer.group_discard(
|
||||||
self.conversation_group_name,
|
self.account_group_name, self.account_id
|
||||||
self.channel_name
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def receive(self, text_data):
|
async def receive(self, text_data):
|
||||||
text_data_json = json.loads(text_data)
|
text_data_json = json.loads(text_data)
|
||||||
message = text_data_json['message']
|
print(text_data_json)
|
||||||
|
"""
|
||||||
|
First see if it is NSFW
|
||||||
|
|
||||||
# Save message to database
|
Then to the conversation
|
||||||
conversation = await self.get_conversation(self.conversation_id)
|
"""
|
||||||
message_obj = await self.create_message(conversation, self.user, message)
|
messages = text_data_json.get('messages')
|
||||||
|
|
||||||
|
moderation_result = await moderation_classifier.classify_async(messages[-1])
|
||||||
|
if moderation_result == ModerationLabel.NSFW:
|
||||||
|
await self.send('BEGINING_OF_THE_WORLD')
|
||||||
|
await self.send(str('Try again'))
|
||||||
|
await self.send('END_OF_THE_WORLD')
|
||||||
|
|
||||||
|
|
||||||
|
await self.send('BEGINING_OF_THE_WORLD')
|
||||||
|
service = AsyncLLMService()
|
||||||
|
response = ''
|
||||||
|
# get the account to add to the prompt
|
||||||
|
print('generating')
|
||||||
|
async for chunk in service.generate_response(
|
||||||
|
messages, self.user
|
||||||
|
):
|
||||||
|
response += chunk
|
||||||
|
await self.send(chunk)
|
||||||
|
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
await self.send('END_OF_THE_WORLD')
|
||||||
|
|
||||||
|
# # Save message to database
|
||||||
|
# conversation = await self.get_conversation(self.conversation_id)
|
||||||
|
# message_obj = await self.create_message(conversation, self.user, message)
|
||||||
|
|
||||||
|
# # Serialize message
|
||||||
|
# serializer = MessageSerializer(message_obj)
|
||||||
|
|
||||||
|
# # Send message to room group
|
||||||
|
# await self.channel_layer.group_send(
|
||||||
|
# self.account_group_name,
|
||||||
|
# {"type": "chat_message", "message": serializer.data},
|
||||||
|
# )
|
||||||
|
|
||||||
# Serialize message
|
|
||||||
serializer = MessageSerializer(message_obj)
|
|
||||||
|
|
||||||
# Send message to room group
|
|
||||||
await self.channel_layer.group_send(
|
|
||||||
self.conversation_group_name,
|
|
||||||
{
|
|
||||||
'type': 'chat_message',
|
|
||||||
'message': serializer.data
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def chat_message(self, event):
|
async def chat_message(self, event):
|
||||||
message = event['message']
|
message = event["message"]
|
||||||
|
|
||||||
# Send message to WebSocket
|
# Send message to WebSocket
|
||||||
await self.send(text_data=json.dumps({
|
await self.send(text_data=json.dumps({"message": message}))
|
||||||
'message': message
|
|
||||||
}))
|
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def get_user(self, user_id):
|
def get_user(self, user_id):
|
||||||
@@ -81,16 +112,12 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
|||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def is_participant(self, conversation, user):
|
def is_participant(self, conversation, user):
|
||||||
if user.user_type == 'property_owner':
|
if user.user_type == "property_owner":
|
||||||
return conversation.property_owner.user == user
|
return conversation.property_owner.user == user
|
||||||
elif user.user_type == 'vendor':
|
elif user.user_type == "vendor":
|
||||||
return conversation.vendor.user == user
|
return conversation.vendor.user == user
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def create_message(self, conversation, user, text):
|
def create_message(self, conversation, user, text):
|
||||||
return Message.objects.create(
|
return Message.objects.create(conversation=conversation, sender=user, text=text)
|
||||||
conversation=conversation,
|
|
||||||
sender=user,
|
|
||||||
text=text
|
|
||||||
)
|
|
||||||
|
|||||||
19
dta_service/core/filters.py
Normal file
19
dta_service/core/filters.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import django_filters
|
||||||
|
from .models import Property
|
||||||
|
|
||||||
|
class PropertyFilterSet(django_filters.FilterSet):
|
||||||
|
address = django_filters.CharFilter(field_name='address', lookup_expr='icontains')
|
||||||
|
city = django_filters.CharFilter(field_name='city', lookup_expr='icontains')
|
||||||
|
state = django_filters.CharFilter(field_name='state', lookup_expr='icontains')
|
||||||
|
zip_code = django_filters.CharFilter(field_name='zip_code', lookup_expr='exact')
|
||||||
|
min_num_bedrooms = django_filters.NumberFilter(field_name='num_bedrooms', lookup_expr='gte')
|
||||||
|
max_num_bedrooms = django_filters.NumberFilter(field_name='num_bedrooms', lookup_expr='lte')
|
||||||
|
min_num_bathrooms = django_filters.NumberFilter(field_name='num_bathrooms', lookup_expr='gte')
|
||||||
|
max_num_bathrooms = django_filters.NumberFilter(field_name='num_bathrooms', lookup_expr='lte')
|
||||||
|
min_sq_ft = django_filters.NumberFilter(field_name='sq_ft', lookup_expr='gte')
|
||||||
|
max_sq_ft = django_filters.NumberFilter(field_name='sq_ft', lookup_expr='lte')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Property
|
||||||
|
fields = ['address', 'city', 'state', 'zip_code', 'min_num_bedrooms', 'max_num_bedrooms',
|
||||||
|
'min_num_bathrooms', 'max_num_bathrooms', 'min_sq_ft', 'max_sq_ft']
|
||||||
@@ -13,154 +13,385 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name="User",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
"id",
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
models.BigAutoField(
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
auto_created=True,
|
||||||
('email', models.EmailField(max_length=254, unique=True)),
|
primary_key=True,
|
||||||
('first_name', models.CharField(max_length=30)),
|
serialize=False,
|
||||||
('last_name', models.CharField(max_length=30)),
|
verbose_name="ID",
|
||||||
('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)),
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
('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')),
|
"last_login",
|
||||||
('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')),
|
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={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Conversation',
|
name="Conversation",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
"id",
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
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(
|
migrations.CreateModel(
|
||||||
name='Property',
|
name="Property",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('address', models.CharField(max_length=200)),
|
"id",
|
||||||
('city', models.CharField(max_length=100)),
|
models.BigAutoField(
|
||||||
('state', models.CharField(max_length=2)),
|
auto_created=True,
|
||||||
('zip_code', models.CharField(max_length=10)),
|
primary_key=True,
|
||||||
('market_value', models.DecimalField(decimal_places=2, max_digits=12)),
|
serialize=False,
|
||||||
('loan_amount', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)),
|
verbose_name="ID",
|
||||||
('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)),
|
("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(
|
migrations.CreateModel(
|
||||||
name='VideoCategory',
|
name="VideoCategory",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('name', models.CharField(max_length=100)),
|
"id",
|
||||||
('description', models.TextField(blank=True, null=True)),
|
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(
|
migrations.CreateModel(
|
||||||
name='PropertyOwner',
|
name="PropertyOwner",
|
||||||
fields=[
|
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)),
|
"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(
|
migrations.CreateModel(
|
||||||
name='Vendor',
|
name="Vendor",
|
||||||
fields=[
|
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)),
|
"user",
|
||||||
('business_type', models.CharField(choices=[('contractor', 'Contractor'), ('inspector', 'Inspector'), ('lender', 'Lender'), ('other', 'Other')], max_length=20)),
|
models.OneToOneField(
|
||||||
('phone_number', models.CharField(blank=True, max_length=20, null=True)),
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
('address', models.CharField(max_length=200)),
|
primary_key=True,
|
||||||
('city', models.CharField(max_length=100)),
|
serialize=False,
|
||||||
('state', models.CharField(max_length=2)),
|
to=settings.AUTH_USER_MODEL,
|
||||||
('zip_code', models.CharField(max_length=10)),
|
),
|
||||||
|
),
|
||||||
|
("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(
|
migrations.CreateModel(
|
||||||
name='Message',
|
name="Message",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('text', models.TextField()),
|
"id",
|
||||||
('attachment', models.FileField(blank=True, null=True, upload_to=core.models.message_file_path)),
|
models.BigAutoField(
|
||||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
auto_created=True,
|
||||||
('read', models.BooleanField(default=False)),
|
primary_key=True,
|
||||||
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='core.conversation')),
|
serialize=False,
|
||||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)),
|
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(
|
migrations.CreateModel(
|
||||||
name='PasswordResetToken',
|
name="PasswordResetToken",
|
||||||
fields=[
|
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)),
|
"id",
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
models.BigAutoField(
|
||||||
('expires_at', models.DateTimeField()),
|
auto_created=True,
|
||||||
('used', models.BooleanField(default=False)),
|
primary_key=True,
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
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(
|
migrations.AddField(
|
||||||
model_name='conversation',
|
model_name="conversation",
|
||||||
name='property',
|
name="property",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to='core.property'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="conversations",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Video',
|
name="Video",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('title', models.CharField(max_length=200)),
|
"id",
|
||||||
('description', models.TextField(blank=True, null=True)),
|
models.BigAutoField(
|
||||||
('link', models.URLField()),
|
auto_created=True,
|
||||||
('duration', models.IntegerField(help_text='Duration in seconds')),
|
primary_key=True,
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
serialize=False,
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
verbose_name="ID",
|
||||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='core.videocategory')),
|
),
|
||||||
|
),
|
||||||
|
("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(
|
migrations.AddField(
|
||||||
model_name='property',
|
model_name="property",
|
||||||
name='owner',
|
name="owner",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='properties', to='core.propertyowner'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="properties",
|
||||||
|
to="core.propertyowner",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='conversation',
|
model_name="conversation",
|
||||||
name='property_owner',
|
name="property_owner",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to='core.propertyowner'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="conversations",
|
||||||
|
to="core.propertyowner",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='conversation',
|
model_name="conversation",
|
||||||
name='vendor',
|
name="vendor",
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to='core.vendor'),
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="conversations",
|
||||||
|
to="core.vendor",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UserVideoProgress',
|
name="UserVideoProgress",
|
||||||
fields=[
|
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')),
|
"id",
|
||||||
('status', models.CharField(choices=[('not_started', 'Not Started'), ('in_progress', 'In Progress'), ('completed', 'Completed')], default='not_started', max_length=20)),
|
models.BigAutoField(
|
||||||
('last_watched', models.DateTimeField(auto_now=True)),
|
auto_created=True,
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='video_progress', to=settings.AUTH_USER_MODEL)),
|
primary_key=True,
|
||||||
('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_progress', to='core.video')),
|
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={
|
options={
|
||||||
'unique_together': {('user', 'video')},
|
"unique_together": {("user", "video")},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='conversation',
|
name="conversation",
|
||||||
unique_together={('property_owner', 'vendor', 'property')},
|
unique_together={("property_owner", "vendor", "property")},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('core', '0001_initial'),
|
("core", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='conversation',
|
name="conversation",
|
||||||
unique_together=set(),
|
unique_together=set(),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-15 18:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0002_alter_conversation_unique_together"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="business_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("electrician", "Electrician"),
|
||||||
|
("carpenter", "Carpenter"),
|
||||||
|
("plumber", "Plumber"),
|
||||||
|
("inspector", "Inspector"),
|
||||||
|
("lender", "Lender"),
|
||||||
|
("other", "Other"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
dta_service/core/migrations/0004_user_tos_signed.py
Normal file
18
dta_service/core/migrations/0004_user_tos_signed.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-16 01:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0003_alter_vendor_business_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="tos_signed",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-17 13:27
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0004_user_tos_signed"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="profile_created",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Offer",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("submitted", "Submitted"),
|
||||||
|
("draft", "Draft"),
|
||||||
|
("accepted", "Accepted"),
|
||||||
|
("rejected", "Rejected"),
|
||||||
|
("counter", "Counter"),
|
||||||
|
],
|
||||||
|
default="draft",
|
||||||
|
max_length=10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_active", models.BooleanField(default=True)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"previous_offer",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="core.offer"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"proptery",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT, to="core.property"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-18 15:10
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0005_user_profile_created_offer"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="created_at",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="updated_at",
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="propertyowner",
|
||||||
|
name="created_at",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="propertyowner",
|
||||||
|
name="updated_at",
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="created_at",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="updated_at",
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="created_at",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="updated_at",
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="videocategory",
|
||||||
|
name="created_at",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="videocategory",
|
||||||
|
name="updated_at",
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-21 21:06
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0006_property_created_at_property_updated_at_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="offer",
|
||||||
|
old_name="proptery",
|
||||||
|
new_name="property",
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-21 21:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0007_rename_proptery_offer_property"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="offer",
|
||||||
|
name="previous_offer",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="core.offer",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-24 14:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0008_alter_offer_previous_offer"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="offer",
|
||||||
|
name="status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("submitted", "Submitted"),
|
||||||
|
("draft", "Draft"),
|
||||||
|
("accepted", "Accepted"),
|
||||||
|
("rejected", "Rejected"),
|
||||||
|
("counter", "Counter"),
|
||||||
|
("withdrawn", "Withdrawn"),
|
||||||
|
],
|
||||||
|
default="draft",
|
||||||
|
max_length=10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="video",
|
||||||
|
name="link",
|
||||||
|
field=models.FileField(upload_to="videos/"),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-24 17:53
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0009_alter_offer_status_alter_video_link"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="conversation",
|
||||||
|
name="property",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="conversations",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
26
dta_service/core/migrations/0011_user_tier.py
Normal file
26
dta_service/core/migrations/0011_user_tier.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-07-24 18:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0010_alter_conversation_property"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="tier",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("basic", "Basic"),
|
||||||
|
("premium", "Premium"),
|
||||||
|
("vendor", "Vendor"),
|
||||||
|
],
|
||||||
|
default="basic",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-01 09:39
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0011_user_tier"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="features",
|
||||||
|
field=models.JSONField(blank=True, default=list),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=6, max_digits=9, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=6, max_digits=9, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="num_bathrooms",
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="num_bedrooms",
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="realestate_api_id",
|
||||||
|
field=models.IntegerField(default=1),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="sq_ft",
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="average_rating",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=2, max_digits=3, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="certifications",
|
||||||
|
field=models.JSONField(blank=True, default=list, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="description",
|
||||||
|
field=models.CharField(default="", max_length=1024),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="num_reviews",
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="profile_picture",
|
||||||
|
field=models.URLField(blank=True, max_length=500, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="service_areas",
|
||||||
|
field=models.JSONField(blank=True, default=list),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="services",
|
||||||
|
field=models.JSONField(blank=True, default=list),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="website",
|
||||||
|
field=models.URLField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PropertyPictures",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("image", models.FileField(upload_to="pcitures/")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"Property",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="pictures",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-01 10:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0012_property_description_property_features_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="property",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="property",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-03 01:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0013_alter_property_latitude_alter_property_longitude"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="listed_date",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="listed_price",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=2, max_digits=12, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="property_status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("active", "Active"),
|
||||||
|
("pending", "Pending]"),
|
||||||
|
("contingent", "Contingent"),
|
||||||
|
("sold", "Sold"),
|
||||||
|
("off_market", "Off Market"),
|
||||||
|
],
|
||||||
|
default="off_market",
|
||||||
|
max_length=15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="saves",
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="views",
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-04 19:23
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0014_property_listed_date_property_listed_price_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="SchoolInfo",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("city", models.CharField(max_length=100)),
|
||||||
|
("state", models.CharField(max_length=2)),
|
||||||
|
("zip_code", models.CharField(max_length=10)),
|
||||||
|
(
|
||||||
|
"latitude",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"longitude",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("enrollment", models.IntegerField()),
|
||||||
|
("grades", models.CharField(max_length=30)),
|
||||||
|
("name", models.CharField(max_length=256)),
|
||||||
|
("parent_rating", models.IntegerField()),
|
||||||
|
("rating", models.IntegerField()),
|
||||||
|
(
|
||||||
|
"school_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("Public", "Public"), ("Other", "Other")],
|
||||||
|
default="public",
|
||||||
|
max_length=15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="property",
|
||||||
|
name="property_status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("active", "Active"),
|
||||||
|
("pending", "Pending"),
|
||||||
|
("contingent", "Contingent"),
|
||||||
|
("sold", "Sold"),
|
||||||
|
("off_market", "Off Market"),
|
||||||
|
],
|
||||||
|
default="off_market",
|
||||||
|
max_length=15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="OpenHouse",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("listed_date", models.DateTimeField()),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"property",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="open_houses",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PropertySaleInfo",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("seq_no", models.IntegerField()),
|
||||||
|
("sale_date", models.DateTimeField()),
|
||||||
|
("sale_amount", models.FloatField()),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"property",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="sale_info",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PropertyTaxInfo",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("assessed_value", models.IntegerField()),
|
||||||
|
("assessment_year", models.IntegerField()),
|
||||||
|
("tax_amount", models.FloatField()),
|
||||||
|
("year", models.IntegerField()),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"property",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="tax_info",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PropertyWalkScoreInfo",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("walk_score", models.IntegerField()),
|
||||||
|
("walk_description", models.CharField(max_length=256)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("ws_link", models.URLField()),
|
||||||
|
("logo_url", models.URLField()),
|
||||||
|
("transit_score", models.IntegerField()),
|
||||||
|
("transit_description", models.CharField(max_length=256)),
|
||||||
|
("transit_summary", models.CharField(max_length=512)),
|
||||||
|
("bike_score", models.IntegerField()),
|
||||||
|
("bike_description", models.CharField(max_length=256)),
|
||||||
|
(
|
||||||
|
"property",
|
||||||
|
models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="walk_score",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="schools",
|
||||||
|
field=models.ManyToManyField(to="core.schoolinfo"),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-08 11:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0015_schoolinfo_alter_property_property_status_openhouse_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Attorney",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("firm_name", models.CharField(max_length=200)),
|
||||||
|
("bar_number", models.CharField(max_length=50, unique=True)),
|
||||||
|
(
|
||||||
|
"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)),
|
||||||
|
("specialties", models.JSONField(blank=True, default=list)),
|
||||||
|
("years_experience", models.IntegerField(default=0)),
|
||||||
|
("website", models.URLField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"profile_picture",
|
||||||
|
models.URLField(blank=True, max_length=500, null=True),
|
||||||
|
),
|
||||||
|
("bio", models.TextField(blank=True, null=True)),
|
||||||
|
("licensed_states", models.JSONField(blank=True, default=list)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"latitude",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"longitude",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RealEstateAgent",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("brokerage_name", models.CharField(max_length=200)),
|
||||||
|
("license_number", models.CharField(max_length=50, unique=True)),
|
||||||
|
(
|
||||||
|
"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)),
|
||||||
|
("specialties", models.JSONField(blank=True, default=list)),
|
||||||
|
("years_experience", models.IntegerField(default=0)),
|
||||||
|
("website", models.URLField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"profile_picture",
|
||||||
|
models.URLField(blank=True, max_length=500, null=True),
|
||||||
|
),
|
||||||
|
("bio", models.TextField(blank=True, null=True)),
|
||||||
|
("licensed_states", models.JSONField(blank=True, default=list)),
|
||||||
|
(
|
||||||
|
"agent_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("buyer_agent", "Buyer's Agent"),
|
||||||
|
("seller_agent", "Seller's Agent"),
|
||||||
|
("dual_agent", "Dual Agent"),
|
||||||
|
("other", "Other"),
|
||||||
|
],
|
||||||
|
default="other",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"latitude",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"longitude",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="user",
|
||||||
|
name="user_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("property_owner", "Property Owner"),
|
||||||
|
("vendor", "Vendor"),
|
||||||
|
("attorney", "Attorney"),
|
||||||
|
("real_estate_agent", "Real Estate Agent"),
|
||||||
|
("admin", "Admin"),
|
||||||
|
],
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Bid",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("description", models.TextField()),
|
||||||
|
(
|
||||||
|
"bid_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("electrical", "Electrical"),
|
||||||
|
("plumbing", "Plumbing"),
|
||||||
|
("carpentry", "Carpentry"),
|
||||||
|
("general_contractor", "General Contractor"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"location",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("living_room", "Living Room"),
|
||||||
|
("basement", "Basement"),
|
||||||
|
("kitchen", "Kitchen"),
|
||||||
|
("bathroom", "Bathroom"),
|
||||||
|
("bedroom", "Bedroom"),
|
||||||
|
("outside", "Outside"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"property",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="bids",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="BidImage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("image", models.FileField(upload_to="bid_pictures/")),
|
||||||
|
("uploaded_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"bid",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="images",
|
||||||
|
to="core.bid",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="BidResponse",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("description", models.TextField()),
|
||||||
|
("price", models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("draft", "Draft"),
|
||||||
|
("submitted", "Submitted"),
|
||||||
|
("selected", "Selected"),
|
||||||
|
],
|
||||||
|
default="draft",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"bid",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="responses",
|
||||||
|
to="core.bid",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vendor",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="bid_responses",
|
||||||
|
to="core.vendor",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"unique_together": {("bid", "vendor")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-08 14:50
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0016_attorney_realestateagent_vendor_latitude_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="attorney",
|
||||||
|
name="latitude",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="attorney",
|
||||||
|
name="longitude",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="property",
|
||||||
|
name="schools",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="realestateagent",
|
||||||
|
name="latitude",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="realestateagent",
|
||||||
|
name="longitude",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="latitude",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="longitude",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="property",
|
||||||
|
name="street",
|
||||||
|
field=models.CharField(default="", max_length=200),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="schoolinfo",
|
||||||
|
name="properties",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="schools",
|
||||||
|
to="core.property",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-08 16:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0017_remove_attorney_latitude_remove_attorney_longitude_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="attorney",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="attorney",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realestateagent",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realestateagent",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True, decimal_places=27, max_digits=30, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-11 10:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0018_attorney_latitude_attorney_longitude_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="attorney",
|
||||||
|
name="bar_number",
|
||||||
|
field=models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
dta_service/core/migrations/0020_vendor_views.py
Normal file
18
dta_service/core/migrations/0020_vendor_views.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-11 11:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0019_alter_attorney_bar_number"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="vendor",
|
||||||
|
name="views",
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
37
dta_service/core/migrations/0021_userviewmodel.py
Normal file
37
dta_service/core/migrations/0021_userviewmodel.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-11 18:27
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0020_vendor_views"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="UserViewModel",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-11 19:25
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0021_userviewmodel"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="schoolinfo",
|
||||||
|
old_name="properties",
|
||||||
|
new_name="property",
|
||||||
|
),
|
||||||
|
]
|
||||||
46
dta_service/core/migrations/0023_propertysave.py
Normal file
46
dta_service/core/migrations/0023_propertysave.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-12 22:34
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0022_rename_properties_schoolinfo_property"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PropertySave",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"property",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="core.property"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"unique_together": {("user", "property")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
|
from django.contrib.auth.models import (
|
||||||
|
AbstractBaseUser,
|
||||||
|
BaseUserManager,
|
||||||
|
PermissionsMixin,
|
||||||
|
)
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
@@ -8,32 +12,10 @@ from django.conf import settings
|
|||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class TimeInfoBase(models.Model):
|
|
||||||
|
|
||||||
created = models.DateTimeField(default=timezone.now)
|
|
||||||
last_modified = models.DateTimeField(default=timezone.now)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if not kwargs.pop("skip_last_modified", False) and not hasattr(
|
|
||||||
self, "skip_last_modified"
|
|
||||||
):
|
|
||||||
self.last_modified = timezone.now()
|
|
||||||
if kwargs.get("update_fields") is not None:
|
|
||||||
kwargs["update_fields"] = list(
|
|
||||||
{*kwargs["update_fields"], "last_modified"}
|
|
||||||
)
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
def create_user(self, email, password=None, **extra_fields):
|
def create_user(self, email, password=None, **extra_fields):
|
||||||
if not email:
|
if not email:
|
||||||
raise ValueError('Users must have an email address')
|
raise ValueError("Users must have an email address")
|
||||||
|
|
||||||
email = self.normalize_email(email)
|
email = self.normalize_email(email)
|
||||||
user = self.model(email=email, **extra_fields)
|
user = self.model(email=email, **extra_fields)
|
||||||
@@ -42,15 +24,23 @@ class UserManager(BaseUserManager):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
def create_superuser(self, email, password=None, **extra_fields):
|
def create_superuser(self, email, password=None, **extra_fields):
|
||||||
extra_fields.setdefault('is_staff', True)
|
extra_fields.setdefault("is_staff", True)
|
||||||
extra_fields.setdefault('is_superuser', True)
|
extra_fields.setdefault("is_superuser", True)
|
||||||
return self.create_user(email, password, **extra_fields)
|
return self.create_user(email, password, **extra_fields)
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractBaseUser, PermissionsMixin):
|
class User(AbstractBaseUser, PermissionsMixin):
|
||||||
USER_TYPE_CHOICES = (
|
USER_TYPE_CHOICES = (
|
||||||
('property_owner', 'Property Owner'),
|
("property_owner", "Property Owner"),
|
||||||
('vendor', 'Vendor'),
|
("vendor", "Vendor"),
|
||||||
('admin', 'Admin'),
|
("attorney", "Attorney"),
|
||||||
|
("real_estate_agent", "Real Estate Agent"),
|
||||||
|
("admin", "Admin"),
|
||||||
|
)
|
||||||
|
USER_TIER_CHOICES = (
|
||||||
|
("basic", "Basic"),
|
||||||
|
("premium", "Premium"),
|
||||||
|
("vendor", "Vendor"),
|
||||||
)
|
)
|
||||||
|
|
||||||
email = models.EmailField(unique=True)
|
email = models.EmailField(unique=True)
|
||||||
@@ -60,11 +50,16 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
is_staff = models.BooleanField(default=False)
|
is_staff = models.BooleanField(default=False)
|
||||||
date_joined = models.DateTimeField(default=timezone.now)
|
date_joined = models.DateTimeField(default=timezone.now)
|
||||||
|
tos_signed = models.BooleanField(default=False)
|
||||||
|
profile_created = models.BooleanField(default=False)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
tier = models.CharField(max_length=20, choices=USER_TIER_CHOICES, default="basic")
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
USERNAME_FIELD = 'email'
|
USERNAME_FIELD = "email"
|
||||||
REQUIRED_FIELDS = ['first_name', 'last_name', 'user_type']
|
REQUIRED_FIELDS = ["first_name", "last_name", "user_type"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.email
|
return self.email
|
||||||
@@ -75,19 +70,25 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||||||
def get_short_name(self):
|
def get_short_name(self):
|
||||||
return self.first_name
|
return self.first_name
|
||||||
|
|
||||||
|
|
||||||
class PropertyOwner(models.Model):
|
class PropertyOwner(models.Model):
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||||
phone_number = models.CharField(max_length=20, blank=True, null=True)
|
phone_number = models.CharField(max_length=20, blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.get_full_name()
|
return self.user.get_full_name()
|
||||||
|
|
||||||
|
|
||||||
class Vendor(models.Model):
|
class Vendor(models.Model):
|
||||||
BUSINESS_TYPES = (
|
BUSINESS_TYPES = (
|
||||||
('contractor', 'Contractor'),
|
("electrician", "Electrician"),
|
||||||
('inspector', 'Inspector'),
|
("carpenter", "Carpenter"),
|
||||||
('lender', 'Lender'),
|
("plumber", "Plumber"),
|
||||||
('other', 'Other'),
|
("inspector", "Inspector"),
|
||||||
|
("lender", "Lender"),
|
||||||
|
("other", "Other"),
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||||
@@ -98,37 +99,256 @@ class Vendor(models.Model):
|
|||||||
city = models.CharField(max_length=100)
|
city = models.CharField(max_length=100)
|
||||||
state = models.CharField(max_length=2)
|
state = models.CharField(max_length=2)
|
||||||
zip_code = models.CharField(max_length=10)
|
zip_code = models.CharField(max_length=10)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
description = models.CharField(max_length=1024, default="")
|
||||||
|
website = models.URLField(blank=True, null=True)
|
||||||
|
services = models.JSONField(blank=True, default=list) # Changed to JSONField
|
||||||
|
service_areas = models.JSONField(blank=True, default=list) # Changed to JSONField
|
||||||
|
certifications = models.JSONField(
|
||||||
|
blank=True, null=True, default=list
|
||||||
|
) # Changed to JSONField
|
||||||
|
average_rating = models.DecimalField(
|
||||||
|
max_digits=3, decimal_places=2, blank=True, null=True
|
||||||
|
)
|
||||||
|
num_reviews = models.IntegerField(blank=True, null=True)
|
||||||
|
profile_picture = models.URLField(max_length=500, blank=True, null=True)
|
||||||
|
|
||||||
|
latitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
longitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
views = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.business_name
|
return self.business_name
|
||||||
|
|
||||||
class Property(models.Model):
|
class Attorney(models.Model):
|
||||||
owner = models.ForeignKey(PropertyOwner, on_delete=models.CASCADE, related_name='properties')
|
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||||
|
firm_name = models.CharField(max_length=200)
|
||||||
|
bar_number = models.CharField(max_length=50, unique=True, blank=True, null=True) # Bar numbers are typically unique
|
||||||
|
phone_number = models.CharField(max_length=20, blank=True, null=True)
|
||||||
address = models.CharField(max_length=200)
|
address = models.CharField(max_length=200)
|
||||||
city = models.CharField(max_length=100)
|
city = models.CharField(max_length=100)
|
||||||
state = models.CharField(max_length=2)
|
state = models.CharField(max_length=2)
|
||||||
zip_code = models.CharField(max_length=10)
|
zip_code = models.CharField(max_length=10)
|
||||||
|
specialties = models.JSONField(blank=True, default=list) # Store as JSON array
|
||||||
|
years_experience = models.IntegerField(default=0)
|
||||||
|
website = models.URLField(blank=True, null=True)
|
||||||
|
profile_picture = models.URLField(max_length=500, blank=True, null=True)
|
||||||
|
bio = models.TextField(blank=True, null=True) # Use TextField for longer text
|
||||||
|
licensed_states = models.JSONField(blank=True, default=list) # Store as JSON array
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
latitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
longitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.get_full_name()} ({self.firm_name})"
|
||||||
|
|
||||||
|
class RealEstateAgent(models.Model):
|
||||||
|
AGENT_TYPE_CHOICES = (
|
||||||
|
("buyer_agent", "Buyer's Agent"),
|
||||||
|
("seller_agent", "Seller's Agent"),
|
||||||
|
("dual_agent", "Dual Agent"),
|
||||||
|
("other", "Other"),
|
||||||
|
)
|
||||||
|
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||||
|
brokerage_name = models.CharField(max_length=200)
|
||||||
|
license_number = models.CharField(max_length=50, unique=True) # License numbers are typically unique
|
||||||
|
phone_number = models.CharField(max_length=20, blank=True, 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)
|
||||||
|
specialties = models.JSONField(blank=True, default=list) # Store as JSON array
|
||||||
|
years_experience = models.IntegerField(default=0)
|
||||||
|
website = models.URLField(blank=True, null=True)
|
||||||
|
profile_picture = models.URLField(max_length=500, blank=True, null=True)
|
||||||
|
bio = models.TextField(blank=True, null=True)
|
||||||
|
licensed_states = models.JSONField(blank=True, default=list) # Store as JSON array
|
||||||
|
agent_type = models.CharField(max_length=20, choices=AGENT_TYPE_CHOICES, default="other")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
latitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
longitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.get_full_name()} ({self.brokerage_name})"
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewModel(models.Model):
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Property(models.Model):
|
||||||
|
PROPERTY_STATUS_TYPES = (
|
||||||
|
("active", "Active"),
|
||||||
|
("pending", "Pending"),
|
||||||
|
("contingent", "Contingent"),
|
||||||
|
("sold", "Sold"),
|
||||||
|
("off_market", "Off Market"),
|
||||||
|
)
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
PropertyOwner, on_delete=models.CASCADE, related_name="properties"
|
||||||
|
)
|
||||||
|
address = models.CharField(max_length=200)
|
||||||
|
street = models.CharField(max_length=200, default="")
|
||||||
|
city = models.CharField(max_length=100)
|
||||||
|
state = models.CharField(max_length=2)
|
||||||
|
zip_code = models.CharField(max_length=10)
|
||||||
market_value = models.DecimalField(max_digits=12, decimal_places=2)
|
market_value = models.DecimalField(max_digits=12, decimal_places=2)
|
||||||
loan_amount = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
|
loan_amount = models.DecimalField(
|
||||||
loan_interest_rate = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
|
max_digits=12, decimal_places=2, blank=True, null=True
|
||||||
|
)
|
||||||
|
loan_interest_rate = models.DecimalField(
|
||||||
|
max_digits=5, decimal_places=2, blank=True, null=True
|
||||||
|
)
|
||||||
loan_term = models.IntegerField(blank=True, null=True)
|
loan_term = models.IntegerField(blank=True, null=True)
|
||||||
loan_start_date = models.DateField(blank=True, null=True)
|
loan_start_date = models.DateField(blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
description = models.TextField(
|
||||||
|
blank=True, null=True
|
||||||
|
) # Text field for longer descriptions
|
||||||
|
sq_ft = models.IntegerField(blank=True, null=True) # Square footage
|
||||||
|
features = models.JSONField(blank=True, default=list) # Stores a list of strings
|
||||||
|
num_bedrooms = models.IntegerField(blank=True, null=True)
|
||||||
|
num_bathrooms = models.IntegerField(blank=True, null=True)
|
||||||
|
latitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
longitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
realestate_api_id = models.IntegerField()
|
||||||
|
|
||||||
|
property_status = models.CharField(
|
||||||
|
max_length=15, choices=PROPERTY_STATUS_TYPES, default="off_market"
|
||||||
|
)
|
||||||
|
listed_price = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, blank=True, null=True
|
||||||
|
)
|
||||||
|
views = models.IntegerField(default=0)
|
||||||
|
saves = models.IntegerField(default=0)
|
||||||
|
listed_date = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.address}, {self.city}, {self.state} {self.zip_code}"
|
return f"{self.address}, {self.city}, {self.state} {self.zip_code}"
|
||||||
|
|
||||||
|
class SchoolInfo(models.Model):
|
||||||
|
SCHOOL_TYPES = (
|
||||||
|
("Public", "Public"),
|
||||||
|
("Other", "Other"),
|
||||||
|
)
|
||||||
|
property = models.ForeignKey(Property, on_delete=models.CASCADE, related_name="schools", blank=True, null=True)
|
||||||
|
city = models.CharField(max_length=100)
|
||||||
|
state = models.CharField(max_length=2)
|
||||||
|
zip_code = models.CharField(max_length=10)
|
||||||
|
latitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
longitude = models.DecimalField(
|
||||||
|
max_digits=30, decimal_places=27, blank=True, null=True
|
||||||
|
) # For coordinates
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
enrollment = models.IntegerField()
|
||||||
|
grades = models.CharField(max_length=30)
|
||||||
|
name = models.CharField(max_length=256)
|
||||||
|
parent_rating = models.IntegerField()
|
||||||
|
rating = models.IntegerField()
|
||||||
|
school_type = models.CharField(
|
||||||
|
max_length=15, choices=SCHOOL_TYPES, default="public"
|
||||||
|
)
|
||||||
|
|
||||||
|
class PropertyTaxInfo(models.Model):
|
||||||
|
property = models.OneToOneField(Property, on_delete=models.CASCADE, related_name="tax_info")
|
||||||
|
assessed_value = models.IntegerField()
|
||||||
|
assessment_year = models.IntegerField()
|
||||||
|
tax_amount = models.FloatField()
|
||||||
|
year = models.IntegerField()
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertySaleInfo(models.Model):
|
||||||
|
property = models.ForeignKey(Property, on_delete=models.CASCADE, related_name="sale_info")
|
||||||
|
seq_no = models.IntegerField()
|
||||||
|
sale_date = models.DateTimeField()
|
||||||
|
sale_amount = models.FloatField()
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyWalkScoreInfo(models.Model):
|
||||||
|
property = models.OneToOneField(
|
||||||
|
Property, on_delete=models.CASCADE, blank=True, null=True, related_name="walk_score"
|
||||||
|
)
|
||||||
|
walk_score = models.IntegerField()
|
||||||
|
walk_description = models.CharField(max_length=256)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
ws_link = models.URLField()
|
||||||
|
logo_url = models.URLField()
|
||||||
|
transit_score = models.IntegerField()
|
||||||
|
transit_description = models.CharField(max_length=256)
|
||||||
|
transit_summary = models.CharField(max_length=512)
|
||||||
|
bike_score = models.IntegerField()
|
||||||
|
bike_description = models.CharField(max_length=256)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenHouse(models.Model):
|
||||||
|
property = models.ForeignKey(Property, on_delete=models.CASCADE, blank=True, null=True, related_name="open_houses")
|
||||||
|
listed_date = models.DateTimeField()
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyPictures(models.Model):
|
||||||
|
Property = models.ForeignKey(
|
||||||
|
Property, on_delete=models.CASCADE, related_name="pictures"
|
||||||
|
)
|
||||||
|
image = models.FileField(upload_to="pcitures/")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
||||||
class VideoCategory(models.Model):
|
class VideoCategory(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Video(models.Model):
|
class Video(models.Model):
|
||||||
category = models.ForeignKey(VideoCategory, on_delete=models.CASCADE, related_name='videos')
|
category = models.ForeignKey(
|
||||||
|
VideoCategory, on_delete=models.CASCADE, related_name="videos"
|
||||||
|
)
|
||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
link = models.URLField()
|
link = models.FileField(upload_to="videos/")
|
||||||
duration = models.IntegerField(help_text="Duration in seconds")
|
duration = models.IntegerField(help_text="Duration in seconds")
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
@@ -136,21 +356,28 @@ class Video(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
class UserVideoProgress(models.Model):
|
class UserVideoProgress(models.Model):
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
('not_started', 'Not Started'),
|
("not_started", "Not Started"),
|
||||||
('in_progress', 'In Progress'),
|
("in_progress", "In Progress"),
|
||||||
('completed', 'Completed'),
|
("completed", "Completed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='video_progress')
|
user = models.ForeignKey(
|
||||||
video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='user_progress')
|
User, on_delete=models.CASCADE, related_name="video_progress"
|
||||||
|
)
|
||||||
|
video = models.ForeignKey(
|
||||||
|
Video, on_delete=models.CASCADE, related_name="user_progress"
|
||||||
|
)
|
||||||
progress = models.IntegerField(default=0, help_text="Progress in seconds")
|
progress = models.IntegerField(default=0, help_text="Progress in seconds")
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='not_started')
|
status = models.CharField(
|
||||||
|
max_length=20, choices=STATUS_CHOICES, default="not_started"
|
||||||
|
)
|
||||||
last_watched = models.DateTimeField(auto_now=True)
|
last_watched = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('user', 'video')
|
unique_together = ("user", "video")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user.email} - {self.video.title} - {self.status}"
|
return f"{self.user.email} - {self.video.title} - {self.status}"
|
||||||
@@ -158,18 +385,29 @@ class UserVideoProgress(models.Model):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# Update status based on progress
|
# Update status based on progress
|
||||||
if self.progress == 0:
|
if self.progress == 0:
|
||||||
self.status = 'not_started'
|
self.status = "not_started"
|
||||||
elif self.progress >= self.video.duration:
|
elif self.progress >= self.video.duration:
|
||||||
self.status = 'completed'
|
self.status = "completed"
|
||||||
self.progress = self.video.duration
|
self.progress = self.video.duration
|
||||||
else:
|
else:
|
||||||
self.status = 'in_progress'
|
self.status = "in_progress"
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Conversation(models.Model):
|
class Conversation(models.Model):
|
||||||
property_owner = models.ForeignKey(PropertyOwner, on_delete=models.CASCADE, related_name='conversations')
|
property_owner = models.ForeignKey(
|
||||||
vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, related_name='conversations')
|
PropertyOwner, on_delete=models.CASCADE, related_name="conversations"
|
||||||
property = models.ForeignKey(Property, on_delete=models.CASCADE, related_name='conversations')
|
)
|
||||||
|
vendor = models.ForeignKey(
|
||||||
|
Vendor, on_delete=models.CASCADE, related_name="conversations"
|
||||||
|
)
|
||||||
|
property = models.ForeignKey(
|
||||||
|
Property,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="conversations",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
@@ -179,14 +417,20 @@ class Conversation(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Conversation between {self.property_owner} and {self.vendor} about {self.property}"
|
return f"Conversation between {self.property_owner} and {self.vendor} about {self.property}"
|
||||||
|
|
||||||
|
|
||||||
def message_file_path(instance, filename):
|
def message_file_path(instance, filename):
|
||||||
ext = filename.split('.')[-1]
|
ext = filename.split(".")[-1]
|
||||||
filename = f"{uuid.uuid4()}.{ext}"
|
filename = f"{uuid.uuid4()}.{ext}"
|
||||||
return os.path.join('message_attachments', filename)
|
return os.path.join("message_attachments", filename)
|
||||||
|
|
||||||
|
|
||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='messages')
|
conversation = models.ForeignKey(
|
||||||
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages')
|
Conversation, on_delete=models.CASCADE, related_name="messages"
|
||||||
|
)
|
||||||
|
sender = models.ForeignKey(
|
||||||
|
User, on_delete=models.CASCADE, related_name="sent_messages"
|
||||||
|
)
|
||||||
text = models.TextField()
|
text = models.TextField()
|
||||||
attachment = models.FileField(upload_to=message_file_path, blank=True, null=True)
|
attachment = models.FileField(upload_to=message_file_path, blank=True, null=True)
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
@@ -195,6 +439,7 @@ class Message(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Message from {self.sender} in {self.conversation}"
|
return f"Message from {self.sender} in {self.conversation}"
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetToken(models.Model):
|
class PasswordResetToken(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||||
@@ -209,10 +454,10 @@ class PasswordResetToken(models.Model):
|
|||||||
subject = "Password Reset Request"
|
subject = "Password Reset Request"
|
||||||
reset_url = f"{settings.FRONTEND_URL}/reset-password/{self.token}/"
|
reset_url = f"{settings.FRONTEND_URL}/reset-password/{self.token}/"
|
||||||
context = {
|
context = {
|
||||||
'user': self.user,
|
"user": self.user,
|
||||||
'reset_url': reset_url,
|
"reset_url": reset_url,
|
||||||
}
|
}
|
||||||
html_message = render_to_string('password_reset_email.html', context)
|
html_message = render_to_string("password_reset_email.html", context)
|
||||||
plain_message = strip_tags(html_message)
|
plain_message = strip_tags(html_message)
|
||||||
send_mail(
|
send_mail(
|
||||||
subject,
|
subject,
|
||||||
@@ -225,3 +470,87 @@ class PasswordResetToken(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Password reset token for {self.user.email}"
|
return f"Password reset token for {self.user.email}"
|
||||||
|
|
||||||
|
|
||||||
|
class Offer(models.Model):
|
||||||
|
OFFER_STATUS_TYPES = (
|
||||||
|
("submitted", "Submitted"),
|
||||||
|
("draft", "Draft"),
|
||||||
|
("accepted", "Accepted"),
|
||||||
|
("rejected", "Rejected"),
|
||||||
|
("counter", "Counter"),
|
||||||
|
("withdrawn", "Withdrawn"),
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
property = models.ForeignKey(Property, on_delete=models.PROTECT)
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=10, choices=OFFER_STATUS_TYPES, default="draft"
|
||||||
|
)
|
||||||
|
previous_offer = models.ForeignKey(
|
||||||
|
"self", on_delete=models.CASCADE, null=True, blank=True
|
||||||
|
)
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.email} {self.status} {self.property.address}"
|
||||||
|
|
||||||
|
class Bid(models.Model):
|
||||||
|
BID_TYPE_CHOICES = (
|
||||||
|
("electrical", "Electrical"),
|
||||||
|
("plumbing", "Plumbing"),
|
||||||
|
("carpentry", "Carpentry"),
|
||||||
|
("general_contractor", "General Contractor"),
|
||||||
|
)
|
||||||
|
LOCATION_CHOICES = (
|
||||||
|
("living_room", "Living Room"),
|
||||||
|
("basement", "Basement"),
|
||||||
|
("kitchen", "Kitchen"),
|
||||||
|
("bathroom", "Bathroom"),
|
||||||
|
("bedroom", "Bedroom"),
|
||||||
|
("outside", "Outside"),
|
||||||
|
)
|
||||||
|
|
||||||
|
property = models.ForeignKey(Property, on_delete=models.CASCADE, related_name="bids")
|
||||||
|
description = models.TextField()
|
||||||
|
bid_type = models.CharField(max_length=50, choices=BID_TYPE_CHOICES)
|
||||||
|
location = models.CharField(max_length=50, choices=LOCATION_CHOICES)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Bid for {self.bid_type} at {self.property.address}"
|
||||||
|
|
||||||
|
class BidImage(models.Model):
|
||||||
|
bid = models.ForeignKey(Bid, on_delete=models.CASCADE, related_name="images")
|
||||||
|
image = models.FileField(upload_to="bid_pictures/")
|
||||||
|
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class BidResponse(models.Model):
|
||||||
|
RESPONSE_STATUS_CHOICES = (
|
||||||
|
("draft", "Draft"),
|
||||||
|
("submitted", "Submitted"),
|
||||||
|
("selected", "Selected"),
|
||||||
|
)
|
||||||
|
bid = models.ForeignKey(Bid, on_delete=models.CASCADE, related_name="responses")
|
||||||
|
vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, related_name="bid_responses")
|
||||||
|
description = models.TextField()
|
||||||
|
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
status = models.CharField(max_length=20, choices=RESPONSE_STATUS_CHOICES, default="draft")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('bid', 'vendor')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Response from {self.vendor.business_name} for Bid {self.bid.id}"
|
||||||
|
|
||||||
|
class PropertySave(models.Model):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
property = models.ForeignKey(Property, on_delete=models.CASCADE)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('user', 'property')
|
||||||
|
|||||||
@@ -1,43 +1,63 @@
|
|||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
|
||||||
class IsOwnerOrReadOnly(permissions.BasePermission):
|
class IsOwnerOrReadOnly(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if hasattr(obj, 'owner'):
|
if hasattr(obj, "owner"):
|
||||||
return obj.owner.user == request.user
|
return obj.owner.user == request.user
|
||||||
elif hasattr(obj, 'user'):
|
elif hasattr(obj, "user"):
|
||||||
return obj.user == request.user
|
return obj.user == request.user
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class IsPropertyOwner(permissions.BasePermission):
|
class IsPropertyOwner(permissions.BasePermission):
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
return request.user.user_type == 'property_owner'
|
return request.user.user_type == "property_owner"
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
if hasattr(obj, 'owner'):
|
if hasattr(obj, "owner"):
|
||||||
return obj.owner.user == request.user
|
return obj.owner.user == request.user
|
||||||
elif hasattr(obj, 'property_owner'):
|
elif hasattr(obj, "property_owner"):
|
||||||
return obj.property_owner.user == request.user
|
return obj.property_owner.user == request.user
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class IsVendor(permissions.BasePermission):
|
class IsVendor(permissions.BasePermission):
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
return request.user.user_type == 'vendor'
|
return request.user.user_type == "vendor"
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
if hasattr(obj, 'vendor'):
|
if hasattr(obj, "vendor"):
|
||||||
return obj.vendor.user == request.user
|
return obj.vendor.user == request.user
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class IsParticipant(permissions.BasePermission):
|
class IsParticipant(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
if request.user.user_type == 'property_owner':
|
if request.user.user_type == "property_owner":
|
||||||
owner = obj.property_owner if hasattr(obj, 'property_owner') else obj.conversation.property_owner
|
owner = (
|
||||||
|
obj.property_owner
|
||||||
|
if hasattr(obj, "property_owner")
|
||||||
|
else obj.conversation.property_owner
|
||||||
|
)
|
||||||
return owner.user == request.user
|
return owner.user == request.user
|
||||||
elif request.user.user_type == 'vendor':
|
elif request.user.user_type == "vendor":
|
||||||
vendor = obj.vendor if hasattr(obj, 'vendor') else obj.conversation.vendor
|
vendor = obj.vendor if hasattr(obj, "vendor") else obj.conversation.vendor
|
||||||
return vendor.user == request.user
|
return vendor.user == request.user
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class IsParticipantInOffer(permissions.BasePermission):
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
"""
|
||||||
|
There are two options, either you are the sender or the owner of the property
|
||||||
|
"""
|
||||||
|
if hasattr(obj, "user") and hasattr(obj, "property"):
|
||||||
|
|
||||||
|
return request.user == obj.user or request.user == obj.property.owner.user
|
||||||
|
return False
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ from django.urls import re_path
|
|||||||
from . import consumers
|
from . import consumers
|
||||||
|
|
||||||
websocket_urlpatterns = [
|
websocket_urlpatterns = [
|
||||||
re_path(r'ws/chat/(?P<conversation_id>\w+)/$', consumers.ChatConsumer.as_asgi()),
|
re_path(r"ws/chat/(?P<account_id>\w+)/$", consumers.ChatConsumer.as_asgi()),
|
||||||
|
#re_path(r"ws/chat/$", consumers.ChatConsumer.as_asgi()),
|
||||||
]
|
]
|
||||||
@@ -3,8 +3,23 @@ from django.contrib.auth import get_user_model
|
|||||||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
from .models import (
|
from .models import (
|
||||||
PropertyOwner, Vendor, Property, VideoCategory, Video,
|
PropertyOwner,
|
||||||
UserVideoProgress, Conversation, Message, PasswordResetToken
|
Vendor,
|
||||||
|
Property,
|
||||||
|
VideoCategory,
|
||||||
|
Video,
|
||||||
|
UserVideoProgress,
|
||||||
|
Conversation,
|
||||||
|
Message,
|
||||||
|
PasswordResetToken,
|
||||||
|
Offer,
|
||||||
|
PropertyPictures,
|
||||||
|
OpenHouse,
|
||||||
|
PropertySaleInfo,
|
||||||
|
PropertyTaxInfo,
|
||||||
|
PropertyWalkScoreInfo,
|
||||||
|
SchoolInfo,
|
||||||
|
Bid, BidImage, BidResponse, RealEstateAgent, Attorney, UserViewModel, PropertySave
|
||||||
)
|
)
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
@@ -15,16 +30,17 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_token(cls, user):
|
def get_token(cls, user):
|
||||||
token = super().get_token(user)
|
token = super().get_token(user)
|
||||||
|
|
||||||
# Add custom claims
|
# Add custom claims
|
||||||
token['email'] = user.email
|
token["email"] = user.email
|
||||||
token['first_name'] = user.first_name
|
token["first_name"] = user.first_name
|
||||||
token['last_name'] = user.last_name
|
token["last_name"] = user.last_name
|
||||||
token['user_type'] = user.user_type
|
token["user_type"] = user.user_type
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
@@ -33,24 +49,37 @@ class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
|||||||
|
|
||||||
# Add additional responses
|
# Add additional responses
|
||||||
refresh = self.get_token(self.user)
|
refresh = self.get_token(self.user)
|
||||||
data['refresh'] = str(refresh)
|
data["refresh"] = str(refresh)
|
||||||
data['access'] = str(refresh.access_token)
|
data["access"] = str(refresh.access_token)
|
||||||
|
|
||||||
# Add user details
|
# Add user details
|
||||||
data['user'] = {
|
data["user"] = {
|
||||||
'email': self.user.email,
|
"email": self.user.email,
|
||||||
'first_name': self.user.first_name,
|
"first_name": self.user.first_name,
|
||||||
'last_name': self.user.last_name,
|
"last_name": self.user.last_name,
|
||||||
'user_type': self.user.user_type,
|
"user_type": self.user.user_type,
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['id', 'email', 'first_name', 'last_name', 'user_type', 'is_active', 'date_joined']
|
fields = [
|
||||||
read_only_fields = ['id', 'is_active', 'date_joined']
|
"id",
|
||||||
|
"email",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"user_type",
|
||||||
|
"is_active",
|
||||||
|
"date_joined",
|
||||||
|
"tos_signed",
|
||||||
|
"profile_created",
|
||||||
|
"tier",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "is_active", "date_joined"]
|
||||||
|
|
||||||
|
|
||||||
class UserRegisterSerializer(serializers.ModelSerializer):
|
class UserRegisterSerializer(serializers.ModelSerializer):
|
||||||
password = serializers.CharField(write_only=True, required=True)
|
password = serializers.CharField(write_only=True, required=True)
|
||||||
@@ -58,37 +87,48 @@ class UserRegisterSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['email', 'first_name', 'last_name', 'user_type', 'password', 'password2']
|
fields = [
|
||||||
|
"email",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"user_type",
|
||||||
|
"password",
|
||||||
|
"password2",
|
||||||
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'password': {'write_only': True},
|
"password": {"write_only": True},
|
||||||
'password2': {'write_only': True},
|
"password2": {"write_only": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
if attrs['password'] != attrs['password2']:
|
if attrs["password"] != attrs["password2"]:
|
||||||
raise serializers.ValidationError({"password": "Password fields didn't match."})
|
raise serializers.ValidationError(
|
||||||
|
{"password": "Password fields didn't match."}
|
||||||
|
)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data.pop('password2')
|
validated_data.pop("password2")
|
||||||
user = User.objects.create_user(**validated_data)
|
user = User.objects.create_user(**validated_data)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
class PropertyOwnerSerializer(serializers.ModelSerializer):
|
class PropertyOwnerSerializer(serializers.ModelSerializer):
|
||||||
user = UserSerializer()
|
user = UserSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PropertyOwner
|
model = PropertyOwner
|
||||||
fields = ['user', 'phone_number']
|
fields = ["user", "phone_number"]
|
||||||
|
read_only_fields = ["created_at", "updated_at"]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
user_data = validated_data.pop('user')
|
user_data = validated_data.pop("user")
|
||||||
user = User.objects.create_user(**user_data)
|
user = User.objects.create_user(**user_data)
|
||||||
property_owner = PropertyOwner.objects.create(user=user, **validated_data)
|
property_owner = PropertyOwner.objects.create(user=user, **validated_data)
|
||||||
return property_owner
|
return property_owner
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
user_data = validated_data.pop('user', None)
|
user_data = validated_data.pop("user", None)
|
||||||
if user_data:
|
if user_data:
|
||||||
user = instance.user
|
user = instance.user
|
||||||
for attr, value in user_data.items():
|
for attr, value in user_data.items():
|
||||||
@@ -100,77 +140,320 @@ class PropertyOwnerSerializer(serializers.ModelSerializer):
|
|||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class VendorSerializer(serializers.ModelSerializer):
|
class VendorSerializer(serializers.ModelSerializer):
|
||||||
user = UserSerializer()
|
user = UserSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Vendor
|
model = Vendor
|
||||||
fields = ['user', 'business_name', 'business_type', 'phone_number',
|
fields = [
|
||||||
'address', 'city', 'state', 'zip_code']
|
# List all Vendor fields you want to expose/update, but not the user field
|
||||||
|
"business_name",
|
||||||
|
"business_type",
|
||||||
|
"phone_number",
|
||||||
|
"address",
|
||||||
|
"city",
|
||||||
|
"state",
|
||||||
|
"zip_code",
|
||||||
|
"description",
|
||||||
|
"website",
|
||||||
|
"services",
|
||||||
|
"service_areas",
|
||||||
|
"certifications",
|
||||||
|
"longitude",
|
||||||
|
"latitude",
|
||||||
|
"profile_picture",
|
||||||
|
"user",
|
||||||
|
"views"
|
||||||
|
]
|
||||||
|
read_only_fields = [
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"average_rating",
|
||||||
|
"num_reviews",
|
||||||
|
]
|
||||||
|
# This create method is fine for creating a new vendor and user
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Extract user data
|
user_data = validated_data.pop("user")
|
||||||
user_data = validated_data.pop('user')
|
user = User.objects.create_user(**user_data) # Use create_user to hash the password if present
|
||||||
|
|
||||||
# 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)
|
vendor = Vendor.objects.create(user=user, **validated_data)
|
||||||
return vendor
|
return vendor
|
||||||
|
|
||||||
|
# Override the update method to handle the nested user data
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
user_data = validated_data.pop('user', None)
|
# Pop the user data to handle it separately
|
||||||
|
user_data = validated_data.pop("user", {})
|
||||||
|
user_instance = instance.user
|
||||||
|
|
||||||
# Update Vendor fields
|
# Update the Vendor instance fields
|
||||||
for attr, value in validated_data.items():
|
for attr, value in validated_data.items():
|
||||||
setattr(instance, attr, value)
|
setattr(instance, attr, value)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
# Update nested User fields if provided
|
# Update the nested User instance fields
|
||||||
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():
|
for attr, value in user_data.items():
|
||||||
setattr(user, attr, value)
|
setattr(user_instance, attr, value)
|
||||||
user.save()
|
user_instance.save()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class PropertySerializer(serializers.ModelSerializer):
|
|
||||||
|
class PropertyPictureSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = PropertyPictures
|
||||||
|
fields = ["id", "created_at", "updated_at", "image", "Property"]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
property_id = self.context["request"].data.get("Property")
|
||||||
|
try:
|
||||||
|
property_instance = Property.objects.get(id=property_id)
|
||||||
|
|
||||||
|
except Property.DoesNotExist:
|
||||||
|
raise serializers.ValidationError("Invalid property ID.")
|
||||||
|
|
||||||
|
validated_data["Property"] = property_instance
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenHouseSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = OpenHouse
|
||||||
|
fields = ["id", "created_at", "updated_at", "listed_date", "property"]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
property_id = self.context["request"].data.get("property")
|
||||||
|
try:
|
||||||
|
property_instance = Property.objects.get(id=property_id)
|
||||||
|
|
||||||
|
except Property.DoesNotExist:
|
||||||
|
raise serializers.ValidationError("Invalid property ID.")
|
||||||
|
|
||||||
|
validated_data["property"] = property_instance
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolInfoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SchoolInfo
|
||||||
|
fields = [
|
||||||
|
"city",
|
||||||
|
"state",
|
||||||
|
"zip_code",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"enrollment",
|
||||||
|
"grades",
|
||||||
|
"name",
|
||||||
|
"parent_rating",
|
||||||
|
"rating",
|
||||||
|
"school_type",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyWalkScoreInfoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = PropertyWalkScoreInfo
|
||||||
|
fields = [
|
||||||
|
"walk_score",
|
||||||
|
"walk_description",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"ws_link",
|
||||||
|
"logo_url",
|
||||||
|
"transit_score",
|
||||||
|
"transit_description",
|
||||||
|
"transit_summary",
|
||||||
|
"bike_score",
|
||||||
|
"bike_description",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
property_id = self.context["request"].data.get("property")
|
||||||
|
try:
|
||||||
|
property_instance = Property.objects.get(id=property_id)
|
||||||
|
|
||||||
|
except Property.DoesNotExist:
|
||||||
|
raise serializers.ValidationError("Invalid property ID.")
|
||||||
|
|
||||||
|
validated_data["property"] = property_instance
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyTaxInfoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = PropertyTaxInfo
|
||||||
|
fields = [
|
||||||
|
"assessed_value",
|
||||||
|
"assessment_year",
|
||||||
|
"tax_amount",
|
||||||
|
"year",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class PropertySaleInfoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = PropertySaleInfo
|
||||||
|
fields = [
|
||||||
|
"seq_no",
|
||||||
|
"sale_date",
|
||||||
|
"sale_amount",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyResponseSerializer(serializers.ModelSerializer):
|
||||||
|
owner = PropertyOwnerSerializer()
|
||||||
|
pictures = PropertyPictureSerializer(many=True)
|
||||||
|
open_houses = OpenHouseSerializer(many=True)
|
||||||
|
schools = SchoolInfoSerializer(many=True)
|
||||||
|
walk_score = PropertyWalkScoreInfoSerializer(many=False)
|
||||||
|
tax_info = PropertyTaxInfoSerializer(many=False)
|
||||||
|
sale_info = PropertySaleInfoSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Property
|
model = Property
|
||||||
fields = ['id', 'owner', 'address', 'city', 'state', 'zip_code',
|
fields = [
|
||||||
'market_value', 'loan_amount', 'loan_interest_rate',
|
"id",
|
||||||
'loan_term', 'loan_start_date']
|
"owner",
|
||||||
read_only_fields = ['id']
|
"address",
|
||||||
|
"street",
|
||||||
|
"city",
|
||||||
|
"state",
|
||||||
|
"zip_code",
|
||||||
|
"market_value",
|
||||||
|
"loan_amount",
|
||||||
|
"loan_interest_rate",
|
||||||
|
"loan_term",
|
||||||
|
"loan_start_date",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"description",
|
||||||
|
"sq_ft",
|
||||||
|
"features",
|
||||||
|
"num_bedrooms",
|
||||||
|
"num_bathrooms",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"realestate_api_id",
|
||||||
|
"property_status",
|
||||||
|
"views",
|
||||||
|
"saves",
|
||||||
|
"listed_date",
|
||||||
|
"pictures",
|
||||||
|
'open_houses',
|
||||||
|
'schools',
|
||||||
|
'walk_score',
|
||||||
|
'tax_info',
|
||||||
|
'sale_info',
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyRequestSerializer(serializers.ModelSerializer):
|
||||||
|
schools = SchoolInfoSerializer(many=True)
|
||||||
|
tax_info = PropertyTaxInfoSerializer()
|
||||||
|
sale_info = PropertySaleInfoSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Property
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"owner",
|
||||||
|
"address",
|
||||||
|
"street",
|
||||||
|
"city",
|
||||||
|
"state",
|
||||||
|
"zip_code",
|
||||||
|
"market_value",
|
||||||
|
"loan_amount",
|
||||||
|
"loan_interest_rate",
|
||||||
|
"loan_term",
|
||||||
|
"loan_start_date",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"description",
|
||||||
|
"sq_ft",
|
||||||
|
"features",
|
||||||
|
"num_bedrooms",
|
||||||
|
"num_bathrooms",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"realestate_api_id",
|
||||||
|
"property_status",
|
||||||
|
"views",
|
||||||
|
"saves",
|
||||||
|
"listed_date",
|
||||||
|
"tax_info",
|
||||||
|
"sale_info",
|
||||||
|
"schools"
|
||||||
|
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at", "views", "saves"]
|
||||||
|
def create(self, validated_data):
|
||||||
|
# tax_info_data = validated_data.pop("tax_info")
|
||||||
|
# tax_info_data = validated_data.pop("tax_info")
|
||||||
|
walk_score = validated_data.pop("walk_score")
|
||||||
|
schools_data = validated_data.pop("schools")
|
||||||
|
tax_info = validated_data.pop("tax_info")
|
||||||
|
sale_info = validated_data.pop("sale_info")
|
||||||
|
schools = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
property_instance = Property.objects.create(**validated_data)
|
||||||
|
|
||||||
|
sale_infos = []
|
||||||
|
for sale_in in sale_info:
|
||||||
|
sale_infos.append(PropertySaleInfo.objects.create(**sale_in, property=property_instance))
|
||||||
|
|
||||||
|
|
||||||
|
for school_data in schools_data:
|
||||||
|
schools.append(SchoolInfo.objects.create(**school_data, property=property_instance))
|
||||||
|
|
||||||
|
PropertyTaxInfo.objects.create(**tax_info, property=property_instance)
|
||||||
|
walk_score.property = property_instance
|
||||||
|
walk_score.save()
|
||||||
|
|
||||||
|
return property_instance
|
||||||
|
|
||||||
|
|
||||||
class VideoCategorySerializer(serializers.ModelSerializer):
|
class VideoCategorySerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VideoCategory
|
model = VideoCategory
|
||||||
fields = ['id', 'name', 'description']
|
fields = ["id", "name", "description"]
|
||||||
read_only_fields = ['id']
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
class VideoSerializer(serializers.ModelSerializer):
|
class VideoSerializer(serializers.ModelSerializer):
|
||||||
category = VideoCategorySerializer()
|
category = VideoCategorySerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Video
|
model = Video
|
||||||
fields = ['id', 'category', 'title', 'description', 'link', 'duration', 'created_at', 'updated_at']
|
fields = [
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
"id",
|
||||||
|
"category",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"link",
|
||||||
|
"duration",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Extract category data
|
# Extract category data
|
||||||
category_data = validated_data.pop('category')
|
category_data = validated_data.pop("category")
|
||||||
|
|
||||||
# Get or create category
|
# Get or create category
|
||||||
category, _ = VideoCategory.objects.get_or_create(**category_data)
|
category, _ = VideoCategory.objects.get_or_create(**category_data)
|
||||||
@@ -181,7 +464,7 @@ class VideoSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
# Handle category update if provided
|
# Handle category update if provided
|
||||||
category_data = validated_data.pop('category', None)
|
category_data = validated_data.pop("category", None)
|
||||||
if category_data:
|
if category_data:
|
||||||
category, _ = VideoCategory.objects.get_or_create(**category_data)
|
category, _ = VideoCategory.objects.get_or_create(**category_data)
|
||||||
instance.category = category
|
instance.category = category
|
||||||
@@ -193,91 +476,128 @@ class VideoSerializer(serializers.ModelSerializer):
|
|||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class UserVideoProgressSerializer(serializers.ModelSerializer):
|
class UserVideoProgressSerializer(serializers.ModelSerializer):
|
||||||
video = VideoSerializer()
|
video = VideoSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserVideoProgress
|
model = UserVideoProgress
|
||||||
fields = ['video', 'progress', 'status', 'last_watched', 'user']
|
fields = ["id", "video", "progress", "status", "last_watched", "user"]
|
||||||
read_only_fields = ['status', 'last_watched']
|
read_only_fields = ["id", "status", "last_watched"]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Extract video data
|
# Extract video data
|
||||||
video_data = validated_data.pop('video')
|
video_data = validated_data.pop("video")
|
||||||
|
|
||||||
# Get or create video
|
# Get or create video
|
||||||
video_serializer = VideoSerializer(data=video_data)
|
video_serializer = VideoSerializer(data=video_data)
|
||||||
video_serializer.is_valid(raise_exception=True)
|
video_serializer.is_valid(raise_exception=True)
|
||||||
video = video_serializer.save()
|
video = video_serializer.save()
|
||||||
user = validated_data.pop('user')
|
user = validated_data.pop("user")
|
||||||
# Create progress record
|
# Create progress record
|
||||||
progress = UserVideoProgress.objects.create(
|
progress = UserVideoProgress.objects.create(
|
||||||
user=user,
|
user=user, video=video, **validated_data
|
||||||
video=video,
|
|
||||||
**validated_data
|
|
||||||
)
|
)
|
||||||
return progress
|
return progress
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
# Handle video update if provided
|
# Handle video update if provided
|
||||||
video_data = validated_data.pop('video', None)
|
video_data = validated_data.pop("video", None)
|
||||||
if video_data:
|
if video_data:
|
||||||
video_serializer = VideoSerializer(instance.video, data=video_data, partial=True)
|
video_serializer = VideoSerializer(
|
||||||
|
instance.video, data=video_data, partial=True
|
||||||
|
)
|
||||||
video_serializer.is_valid(raise_exception=True)
|
video_serializer.is_valid(raise_exception=True)
|
||||||
video_serializer.save()
|
video_serializer.save()
|
||||||
|
|
||||||
# Update progress
|
# Update progress
|
||||||
instance.progress = validated_data.get('progress', instance.progress)
|
instance.progress = validated_data.get("progress", instance.progress)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class ConversationSerializer(serializers.ModelSerializer):
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Conversation
|
|
||||||
fields = ['id', 'property_owner', 'vendor', 'property', 'created_at', 'updated_at']
|
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
|
||||||
|
|
||||||
class MessageSerializer(serializers.ModelSerializer):
|
class MessageSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Message
|
model = Message
|
||||||
fields = ['id', 'conversation', 'sender', 'text', 'attachment', 'timestamp', 'read']
|
fields = [
|
||||||
read_only_fields = ['id', 'timestamp']
|
"id",
|
||||||
|
"conversation",
|
||||||
|
"sender",
|
||||||
|
"text",
|
||||||
|
"attachment",
|
||||||
|
"timestamp",
|
||||||
|
"read",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "timestamp"]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Extract user data
|
# Extract user data
|
||||||
sender = validated_data.pop('sender')
|
sender = validated_data.pop("sender")
|
||||||
|
|
||||||
message = Message.objects.create(sender=sender, **validated_data)
|
message = Message.objects.create(sender=sender, **validated_data)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
"""
|
"""
|
||||||
Handle updates to message fields.
|
Handle updates to message fields.
|
||||||
Note: sender and conversation are typically read-only in updates
|
Note: sender and conversation are typically read-only in updates
|
||||||
"""
|
"""
|
||||||
# Update text if provided
|
# Update text if provided
|
||||||
instance.text = validated_data.get('text', instance.text)
|
instance.text = validated_data.get("text", instance.text)
|
||||||
|
|
||||||
# Update read status if provided
|
# Update read status if provided
|
||||||
if 'read' in validated_data:
|
if "read" in validated_data:
|
||||||
instance.read = validated_data['read']
|
instance.read = validated_data["read"]
|
||||||
|
|
||||||
# Handle attachment updates (if needed)
|
# Handle attachment updates (if needed)
|
||||||
if 'attachment' in validated_data:
|
if "attachment" in validated_data:
|
||||||
# Delete old attachment if exists
|
# Delete old attachment if exists
|
||||||
if instance.attachment:
|
if instance.attachment:
|
||||||
instance.attachment.delete()
|
instance.attachment.delete()
|
||||||
instance.attachment = validated_data['attachment']
|
instance.attachment = validated_data["attachment"]
|
||||||
|
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationResponseSerializer(serializers.ModelSerializer):
|
||||||
|
vendor = VendorSerializer()
|
||||||
|
property_owner = PropertyOwnerSerializer()
|
||||||
|
messages = MessageSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Conversation
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"property_owner",
|
||||||
|
"vendor",
|
||||||
|
"property",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"messages",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationRequestSerializer(serializers.ModelSerializer):
|
||||||
|
messages = MessageSerializer(many=True, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Conversation
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"property_owner",
|
||||||
|
"vendor",
|
||||||
|
"property",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"messages",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetRequestSerializer(serializers.Serializer):
|
class PasswordResetRequestSerializer(serializers.Serializer):
|
||||||
email = serializers.EmailField()
|
email = serializers.EmailField()
|
||||||
|
|
||||||
@@ -289,15 +609,13 @@ class PasswordResetRequestSerializer(serializers.Serializer):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
user = User.objects.get(email=self.validated_data['email'])
|
user = User.objects.get(email=self.validated_data["email"])
|
||||||
expires_at = datetime.now() + timedelta(hours=24)
|
expires_at = datetime.now() + timedelta(hours=24)
|
||||||
token = PasswordResetToken.objects.create(
|
token = PasswordResetToken.objects.create(user=user, expires_at=expires_at)
|
||||||
user=user,
|
|
||||||
expires_at=expires_at
|
|
||||||
)
|
|
||||||
token.send_reset_email()
|
token.send_reset_email()
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetConfirmSerializer(serializers.Serializer):
|
class PasswordResetConfirmSerializer(serializers.Serializer):
|
||||||
token = serializers.UUIDField()
|
token = serializers.UUIDField()
|
||||||
new_password = serializers.CharField(write_only=True)
|
new_password = serializers.CharField(write_only=True)
|
||||||
@@ -306,23 +624,219 @@ class PasswordResetConfirmSerializer(serializers.Serializer):
|
|||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token = PasswordResetToken.objects.get(token=attrs['token'])
|
token = PasswordResetToken.objects.get(token=attrs["token"])
|
||||||
except PasswordResetToken.DoesNotExist:
|
except PasswordResetToken.DoesNotExist:
|
||||||
raise serializers.ValidationError({"token": "Invalid token."})
|
raise serializers.ValidationError({"token": "Invalid token."})
|
||||||
|
|
||||||
if not token.is_valid():
|
if not token.is_valid():
|
||||||
raise serializers.ValidationError({"token": "Token is invalid or has expired."})
|
raise serializers.ValidationError(
|
||||||
|
{"token": "Token is invalid or has expired."}
|
||||||
|
)
|
||||||
|
|
||||||
if attrs['new_password'] != attrs['new_password2']:
|
if attrs["new_password"] != attrs["new_password2"]:
|
||||||
raise serializers.ValidationError({"new_password": "Password fields didn't match."})
|
raise serializers.ValidationError(
|
||||||
|
{"new_password": "Password fields didn't match."}
|
||||||
|
)
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
token = PasswordResetToken.objects.get(token=self.validated_data['token'])
|
token = PasswordResetToken.objects.get(token=self.validated_data["token"])
|
||||||
user = token.user
|
user = token.user
|
||||||
user.set_password(self.validated_data['new_password'])
|
user.set_password(self.validated_data["new_password"])
|
||||||
user.save()
|
user.save()
|
||||||
token.used = True
|
token.used = True
|
||||||
token.save()
|
token.save()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class OfferRequestSerializer(serializers.ModelSerializer):
|
||||||
|
previous_offer = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Offer
|
||||||
|
fields = ["id", "user", "property", "status", "previous_offer", "is_active"]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
def get_previous_offer(self, model_field):
|
||||||
|
return OfferRequestSerializer()
|
||||||
|
|
||||||
|
|
||||||
|
class OfferResponseSerializer(serializers.ModelSerializer):
|
||||||
|
previous_offer = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
|
user = UserSerializer()
|
||||||
|
property = PropertyResponseSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Offer
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"user",
|
||||||
|
"property",
|
||||||
|
"status",
|
||||||
|
"previous_offer",
|
||||||
|
"is_active",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at"]
|
||||||
|
|
||||||
|
def get_previous_offer(self, model_field):
|
||||||
|
return OfferResponseSerializer()
|
||||||
|
|
||||||
|
def validate_status(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
class BidImageSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BidImage
|
||||||
|
fields = ["id", "image"]
|
||||||
|
|
||||||
|
class BidResponseSerializer(serializers.ModelSerializer):
|
||||||
|
vendor = VendorSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = BidResponse
|
||||||
|
fields = ["id", "bid", "vendor", "description", "price", "status", "created_at", "updated_at"]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at", "vendor"]
|
||||||
|
|
||||||
|
class BidSerializer(serializers.ModelSerializer):
|
||||||
|
images = BidImageSerializer(many=True, read_only=True)
|
||||||
|
responses = BidResponseSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Bid
|
||||||
|
fields = ["id", "property", "description", "bid_type", "location", "created_at", "updated_at", "images", "responses"]
|
||||||
|
read_only_fields = ["id", "created_at", "updated_at", "responses"]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
images_data = self.context.get('request').FILES.getlist('images')
|
||||||
|
bid = Bid.objects.create(**validated_data)
|
||||||
|
for image_data in images_data:
|
||||||
|
# Assuming you have an image upload logic, like storing to S3 and getting a URL
|
||||||
|
BidImage.objects.create(bid=bid, image=image_data)
|
||||||
|
return bid
|
||||||
|
|
||||||
|
class AttorneySerializer(serializers.ModelSerializer):
|
||||||
|
user = UserSerializer(read_only=True) # Nested serializer for the related User object
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Attorney
|
||||||
|
fields = [
|
||||||
|
'user', 'firm_name', 'phone_number', 'address', 'city',
|
||||||
|
'state', 'zip_code', 'specialties', 'years_experience', 'website',
|
||||||
|
'profile_picture', 'bio', 'licensed_states', 'created_at', 'updated_at',
|
||||||
|
"longitude",
|
||||||
|
"latitude",
|
||||||
|
]
|
||||||
|
read_only_fields = ['created_at', 'updated_at']
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# When creating an Attorney, the User object should already exist or be created separately.
|
||||||
|
# This serializer assumes the user is already linked or passed in the context.
|
||||||
|
# For simplicity, we'll assume the user is passed directly to the view.
|
||||||
|
# In a real scenario, you'd handle user creation/association in the view or a custom manager.
|
||||||
|
user_instance = self.context.get('user')
|
||||||
|
if not user_instance:
|
||||||
|
raise serializers.ValidationError("User instance must be provided to create an Attorney.")
|
||||||
|
|
||||||
|
# Ensure the user_type is correctly set for the new user
|
||||||
|
if user_instance.user_type != 'attorney':
|
||||||
|
user_instance.user_type = 'attorney'
|
||||||
|
user_instance.save()
|
||||||
|
|
||||||
|
attorney = Attorney.objects.create(user=user_instance, **validated_data)
|
||||||
|
return attorney
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
# Handle updates for Attorney fields
|
||||||
|
instance.firm_name = validated_data.get('firm_name', instance.firm_name)
|
||||||
|
instance.bar_number = validated_data.get('bar_number', instance.bar_number)
|
||||||
|
instance.phone_number = validated_data.get('phone_number', instance.phone_number)
|
||||||
|
instance.address = validated_data.get('address', instance.address)
|
||||||
|
instance.city = validated_data.get('city', instance.city)
|
||||||
|
instance.state = validated_data.get('state', instance.state)
|
||||||
|
instance.zip_code = validated_data.get('zip_code', instance.zip_code)
|
||||||
|
instance.specialties = validated_data.get('specialties', instance.specialties)
|
||||||
|
instance.years_experience = validated_data.get('years_experience', instance.years_experience)
|
||||||
|
instance.website = validated_data.get('website', instance.website)
|
||||||
|
instance.profile_picture = validated_data.get('profile_picture', instance.profile_picture)
|
||||||
|
instance.bio = validated_data.get('bio', instance.bio)
|
||||||
|
instance.licensed_states = validated_data.get('licensed_states', instance.licensed_states)
|
||||||
|
instance.longitude = validated_data.get('longitude', instance.longitude)
|
||||||
|
instance.latitude = validated_data.get('latitude', instance.latitude)
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class RealEstateAgentSerializer(serializers.ModelSerializer):
|
||||||
|
user = UserSerializer(read_only=True) # Nested serializer for the related User object
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = RealEstateAgent
|
||||||
|
fields = [
|
||||||
|
'user', 'brokerage_name', 'license_number', 'phone_number', 'address', 'city',
|
||||||
|
'state', 'zip_code', 'specialties', 'years_experience', 'website',
|
||||||
|
'profile_picture', 'bio', 'licensed_states', 'agent_type', 'created_at', 'updated_at',
|
||||||
|
"longitude",
|
||||||
|
"latitude",
|
||||||
|
]
|
||||||
|
read_only_fields = ['created_at', 'updated_at']
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user_instance = self.context.get('user')
|
||||||
|
if not user_instance:
|
||||||
|
raise serializers.ValidationError("User instance must be provided to create a RealEstateAgent.")
|
||||||
|
|
||||||
|
# Ensure the user_type is correctly set for the new user
|
||||||
|
if user_instance.user_type != 'real_estate_agent':
|
||||||
|
user_instance.user_type = 'real_estate_agent'
|
||||||
|
user_instance.save()
|
||||||
|
|
||||||
|
agent = RealEstateAgent.objects.create(user=user_instance, **validated_data)
|
||||||
|
return agent
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
# Handle updates for RealEstateAgent fields
|
||||||
|
instance.brokerage_name = validated_data.get('brokerage_name', instance.brokerage_name)
|
||||||
|
instance.license_number = validated_data.get('license_number', instance.license_number)
|
||||||
|
instance.phone_number = validated_data.get('phone_number', instance.phone_number)
|
||||||
|
instance.address = validated_data.get('address', instance.address)
|
||||||
|
instance.city = validated_data.get('city', instance.city)
|
||||||
|
instance.state = validated_data.get('state', instance.state)
|
||||||
|
instance.zip_code = validated_data.get('zip_code', instance.zip_code)
|
||||||
|
instance.specialties = validated_data.get('specialties', instance.specialties)
|
||||||
|
instance.years_experience = validated_data.get('years_experience', instance.years_experience)
|
||||||
|
instance.website = validated_data.get('website', instance.website)
|
||||||
|
instance.profile_picture = validated_data.get('profile_picture', instance.profile_picture)
|
||||||
|
instance.bio = validated_data.get('bio', instance.bio)
|
||||||
|
instance.licensed_states = validated_data.get('licensed_states', instance.licensed_states)
|
||||||
|
instance.agent_type = validated_data.get('agent_type', instance.agent_type)
|
||||||
|
instance.longitude = validated_data.get('longitude', instance.longitude)
|
||||||
|
instance.latitude = validated_data.get('latitude', instance.latitude)
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
class PropertySaveSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for the PropertySave model.
|
||||||
|
"""
|
||||||
|
property = serializers.PrimaryKeyRelatedField(queryset=Property.objects.all())
|
||||||
|
user = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PropertySave
|
||||||
|
fields = ['id', 'user', 'property', 'created_at']
|
||||||
|
read_only_fields = ['created_at']
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
"""
|
||||||
|
Check for a unique user-property combination before creation.
|
||||||
|
"""
|
||||||
|
user = self.context['request'].user
|
||||||
|
property_id = data.get('property').id
|
||||||
|
if PropertySave.objects.filter(user=user, property=property_id).exists():
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"This property is already saved by the user."
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|||||||
0
dta_service/core/services/__init__.py
Normal file
0
dta_service/core/services/__init__.py
Normal file
18
dta_service/core/services/base_llm_service.py
Normal file
18
dta_service/core/services/base_llm_service.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from langchain_ollama import OllamaLLM
|
||||||
|
from langchain_core.output_parsers import StrOutputParser
|
||||||
|
|
||||||
|
|
||||||
|
class BaseService(ABC):
|
||||||
|
"""Abstract base class for LLM conversation services."""
|
||||||
|
|
||||||
|
def __init__(self, temperature=0.7):
|
||||||
|
self.llm = OllamaLLM(
|
||||||
|
model="llama3.2",
|
||||||
|
temperature=0.7,
|
||||||
|
top_k=50,
|
||||||
|
top_p=0.9,
|
||||||
|
repeat_penalty=1.1,
|
||||||
|
num_ctx=4096,
|
||||||
|
)
|
||||||
|
self.output_parser = StrOutputParser()
|
||||||
136
dta_service/core/services/llm_service.py
Normal file
136
dta_service/core/services/llm_service.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import AsyncGenerator, Generator, Optional
|
||||||
|
|
||||||
|
# from langchain_community.llms import Ollama
|
||||||
|
from langchain_ollama import OllamaLLM
|
||||||
|
from langchain_core.output_parsers import StrOutputParser
|
||||||
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
|
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
class LLMService(ABC):
|
||||||
|
"""sd;ofisdf"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.llm = OllamaLLM(
|
||||||
|
model="llama3.2",
|
||||||
|
temperature=0.7,
|
||||||
|
top_k=50,
|
||||||
|
top_p=0.9,
|
||||||
|
repeat_penalty=1.1,
|
||||||
|
num_ctx=4096,
|
||||||
|
)
|
||||||
|
self.output_parser = StrOutputParser()
|
||||||
|
@abstractmethod
|
||||||
|
def generate_response(self, query: str, **kwargs):
|
||||||
|
"""Generate a response to a query within a conversation context."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# def _format_history(self, conversation: Conversation) -> str:
|
||||||
|
# """Format conversation history for the prompt."""
|
||||||
|
# prompts = Prompt.objects.filter(conversation=conversation).order_by(
|
||||||
|
# "created_at"
|
||||||
|
# )
|
||||||
|
# return "\n".join(
|
||||||
|
# f"{'User' if prompt.is_user else 'AI'}: {prompt.text}" for prompt in prompts
|
||||||
|
# )
|
||||||
|
|
||||||
|
class AsyncLLMService(LLMService):
|
||||||
|
"""Asynchronous LLM conversation service."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._setup_chain()
|
||||||
|
|
||||||
|
def _setup_chain(self):
|
||||||
|
"""Setup the conversation chain."""
|
||||||
|
template = """Role:
|
||||||
|
You are HomeSale Helper, an AI assistant designed to educate and guide residential property owners through the process of selling their homes. Your goal is to provide clear, trustworthy, and personalized advice on pricing, marketing, staging, negotiations, legal considerations, and closing processes.
|
||||||
|
|
||||||
|
Conversation: {conversation}
|
||||||
|
|
||||||
|
Knowledge Base:
|
||||||
|
|
||||||
|
Stay updated on real estate trends (without providing outdated data).
|
||||||
|
|
||||||
|
Understand local market variations (if location is provided).
|
||||||
|
|
||||||
|
Know best practices for home staging, photography, and listing optimization.
|
||||||
|
|
||||||
|
Explain legal/financial steps (disclosures, contracts, closing costs) in simple terms.
|
||||||
|
|
||||||
|
Tone & Style:
|
||||||
|
|
||||||
|
Friendly, professional, and empathetic (selling a home is emotional).
|
||||||
|
|
||||||
|
Avoid jargon; simplify complex topics.
|
||||||
|
|
||||||
|
Be neutral—never pressure users or favor specific agents/buyers.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
Never give financial/legal advice—direct users to consult professionals.
|
||||||
|
|
||||||
|
Never speculate on exact home values—instead, suggest comparative market analysis (CMA) methods.
|
||||||
|
|
||||||
|
Prioritize actionable steps (e.g., "Here’s how to improve curb appeal: …").
|
||||||
|
|
||||||
|
If asked about trends, clarify whether data is general or location-specific.
|
||||||
|
|
||||||
|
Example Responses:
|
||||||
|
|
||||||
|
"To attract buyers, focus on decluttering and neutral paint colors. Would you like staging tips?"
|
||||||
|
|
||||||
|
"Closing costs typically range from 2%–5% of the sale price. A real estate attorney can clarify specifics for your area."
|
||||||
|
|
||||||
|
"I can’t calculate your home’s exact value, but here’s how to research local comps…"
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.prompt = ChatPromptTemplate.from_template(template)
|
||||||
|
|
||||||
|
self.conversation_chain = (
|
||||||
|
{
|
||||||
|
# "context":lambda x: x["conversation"],
|
||||||
|
# "recent_history":lambda x: x['recent_conversation'],
|
||||||
|
"conversation": lambda x: x["conversation"],
|
||||||
|
}
|
||||||
|
| self.prompt
|
||||||
|
| self.llm
|
||||||
|
| self.output_parser
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _format_history(self, conversation: list) -> str:
|
||||||
|
"""Async version of format conversation history."""
|
||||||
|
# prompts = list(
|
||||||
|
# await Prompt.objects.filter(conversation_id=conversation_id)
|
||||||
|
# .order_by("created")
|
||||||
|
|
||||||
|
# )
|
||||||
|
# return "\n".join(
|
||||||
|
# f"{'User' if prompt.is_user else 'AI'}: {prompt.text}" for prompt in prompts
|
||||||
|
# )
|
||||||
|
return "\n".join([f"{"User" if prompt.get('sender')=="user" else "AI"}: {prompt.get('text')}" for prompt in conversation])
|
||||||
|
|
||||||
|
# async def _get_recent_messages(self, conversation: list) -> str:
|
||||||
|
# """Async version of format conversation history."""
|
||||||
|
|
||||||
|
# # prompts = list(
|
||||||
|
# # await Prompt.objects.filter(conversation_id=conversation_id)
|
||||||
|
# # .order_by("created")
|
||||||
|
# # [-6:]
|
||||||
|
# # )
|
||||||
|
# # return "\n".join(
|
||||||
|
# # f"{'User' if prompt.is_user else 'AI'}: {prompt.text}" for prompt in prompts
|
||||||
|
# # )
|
||||||
|
# return "\n".join([f"{"User" if prompt.type=="human" else "AI"}: {prompt.text()}" for prompt in conversation])
|
||||||
|
|
||||||
|
async def generate_response(
|
||||||
|
self, conversation: list[dict[str,str]], user: User
|
||||||
|
) -> AsyncGenerator[str, None]:
|
||||||
|
"""Generate response with async streaming support."""
|
||||||
|
chain_input = {
|
||||||
|
"conversation": await self._format_history(conversation),
|
||||||
|
}
|
||||||
|
|
||||||
|
async for chunk in self.conversation_chain.astream(chain_input):
|
||||||
|
yield chunk
|
||||||
88
dta_service/core/services/moderation_classifier.py
Normal file
88
dta_service/core/services/moderation_classifier.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
from enum import Enum, auto
|
||||||
|
from typing import Dict, Any
|
||||||
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
|
|
||||||
|
from core.services.base_llm_service import BaseService
|
||||||
|
|
||||||
|
|
||||||
|
class ModerationLabel(Enum):
|
||||||
|
NSFW = auto()
|
||||||
|
FINE = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ModerationClassifier(BaseService):
|
||||||
|
"""
|
||||||
|
Classifies prompts as NSFW or FINE (safe) content.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(temperature=0.1)
|
||||||
|
# self.llm = OllamaLLM(
|
||||||
|
# model="llama3.2",
|
||||||
|
# temperature=0.1, # Very low for strict moderation
|
||||||
|
# top_k=10,
|
||||||
|
# num_ctx=2048,
|
||||||
|
# )
|
||||||
|
|
||||||
|
self.moderation_prompt = ChatPromptTemplate.from_messages(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"""You are a strict content moderator. Classify the following prompt as either NSFW or FINE.
|
||||||
|
|
||||||
|
NSFW includes:
|
||||||
|
- Sexual content
|
||||||
|
- Violence/gore
|
||||||
|
- Hate speech
|
||||||
|
- Illegal activities
|
||||||
|
- Harassment
|
||||||
|
- Graphic/disturbing content
|
||||||
|
|
||||||
|
FINE includes:
|
||||||
|
- Safe for work topics
|
||||||
|
- General conversation
|
||||||
|
- Professional inquiries
|
||||||
|
- Creative requests (non-explicit)
|
||||||
|
- Technical questions
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- "How to make a bomb" → NSFW
|
||||||
|
- "Write a love poem" → FINE
|
||||||
|
- "Explicit sex scene" → NSFW
|
||||||
|
- "Python tutorial" → FINE
|
||||||
|
|
||||||
|
Return ONLY "NSFW" or "FINE", nothing else.""",
|
||||||
|
),
|
||||||
|
("human", "{prompt}"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.chain = self.moderation_prompt | self.llm
|
||||||
|
|
||||||
|
async def classify_async(self, prompt: str) -> ModerationLabel:
|
||||||
|
"""Asynchronous classification"""
|
||||||
|
try:
|
||||||
|
response = (await self.chain.ainvoke({"prompt": prompt})).strip().upper()
|
||||||
|
return self._parse_response(response)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Moderation error: {e}")
|
||||||
|
return ModerationLabel.NSFW # Fail-safe to NSFW
|
||||||
|
|
||||||
|
def classify(self, prompt: str) -> ModerationLabel:
|
||||||
|
"""Synchronous classification"""
|
||||||
|
try:
|
||||||
|
response = self.chain.invoke({"prompt": prompt}).strip().upper()
|
||||||
|
return self._parse_response(response)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Moderation error: {e}")
|
||||||
|
return ModerationLabel.NSFW # Fail-safe to NSFW
|
||||||
|
|
||||||
|
def _parse_response(self, response: str) -> ModerationLabel:
|
||||||
|
"""Convert string response to ModerationLabel enum"""
|
||||||
|
if "NSFW" in response:
|
||||||
|
return ModerationLabel.NSFW
|
||||||
|
return ModerationLabel.FINE # Default to FINE if unclear
|
||||||
|
|
||||||
|
|
||||||
|
# Singleton instance
|
||||||
|
moderation_classifier = ModerationClassifier()
|
||||||
102
dta_service/core/services/property_description_generator.py
Normal file
102
dta_service/core/services/property_description_generator.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from langchain_ollama import OllamaLLM
|
||||||
|
from langchain_core.output_parsers import StrOutputParser
|
||||||
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
|
from .base_llm_service import BaseService
|
||||||
|
from core.models import Property
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyDescriptionGenerator(BaseService):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._setup_chain()
|
||||||
|
# format_instructions = """
|
||||||
|
# Output should use ** for bold and acutal newlines.
|
||||||
|
# Example: '**Stunning** kitchen with \nprofessonal appliances'
|
||||||
|
# """
|
||||||
|
# self.output_parser = StrOutputParser().from_response_schemas([], format_instructions)
|
||||||
|
|
||||||
|
def _setup_chain(self):
|
||||||
|
template = """You are an expert real estate copywriter specializing in creating compelling residential property listings. Write a detailed, engaging description for the following property by incorporating its features, local context, and market appeal.
|
||||||
|
|
||||||
|
**Property Details:**
|
||||||
|
- Address: {address}
|
||||||
|
- City: {city}
|
||||||
|
- State: {state}
|
||||||
|
- ZIP: {zip_code}
|
||||||
|
- Market Value: ${market_value}
|
||||||
|
- Square Footage: {sq_ft}
|
||||||
|
- Bedrooms: {num_bedrooms}
|
||||||
|
- Bathrooms: {num_bathrooms}
|
||||||
|
- Features: {features_list}
|
||||||
|
- Coordinates: ({lat}, {lon})
|
||||||
|
|
||||||
|
**Instructions:**
|
||||||
|
1. First analyze the property's key selling points based on its features, size, and value proposition
|
||||||
|
2. Use the attached [property photos] to note any visible architectural styles, finishes, or unique elements
|
||||||
|
3. Make API calls (when available) to gather:
|
||||||
|
- Walkability score (0-100) from coordinates
|
||||||
|
- Nearby school ratings (GreatSchools or similar)
|
||||||
|
- Distance/time to major downtown areas
|
||||||
|
- Notable nearby amenities (parks, transit, shopping)
|
||||||
|
4. Craft the description with this structure:
|
||||||
|
- Engaging opening highlighting the most unique aspect
|
||||||
|
- Property details with flow that moves from exterior to interior
|
||||||
|
- Neighborhood context with hyper-local details
|
||||||
|
- Closing call-to-action emphasizing urgency or exclusivity
|
||||||
|
|
||||||
|
**Style Guidelines:**
|
||||||
|
- Use vivid but professional language (avoid clichés like 'dream home')
|
||||||
|
- Include specific local references (landmarks, neighborhood names)
|
||||||
|
- For pricing, use phrases like 'valued at' or 'priced to compete at'
|
||||||
|
- Mention 2-3 most impressive features first
|
||||||
|
- Keep between 150-250 words
|
||||||
|
- End with a 'Schedule your showing today!' variation
|
||||||
|
|
||||||
|
**API Tools Available (call if needed):**
|
||||||
|
- get_walkability_score(lat, lon)
|
||||||
|
- get_school_ratings(zip_code)
|
||||||
|
- get_nearby_amenities(lat, lon, radius=1mi)
|
||||||
|
- get_downtown_distance(lat, lon)
|
||||||
|
|
||||||
|
**Example Output Structure:**
|
||||||
|
'[Neighborhood/Architectural Hook]... This [bed/bath] [home_type] at [address] offers [key features]. Step inside to find [notable interior details]. The [specific room] features [detail]. Located just [time] from [landmark], enjoy [local perks]. [Call to action]'"""
|
||||||
|
self.prompt = ChatPromptTemplate.from_template(template)
|
||||||
|
|
||||||
|
self.conversation_chain = (
|
||||||
|
{
|
||||||
|
"address": lambda x: x["address"],
|
||||||
|
"city": lambda x: x["city"],
|
||||||
|
"state": lambda x: x["state"],
|
||||||
|
"zip_code": lambda x: x["zip_code"],
|
||||||
|
"market_value": lambda x: x["market_value"],
|
||||||
|
"sq_ft": lambda x: x["sq_ft"],
|
||||||
|
"num_bedrooms": lambda x: x["num_bedrooms"],
|
||||||
|
"num_bathrooms": lambda x: x["num_bathrooms"],
|
||||||
|
"features_list": lambda x: x["features_list"],
|
||||||
|
"lat": lambda x: x["lat"],
|
||||||
|
"lon": lambda x: x["lon"],
|
||||||
|
}
|
||||||
|
| self.prompt
|
||||||
|
| self.llm
|
||||||
|
| self.output_parser
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_response(
|
||||||
|
self, property: Property, **kwargs
|
||||||
|
) -> Generator[str, None, None]:
|
||||||
|
chain_input = {
|
||||||
|
"address": property.address,
|
||||||
|
"city": property.city,
|
||||||
|
"state": property.state,
|
||||||
|
"zip_code": property.zip_code,
|
||||||
|
"market_value": property.market_value,
|
||||||
|
"sq_ft": property.sq_ft,
|
||||||
|
"num_bedrooms": property.num_bedrooms,
|
||||||
|
"num_bathrooms": property.num_bathrooms,
|
||||||
|
"features_list": property.features,
|
||||||
|
"lat": property.latitude,
|
||||||
|
"lon": property.longitude,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.conversation_chain.invoke(chain_input)
|
||||||
8
dta_service/core/urls/attorney.py
Normal file
8
dta_service/core/urls/attorney.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from core.views import AttorneyViewSet
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r"", AttorneyViewSet, basename="attorney")
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
8
dta_service/core/urls/bid.py
Normal file
8
dta_service/core/urls/bid.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from core.views import BidViewSet, BidResponseViewSet
|
||||||
|
# Create a router and register our viewsets with it.
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'bids', BidViewSet, basename='bids')
|
||||||
|
router.register(r'bid-responses', BidResponseViewSet, basename='bid-responses')
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -3,19 +3,26 @@ from rest_framework.routers import DefaultRouter
|
|||||||
from core.views import ConversationViewSet, MessageViewSet
|
from core.views import ConversationViewSet, MessageViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'', ConversationViewSet, basename='conversation')
|
router.register(r"", ConversationViewSet, basename="conversation")
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('<int:conversation_id>/messages/', MessageViewSet.as_view({
|
path(
|
||||||
'get': 'list',
|
"<int:conversation_id>/messages/",
|
||||||
'post': 'create'
|
MessageViewSet.as_view({"get": "list", "post": "create"}),
|
||||||
}), name='conversation-messages'),
|
name="conversation-messages",
|
||||||
path('<int:conversation_id>/messages/<int:pk>/', MessageViewSet.as_view({
|
),
|
||||||
'get': 'retrieve',
|
path(
|
||||||
'put': 'update',
|
"<int:conversation_id>/messages/<int:pk>/",
|
||||||
'patch': 'partial_update',
|
MessageViewSet.as_view(
|
||||||
'delete': 'destroy'
|
{
|
||||||
}), name='message-detail'),
|
"get": "retrieve",
|
||||||
|
"put": "update",
|
||||||
|
"patch": "partial_update",
|
||||||
|
"delete": "destroy",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
name="message-detail",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
8
dta_service/core/urls/offer.py
Normal file
8
dta_service/core/urls/offer.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from core.views import OfferViewSet
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r"", OfferViewSet, basename="offer")
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from core.views import PropertyViewSet
|
from core.views import PropertyViewSet, PropertyPictureViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'', PropertyViewSet, basename='property')
|
router.register(r"pictures", PropertyPictureViewSet, basename="property-pictures")
|
||||||
|
router.register(r"", PropertyViewSet, basename="property")
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
@@ -3,6 +3,6 @@ from rest_framework.routers import DefaultRouter
|
|||||||
from core.views import PropertyOwnerViewSet
|
from core.views import PropertyOwnerViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'', PropertyOwnerViewSet, basename='property-owner')
|
router.register(r"", PropertyOwnerViewSet, basename="property-owner")
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
11
dta_service/core/urls/property_save.py
Normal file
11
dta_service/core/urls/property_save.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# urls.py
|
||||||
|
from django.urls import path, include
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from core.views import PropertySaveViewSet
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'', PropertySaveViewSet, basename='propertysave')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
]
|
||||||
8
dta_service/core/urls/real_estate_agent.py
Normal file
8
dta_service/core/urls/real_estate_agent.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from core.views import RealEstateAgentViewSet
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r"", RealEstateAgentViewSet, basename="real_estate_agent")
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -3,6 +3,6 @@ from rest_framework.routers import DefaultRouter
|
|||||||
from core.views import VendorViewSet
|
from core.views import VendorViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'', VendorViewSet, basename='vendor')
|
router.register(r"", VendorViewSet, basename="vendor")
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
@@ -3,8 +3,9 @@ from rest_framework.routers import DefaultRouter
|
|||||||
from core.views import VideoCategoryViewSet, VideoViewSet, UserVideoProgressViewSet
|
from core.views import VideoCategoryViewSet, VideoViewSet, UserVideoProgressViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'categories', VideoCategoryViewSet, basename='video-category')
|
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'progress', UserVideoProgressViewSet, basename='video-progress')
|
router.register(r"", VideoViewSet, basename="video")
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
@@ -3,34 +3,103 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
|
import requests
|
||||||
|
from rest_framework.decorators import action
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from .models import (
|
from .models import (
|
||||||
PropertyOwner, Vendor, Property, VideoCategory, Video,
|
PropertyOwner,
|
||||||
UserVideoProgress, Conversation, Message
|
Vendor,
|
||||||
|
Property,
|
||||||
|
VideoCategory,
|
||||||
|
Video,
|
||||||
|
UserVideoProgress,
|
||||||
|
Conversation,
|
||||||
|
Message,
|
||||||
|
Offer,
|
||||||
|
PropertyWalkScoreInfo,
|
||||||
|
PropertyTaxInfo,
|
||||||
|
SchoolInfo,Bid, BidResponse, Attorney, RealEstateAgent, UserViewModel, PropertySave
|
||||||
)
|
)
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
CustomTokenObtainPairSerializer, UserSerializer, UserRegisterSerializer,
|
CustomTokenObtainPairSerializer,
|
||||||
PropertyOwnerSerializer, VendorSerializer, PropertySerializer,
|
UserSerializer,
|
||||||
VideoCategorySerializer, VideoSerializer, UserVideoProgressSerializer,
|
UserRegisterSerializer,
|
||||||
ConversationSerializer, MessageSerializer, PasswordResetRequestSerializer,
|
PropertyOwnerSerializer,
|
||||||
PasswordResetConfirmSerializer
|
VendorSerializer,
|
||||||
|
PropertyResponseSerializer,
|
||||||
|
PropertyRequestSerializer,
|
||||||
|
VideoCategorySerializer,
|
||||||
|
VideoSerializer,
|
||||||
|
UserVideoProgressSerializer,
|
||||||
|
ConversationRequestSerializer,
|
||||||
|
ConversationResponseSerializer,
|
||||||
|
MessageSerializer,
|
||||||
|
PasswordResetRequestSerializer,
|
||||||
|
PasswordResetConfirmSerializer,
|
||||||
|
OfferRequestSerializer,
|
||||||
|
OfferResponseSerializer,
|
||||||
|
PropertyPictureSerializer, BidSerializer, BidResponseSerializer, AttorneySerializer, RealEstateAgentSerializer, PropertySaveSerializer
|
||||||
)
|
)
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from .permissions import IsOwnerOrReadOnly, IsPropertyOwner, IsVendor, IsParticipant
|
from .permissions import (
|
||||||
|
IsOwnerOrReadOnly,
|
||||||
|
IsPropertyOwner,
|
||||||
|
IsVendor,
|
||||||
|
IsParticipant,
|
||||||
|
IsParticipantInOffer,
|
||||||
|
)
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework import filters
|
from rest_framework import filters
|
||||||
|
from django.db.models import Q
|
||||||
|
from .services.property_description_generator import PropertyDescriptionGenerator
|
||||||
|
from .filters import PropertyFilterSet
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class CustomTokenObtainPairView(TokenObtainPairView):
|
class CustomTokenObtainPairView(TokenObtainPairView):
|
||||||
serializer_class = CustomTokenObtainPairSerializer
|
serializer_class = CustomTokenObtainPairSerializer
|
||||||
|
|
||||||
|
|
||||||
class UserRegisterView(generics.CreateAPIView):
|
class UserRegisterView(generics.CreateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserRegisterSerializer
|
serializer_class = UserRegisterSerializer
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
||||||
|
|
||||||
|
class UserRetrieveView(generics.RetrieveAPIView, generics.UpdateAPIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
serializer = UserSerializer(request.user)
|
||||||
|
return Response(status=status.HTTP_200_OK, data=serializer.data)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
serializer = UserSerializer(request.user, data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response(status=status.HTTP_200_OK, data=serializer.data)
|
||||||
|
else:
|
||||||
|
print(serializer.errors)
|
||||||
|
return Response(
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
data=UserSerializer(request.user).data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSignTosView(generics.UpdateAPIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def put(self, request):
|
||||||
|
user = User.objects.get(email=request.user.email)
|
||||||
|
user.tos_signed = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
serializer = UserSerializer(user)
|
||||||
|
return Response(status=status.HTTP_200_OK, data=serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(APIView):
|
class LogoutView(APIView):
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
authentication_classes = ()
|
authentication_classes = ()
|
||||||
@@ -44,6 +113,7 @@ class LogoutView(APIView):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetRequestView(APIView):
|
class PasswordResetRequestView(APIView):
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
||||||
@@ -53,10 +123,11 @@ class PasswordResetRequestView(APIView):
|
|||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(
|
return Response(
|
||||||
{"detail": "Password reset email has been sent."},
|
{"detail": "Password reset email has been sent."},
|
||||||
status=status.HTTP_200_OK
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetConfirmView(APIView):
|
class PasswordResetConfirmView(APIView):
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
||||||
@@ -66,108 +137,465 @@ class PasswordResetConfirmView(APIView):
|
|||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(
|
return Response(
|
||||||
{"detail": "Password has been reset successfully."},
|
{"detail": "Password has been reset successfully."},
|
||||||
status=status.HTTP_200_OK
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class PropertyOwnerViewSet(viewsets.ModelViewSet):
|
class PropertyOwnerViewSet(viewsets.ModelViewSet):
|
||||||
queryset = PropertyOwner.objects.all()
|
|
||||||
serializer_class = PropertyOwnerSerializer
|
serializer_class = PropertyOwnerSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
||||||
search_fields = ['user__first_name', 'user__last_name', 'user__email']
|
search_fields = ["user__first_name", "user__last_name", "user__email"]
|
||||||
|
|
||||||
class VendorViewSet(viewsets.ModelViewSet):
|
|
||||||
queryset = Vendor.objects.all()
|
|
||||||
serializer_class = VendorSerializer
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
|
||||||
search_fields = ['business_name', 'user__first_name', 'user__last_name', 'user__email']
|
|
||||||
filterset_fields = ['business_type']
|
|
||||||
|
|
||||||
class PropertyViewSet(viewsets.ModelViewSet):
|
|
||||||
serializer_class = PropertySerializer
|
|
||||||
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
|
||||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
|
||||||
search_fields = ['address', 'city', 'state', 'zip_code']
|
|
||||||
filterset_fields = ['owner', 'state', 'city']
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
if user.user_type == 'property_owner':
|
if user.user_type == "property_owner":
|
||||||
|
if(PropertyOwner.objects.filter(user=user).count() == 0):
|
||||||
|
return PropertyOwner.objects.create(
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
return PropertyOwner.objects.filter(user=user)
|
||||||
|
else:
|
||||||
|
return PropertyOwner.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class VendorViewSet(viewsets.ModelViewSet):
|
||||||
|
serializer_class = VendorSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
||||||
|
search_fields = [
|
||||||
|
"business_name",
|
||||||
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"user__email",
|
||||||
|
]
|
||||||
|
filterset_fields = ["business_type"]
|
||||||
|
lookup_field = "user__id" # or 'user__id' if you want to be explicit
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
if self.action in ['increment_view_count', 'increment_save_count']:
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
else:
|
||||||
|
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
||||||
|
return [permission() for permission in permission_classes]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
# Your existing logic is fine here
|
||||||
|
user = self.request.user
|
||||||
|
if user.user_type == "vendor":
|
||||||
|
# If the Vendor profile doesn't exist, create it
|
||||||
|
if not Vendor.objects.filter(user=user).exists():
|
||||||
|
return Vendor.objects.create(user=user)
|
||||||
|
return Vendor.objects.filter(user=user)
|
||||||
|
return Vendor.objects.all()
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
# Override get_object to ensure the user can only access their own Vendor profile
|
||||||
|
# when a specific ID is provided in the URL.
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
obj = generics.get_object_or_404(queryset, user=self.request.user)
|
||||||
|
self.check_object_permissions(self.request, obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
# The update method will now handle the vendor profile correctly
|
||||||
|
# and ignore any user data in the payload.
|
||||||
|
partial = kwargs.pop('partial', False)
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
self.perform_update(serializer)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def increment_view_count(self, request, user__id=None):
|
||||||
|
vendor_obj = Vendor.objects.get(user__id=user__id)
|
||||||
|
vendor_obj.views += 1
|
||||||
|
vendor_obj.save()
|
||||||
|
UserViewModel.objects.create(
|
||||||
|
user_id=user__id
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response({'views': vendor_obj.views}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Attorney ViewSet
|
||||||
|
class AttorneyViewSet(viewsets.ModelViewSet):
|
||||||
|
serializer_class = AttorneySerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
# When creating an Attorney, link it to the currently authenticated user
|
||||||
|
# or handle user creation/association logic here.
|
||||||
|
# For demonstration, we'll assume request.user is the user to link.
|
||||||
|
# In a real app, you might have more complex logic (e.g., admin creating for another user).
|
||||||
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
if user.user_type == "attorney":
|
||||||
|
if not Attorney.objects.filter(user=user).exists():
|
||||||
|
return Attorney.objects.create(user=user)
|
||||||
|
|
||||||
|
return Attorney.objects.filter(user=user)
|
||||||
|
else:
|
||||||
|
return Attorney.objects.all()
|
||||||
|
|
||||||
|
# Real Estate Agent ViewSet
|
||||||
|
class RealEstateAgentViewSet(viewsets.ModelViewSet):
|
||||||
|
serializer_class = RealEstateAgentSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
# Link to the currently authenticated user
|
||||||
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
if user.user_type == "real_estate_agent":
|
||||||
|
if not RealEstateAgent.objects.filter(user=user).exists():
|
||||||
|
return RealEstateAgent.objects.create(user=user)
|
||||||
|
return RealEstateAgent.objects.filter(user=user)
|
||||||
|
else:
|
||||||
|
return RealEstateAgent.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyViewSet(viewsets.ModelViewSet):
|
||||||
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
||||||
|
filterset_class = PropertyFilterSet
|
||||||
|
search_fields = ["address", "city", "state", "zip_code"]
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
if self.action in ['increment_view_count', 'increment_save_count']:
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
else:
|
||||||
|
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
||||||
|
return [permission() for permission in permission_classes]
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
"""
|
||||||
|
Returns the serializer class to use depending on the action.
|
||||||
|
- For 'list' and 'retrieve' (read operations), use PropertyResponseSerializer.
|
||||||
|
- For 'create', 'update', 'partial_update' (write operations), use PropertyRequestSerializer.
|
||||||
|
"""
|
||||||
|
if self.action in ["list", "retrieve"]:
|
||||||
|
return PropertyResponseSerializer
|
||||||
|
return PropertyRequestSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
is_searching_others = bool(
|
||||||
|
self.request.query_params.get(filters.SearchFilter.search_param)
|
||||||
|
)
|
||||||
|
if user.user_type == "property_owner":
|
||||||
|
if is_searching_others:
|
||||||
|
return Property.objects.exclude(owner__user=user)
|
||||||
|
|
||||||
|
else:
|
||||||
return Property.objects.filter(owner__user=user)
|
return Property.objects.filter(owner__user=user)
|
||||||
|
|
||||||
return Property.objects.all()
|
return Property.objects.all()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
if self.request.user.user_type == 'property_owner':
|
|
||||||
|
if self.request.user.user_type == "property_owner":
|
||||||
owner = PropertyOwner.objects.get(user=self.request.user)
|
owner = PropertyOwner.objects.get(user=self.request.user)
|
||||||
|
## attempt to get the walkscore
|
||||||
|
res = requests.get(f'https://api.walkscore.com/score?format=json&address={self.request.data['address']}&lat={self.request.data['latitude']}&lon={self.request.data['longitude']}&transit=1&bike=1&wsapikey={'4430c9adf62a4d93cd1efbdcd018b4d4'}')
|
||||||
|
if res.ok:
|
||||||
|
data = res.json()
|
||||||
|
has_transit = data.get('transit')
|
||||||
|
has_bike = data.get('bike')
|
||||||
|
walk_score = PropertyWalkScoreInfo.objects.create(
|
||||||
|
walk_score = data.get('walkscore'),
|
||||||
|
walk_description = data.get('description'),
|
||||||
|
ws_link = data.get('ws_link'),
|
||||||
|
logo_url = data.get('logo_url'),
|
||||||
|
transit_score = data.get('transit').get('score') if has_transit else None,
|
||||||
|
transit_description = data.get('transit').get('description') if has_transit else None,
|
||||||
|
transit_summary = data.get('transit').get('summary') if has_transit else None,
|
||||||
|
bike_score = data.get('bike').get('score') if has_bike else None,
|
||||||
|
bike_description = data.get('bike').get('description') if has_bike else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
serializer.save(owner=owner, walk_score=walk_score)
|
||||||
|
else:
|
||||||
serializer.save(owner=owner)
|
serializer.save(owner=owner)
|
||||||
else:
|
else:
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def increment_view_count(self, request, pk=None):
|
||||||
|
property_obj = self.get_object()
|
||||||
|
property_obj.views += 1
|
||||||
|
property_obj.save()
|
||||||
|
# Create the user view model
|
||||||
|
# UserViewModel.objects.create(
|
||||||
|
# user__id=pk
|
||||||
|
# )
|
||||||
|
return Response({'views': property_obj.views}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def increment_save_count(self, request, pk=None):
|
||||||
|
property_obj = self.get_object()
|
||||||
|
property_obj.saves += 1
|
||||||
|
property_obj.save()
|
||||||
|
return Response({'saves': property_obj.saves}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyPictureViewSet(viewsets.ModelViewSet):
|
||||||
|
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
||||||
|
serializer_class = PropertyPictureSerializer
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
serializer.save()
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyDescriptionView(generics.UpdateAPIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def put(self, request, property_id):
|
||||||
|
# check to make sure the property belongs to the user
|
||||||
|
properties = Property.objects.filter(owner__user=request.user, id=property_id)
|
||||||
|
if len(properties) == 0:
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
elif len([properties]) > 1:
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
# generate the description
|
||||||
|
prop = properties.first()
|
||||||
|
generator = PropertyDescriptionGenerator()
|
||||||
|
description = generator.generate_response(prop)
|
||||||
|
print(description)
|
||||||
|
# save the description
|
||||||
|
prop.description = description
|
||||||
|
prop.save()
|
||||||
|
serializer = PropertyResponseSerializer(prop)
|
||||||
|
|
||||||
|
# return the description
|
||||||
|
return Response(status=status.HTTP_200_OK, data=serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class VideoCategoryViewSet(viewsets.ModelViewSet):
|
class VideoCategoryViewSet(viewsets.ModelViewSet):
|
||||||
queryset = VideoCategory.objects.all()
|
queryset = VideoCategory.objects.all()
|
||||||
serializer_class = VideoCategorySerializer
|
serializer_class = VideoCategorySerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
class VideoViewSet(viewsets.ModelViewSet):
|
class VideoViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Video.objects.all()
|
queryset = Video.objects.all()
|
||||||
serializer_class = VideoSerializer
|
serializer_class = VideoSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
|
||||||
search_fields = ['title', 'description']
|
search_fields = ["title", "description"]
|
||||||
filterset_fields = ['category']
|
filterset_fields = ["category"]
|
||||||
|
|
||||||
|
|
||||||
class UserVideoProgressViewSet(viewsets.ModelViewSet):
|
class UserVideoProgressViewSet(viewsets.ModelViewSet):
|
||||||
queryset = UserVideoProgress.objects.all()
|
|
||||||
serializer_class = UserVideoProgressSerializer
|
serializer_class = UserVideoProgressSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
# first make sure that there is a progress for each video
|
||||||
|
videos = Video.objects.all()
|
||||||
|
for video in videos:
|
||||||
|
UserVideoProgress.objects.get_or_create(
|
||||||
|
user=self.request.user,
|
||||||
|
video=video,
|
||||||
|
)
|
||||||
return UserVideoProgress.objects.filter(user=self.request.user)
|
return UserVideoProgress.objects.filter(user=self.request.user)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(user=self.request.user)
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
class ConversationViewSet(viewsets.ModelViewSet):
|
class ConversationViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = ConversationSerializer
|
|
||||||
permission_classes = [IsAuthenticated, IsParticipant]
|
permission_classes = [IsAuthenticated, IsParticipant]
|
||||||
|
search_fields = ["vendor", "property_owner"]
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
"""
|
||||||
|
Returns the serializer class to use depending on the action.
|
||||||
|
- For 'list' and 'retrieve' (read operations), use PropertyResponseSerializer.
|
||||||
|
- For 'create', 'update', 'partial_update' (write operations), use PropertyRequestSerializer.
|
||||||
|
"""
|
||||||
|
if self.action in ["list", "retrieve"]:
|
||||||
|
return ConversationResponseSerializer
|
||||||
|
return ConversationRequestSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
if user.user_type == 'property_owner':
|
if user.user_type == "property_owner":
|
||||||
owner = PropertyOwner.objects.get(user=user)
|
owner = PropertyOwner.objects.get(user=user)
|
||||||
|
vendor_id = self.request.query_params.get("vendor")
|
||||||
|
if vendor_id:
|
||||||
|
return Conversation.objects.filter(
|
||||||
|
property_owner=owner, vendor=vendor_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
return Conversation.objects.filter(property_owner=owner)
|
return Conversation.objects.filter(property_owner=owner)
|
||||||
elif user.user_type == 'vendor':
|
elif user.user_type == "vendor":
|
||||||
vendor = Vendor.objects.get(user=user)
|
vendor = Vendor.objects.get(user=user)
|
||||||
return Conversation.objects.filter(vendor=vendor)
|
return Conversation.objects.filter(vendor=vendor)
|
||||||
return Conversation.objects.none()
|
return Conversation.objects.none()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
if self.request.user.user_type == 'property_owner':
|
if self.request.user.user_type == "property_owner":
|
||||||
owner = PropertyOwner.objects.get(user=self.request.user)
|
owner = PropertyOwner.objects.get(user=self.request.user)
|
||||||
serializer.save(property_owner=owner)
|
serializer.save(property_owner=owner)
|
||||||
elif self.request.user.user_type == 'vendor':
|
elif self.request.user.user_type == "vendor":
|
||||||
vendor = Vendor.objects.get(user=self.request.user)
|
vendor = Vendor.objects.get(user=self.request.user)
|
||||||
serializer.save(vendor=vendor)
|
serializer.save(vendor=vendor)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MessageViewSet(viewsets.ModelViewSet):
|
class MessageViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = MessageSerializer
|
serializer_class = MessageSerializer
|
||||||
permission_classes = [IsAuthenticated, IsParticipant]
|
permission_classes = [IsAuthenticated, IsParticipant]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
conversation_id = self.kwargs.get('conversation_id')
|
conversation_id = self.kwargs.get("conversation_id")
|
||||||
conversation = get_object_or_404(Conversation, id=conversation_id)
|
conversation = get_object_or_404(Conversation, id=conversation_id)
|
||||||
self.check_object_permissions(self.request, conversation)
|
self.check_object_permissions(self.request, conversation)
|
||||||
return Message.objects.filter(conversation=conversation).order_by('timestamp')
|
return Message.objects.filter(conversation=conversation).order_by("timestamp")
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
conversation_id = self.kwargs.get('conversation_id')
|
conversation_id = self.kwargs.get("conversation_id")
|
||||||
conversation = get_object_or_404(Conversation, id=conversation_id)
|
conversation = get_object_or_404(Conversation, id=conversation_id)
|
||||||
self.check_object_permissions(self.request, conversation)
|
self.check_object_permissions(self.request, conversation)
|
||||||
serializer.save(conversation=conversation, sender=self.request.user)
|
serializer.save(conversation=conversation, sender=self.request.user)
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
context = super().get_serializer_context()
|
context = super().get_serializer_context()
|
||||||
context['conversation_id'] = self.kwargs.get('conversation_id')
|
context["conversation_id"] = self.kwargs.get("conversation_id")
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class OfferViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
|
permission_classes = [IsAuthenticated, IsParticipantInOffer]
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
"""
|
||||||
|
Returns the serializer class to use depending on the action.
|
||||||
|
- For 'list' and 'retrieve' (read operations), use PropertyResponseSerializer.
|
||||||
|
- For 'create', 'update', 'partial_update' (write operations), use PropertyRequestSerializer.
|
||||||
|
"""
|
||||||
|
if self.action in ["list", "retrieve"]:
|
||||||
|
return OfferResponseSerializer
|
||||||
|
return OfferRequestSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user_lookup = Q(user=self.request.user)
|
||||||
|
property_lookup = Q(property__owner__user=self.request.user)
|
||||||
|
null_previous_offer = Q(previous_offer=None)
|
||||||
|
return Offer.objects.filter(
|
||||||
|
(property_lookup & null_previous_offer)
|
||||||
|
| (user_lookup & null_previous_offer)
|
||||||
|
)
|
||||||
|
|
||||||
|
class BidViewSet(viewsets.ModelViewSet):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
if user.user_type == 'property_owner':
|
||||||
|
return Bid.objects.filter(property__owner__user=user).order_by('-created_at')
|
||||||
|
elif user.user_type == 'vendor':
|
||||||
|
# Vendors should see all bids, but only their own responses
|
||||||
|
return Bid.objects.all().order_by('-created_at')
|
||||||
|
return Bid.objects.none()
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
return BidSerializer
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def select_response(self, request, pk=None):
|
||||||
|
bid = self.get_object()
|
||||||
|
response_id = request.data.get('response_id')
|
||||||
|
try:
|
||||||
|
response = BidResponse.objects.get(id=response_id, bid=bid)
|
||||||
|
# Ensure the current user is the property owner of the bid
|
||||||
|
if request.user == bid.property.owner.user:
|
||||||
|
# Unselect any previously selected response for this bid
|
||||||
|
BidResponse.objects.filter(bid=bid, status='selected').update(status='submitted')
|
||||||
|
# Select the new response
|
||||||
|
response.status = 'selected'
|
||||||
|
response.save()
|
||||||
|
return Response({'status': 'response selected'})
|
||||||
|
return Response({'error': 'You do not have permission to perform this action.'}, status=403)
|
||||||
|
except BidResponse.DoesNotExist:
|
||||||
|
return Response({'error': 'Response not found.'}, status=404)
|
||||||
|
|
||||||
|
class BidResponseViewSet(viewsets.ModelViewSet):
|
||||||
|
serializer_class = BidResponseSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
if user.user_type == 'property_owner':
|
||||||
|
return BidResponse.objects.filter(bid__property__owner__user=user).order_by('-created_at')
|
||||||
|
elif user.user_type == 'vendor':
|
||||||
|
return BidResponse.objects.filter(vendor__user=user).order_by('-created_at')
|
||||||
|
return BidResponse.objects.none()
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
# A vendor can only create one response per bid
|
||||||
|
bid = serializer.validated_data['bid']
|
||||||
|
vendor = self.request.user.vendor
|
||||||
|
if BidResponse.objects.filter(bid=bid, vendor=vendor).exists():
|
||||||
|
raise serializers.ValidationError("You have already responded to this bid.")
|
||||||
|
serializer.save(vendor=vendor, status='submitted')
|
||||||
|
|
||||||
|
|
||||||
|
class PropertySaveViewSet(
|
||||||
|
viewsets.mixins.CreateModelMixin,
|
||||||
|
viewsets.mixins.ListModelMixin,
|
||||||
|
viewsets.mixins.DestroyModelMixin,
|
||||||
|
viewsets.GenericViewSet
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
A viewset that provides 'create', 'list', and 'destroy' actions
|
||||||
|
for saved properties.
|
||||||
|
"""
|
||||||
|
queryset = PropertySave.objects.all()
|
||||||
|
serializer_class = PropertySaveSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
This view should return a list of all saved properties
|
||||||
|
for the currently authenticated user.
|
||||||
|
"""
|
||||||
|
user = self.request.user
|
||||||
|
return PropertySave.objects.filter(user=user).order_by('-created_at')
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
"""
|
||||||
|
Saves the new PropertySave instance, associating it with
|
||||||
|
the current authenticated user.
|
||||||
|
"""
|
||||||
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Unsaves a property.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
instance = self.get_object()
|
||||||
|
self.perform_destroy(instance)
|
||||||
|
return Response(
|
||||||
|
{"detail": "Property successfully unsaved."},
|
||||||
|
status=status.HTTP_204_NO_CONTENT
|
||||||
|
)
|
||||||
|
except PropertySave.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{"detail": "PropertySave instance not found."},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|||||||
@@ -8,17 +8,21 @@ https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
import django
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||||
|
django.setup()
|
||||||
|
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
from channels.auth import AuthMiddlewareStack
|
from channels.auth import AuthMiddlewareStack
|
||||||
import core.routing
|
import core.routing
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
application = ProtocolTypeRouter(
|
||||||
|
{
|
||||||
application = ProtocolTypeRouter({
|
|
||||||
"http": get_asgi_application(),
|
"http": get_asgi_application(),
|
||||||
"websocket": AuthMiddlewareStack(
|
"websocket": AuthMiddlewareStack(
|
||||||
URLRouter(
|
URLRouter(core.routing.websocket_urlpatterns)
|
||||||
core.routing.websocket_urlpatterns
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -31,10 +31,21 @@ DEBUG = True
|
|||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
CORS_ALLOW_CREDENTIALS = False
|
||||||
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
|
CSRF_TRUSTED_ORIGINS = ["http://localhost", "http://127.0.0.1", "http://localhost:3000"]
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1",
|
||||||
|
"localhost:3000",
|
||||||
|
"127.0.0.1:3000",
|
||||||
|
"10.0.0.9 njm:3000",
|
||||||
|
]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
"daphne",
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@@ -54,6 +65,7 @@ INSTALLED_APPS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
@@ -82,7 +94,7 @@ TEMPLATES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'dta_service.wsgi.application'
|
WSGI_APPLICATION = 'dta_service.wsgi.application'
|
||||||
ASGI_APPLICATION = 'config.asgi.application'
|
ASGI_APPLICATION = 'dta_service.asgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
@@ -152,6 +164,10 @@ REST_FRAMEWORK = {
|
|||||||
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
X_FRAME_OPTIONS = 'ALLOW-FROM 127.0.0.1:8010/'
|
||||||
|
|
||||||
|
|
||||||
# JWT Settings
|
# JWT Settings
|
||||||
SIMPLE_JWT = {
|
SIMPLE_JWT = {
|
||||||
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
|
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
|
||||||
@@ -168,8 +184,7 @@ SIMPLE_JWT = {
|
|||||||
'JWK_URL': None,
|
'JWK_URL': None,
|
||||||
'LEEWAY': 0,
|
'LEEWAY': 0,
|
||||||
|
|
||||||
'AUTH_HEADER_TYPES': ('Bearer',),
|
"AUTH_HEADER_TYPES": ("JWT",),
|
||||||
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
|
|
||||||
'USER_ID_FIELD': 'id',
|
'USER_ID_FIELD': 'id',
|
||||||
'USER_ID_CLAIM': 'user_id',
|
'USER_ID_CLAIM': 'user_id',
|
||||||
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
|
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
|
||||||
@@ -210,3 +225,7 @@ SIMPLE_JWT = {
|
|||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
TEST_DISCOVER_PATTERN = "test_*.py"
|
TEST_DISCOVER_PATTERN = "test_*.py"
|
||||||
|
|
||||||
|
|
||||||
|
# Realestate api
|
||||||
|
REAL_ESTATE_API_KEY = "AIMLOPERATIONSLLC-a7ce-7525-b38f-cf8b4d52ca70"
|
||||||
|
|||||||
@@ -22,16 +22,10 @@ from rest_framework_simplejwt.views import (
|
|||||||
from core.views import (
|
from core.views import (
|
||||||
CustomTokenObtainPairView, LogoutView,
|
CustomTokenObtainPairView, LogoutView,
|
||||||
PasswordResetRequestView, PasswordResetConfirmView,
|
PasswordResetRequestView, PasswordResetConfirmView,
|
||||||
UserRegisterView
|
UserRegisterView, UserRetrieveView, UserSignTosView, PropertyDescriptionView
|
||||||
)
|
)
|
||||||
from rest_framework.routers import DefaultRouter
|
from django.conf import settings
|
||||||
from core.views import VideoCategoryViewSet, VideoViewSet, UserVideoProgressViewSet, VendorViewSet
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
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 = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
@@ -45,9 +39,29 @@ urlpatterns = [
|
|||||||
path('api/password-reset/confirm/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
|
path('api/password-reset/confirm/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
|
||||||
|
|
||||||
# API endpoints
|
# API endpoints
|
||||||
|
path('api/user/', UserRetrieveView.as_view(), name='get_user'),
|
||||||
|
path('api/user/acknowledge_tos/', UserSignTosView.as_view(), name='sign_tos'),
|
||||||
|
path('api/property-description-generator/<int:property_id>/', PropertyDescriptionView.as_view(), name='property-description-generator'),
|
||||||
path('api/property-owners/', include('core.urls.property_owner')),
|
path('api/property-owners/', include('core.urls.property_owner')),
|
||||||
path('api/vendors/', include('core.urls.vendor')),
|
path('api/vendors/', include('core.urls.vendor')),
|
||||||
path('api/properties/', include('core.urls.property')),
|
path('api/properties/', include('core.urls.property')),
|
||||||
path('api/videos/', include('core.urls.video')),
|
path('api/videos/', include('core.urls.video')),
|
||||||
path('api/conversations/', include('core.urls.conversation')),
|
path('api/conversations/', include('core.urls.conversation')),
|
||||||
|
path('api/offers/', include('core.urls.offer')),
|
||||||
|
path('api/attorney/', include('core.urls.attorney')),
|
||||||
|
path('api/real_estate_agent/', include('core.urls.real_estate_agent')),
|
||||||
|
path('api/saved-properties/', include('core.urls.property_save')),
|
||||||
|
path('api/', include('core.urls.bid')),
|
||||||
]
|
]
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
def print_patterns(patterns, base_path=""):
|
||||||
|
for pattern in patterns:
|
||||||
|
if hasattr(pattern, 'url_patterns'): # It's a URLResolver
|
||||||
|
print_patterns(pattern.url_patterns, base_path + str(pattern.pattern))
|
||||||
|
else: # It's a URLPattern
|
||||||
|
full_path = base_path + str(pattern.pattern)
|
||||||
|
view_name = pattern.callback.__module__ + "." + pattern.callback.__name__
|
||||||
|
print(f"URL: /{full_path.replace('^', '').replace('$', '')} -> View: {view_name}")
|
||||||
|
|
||||||
|
print_patterns(urlpatterns)
|
||||||
|
|||||||
@@ -1,234 +1,244 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from core.models import (
|
from core.models import (
|
||||||
PropertyOwner, Vendor, Property, VideoCategory, Video,
|
PropertyOwner,
|
||||||
UserVideoProgress, Conversation, Message, PasswordResetToken
|
Vendor,
|
||||||
|
Property,
|
||||||
|
VideoCategory,
|
||||||
|
Video,
|
||||||
|
UserVideoProgress,
|
||||||
|
Conversation,
|
||||||
|
Message,
|
||||||
|
PasswordResetToken,
|
||||||
)
|
)
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class UserModelTest(TestCase):
|
class UserModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='test@example.com',
|
email="test@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_user_creation(self):
|
def test_user_creation(self):
|
||||||
self.assertEqual(self.user.email, 'test@example.com')
|
self.assertEqual(self.user.email, "test@example.com")
|
||||||
self.assertEqual(self.user.first_name, 'Test')
|
self.assertEqual(self.user.first_name, "Test")
|
||||||
self.assertEqual(self.user.last_name, 'User')
|
self.assertEqual(self.user.last_name, "User")
|
||||||
self.assertEqual(self.user.user_type, 'property_owner')
|
self.assertEqual(self.user.user_type, "property_owner")
|
||||||
self.assertTrue(self.user.check_password('testpass123'))
|
self.assertTrue(self.user.check_password("testpass123"))
|
||||||
|
|
||||||
def test_user_str(self):
|
def test_user_str(self):
|
||||||
self.assertEqual(str(self.user), 'test@example.com')
|
self.assertEqual(str(self.user), "test@example.com")
|
||||||
|
|
||||||
|
|
||||||
class PropertyOwnerModelTest(TestCase):
|
class PropertyOwnerModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(
|
self.owner = PropertyOwner.objects.create(
|
||||||
user=self.user,
|
user=self.user, phone_number="1234567890"
|
||||||
phone_number='1234567890'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_property_owner_creation(self):
|
def test_property_owner_creation(self):
|
||||||
self.assertEqual(self.owner.user, self.user)
|
self.assertEqual(self.owner.user, self.user)
|
||||||
self.assertEqual(self.owner.phone_number, '1234567890')
|
self.assertEqual(self.owner.phone_number, "1234567890")
|
||||||
|
|
||||||
def test_property_owner_str(self):
|
def test_property_owner_str(self):
|
||||||
self.assertEqual(str(self.owner), 'Property Owner')
|
self.assertEqual(str(self.owner), "Property Owner")
|
||||||
|
|
||||||
|
|
||||||
class VendorModelTest(TestCase):
|
class VendorModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor',
|
business_type="contractor",
|
||||||
phone_number='1234567890',
|
phone_number="1234567890",
|
||||||
address='123 Test St',
|
address="123 Test St",
|
||||||
city='Testville',
|
city="Testville",
|
||||||
state='TS',
|
state="TS",
|
||||||
zip_code='12345'
|
zip_code="12345",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_vendor_creation(self):
|
def test_vendor_creation(self):
|
||||||
self.assertEqual(self.vendor.user, self.user)
|
self.assertEqual(self.vendor.user, self.user)
|
||||||
self.assertEqual(self.vendor.business_name, 'Test Vendor')
|
self.assertEqual(self.vendor.business_name, "Test Vendor")
|
||||||
self.assertEqual(self.vendor.business_type, 'contractor')
|
self.assertEqual(self.vendor.business_type, "contractor")
|
||||||
self.assertEqual(self.vendor.phone_number, '1234567890')
|
self.assertEqual(self.vendor.phone_number, "1234567890")
|
||||||
self.assertEqual(self.vendor.address, '123 Test St')
|
self.assertEqual(self.vendor.address, "123 Test St")
|
||||||
self.assertEqual(self.vendor.city, 'Testville')
|
self.assertEqual(self.vendor.city, "Testville")
|
||||||
self.assertEqual(self.vendor.state, 'TS')
|
self.assertEqual(self.vendor.state, "TS")
|
||||||
self.assertEqual(self.vendor.zip_code, '12345')
|
self.assertEqual(self.vendor.zip_code, "12345")
|
||||||
|
|
||||||
def test_vendor_str(self):
|
def test_vendor_str(self):
|
||||||
self.assertEqual(str(self.vendor), 'Test Vendor')
|
self.assertEqual(str(self.vendor), "Test Vendor")
|
||||||
|
|
||||||
|
|
||||||
class PropertyModelTest(TestCase):
|
class PropertyModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.user)
|
self.owner = PropertyOwner.objects.create(user=self.user)
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00,
|
market_value=500000.00,
|
||||||
loan_amount=400000.00,
|
loan_amount=400000.00,
|
||||||
loan_interest_rate=3.5,
|
loan_interest_rate=3.5,
|
||||||
loan_term=30,
|
loan_term=30,
|
||||||
loan_start_date='2020-01-01'
|
loan_start_date="2020-01-01",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_property_creation(self):
|
def test_property_creation(self):
|
||||||
self.assertEqual(self.property.owner, self.owner)
|
self.assertEqual(self.property.owner, self.owner)
|
||||||
self.assertEqual(self.property.address, '123 Main St')
|
self.assertEqual(self.property.address, "123 Main St")
|
||||||
self.assertEqual(self.property.city, 'Anytown')
|
self.assertEqual(self.property.city, "Anytown")
|
||||||
self.assertEqual(self.property.state, 'CA')
|
self.assertEqual(self.property.state, "CA")
|
||||||
self.assertEqual(self.property.zip_code, '90210')
|
self.assertEqual(self.property.zip_code, "90210")
|
||||||
self.assertEqual(self.property.market_value, 500000.00)
|
self.assertEqual(self.property.market_value, 500000.00)
|
||||||
self.assertEqual(self.property.loan_amount, 400000.00)
|
self.assertEqual(self.property.loan_amount, 400000.00)
|
||||||
self.assertEqual(self.property.loan_interest_rate, 3.5)
|
self.assertEqual(self.property.loan_interest_rate, 3.5)
|
||||||
self.assertEqual(self.property.loan_term, 30)
|
self.assertEqual(self.property.loan_term, 30)
|
||||||
self.assertEqual(str(self.property.loan_start_date), '2020-01-01')
|
self.assertEqual(str(self.property.loan_start_date), "2020-01-01")
|
||||||
|
|
||||||
def test_property_str(self):
|
def test_property_str(self):
|
||||||
self.assertEqual(str(self.property), '123 Main St, Anytown, CA 90210')
|
self.assertEqual(str(self.property), "123 Main St, Anytown, CA 90210")
|
||||||
|
|
||||||
|
|
||||||
class VideoModelTest(TestCase):
|
class VideoModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.category = VideoCategory.objects.create(
|
self.category = VideoCategory.objects.create(
|
||||||
name='Test Category',
|
name="Test Category", description="Test Description"
|
||||||
description='Test Description'
|
|
||||||
)
|
)
|
||||||
self.video = Video.objects.create(
|
self.video = Video.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
title='Test Video',
|
title="Test Video",
|
||||||
description='Test Video Description',
|
description="Test Video Description",
|
||||||
link='https://example.com/video',
|
link="https://example.com/video",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_video_creation(self):
|
def test_video_creation(self):
|
||||||
self.assertEqual(self.video.category, self.category)
|
self.assertEqual(self.video.category, self.category)
|
||||||
self.assertEqual(self.video.title, 'Test Video')
|
self.assertEqual(self.video.title, "Test Video")
|
||||||
self.assertEqual(self.video.description, 'Test Video Description')
|
self.assertEqual(self.video.description, "Test Video Description")
|
||||||
self.assertEqual(self.video.link, 'https://example.com/video')
|
self.assertEqual(self.video.link, "https://example.com/video")
|
||||||
self.assertEqual(self.video.duration, 300)
|
self.assertEqual(self.video.duration, 300)
|
||||||
|
|
||||||
def test_video_str(self):
|
def test_video_str(self):
|
||||||
self.assertEqual(str(self.video), 'Test Video')
|
self.assertEqual(str(self.video), "Test Video")
|
||||||
|
|
||||||
|
|
||||||
class UserVideoProgressModelTest(TestCase):
|
class UserVideoProgressModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='user@example.com',
|
email="user@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.category = VideoCategory.objects.create(name='Test Category')
|
self.category = VideoCategory.objects.create(name="Test Category")
|
||||||
self.video = Video.objects.create(
|
self.video = Video.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
title='Test Video',
|
title="Test Video",
|
||||||
link='https://example.com/video',
|
link="https://example.com/video",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
)
|
||||||
self.progress = UserVideoProgress.objects.create(
|
self.progress = UserVideoProgress.objects.create(
|
||||||
user=self.user,
|
user=self.user, video=self.video, progress=150
|
||||||
video=self.video,
|
|
||||||
progress=150
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_progress_creation(self):
|
def test_progress_creation(self):
|
||||||
self.assertEqual(self.progress.user, self.user)
|
self.assertEqual(self.progress.user, self.user)
|
||||||
self.assertEqual(self.progress.video, self.video)
|
self.assertEqual(self.progress.video, self.video)
|
||||||
self.assertEqual(self.progress.progress, 150)
|
self.assertEqual(self.progress.progress, 150)
|
||||||
self.assertEqual(self.progress.status, 'in_progress')
|
self.assertEqual(self.progress.status, "in_progress")
|
||||||
|
|
||||||
def test_status_update(self):
|
def test_status_update(self):
|
||||||
# Test not started
|
# Test not started
|
||||||
self.progress.progress = 0
|
self.progress.progress = 0
|
||||||
self.progress.save()
|
self.progress.save()
|
||||||
self.assertEqual(self.progress.status, 'not_started')
|
self.assertEqual(self.progress.status, "not_started")
|
||||||
|
|
||||||
# Test completed
|
# Test completed
|
||||||
self.progress.progress = 300
|
self.progress.progress = 300
|
||||||
self.progress.save()
|
self.progress.save()
|
||||||
self.assertEqual(self.progress.status, 'completed')
|
self.assertEqual(self.progress.status, "completed")
|
||||||
|
|
||||||
# Test in progress
|
# Test in progress
|
||||||
self.progress.progress = 150
|
self.progress.progress = 150
|
||||||
self.progress.save()
|
self.progress.save()
|
||||||
self.assertEqual(self.progress.status, 'in_progress')
|
self.assertEqual(self.progress.status, "in_progress")
|
||||||
|
|
||||||
def test_progress_str(self):
|
def test_progress_str(self):
|
||||||
self.assertEqual(str(self.progress), 'user@example.com - Test Video - in_progress')
|
self.assertEqual(
|
||||||
|
str(self.progress), "user@example.com - Test Video - in_progress"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConversationModelTest(TestCase):
|
class ConversationModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.owner_user = User.objects.create_user(
|
self.owner_user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
||||||
|
|
||||||
self.vendor_user = User.objects.create_user(
|
self.vendor_user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.vendor_user,
|
user=self.vendor_user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor'
|
business_type="contractor",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.conversation = Conversation.objects.create(
|
self.conversation = Conversation.objects.create(
|
||||||
property_owner=self.owner,
|
property_owner=self.owner, vendor=self.vendor, property=self.property
|
||||||
vendor=self.vendor,
|
|
||||||
property=self.property
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_conversation_creation(self):
|
def test_conversation_creation(self):
|
||||||
@@ -237,76 +247,75 @@ class ConversationModelTest(TestCase):
|
|||||||
self.assertEqual(self.conversation.property, self.property)
|
self.assertEqual(self.conversation.property, self.property)
|
||||||
|
|
||||||
def test_conversation_str(self):
|
def test_conversation_str(self):
|
||||||
expected_str = f"Conversation between {self.owner} and {self.vendor} about {self.property}"
|
expected_str = (
|
||||||
|
f"Conversation between {self.owner} and {self.vendor} about {self.property}"
|
||||||
|
)
|
||||||
self.assertEqual(str(self.conversation), expected_str)
|
self.assertEqual(str(self.conversation), expected_str)
|
||||||
|
|
||||||
|
|
||||||
class MessageModelTest(TestCase):
|
class MessageModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.owner_user = User.objects.create_user(
|
self.owner_user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
||||||
|
|
||||||
self.vendor_user = User.objects.create_user(
|
self.vendor_user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.vendor_user,
|
user=self.vendor_user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor'
|
business_type="contractor",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.conversation = Conversation.objects.create(
|
self.conversation = Conversation.objects.create(
|
||||||
property_owner=self.owner,
|
property_owner=self.owner, vendor=self.vendor, property=self.property
|
||||||
vendor=self.vendor,
|
|
||||||
property=self.property
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.message = Message.objects.create(
|
self.message = Message.objects.create(
|
||||||
conversation=self.conversation,
|
conversation=self.conversation, sender=self.owner_user, text="Test message"
|
||||||
sender=self.owner_user,
|
|
||||||
text='Test message'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_message_creation(self):
|
def test_message_creation(self):
|
||||||
self.assertEqual(self.message.conversation, self.conversation)
|
self.assertEqual(self.message.conversation, self.conversation)
|
||||||
self.assertEqual(self.message.sender, self.owner_user)
|
self.assertEqual(self.message.sender, self.owner_user)
|
||||||
self.assertEqual(self.message.text, 'Test message')
|
self.assertEqual(self.message.text, "Test message")
|
||||||
self.assertFalse(self.message.read)
|
self.assertFalse(self.message.read)
|
||||||
|
|
||||||
def test_message_str(self):
|
def test_message_str(self):
|
||||||
expected_str = f"Message from {self.owner_user} in {self.conversation}"
|
expected_str = f"Message from {self.owner_user} in {self.conversation}"
|
||||||
self.assertEqual(str(self.message), expected_str)
|
self.assertEqual(str(self.message), expected_str)
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetTokenModelTest(TestCase):
|
class PasswordResetTokenModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='user@example.com',
|
email="user@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.token = PasswordResetToken.objects.create(
|
self.token = PasswordResetToken.objects.create(
|
||||||
user=self.user,
|
user=self.user, expires_at=timezone.now() + timedelta(hours=24)
|
||||||
expires_at=timezone.now() + timedelta(hours=24)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_token_creation(self):
|
def test_token_creation(self):
|
||||||
|
|||||||
@@ -2,236 +2,365 @@ from django.test import TestCase
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from core.serializers import (
|
from core.serializers import (
|
||||||
UserRegisterSerializer, PropertyOwnerSerializer, VendorSerializer,
|
UserRegisterSerializer,
|
||||||
PropertySerializer, VideoSerializer, UserVideoProgressSerializer,
|
PropertyOwnerSerializer,
|
||||||
ConversationSerializer, MessageSerializer, PasswordResetRequestSerializer,
|
VendorSerializer,
|
||||||
PasswordResetConfirmSerializer
|
PropertyRequestSerializer,
|
||||||
|
VideoSerializer,
|
||||||
|
UserVideoProgressSerializer,
|
||||||
|
ConversationRequestSerializer,
|
||||||
|
ConversationResponseSerializer,
|
||||||
|
MessageSerializer,
|
||||||
|
PasswordResetRequestSerializer,
|
||||||
|
PasswordResetConfirmSerializer,
|
||||||
|
OfferResponseSerializer,
|
||||||
|
OfferRequestSerializer,
|
||||||
)
|
)
|
||||||
from core.models import (
|
from core.models import (
|
||||||
PropertyOwner, Vendor, Property, VideoCategory, Video,
|
PropertyOwner,
|
||||||
UserVideoProgress, Conversation, Message, PasswordResetToken
|
Vendor,
|
||||||
|
Property,
|
||||||
|
VideoCategory,
|
||||||
|
Video,
|
||||||
|
UserVideoProgress,
|
||||||
|
Conversation,
|
||||||
|
Message,
|
||||||
|
PasswordResetToken,
|
||||||
|
Offer,
|
||||||
)
|
)
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class UserRegisterSerializerTest(TestCase):
|
class UserRegisterSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.valid_data = {
|
self.valid_data = {
|
||||||
'email': 'test@example.com',
|
"email": "test@example.com",
|
||||||
'first_name': 'Test',
|
"first_name": "Test",
|
||||||
'last_name': 'User',
|
"last_name": "User",
|
||||||
'user_type': 'property_owner',
|
"user_type": "property_owner",
|
||||||
'password': 'testpass123',
|
"password": "testpass123",
|
||||||
'password2': 'testpass123'
|
"password2": "testpass123",
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_valid_serializer(self):
|
def test_valid_serializer(self):
|
||||||
serializer = UserRegisterSerializer(data=self.valid_data)
|
serializer = UserRegisterSerializer(data=self.valid_data)
|
||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid())
|
||||||
user = serializer.save()
|
user = serializer.save()
|
||||||
self.assertEqual(user.email, 'test@example.com')
|
self.assertEqual(user.email, "test@example.com")
|
||||||
self.assertEqual(user.first_name, 'Test')
|
self.assertEqual(user.first_name, "Test")
|
||||||
self.assertEqual(user.last_name, 'User')
|
self.assertEqual(user.last_name, "User")
|
||||||
self.assertEqual(user.user_type, 'property_owner')
|
self.assertEqual(user.user_type, "property_owner")
|
||||||
|
|
||||||
def test_password_mismatch(self):
|
def test_password_mismatch(self):
|
||||||
invalid_data = self.valid_data.copy()
|
invalid_data = self.valid_data.copy()
|
||||||
invalid_data['password2'] = 'differentpass'
|
invalid_data["password2"] = "differentpass"
|
||||||
serializer = UserRegisterSerializer(data=invalid_data)
|
serializer = UserRegisterSerializer(data=invalid_data)
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
|
||||||
class PropertyOwnerSerializerTest(TestCase):
|
class PropertyOwnerSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user_data = {
|
self.user_data = {
|
||||||
'email': 'owner@example.com',
|
"email": "owner@example.com",
|
||||||
'first_name': 'Property',
|
"first_name": "Property",
|
||||||
'last_name': 'Owner',
|
"last_name": "Owner",
|
||||||
'user_type': 'property_owner',
|
"user_type": "property_owner",
|
||||||
'password': 'testpass123'
|
"password": "testpass123",
|
||||||
}
|
|
||||||
self.owner_data = {
|
|
||||||
'user': self.user_data,
|
|
||||||
'phone_number': '1234567890'
|
|
||||||
}
|
}
|
||||||
|
self.owner_data = {"user": self.user_data, "phone_number": "1234567890"}
|
||||||
|
|
||||||
def test_create_property_owner(self):
|
def test_create_property_owner(self):
|
||||||
serializer = PropertyOwnerSerializer(data=self.owner_data)
|
serializer = PropertyOwnerSerializer(data=self.owner_data)
|
||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid())
|
||||||
owner = serializer.save()
|
owner = serializer.save()
|
||||||
self.assertEqual(owner.user.email, 'owner@example.com')
|
self.assertEqual(owner.user.email, "owner@example.com")
|
||||||
self.assertEqual(owner.phone_number, '1234567890')
|
self.assertEqual(owner.phone_number, "1234567890")
|
||||||
|
|
||||||
def test_update_property_owner(self):
|
def test_update_property_owner(self):
|
||||||
user = User.objects.create_user(**self.user_data)
|
user = User.objects.create_user(**self.user_data)
|
||||||
owner = PropertyOwner.objects.create(user=user, phone_number='1234567890')
|
owner = PropertyOwner.objects.create(user=user, phone_number="1234567890")
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'user': {
|
"user": {
|
||||||
'first_name': 'NewName',
|
"first_name": "NewName",
|
||||||
'last_name': 'NewLast',
|
"last_name": "NewLast",
|
||||||
'email': 'newowner@example.com'
|
"email": "newowner@example.com",
|
||||||
},
|
},
|
||||||
'phone_number': '9876543210'
|
"phone_number": "9876543210",
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer = PropertyOwnerSerializer(instance=owner, data=update_data, partial=True)
|
serializer = PropertyOwnerSerializer(
|
||||||
|
instance=owner, data=update_data, partial=True
|
||||||
|
)
|
||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid())
|
||||||
updated_owner = serializer.save()
|
updated_owner = serializer.save()
|
||||||
|
|
||||||
self.assertEqual(updated_owner.user.first_name, 'NewName')
|
self.assertEqual(updated_owner.user.first_name, "NewName")
|
||||||
self.assertEqual(updated_owner.user.last_name, 'NewLast')
|
self.assertEqual(updated_owner.user.last_name, "NewLast")
|
||||||
self.assertEqual(updated_owner.phone_number, '9876543210')
|
self.assertEqual(updated_owner.phone_number, "9876543210")
|
||||||
|
|
||||||
|
|
||||||
class VendorSerializerTest(TestCase):
|
class VendorSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user_data = {
|
self.user_data = {
|
||||||
'email': 'vendor@example.com',
|
"email": "vendor@example.com",
|
||||||
'first_name': 'Vendor',
|
"first_name": "Vendor",
|
||||||
'last_name': 'User',
|
"last_name": "User",
|
||||||
'user_type': 'vendor',
|
"user_type": "vendor",
|
||||||
'password': 'testpass123'
|
"password": "testpass123",
|
||||||
}
|
}
|
||||||
self.vendor_data = {
|
self.vendor_data = {
|
||||||
'user': self.user_data,
|
"user": self.user_data,
|
||||||
'business_name': 'Test Vendor',
|
"business_name": "Test Vendor",
|
||||||
'business_type': 'contractor',
|
"business_type": "electrician",
|
||||||
'phone_number': '1234567890',
|
"phone_number": "1234567890",
|
||||||
'address': '123 Test St',
|
"address": "123 Test St",
|
||||||
'city': 'Testville',
|
"city": "Testville",
|
||||||
'state': 'TS',
|
"state": "TS",
|
||||||
'zip_code': '12345'
|
"zip_code": "12345",
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_create_vendor(self):
|
def test_create_vendor(self):
|
||||||
serializer = VendorSerializer(data=self.vendor_data)
|
serializer = VendorSerializer(data=self.vendor_data)
|
||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||||
vendor = serializer.save()
|
vendor = serializer.save()
|
||||||
self.assertEqual(vendor.user.email, 'vendor@example.com')
|
self.assertEqual(vendor.user.email, "vendor@example.com")
|
||||||
self.assertEqual(vendor.business_name, 'Test Vendor')
|
self.assertEqual(vendor.business_name, "Test Vendor")
|
||||||
self.assertEqual(vendor.business_type, 'contractor')
|
self.assertEqual(vendor.business_type, "electrician")
|
||||||
self.assertEqual(vendor.phone_number, '1234567890')
|
self.assertEqual(vendor.phone_number, "1234567890")
|
||||||
self.assertEqual(vendor.address, '123 Test St')
|
self.assertEqual(vendor.address, "123 Test St")
|
||||||
self.assertEqual(vendor.city, 'Testville')
|
self.assertEqual(vendor.city, "Testville")
|
||||||
self.assertEqual(vendor.state, 'TS')
|
self.assertEqual(vendor.state, "TS")
|
||||||
self.assertEqual(vendor.zip_code, '12345')
|
self.assertEqual(vendor.zip_code, "12345")
|
||||||
|
|
||||||
def test_update_vendor(self):
|
def test_update_vendor(self):
|
||||||
user = User.objects.create_user(**self.user_data)
|
user = User.objects.create_user(**self.user_data)
|
||||||
vendor = Vendor.objects.create(
|
vendor = Vendor.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor',
|
business_type="electrician",
|
||||||
phone_number='1234567890',
|
phone_number="1234567890",
|
||||||
address='123 Test St',
|
address="123 Test St",
|
||||||
city='Testville',
|
city="Testville",
|
||||||
state='TS',
|
state="TS",
|
||||||
zip_code='12345'
|
zip_code="12345",
|
||||||
)
|
)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'user': {
|
"user": {
|
||||||
'first_name': 'NewVendor',
|
"first_name": "NewVendor",
|
||||||
'last_name': 'NewUser',
|
"last_name": "NewUser",
|
||||||
'email': 'newvendor@example.com'
|
"email": "newvendor@example.com",
|
||||||
},
|
},
|
||||||
'business_name': 'Updated Vendor',
|
"business_name": "Updated Vendor",
|
||||||
'phone_number': '9876543210'
|
"phone_number": "9876543210",
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer = VendorSerializer(instance=vendor, data=update_data, partial=True)
|
serializer = VendorSerializer(instance=vendor, data=update_data, partial=True)
|
||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid())
|
||||||
updated_vendor = serializer.save()
|
updated_vendor = serializer.save()
|
||||||
|
|
||||||
self.assertEqual(updated_vendor.user.first_name, 'NewVendor')
|
self.assertEqual(updated_vendor.user.first_name, "NewVendor")
|
||||||
self.assertEqual(updated_vendor.user.last_name, 'NewUser')
|
self.assertEqual(updated_vendor.user.last_name, "NewUser")
|
||||||
self.assertEqual(updated_vendor.business_name, 'Updated Vendor')
|
self.assertEqual(updated_vendor.business_name, "Updated Vendor")
|
||||||
self.assertEqual(updated_vendor.phone_number, '9876543210')
|
self.assertEqual(updated_vendor.phone_number, "9876543210")
|
||||||
|
|
||||||
|
|
||||||
class PropertySerializerTest(TestCase):
|
class PropertySerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.user)
|
self.owner = PropertyOwner.objects.create(user=self.user)
|
||||||
|
|
||||||
self.property_data = {
|
self.property_data = {
|
||||||
'owner': self.owner.pk,
|
"owner": self.owner.pk,
|
||||||
'address': '123 Main St',
|
"address": "123 Main St",
|
||||||
'city': 'Anytown',
|
"city": "Anytown",
|
||||||
'state': 'CA',
|
"state": "CA",
|
||||||
'zip_code': '90210',
|
"zip_code": "90210",
|
||||||
'market_value': '500000.00',
|
"market_value": "500000.00",
|
||||||
'loan_amount': '400000.00',
|
"loan_amount": "400000.00",
|
||||||
'loan_interest_rate': '3.50',
|
"loan_interest_rate": "3.50",
|
||||||
'loan_term': 30,
|
"loan_term": 30,
|
||||||
'loan_start_date': '2020-01-01'
|
"loan_start_date": "2020-01-01",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.full_property_data = {
|
||||||
|
"owner": self.owner.pk,
|
||||||
|
"address": "1968 Green Apple Ct, Orange Park, FL, 32073",
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_code": "60189",
|
||||||
|
"latitude": 41.833230929728565,
|
||||||
|
"longitude": -88.12083257242568,
|
||||||
|
"market_value": "742000",
|
||||||
|
"loan_amount": "449000",
|
||||||
|
"loan_term": "360",
|
||||||
|
"loan_start_date": "2021-01-05",
|
||||||
|
"description": "",
|
||||||
|
"features": [],
|
||||||
|
"pictures": [],
|
||||||
|
"num_bedrooms": None,
|
||||||
|
"num_bathrooms": 3,
|
||||||
|
"sq_ft": 2598,
|
||||||
|
"realestate_api_id": 175468968,
|
||||||
|
"views": 0,
|
||||||
|
"saves": 0,
|
||||||
|
"property_status": "off_market",
|
||||||
|
"schools": [
|
||||||
|
{
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_code": "60189",
|
||||||
|
"latitude": 41.834869,
|
||||||
|
"longitude": -88.146118,
|
||||||
|
"school_type": "Public",
|
||||||
|
"enrollment": 1982,
|
||||||
|
"grades": "9-12",
|
||||||
|
"name": "Wheaton Warrenville South High School",
|
||||||
|
"parent_rating": 3,
|
||||||
|
"rating": 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_code": "60189",
|
||||||
|
"latitude": 41.852871,
|
||||||
|
"longitude": -88.109077,
|
||||||
|
"school_type": "Public",
|
||||||
|
"enrollment": 620,
|
||||||
|
"grades": "6-8",
|
||||||
|
"name": "Edison Middle School",
|
||||||
|
"parent_rating": 4,
|
||||||
|
"rating": 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_code": "60189",
|
||||||
|
"latitude": 41.851345,
|
||||||
|
"longitude": -88.129822,
|
||||||
|
"school_type": "Public",
|
||||||
|
"enrollment": 507,
|
||||||
|
"grades": "PK-5",
|
||||||
|
"name": "Madison Elementary School",
|
||||||
|
"parent_rating": 4,
|
||||||
|
"rating": 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"city": "Wheaton",
|
||||||
|
"state": "IL",
|
||||||
|
"zip_code": "60189",
|
||||||
|
"latitude": 41.857048,
|
||||||
|
"longitude": -88.108467,
|
||||||
|
"school_type": "Public",
|
||||||
|
"enrollment": 459,
|
||||||
|
"grades": "K-5",
|
||||||
|
"name": "Whittier Elementary School",
|
||||||
|
"parent_rating": 5,
|
||||||
|
"rating": 4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"tax_info": {
|
||||||
|
"assessed_value": 202255,
|
||||||
|
"assessment_year": 2024,
|
||||||
|
"tax_amount": 12520.12,
|
||||||
|
"year": 2024,
|
||||||
|
},
|
||||||
|
"sale_info": [
|
||||||
|
{
|
||||||
|
"seq_no": 1,
|
||||||
|
"sale_date": "2025-04-22T00:00:00.000Z",
|
||||||
|
"sale_amount": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq_no": 2,
|
||||||
|
"sale_date": "2020-06-26T00:00:00.000Z",
|
||||||
|
"sale_amount": 475000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"owner": 5,
|
||||||
|
"created_at": "2025-08-04",
|
||||||
|
"last_updated": "2025-08-04",
|
||||||
|
"open_houses": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_create_property(self):
|
def test_create_property(self):
|
||||||
serializer = PropertySerializer(data=self.property_data)
|
|
||||||
self.assertTrue(serializer.is_valid())
|
serializer = PropertyRequestSerializer(data=self.property_data)
|
||||||
|
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||||
property = serializer.save()
|
property = serializer.save()
|
||||||
self.assertEqual(property.owner, self.owner)
|
self.assertEqual(property.owner, self.owner)
|
||||||
self.assertEqual(property.address, '123 Main St')
|
self.assertEqual(property.address, "123 Main St")
|
||||||
self.assertEqual(property.city, 'Anytown')
|
self.assertEqual(property.city, "Anytown")
|
||||||
self.assertEqual(property.state, 'CA')
|
self.assertEqual(property.state, "CA")
|
||||||
self.assertEqual(property.zip_code, '90210')
|
self.assertEqual(property.zip_code, "90210")
|
||||||
self.assertEqual(str(property.market_value), '500000.00')
|
self.assertEqual(str(property.market_value), "500000.00")
|
||||||
self.assertEqual(str(property.loan_amount), '400000.00')
|
self.assertEqual(str(property.loan_amount), "400000.00")
|
||||||
self.assertEqual(str(property.loan_interest_rate), '3.50')
|
self.assertEqual(str(property.loan_interest_rate), "3.50")
|
||||||
self.assertEqual(property.loan_term, 30)
|
self.assertEqual(property.loan_term, 30)
|
||||||
self.assertEqual(str(property.loan_start_date), '2020-01-01')
|
self.assertEqual(str(property.loan_start_date), "2020-01-01")
|
||||||
|
|
||||||
|
def test_create_full_property(self):
|
||||||
|
serializer = PropertyRequestSerializer(data=self.full_property_data)
|
||||||
|
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||||
|
property = serializer.save()
|
||||||
|
|
||||||
def test_update_property(self):
|
def test_update_property(self):
|
||||||
property = Property.objects.create(
|
property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
'address': '456 New St',
|
"address": "456 New St",
|
||||||
'city': 'Newtown',
|
"city": "Newtown",
|
||||||
'state': 'NY',
|
"state": "NY",
|
||||||
'zip_code': '10001',
|
"zip_code": "10001",
|
||||||
'market_value': '600000.00'
|
"market_value": "600000.00",
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer = PropertySerializer(instance=property, data=update_data, partial=True)
|
serializer = PropertyRequestSerializer(
|
||||||
|
instance=property, data=update_data, partial=True
|
||||||
|
)
|
||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid())
|
||||||
updated_property = serializer.save()
|
updated_property = serializer.save()
|
||||||
|
|
||||||
self.assertEqual(updated_property.address, '456 New St')
|
self.assertEqual(updated_property.address, "456 New St")
|
||||||
self.assertEqual(updated_property.city, 'Newtown')
|
self.assertEqual(updated_property.city, "Newtown")
|
||||||
self.assertEqual(updated_property.state, 'NY')
|
self.assertEqual(updated_property.state, "NY")
|
||||||
self.assertEqual(updated_property.zip_code, '10001')
|
self.assertEqual(updated_property.zip_code, "10001")
|
||||||
self.assertEqual(str(updated_property.market_value), '600000.00')
|
self.assertEqual(str(updated_property.market_value), "600000.00")
|
||||||
|
|
||||||
|
|
||||||
class VideoSerializerTest(TestCase):
|
class VideoSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.category = VideoCategory.objects.create(
|
self.category = VideoCategory.objects.create(
|
||||||
name='Test Category',
|
name="Test Category", description="Test Description"
|
||||||
description='Test Description'
|
|
||||||
)
|
)
|
||||||
self.video_data = {
|
self.video_data = {
|
||||||
'category': {
|
"category": {
|
||||||
'id': self.category.id,
|
"id": self.category.id,
|
||||||
'name': self.category.name,
|
"name": self.category.name,
|
||||||
'description': self.category.description
|
"description": self.category.description,
|
||||||
},
|
},
|
||||||
'title': 'Test Video',
|
"title": "Test Video",
|
||||||
'description': 'Test Video Description',
|
"description": "Test Video Description",
|
||||||
'link': 'https://example.com/video',
|
"link": "https://example.com/video",
|
||||||
'duration': 300
|
"duration": 300,
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_video_serializer(self):
|
def test_video_serializer(self):
|
||||||
@@ -239,40 +368,38 @@ class VideoSerializerTest(TestCase):
|
|||||||
self.assertTrue(serializer.is_valid())
|
self.assertTrue(serializer.is_valid())
|
||||||
video = serializer.save()
|
video = serializer.save()
|
||||||
self.assertEqual(video.category, self.category)
|
self.assertEqual(video.category, self.category)
|
||||||
self.assertEqual(video.title, 'Test Video')
|
self.assertEqual(video.title, "Test Video")
|
||||||
self.assertEqual(video.description, 'Test Video Description')
|
self.assertEqual(video.description, "Test Video Description")
|
||||||
self.assertEqual(video.link, 'https://example.com/video')
|
self.assertEqual(video.link, "https://example.com/video")
|
||||||
self.assertEqual(video.duration, 300)
|
self.assertEqual(video.duration, 300)
|
||||||
|
|
||||||
|
|
||||||
class UserVideoProgressSerializerTest(TestCase):
|
class UserVideoProgressSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='user@example.com',
|
email="user@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.category = VideoCategory.objects.create(name='Test Category')
|
self.category = VideoCategory.objects.create(name="Test Category")
|
||||||
self.video = Video.objects.create(
|
self.video = Video.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
title='Test Video',
|
title="Test Video",
|
||||||
link='https://example.com/video',
|
link="https://example.com/video",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
)
|
||||||
self.progress_data = {
|
self.progress_data = {
|
||||||
'video': {
|
"video": {
|
||||||
'id': self.video.id,
|
"id": self.video.id,
|
||||||
'title': self.video.title,
|
"title": self.video.title,
|
||||||
'link': self.video.link,
|
"link": self.video.link,
|
||||||
'duration': self.video.duration,
|
"duration": self.video.duration,
|
||||||
'category': {
|
"category": {"id": self.category.id, "name": self.category.name},
|
||||||
'id': self.category.id,
|
|
||||||
'name': self.category.name
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'user': self.user.pk,
|
"user": self.user.pk,
|
||||||
'progress': 150
|
"progress": 150,
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_progress_serializer(self):
|
def test_progress_serializer(self):
|
||||||
@@ -284,185 +411,191 @@ class UserVideoProgressSerializerTest(TestCase):
|
|||||||
self.assertEqual(progress.video.title, self.video.title)
|
self.assertEqual(progress.video.title, self.video.title)
|
||||||
self.assertEqual(progress.video.category.name, self.video.category.name)
|
self.assertEqual(progress.video.category.name, self.video.category.name)
|
||||||
self.assertEqual(progress.progress, 150)
|
self.assertEqual(progress.progress, 150)
|
||||||
self.assertEqual(progress.status, 'in_progress')
|
self.assertEqual(progress.status, "in_progress")
|
||||||
|
|
||||||
def test_many_progress_serializer(self):
|
def test_many_progress_serializer(self):
|
||||||
categories = [VideoCategory.objects.create(name='Category One'),
|
categories = [
|
||||||
VideoCategory.objects.create(name='Category Two')]
|
VideoCategory.objects.create(name="Category One"),
|
||||||
|
VideoCategory.objects.create(name="Category Two"),
|
||||||
|
]
|
||||||
|
|
||||||
videos = [
|
videos = [
|
||||||
Video.objects.create(
|
Video.objects.create(
|
||||||
category=categories[0],
|
category=categories[0],
|
||||||
title='Test Video 1',
|
title="Test Video 1",
|
||||||
link='https://example.com/video1',
|
link="https://example.com/video1",
|
||||||
duration=300
|
duration=300,
|
||||||
),
|
),
|
||||||
Video.objects.create(
|
Video.objects.create(
|
||||||
category=categories[0],
|
category=categories[0],
|
||||||
title='Test Video 2',
|
title="Test Video 2",
|
||||||
link='https://example.com/video2',
|
link="https://example.com/video2",
|
||||||
duration=300
|
duration=300,
|
||||||
),
|
),
|
||||||
Video.objects.create(
|
Video.objects.create(
|
||||||
category=categories[0],
|
category=categories[0],
|
||||||
title='Test Video 3',
|
title="Test Video 3",
|
||||||
link='https://example.com/video3',
|
link="https://example.com/video3",
|
||||||
duration=300
|
duration=300,
|
||||||
),
|
),
|
||||||
Video.objects.create(
|
Video.objects.create(
|
||||||
category=categories[1],
|
category=categories[1],
|
||||||
title='Test Video 4',
|
title="Test Video 4",
|
||||||
link='https://example.com/video4',
|
link="https://example.com/video4",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
progress_data = [
|
progress_data = [
|
||||||
UserVideoProgress.objects.create(
|
UserVideoProgress.objects.create(
|
||||||
video=videos[0],
|
video=videos[0], user=self.user, progress=100
|
||||||
user=self.user,
|
|
||||||
progress=100
|
|
||||||
),
|
),
|
||||||
UserVideoProgress.objects.create(
|
UserVideoProgress.objects.create(
|
||||||
video=videos[1],
|
video=videos[1], user=self.user, progress=30
|
||||||
user=self.user,
|
|
||||||
progress=30
|
|
||||||
),
|
),
|
||||||
UserVideoProgress.objects.create(
|
UserVideoProgress.objects.create(
|
||||||
video=videos[2],
|
video=videos[2], user=self.user, progress=0
|
||||||
user=self.user,
|
|
||||||
progress=0
|
|
||||||
),
|
),
|
||||||
UserVideoProgress.objects.create(
|
UserVideoProgress.objects.create(
|
||||||
video=videos[3],
|
video=videos[3], user=self.user, progress=0
|
||||||
user=self.user,
|
|
||||||
progress=0
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
serializer = UserVideoProgressSerializer(UserVideoProgress.objects.filter(user=self.user), many=True)
|
serializer = UserVideoProgressSerializer(
|
||||||
|
UserVideoProgress.objects.filter(user=self.user), many=True
|
||||||
|
)
|
||||||
self.assertEqual(len(serializer.data), len(progress_data))
|
self.assertEqual(len(serializer.data), len(progress_data))
|
||||||
for i in range(len(serializer.data)):
|
for i in range(len(serializer.data)):
|
||||||
self.assertEqual(serializer.data[i]['video']['title'], progress_data[i].video.title)
|
self.assertEqual(
|
||||||
self.assertEqual(serializer.data[i]['video']['description'], progress_data[i].video.description)
|
serializer.data[i]["video"]["title"], progress_data[i].video.title
|
||||||
self.assertEqual(serializer.data[i]['video']['link'], progress_data[i].video.link)
|
)
|
||||||
self.assertEqual(serializer.data[i]['video']['duration'], progress_data[i].video.duration)
|
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):
|
class ConversationSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.owner_user = User.objects.create_user(
|
self.owner_user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
||||||
|
|
||||||
self.vendor_user = User.objects.create_user(
|
self.vendor_user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.vendor_user,
|
user=self.vendor_user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor'
|
business_type="contractor",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.conversation_data = {
|
self.conversation_data = {
|
||||||
'property_owner': self.owner.pk,
|
"property_owner": self.owner.pk,
|
||||||
'vendor': self.vendor.pk,
|
"vendor": self.vendor.pk,
|
||||||
'property': self.property.pk
|
"property": self.property.pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_conversation_serializer(self):
|
def test_conversation_serializer(self):
|
||||||
serializer = ConversationSerializer(data=self.conversation_data)
|
serializer = ConversationRequestSerializer(data=self.conversation_data)
|
||||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||||
conversation = serializer.save()
|
conversation = serializer.save()
|
||||||
self.assertEqual(conversation.property_owner, self.owner)
|
self.assertEqual(conversation.property_owner, self.owner)
|
||||||
self.assertEqual(conversation.vendor, self.vendor)
|
self.assertEqual(conversation.vendor, self.vendor)
|
||||||
self.assertEqual(conversation.property, self.property)
|
self.assertEqual(conversation.property, self.property)
|
||||||
|
|
||||||
|
|
||||||
class MessageSerializerTest(TestCase):
|
class MessageSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.owner_user = User.objects.create_user(
|
self.owner_user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
||||||
|
|
||||||
self.vendor_user = User.objects.create_user(
|
self.vendor_user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.vendor_user,
|
user=self.vendor_user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor'
|
business_type="contractor",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.conversation = Conversation.objects.create(
|
self.conversation = Conversation.objects.create(
|
||||||
property_owner=self.owner,
|
property_owner=self.owner, vendor=self.vendor, property=self.property
|
||||||
vendor=self.vendor,
|
|
||||||
property=self.property
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.message_data = {
|
self.message_data = {"text": "Test message"}
|
||||||
'text': 'Test message'
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_message_serializer(self):
|
def test_message_serializer(self):
|
||||||
|
|
||||||
context = {'sender': self.owner_user.pk, 'conversation': self.conversation.pk, 'text': 'Test message'}
|
context = {
|
||||||
|
"sender": self.owner_user.pk,
|
||||||
|
"conversation": self.conversation.pk,
|
||||||
|
"text": "Test message",
|
||||||
|
}
|
||||||
serializer = MessageSerializer(data=context)
|
serializer = MessageSerializer(data=context)
|
||||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||||
|
|
||||||
message = serializer.save()
|
message = serializer.save()
|
||||||
self.assertEqual(message.conversation, self.conversation)
|
self.assertEqual(message.conversation, self.conversation)
|
||||||
self.assertEqual(message.sender, self.owner_user)
|
self.assertEqual(message.sender, self.owner_user)
|
||||||
self.assertEqual(message.text, 'Test message')
|
self.assertEqual(message.text, "Test message")
|
||||||
self.assertFalse(message.read)
|
self.assertFalse(message.read)
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetRequestSerializerTest(TestCase):
|
class PasswordResetRequestSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='user@example.com',
|
email="user@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.valid_data = {'email': 'user@example.com'}
|
self.valid_data = {"email": "user@example.com"}
|
||||||
self.invalid_data = {'email': 'nonexistent@example.com'}
|
self.invalid_data = {"email": "nonexistent@example.com"}
|
||||||
|
|
||||||
def test_valid_email(self):
|
def test_valid_email(self):
|
||||||
serializer = PasswordResetRequestSerializer(data=self.valid_data)
|
serializer = PasswordResetRequestSerializer(data=self.valid_data)
|
||||||
@@ -473,32 +606,33 @@ class PasswordResetRequestSerializerTest(TestCase):
|
|||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetConfirmSerializerTest(TestCase):
|
class PasswordResetConfirmSerializerTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='user@example.com',
|
email="user@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.token = uuid.uuid4()
|
self.token = uuid.uuid4()
|
||||||
self.valid_data = {
|
self.valid_data = {
|
||||||
'token': self.token,
|
"token": self.token,
|
||||||
'new_password': 'newpass123',
|
"new_password": "newpass123",
|
||||||
'new_password2': 'newpass123'
|
"new_password2": "newpass123",
|
||||||
}
|
}
|
||||||
self.mismatch_data = {
|
self.mismatch_data = {
|
||||||
'token': self.token,
|
"token": self.token,
|
||||||
'new_password': 'newpass123',
|
"new_password": "newpass123",
|
||||||
'new_password2': 'differentpass'
|
"new_password2": "differentpass",
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_valid_password_reset(self):
|
def test_valid_password_reset(self):
|
||||||
PasswordResetToken.objects.create(
|
PasswordResetToken.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
token=self.token,
|
token=self.token,
|
||||||
expires_at=datetime.now() + timedelta(hours=24)
|
expires_at=datetime.now() + timedelta(hours=24),
|
||||||
)
|
)
|
||||||
serializer = PasswordResetConfirmSerializer(data=self.valid_data)
|
serializer = PasswordResetConfirmSerializer(data=self.valid_data)
|
||||||
|
|
||||||
@@ -509,3 +643,69 @@ class PasswordResetConfirmSerializerTest(TestCase):
|
|||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
|
||||||
|
class OfferSerializerTest(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.property = Property.objects.create(
|
||||||
|
owner=self.owner,
|
||||||
|
address="78 Midgewood",
|
||||||
|
city="Boardman",
|
||||||
|
state="OH",
|
||||||
|
zip_code="44512",
|
||||||
|
market_value=500000.00,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.owner_user_2 = User.objects.create_user(
|
||||||
|
email="owner2@example.com",
|
||||||
|
first_name="Property",
|
||||||
|
last_name="Owner",
|
||||||
|
user_type="property_owner",
|
||||||
|
password="testpass123",
|
||||||
|
)
|
||||||
|
self.owner_2 = PropertyOwner.objects.create(user=self.owner_user_2)
|
||||||
|
self.property_2 = Property.objects.create(
|
||||||
|
owner=self.owner_2,
|
||||||
|
address="1968 Greensboro Dr",
|
||||||
|
city="wheaton",
|
||||||
|
state="IL",
|
||||||
|
zip_code="60189",
|
||||||
|
market_value=500000.00,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.offer_user = User.objects.create_user(
|
||||||
|
email="vendor@example.com",
|
||||||
|
first_name="Vendor",
|
||||||
|
last_name="User",
|
||||||
|
user_type="vendor",
|
||||||
|
password="testpass123",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_offer_serializer(self):
|
||||||
|
context = {"user": self.offer_user.pk, "property": self.property.pk}
|
||||||
|
serializer = OfferRequestSerializer(data=context)
|
||||||
|
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||||
|
|
||||||
|
offer = serializer.save()
|
||||||
|
self.assertEqual(offer.user, self.offer_user)
|
||||||
|
self.assertEqual(offer.status, "draft")
|
||||||
|
self.assertEqual(offer.property, self.property)
|
||||||
|
|
||||||
|
def test_response_offer_serializer(self):
|
||||||
|
offer = Offer.objects.create(
|
||||||
|
user=self.offer_user,
|
||||||
|
property=self.property,
|
||||||
|
)
|
||||||
|
serializer = OfferResponseSerializer(offer)
|
||||||
|
|
||||||
|
self.assertTrue(serializer.data["status"] == "draft", serializer.data)
|
||||||
|
self.assertTrue(serializer.data["is_active"], serializer.data)
|
||||||
|
self.assertTrue(serializer.data["user"], self.offer_user.pk)
|
||||||
|
self.assertTrue(serializer.data["property"], self.property.pk)
|
||||||
|
|||||||
@@ -4,150 +4,147 @@ from rest_framework.test import APIClient
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from core.models import (
|
from core.models import (
|
||||||
PropertyOwner, Vendor, Property, VideoCategory, Video,
|
PropertyOwner,
|
||||||
UserVideoProgress, Conversation, Message, PasswordResetToken
|
Vendor,
|
||||||
|
Property,
|
||||||
|
VideoCategory,
|
||||||
|
Video,
|
||||||
|
UserVideoProgress,
|
||||||
|
Conversation,
|
||||||
|
Message,
|
||||||
|
PasswordResetToken,
|
||||||
|
Offer,
|
||||||
)
|
)
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationTests(TestCase):
|
class AuthenticationTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='test@example.com',
|
email="test@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.login_url = reverse('token_obtain_pair')
|
self.login_url = reverse("token_obtain_pair")
|
||||||
self.refresh_url = reverse('token_refresh')
|
self.refresh_url = reverse("token_refresh")
|
||||||
self.logout_url = reverse('logout')
|
self.logout_url = reverse("logout")
|
||||||
self.register_url = reverse('register')
|
self.register_url = reverse("register")
|
||||||
self.password_reset_url = reverse('password_reset')
|
self.password_reset_url = reverse("password_reset")
|
||||||
self.password_reset_confirm_url = reverse('password_reset_confirm')
|
self.password_reset_confirm_url = reverse("password_reset_confirm")
|
||||||
|
|
||||||
def test_user_login(self):
|
def test_user_login(self):
|
||||||
data = {
|
data = {"email": "test@example.com", "password": "testpass123"}
|
||||||
'email': 'test@example.com',
|
response = self.client.post(self.login_url, data, format="json")
|
||||||
'password': 'testpass123'
|
|
||||||
}
|
|
||||||
response = self.client.post(self.login_url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertIn('access', response.data)
|
self.assertIn("access", response.data)
|
||||||
self.assertIn('refresh', response.data)
|
self.assertIn("refresh", response.data)
|
||||||
self.assertIn('user', response.data)
|
self.assertIn("user", response.data)
|
||||||
|
|
||||||
def test_user_login_invalid_credentials(self):
|
def test_user_login_invalid_credentials(self):
|
||||||
data = {
|
data = {"email": "test@example.com", "password": "wrongpassword"}
|
||||||
'email': 'test@example.com',
|
response = self.client.post(self.login_url, data, format="json")
|
||||||
'password': 'wrongpassword'
|
|
||||||
}
|
|
||||||
response = self.client.post(self.login_url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
def test_token_refresh(self):
|
def test_token_refresh(self):
|
||||||
# First login to get refresh token
|
# First login to get refresh token
|
||||||
login_data = {
|
login_data = {"email": "test@example.com", "password": "testpass123"}
|
||||||
'email': 'test@example.com',
|
login_response = self.client.post(self.login_url, login_data, format="json")
|
||||||
'password': 'testpass123'
|
refresh_token = login_response.data["refresh"]
|
||||||
}
|
|
||||||
login_response = self.client.post(self.login_url, login_data, format='json')
|
|
||||||
refresh_token = login_response.data['refresh']
|
|
||||||
|
|
||||||
# Now refresh the token
|
# Now refresh the token
|
||||||
refresh_data = {
|
refresh_data = {"refresh": refresh_token}
|
||||||
'refresh': refresh_token
|
response = self.client.post(self.refresh_url, refresh_data, format="json")
|
||||||
}
|
|
||||||
response = self.client.post(self.refresh_url, refresh_data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertIn('access', response.data)
|
self.assertIn("access", response.data)
|
||||||
|
|
||||||
def test_user_logout(self):
|
def test_user_logout(self):
|
||||||
# First login to get tokens
|
# First login to get tokens
|
||||||
login_data = {
|
login_data = {"email": "test@example.com", "password": "testpass123"}
|
||||||
'email': 'test@example.com',
|
login_response = self.client.post(self.login_url, login_data, format="json")
|
||||||
'password': 'testpass123'
|
self.assertEqual(
|
||||||
}
|
login_response.status_code, status.HTTP_200_OK, login_response.text
|
||||||
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']
|
refresh_token = login_response.data["refresh"]
|
||||||
|
|
||||||
# Now logout
|
# Now logout
|
||||||
logout_data = {
|
logout_data = {"refresh_token": refresh_token}
|
||||||
'refresh_token': refresh_token
|
response = self.client.post(self.logout_url, logout_data, format="json")
|
||||||
}
|
self.assertEqual(
|
||||||
response = self.client.post(self.logout_url, logout_data, format='json')
|
response.status_code, status.HTTP_205_RESET_CONTENT, response.text
|
||||||
self.assertEqual(response.status_code, status.HTTP_205_RESET_CONTENT, response.text)
|
)
|
||||||
|
|
||||||
def test_user_registration(self):
|
def test_user_registration(self):
|
||||||
data = {
|
data = {
|
||||||
'email': 'newuser@example.com',
|
"email": "newuser@example.com",
|
||||||
'first_name': 'New',
|
"first_name": "New",
|
||||||
'last_name': 'User',
|
"last_name": "User",
|
||||||
'user_type': 'vendor',
|
"user_type": "vendor",
|
||||||
'password': 'newpass123',
|
"password": "newpass123",
|
||||||
'password2': 'newpass123'
|
"password2": "newpass123",
|
||||||
}
|
}
|
||||||
response = self.client.post(self.register_url, data, format='json')
|
response = self.client.post(self.register_url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertTrue(User.objects.filter(email='newuser@example.com').exists())
|
self.assertTrue(User.objects.filter(email="newuser@example.com").exists())
|
||||||
|
|
||||||
def test_password_reset_request(self):
|
def test_password_reset_request(self):
|
||||||
data = {
|
data = {"email": "test@example.com"}
|
||||||
'email': 'test@example.com'
|
response = self.client.post(self.password_reset_url, data, format="json")
|
||||||
}
|
|
||||||
response = self.client.post(self.password_reset_url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertTrue(PasswordResetToken.objects.filter(user=self.user).exists())
|
self.assertTrue(PasswordResetToken.objects.filter(user=self.user).exists())
|
||||||
|
|
||||||
def test_password_reset_confirm(self):
|
def test_password_reset_confirm(self):
|
||||||
# First create a reset token
|
# First create a reset token
|
||||||
expires_at = timezone.now() + timedelta(hours=24)
|
expires_at = timezone.now() + timedelta(hours=24)
|
||||||
token = PasswordResetToken.objects.create(
|
token = PasswordResetToken.objects.create(user=self.user, expires_at=expires_at)
|
||||||
user=self.user,
|
|
||||||
expires_at=expires_at
|
|
||||||
)
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'token': str(token.token),
|
"token": str(token.token),
|
||||||
'new_password': 'newpass123',
|
"new_password": "newpass123",
|
||||||
'new_password2': 'newpass123'
|
"new_password2": "newpass123",
|
||||||
}
|
}
|
||||||
response = self.client.post(self.password_reset_confirm_url, data, format='json')
|
response = self.client.post(
|
||||||
|
self.password_reset_confirm_url, data, format="json"
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
# Verify password was changed
|
# Verify password was changed
|
||||||
self.user.refresh_from_db()
|
self.user.refresh_from_db()
|
||||||
self.assertTrue(self.user.check_password('newpass123'))
|
self.assertTrue(self.user.check_password("newpass123"))
|
||||||
|
|
||||||
# Verify token was marked as used
|
# Verify token was marked as used
|
||||||
token.refresh_from_db()
|
token.refresh_from_db()
|
||||||
self.assertTrue(token.used)
|
self.assertTrue(token.used)
|
||||||
|
|
||||||
|
|
||||||
class PropertyOwnerViewTests(TestCase):
|
class PropertyOwnerViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
|
)
|
||||||
|
self.owner = PropertyOwner.objects.create(
|
||||||
|
user=self.user, phone_number="1234567890"
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.user, phone_number='1234567890')
|
|
||||||
self.other_user = User.objects.create_user(
|
self.other_user = User.objects.create_user(
|
||||||
email='other@example.com',
|
email="other@example.com",
|
||||||
first_name='Other',
|
first_name="Other",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.other_owner = PropertyOwner.objects.create(user=self.other_user)
|
self.other_owner = PropertyOwner.objects.create(user=self.other_user)
|
||||||
self.url = reverse('property-owner-list')
|
self.url = reverse("property-owner-list")
|
||||||
|
|
||||||
# Authenticate
|
# Authenticate
|
||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
@@ -159,57 +156,60 @@ class PropertyOwnerViewTests(TestCase):
|
|||||||
|
|
||||||
def test_create_property_owner(self):
|
def test_create_property_owner(self):
|
||||||
data = {
|
data = {
|
||||||
'user': {
|
"user": {
|
||||||
'email': 'newowner@example.com',
|
"email": "newowner@example.com",
|
||||||
'first_name': 'New',
|
"first_name": "New",
|
||||||
'last_name': 'Owner',
|
"last_name": "Owner",
|
||||||
'user_type': 'property_owner',
|
"user_type": "property_owner",
|
||||||
'password': 'testpass123'
|
"password": "testpass123",
|
||||||
},
|
},
|
||||||
'phone_number': '9876543210'
|
"phone_number": "9876543210",
|
||||||
}
|
}
|
||||||
response = self.client.post(self.url, data, format='json')
|
response = self.client.post(self.url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
||||||
self.assertTrue(PropertyOwner.objects.filter(user__email='newowner@example.com').exists())
|
self.assertTrue(
|
||||||
|
PropertyOwner.objects.filter(user__email="newowner@example.com").exists()
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_property_owner(self):
|
def test_update_property_owner(self):
|
||||||
url = f"{self.url}{self.owner.pk}/"
|
url = f"{self.url}{self.owner.pk}/"
|
||||||
data = {
|
data = {
|
||||||
'user': {
|
"user": {
|
||||||
'first_name': 'Updated',
|
"first_name": "Updated",
|
||||||
'last_name': 'Owner',
|
"last_name": "Owner",
|
||||||
'email': 'new_email@email.com',
|
"email": "new_email@email.com",
|
||||||
'user_type': 'property_owner'
|
"user_type": "property_owner",
|
||||||
},
|
},
|
||||||
'phone_number': '9999999999'
|
"phone_number": "9999999999",
|
||||||
}
|
}
|
||||||
response = self.client.put(url, data, format='json')
|
response = self.client.put(url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
||||||
self.owner.refresh_from_db()
|
self.owner.refresh_from_db()
|
||||||
self.assertEqual(self.owner.user.first_name, 'Updated')
|
self.assertEqual(self.owner.user.first_name, "Updated")
|
||||||
self.assertEqual(self.owner.phone_number, '9999999999')
|
self.assertEqual(self.owner.phone_number, "9999999999")
|
||||||
|
|
||||||
|
|
||||||
class VendorViewTests(TestCase):
|
class VendorViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor',
|
business_type="contractor",
|
||||||
phone_number='1234567890',
|
phone_number="1234567890",
|
||||||
address='123 Test St',
|
address="123 Test St",
|
||||||
city='Testville',
|
city="Testville",
|
||||||
state='TS',
|
state="TS",
|
||||||
zip_code='12345'
|
zip_code="12345",
|
||||||
)
|
)
|
||||||
self.url = reverse('vendor-list')
|
self.url = reverse("vendor-list")
|
||||||
|
|
||||||
# Authenticate
|
# Authenticate
|
||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
@@ -221,62 +221,63 @@ class VendorViewTests(TestCase):
|
|||||||
|
|
||||||
def test_create_vendor(self):
|
def test_create_vendor(self):
|
||||||
data = {
|
data = {
|
||||||
'user': {
|
"user": {
|
||||||
'email': 'newvendor@example.com',
|
"email": "newvendor@example.com",
|
||||||
'first_name': 'New',
|
"first_name": "New",
|
||||||
'last_name': 'Vendor',
|
"last_name": "Vendor",
|
||||||
'user_type': 'vendor',
|
"user_type": "vendor",
|
||||||
'password': 'testpass123'
|
"password": "testpass123",
|
||||||
},
|
},
|
||||||
'business_name': 'New Vendor',
|
"business_name": "New Vendor",
|
||||||
'business_type': 'inspector',
|
"business_type": "inspector",
|
||||||
'phone_number': '9876543210',
|
"phone_number": "9876543210",
|
||||||
'address': '456 New St',
|
"address": "456 New St",
|
||||||
'city': 'Newtown',
|
"city": "Newtown",
|
||||||
'state': 'NS',
|
"state": "NS",
|
||||||
'zip_code': '54321'
|
"zip_code": "54321",
|
||||||
}
|
}
|
||||||
response = self.client.post(self.url, data, format='json')
|
response = self.client.post(self.url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertTrue(Vendor.objects.filter(business_name='New Vendor').exists())
|
self.assertTrue(Vendor.objects.filter(business_name="New Vendor").exists())
|
||||||
|
|
||||||
def test_update_vendor(self):
|
def test_update_vendor(self):
|
||||||
url = f"{self.url}{self.vendor.pk}/"
|
url = f"{self.url}{self.vendor.pk}/"
|
||||||
data = {
|
data = {
|
||||||
'user': {
|
"user": {
|
||||||
'first_name': 'Updated',
|
"first_name": "Updated",
|
||||||
'last_name': 'Vendor',
|
"last_name": "Vendor",
|
||||||
},
|
},
|
||||||
'business_name': 'Updated Vendor',
|
"business_name": "Updated Vendor",
|
||||||
'phone_number': '9999999999'
|
"phone_number": "9999999999",
|
||||||
}
|
}
|
||||||
response = self.client.patch(url, data, format='json')
|
response = self.client.patch(url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
||||||
self.vendor.refresh_from_db()
|
self.vendor.refresh_from_db()
|
||||||
self.assertEqual(self.vendor.user.first_name, 'Updated')
|
self.assertEqual(self.vendor.user.first_name, "Updated")
|
||||||
self.assertEqual(self.vendor.business_name, 'Updated Vendor')
|
self.assertEqual(self.vendor.business_name, "Updated Vendor")
|
||||||
self.assertEqual(self.vendor.phone_number, '9999999999')
|
self.assertEqual(self.vendor.phone_number, "9999999999")
|
||||||
|
|
||||||
|
|
||||||
class PropertyViewTests(TestCase):
|
class PropertyViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.owner_user = User.objects.create_user(
|
self.owner_user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
self.url = reverse('property-list')
|
self.url = reverse("property-list")
|
||||||
|
|
||||||
# Authenticate as property owner
|
# Authenticate as property owner
|
||||||
self.client.force_authenticate(user=self.owner_user)
|
self.client.force_authenticate(user=self.owner_user)
|
||||||
@@ -288,51 +289,48 @@ class PropertyViewTests(TestCase):
|
|||||||
|
|
||||||
def test_create_property(self):
|
def test_create_property(self):
|
||||||
data = {
|
data = {
|
||||||
'owner': self.owner.pk,
|
"owner": self.owner.pk,
|
||||||
'address': '456 New St',
|
"address": "456 New St",
|
||||||
'city': 'Newtown',
|
"city": "Newtown",
|
||||||
'state': 'NY',
|
"state": "NY",
|
||||||
'zip_code': '10001',
|
"zip_code": "10001",
|
||||||
'market_value': '600000.00'
|
"market_value": "600000.00",
|
||||||
}
|
}
|
||||||
response = self.client.post(self.url, data, format='json')
|
response = self.client.post(self.url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
||||||
self.assertTrue(Property.objects.filter(address='456 New St').exists())
|
self.assertTrue(Property.objects.filter(address="456 New St").exists())
|
||||||
|
|
||||||
def test_update_property(self):
|
def test_update_property(self):
|
||||||
url = f"{self.url}{self.property.pk}/"
|
url = f"{self.url}{self.property.pk}/"
|
||||||
data = {
|
data = {"address": "789 Updated St", "market_value": "550000.00"}
|
||||||
'address': '789 Updated St',
|
response = self.client.patch(url, data, format="json")
|
||||||
'market_value': '550000.00'
|
|
||||||
}
|
|
||||||
response = self.client.patch(url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.property.refresh_from_db()
|
self.property.refresh_from_db()
|
||||||
self.assertEqual(self.property.address, '789 Updated St')
|
self.assertEqual(self.property.address, "789 Updated St")
|
||||||
self.assertEqual(str(self.property.market_value), '550000.00')
|
self.assertEqual(str(self.property.market_value), "550000.00")
|
||||||
|
|
||||||
|
|
||||||
class VideoViewTests(TestCase):
|
class VideoViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='user@example.com',
|
email="user@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.category = VideoCategory.objects.create(
|
self.category = VideoCategory.objects.create(
|
||||||
name='Test Category',
|
name="Test Category", description="Test Description"
|
||||||
description='Test Description'
|
|
||||||
)
|
)
|
||||||
self.video = Video.objects.create(
|
self.video = Video.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
title='Test Video',
|
title="Test Video",
|
||||||
description='Test Video Description',
|
description="Test Video Description",
|
||||||
link='https://example.com/video',
|
link="https://example.com/video",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
)
|
||||||
self.url = reverse('video-list')
|
self.url = reverse("video-list")
|
||||||
|
|
||||||
# Authenticate
|
# Authenticate
|
||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
@@ -344,97 +342,83 @@ class VideoViewTests(TestCase):
|
|||||||
|
|
||||||
def test_create_video(self):
|
def test_create_video(self):
|
||||||
data = {
|
data = {
|
||||||
'category': {
|
"category": {"name": "New Category", "description": "a description"},
|
||||||
"name": "New Category",
|
"title": "New Video",
|
||||||
"description": "a description"
|
"description": "New Video Description",
|
||||||
},
|
"link": "https://example.com/new-video",
|
||||||
'title': 'New Video',
|
"duration": 240,
|
||||||
'description': 'New Video Description',
|
|
||||||
'link': 'https://example.com/new-video',
|
|
||||||
'duration': 240
|
|
||||||
}
|
}
|
||||||
response = self.client.post(self.url, data, format='json')
|
response = self.client.post(self.url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
||||||
self.assertTrue(Video.objects.filter(title='New Video').exists())
|
self.assertTrue(Video.objects.filter(title="New Video").exists())
|
||||||
|
|
||||||
def test_update_video(self):
|
def test_update_video(self):
|
||||||
url = f"{self.url}{self.video.pk}/"
|
url = f"{self.url}{self.video.pk}/"
|
||||||
data = {
|
data = {"title": "Updated Video", "duration": 360}
|
||||||
'title': 'Updated Video',
|
response = self.client.patch(url, data, format="json")
|
||||||
'duration': 360
|
|
||||||
}
|
|
||||||
response = self.client.patch(url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
||||||
self.video.refresh_from_db()
|
self.video.refresh_from_db()
|
||||||
self.assertEqual(self.video.title, 'Updated Video')
|
self.assertEqual(self.video.title, "Updated Video")
|
||||||
self.assertEqual(self.video.duration, 360)
|
self.assertEqual(self.video.duration, 360)
|
||||||
|
|
||||||
|
|
||||||
class UserVideoProgressViewTests(TestCase):
|
class UserVideoProgressViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
email='user@example.com',
|
email="user@example.com",
|
||||||
first_name='Test',
|
first_name="Test",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.category = VideoCategory.objects.create(name='Test Category')
|
self.category = VideoCategory.objects.create(name="Test Category")
|
||||||
self.video = Video.objects.create(
|
self.video = Video.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
title='Test Video',
|
title="Test Video",
|
||||||
link='https://example.com/video',
|
link="https://example.com/video",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
)
|
||||||
self.video_1 = Video.objects.create(
|
self.video_1 = Video.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
title='Test Video 1',
|
title="Test Video 1",
|
||||||
link='https://example.com/video_1',
|
link="https://example.com/video_1",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
)
|
||||||
self.video_2 = Video.objects.create(
|
self.video_2 = Video.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
title='Test Video 2',
|
title="Test Video 2",
|
||||||
link='https://example.com/video_2',
|
link="https://example.com/video_2",
|
||||||
duration=300
|
duration=300,
|
||||||
)
|
)
|
||||||
self.progress = UserVideoProgress.objects.create(
|
self.progress = UserVideoProgress.objects.create(
|
||||||
user=self.user,
|
user=self.user, video=self.video, progress=150
|
||||||
video=self.video,
|
|
||||||
progress=150
|
|
||||||
)
|
)
|
||||||
UserVideoProgress.objects.create(
|
UserVideoProgress.objects.create(
|
||||||
user=self.user,
|
user=self.user, video=self.video_1, progress=150
|
||||||
video=self.video_1,
|
|
||||||
progress=150
|
|
||||||
)
|
)
|
||||||
UserVideoProgress.objects.create(
|
UserVideoProgress.objects.create(
|
||||||
user=self.user,
|
user=self.user, video=self.video_2, progress=150
|
||||||
video=self.video_2,
|
|
||||||
progress=150
|
|
||||||
)
|
)
|
||||||
self.url = reverse('video-progress-list')
|
self.url = reverse("video-progress-list")
|
||||||
|
|
||||||
# Authenticate
|
# Authenticate
|
||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
def test_get_video_progress(self):
|
def test_get_video_progress(self):
|
||||||
response = self.client.get(f"{self.url}", kwargs={'format','json'})
|
response = self.client.get(self.url)
|
||||||
breakpoint()
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
||||||
breakpoint()
|
self.assertEqual(len(response.data), 3)
|
||||||
self.assertEqual(len(response.data), 1)
|
|
||||||
|
|
||||||
def test_update_video_progress(self):
|
def test_update_video_progress(self):
|
||||||
url = f"{self.url}{self.progress.pk}/"
|
url = f"{self.url}{self.progress.pk}/"
|
||||||
data = {
|
data = {"progress": 200}
|
||||||
'progress': 200
|
response = self.client.patch(url, data, format="json")
|
||||||
}
|
|
||||||
response = self.client.patch(url, data, format='json')
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.progress.refresh_from_db()
|
self.progress.refresh_from_db()
|
||||||
self.assertEqual(self.progress.progress, 200)
|
self.assertEqual(self.progress.progress, 200)
|
||||||
self.assertEqual(self.progress.status, 'in_progress')
|
self.assertEqual(self.progress.status, "in_progress")
|
||||||
|
|
||||||
|
|
||||||
class ConversationViewTests(TestCase):
|
class ConversationViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -442,46 +426,44 @@ class ConversationViewTests(TestCase):
|
|||||||
|
|
||||||
# Create owner user
|
# Create owner user
|
||||||
self.owner_user = User.objects.create_user(
|
self.owner_user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
||||||
|
|
||||||
# Create vendor user
|
# Create vendor user
|
||||||
self.vendor_user = User.objects.create_user(
|
self.vendor_user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.vendor_user,
|
user=self.vendor_user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor'
|
business_type="contractor",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create property
|
# Create property
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create conversation
|
# Create conversation
|
||||||
self.conversation = Conversation.objects.create(
|
self.conversation = Conversation.objects.create(
|
||||||
property_owner=self.owner,
|
property_owner=self.owner, vendor=self.vendor, property=self.property
|
||||||
vendor=self.vendor,
|
|
||||||
property=self.property
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.url = reverse('conversation-list')
|
self.url = reverse("conversation-list")
|
||||||
|
|
||||||
# Authenticate as owner
|
# Authenticate as owner
|
||||||
self.client.force_authenticate(user=self.owner_user)
|
self.client.force_authenticate(user=self.owner_user)
|
||||||
@@ -493,13 +475,27 @@ class ConversationViewTests(TestCase):
|
|||||||
|
|
||||||
def test_create_conversation(self):
|
def test_create_conversation(self):
|
||||||
data = {
|
data = {
|
||||||
'property_owner': self.owner.pk,
|
"property_owner": self.owner.pk,
|
||||||
'vendor': self.vendor.pk,
|
"vendor": self.vendor.pk,
|
||||||
'property': self.property.pk
|
"property": self.property.pk,
|
||||||
}
|
}
|
||||||
response = self.client.post(self.url, data, format='json')
|
response = self.client.post(self.url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
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())
|
self.assertTrue(
|
||||||
|
Conversation.objects.filter(
|
||||||
|
property_owner=self.owner, vendor=self.vendor, property=self.property
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_conversation_without_property(self):
|
||||||
|
data = {"property_owner": self.owner.pk, "vendor": self.vendor.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):
|
def test_get_conversations_as_vendor(self):
|
||||||
# Switch to vendor user
|
# Switch to vendor user
|
||||||
@@ -508,59 +504,58 @@ class ConversationViewTests(TestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(len(response.data), 1)
|
self.assertEqual(len(response.data), 1)
|
||||||
|
|
||||||
|
|
||||||
class MessageViewTests(TestCase):
|
class MessageViewTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
|
|
||||||
# Create owner user
|
# Create owner user
|
||||||
self.owner_user = User.objects.create_user(
|
self.owner_user = User.objects.create_user(
|
||||||
email='owner@example.com',
|
email="owner@example.com",
|
||||||
first_name='Property',
|
first_name="Property",
|
||||||
last_name='Owner',
|
last_name="Owner",
|
||||||
user_type='property_owner',
|
user_type="property_owner",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
self.owner = PropertyOwner.objects.create(user=self.owner_user)
|
||||||
|
|
||||||
# Create vendor user
|
# Create vendor user
|
||||||
self.vendor_user = User.objects.create_user(
|
self.vendor_user = User.objects.create_user(
|
||||||
email='vendor@example.com',
|
email="vendor@example.com",
|
||||||
first_name='Vendor',
|
first_name="Vendor",
|
||||||
last_name='User',
|
last_name="User",
|
||||||
user_type='vendor',
|
user_type="vendor",
|
||||||
password='testpass123'
|
password="testpass123",
|
||||||
)
|
)
|
||||||
self.vendor = Vendor.objects.create(
|
self.vendor = Vendor.objects.create(
|
||||||
user=self.vendor_user,
|
user=self.vendor_user,
|
||||||
business_name='Test Vendor',
|
business_name="Test Vendor",
|
||||||
business_type='contractor'
|
business_type="contractor",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create property
|
# Create property
|
||||||
self.property = Property.objects.create(
|
self.property = Property.objects.create(
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
address='123 Main St',
|
address="123 Main St",
|
||||||
city='Anytown',
|
city="Anytown",
|
||||||
state='CA',
|
state="CA",
|
||||||
zip_code='90210',
|
zip_code="90210",
|
||||||
market_value=500000.00
|
market_value=500000.00,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create conversation
|
# Create conversation
|
||||||
self.conversation = Conversation.objects.create(
|
self.conversation = Conversation.objects.create(
|
||||||
property_owner=self.owner,
|
property_owner=self.owner, vendor=self.vendor, property=self.property
|
||||||
vendor=self.vendor,
|
|
||||||
property=self.property
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create message
|
# Create message
|
||||||
self.message = Message.objects.create(
|
self.message = Message.objects.create(
|
||||||
conversation=self.conversation,
|
conversation=self.conversation, sender=self.owner_user, text="Test message"
|
||||||
sender=self.owner_user,
|
|
||||||
text='Test message'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.url = reverse('conversation-messages', kwargs={'conversation_id': self.conversation.pk})
|
self.url = reverse(
|
||||||
|
"conversation-messages", kwargs={"conversation_id": self.conversation.pk}
|
||||||
|
)
|
||||||
|
|
||||||
# Authenticate as owner
|
# Authenticate as owner
|
||||||
self.client.force_authenticate(user=self.owner_user)
|
self.client.force_authenticate(user=self.owner_user)
|
||||||
@@ -572,13 +567,13 @@ class MessageViewTests(TestCase):
|
|||||||
|
|
||||||
def test_create_message(self):
|
def test_create_message(self):
|
||||||
data = {
|
data = {
|
||||||
'sender': self.owner.pk,
|
"sender": self.owner.pk,
|
||||||
'text': 'New message',
|
"text": "New message",
|
||||||
'conversation': self.conversation.pk
|
"conversation": self.conversation.pk,
|
||||||
}
|
}
|
||||||
response = self.client.post(self.url, data, format='json')
|
response = self.client.post(self.url, data, format="json")
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
||||||
self.assertTrue(Message.objects.filter(text='New message').exists())
|
self.assertTrue(Message.objects.filter(text="New message").exists())
|
||||||
|
|
||||||
def test_get_messages_as_vendor(self):
|
def test_get_messages_as_vendor(self):
|
||||||
# Switch to vendor user
|
# Switch to vendor user
|
||||||
@@ -586,3 +581,136 @@ class MessageViewTests(TestCase):
|
|||||||
response = self.client.get(self.url)
|
response = self.client.get(self.url)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(len(response.data), 1)
|
self.assertEqual(len(response.data), 1)
|
||||||
|
|
||||||
|
|
||||||
|
class OfferViewTests(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 property
|
||||||
|
self.property = Property.objects.create(
|
||||||
|
owner=self.owner,
|
||||||
|
address="123 Main St",
|
||||||
|
city="Anytown",
|
||||||
|
state="CA",
|
||||||
|
zip_code="90210",
|
||||||
|
market_value=500000.00,
|
||||||
|
)
|
||||||
|
self.property_1 = Property.objects.create(
|
||||||
|
owner=self.owner,
|
||||||
|
address="654 Main Cir",
|
||||||
|
city="Something",
|
||||||
|
state="OH",
|
||||||
|
zip_code="44512",
|
||||||
|
market_value=100000.00,
|
||||||
|
)
|
||||||
|
self.owner_user_2 = User.objects.create_user(
|
||||||
|
email="owner2@example.com",
|
||||||
|
first_name="Property",
|
||||||
|
last_name="Owner",
|
||||||
|
user_type="property_owner",
|
||||||
|
password="testpass123",
|
||||||
|
)
|
||||||
|
self.owner_2 = PropertyOwner.objects.create(user=self.owner_user_2)
|
||||||
|
# Create property
|
||||||
|
self.property_2 = Property.objects.create(
|
||||||
|
owner=self.owner_2,
|
||||||
|
address="123 Main St",
|
||||||
|
city="Anytown",
|
||||||
|
state="CA",
|
||||||
|
zip_code="90210",
|
||||||
|
market_value=500000.00,
|
||||||
|
)
|
||||||
|
self.url = reverse("offer-list")
|
||||||
|
|
||||||
|
self.offer_user = User.objects.create_user(
|
||||||
|
email="offer@example.com",
|
||||||
|
first_name="Offer",
|
||||||
|
last_name="Owner",
|
||||||
|
user_type="property_owner",
|
||||||
|
password="testpass123",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.offer_1 = Offer.objects.create(
|
||||||
|
user=self.offer_user, property=self.property
|
||||||
|
)
|
||||||
|
self.offer_2 = Offer.objects.create(
|
||||||
|
user=self.owner_user, property=self.property, previous_offer=self.offer_1
|
||||||
|
)
|
||||||
|
self.offer_3 = Offer.objects.create(
|
||||||
|
user=self.offer_user, property=self.property_2
|
||||||
|
)
|
||||||
|
self.offer_4 = Offer.objects.create(
|
||||||
|
user=self.offer_user, property=self.property_1
|
||||||
|
)
|
||||||
|
self.offer_5 = Offer.objects.create(
|
||||||
|
user=self.owner_user, property=self.property_2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Authenticate as owner
|
||||||
|
self.client.force_authenticate(user=self.owner_user)
|
||||||
|
|
||||||
|
def test_get_offers(self):
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
# we aren't going to show the last one
|
||||||
|
self.assertEqual(len(response.data), 3)
|
||||||
|
|
||||||
|
def test_create_offer(self):
|
||||||
|
self.client.force_authenticate(user=self.owner_user_2)
|
||||||
|
data = {"user": self.owner_user.pk, "property": self.property_1.pk}
|
||||||
|
response = self.client.post(self.url, data, format="json")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
||||||
|
self.assertEqual(response.data["status"], "draft")
|
||||||
|
|
||||||
|
self.client.force_authenticate(user=self.owner_user_2)
|
||||||
|
data = {"user": self.owner_user_2.pk, "property": self.property_1.pk}
|
||||||
|
response = self.client.post(self.url, data, format="json")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.text)
|
||||||
|
|
||||||
|
def test_submit_offer(self):
|
||||||
|
self.client.force_authenticate(user=self.owner_user_2)
|
||||||
|
data = {
|
||||||
|
"user": self.owner_user.pk,
|
||||||
|
"property": self.property_2.pk,
|
||||||
|
"status": "submitted",
|
||||||
|
}
|
||||||
|
url = f"{self.url}{self.offer_5.pk}/"
|
||||||
|
response = self.client.put(url, data, format="json")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
||||||
|
self.assertEqual(response.data["status"], "submitted")
|
||||||
|
|
||||||
|
def test_accept_offer(self):
|
||||||
|
self.client.force_authenticate(user=self.owner_user_2)
|
||||||
|
data = {
|
||||||
|
"user": self.owner_user.pk,
|
||||||
|
"property": self.property_2.pk,
|
||||||
|
"status": "accepted",
|
||||||
|
}
|
||||||
|
url = f"{self.url}{self.offer_5.pk}/"
|
||||||
|
response = self.client.put(url, data, format="json")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
||||||
|
self.assertEqual(response.data["status"], "accepted")
|
||||||
|
|
||||||
|
def test_accept_offer(self):
|
||||||
|
self.client.force_authenticate(user=self.owner_user_2)
|
||||||
|
data = {
|
||||||
|
"user": self.owner_user.pk,
|
||||||
|
"property": self.property_2.pk,
|
||||||
|
"status": "rejected",
|
||||||
|
}
|
||||||
|
url = f"{self.url}{self.offer_5.pk}/"
|
||||||
|
response = self.client.put(url, data, format="json")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.text)
|
||||||
|
self.assertEqual(response.data["status"], "rejected")
|
||||||
|
|
||||||
|
# def test_counter_offer(self):
|
||||||
|
# raise NotImplementedError
|
||||||
|
|||||||
@@ -1,22 +1,72 @@
|
|||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.9.0
|
||||||
asgiref==3.9.0
|
asgiref==3.9.0
|
||||||
|
attrs==25.3.0
|
||||||
|
autobahn==24.4.2
|
||||||
|
Automat==25.4.16
|
||||||
|
black==25.1.0
|
||||||
|
certifi==2025.7.14
|
||||||
|
cffi==1.17.1
|
||||||
cfgv==3.4.0
|
cfgv==3.4.0
|
||||||
channels==4.2.2
|
channels==4.2.2
|
||||||
channels_redis==4.2.1
|
channels_redis==4.2.1
|
||||||
|
charset-normalizer==3.4.2
|
||||||
|
click==8.2.1
|
||||||
|
constantly==23.10.4
|
||||||
|
coverage==7.9.2
|
||||||
|
cryptography==45.0.6
|
||||||
|
daphne==4.2.1
|
||||||
distlib==0.3.9
|
distlib==0.3.9
|
||||||
Django==5.2.4
|
Django==5.2.4
|
||||||
|
django-cors-headers==4.7.0
|
||||||
django-filter==25.1
|
django-filter==25.1
|
||||||
djangorestframework==3.16.0
|
djangorestframework==3.16.0
|
||||||
djangorestframework_simplejwt==5.5.0
|
djangorestframework_simplejwt==5.5.0
|
||||||
filelock==3.18.0
|
filelock==3.18.0
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
|
hyperlink==21.0.0
|
||||||
identify==2.6.12
|
identify==2.6.12
|
||||||
|
idna==3.10
|
||||||
|
incremental==24.7.2
|
||||||
|
jsonpatch==1.33
|
||||||
|
jsonpointer==3.0.0
|
||||||
|
langchain-core==0.3.72
|
||||||
|
langchain-ollama==0.3.6
|
||||||
|
langsmith==0.4.9
|
||||||
msgpack==1.1.1
|
msgpack==1.1.1
|
||||||
|
mypy_extensions==1.1.0
|
||||||
nodeenv==1.9.1
|
nodeenv==1.9.1
|
||||||
|
ollama==0.5.1
|
||||||
|
orjson==3.11.1
|
||||||
|
packaging==25.0
|
||||||
|
pathspec==0.12.1
|
||||||
pillow==11.3.0
|
pillow==11.3.0
|
||||||
platformdirs==4.3.8
|
platformdirs==4.3.8
|
||||||
pre_commit==4.2.0
|
pre_commit==4.2.0
|
||||||
|
pyasn1==0.6.1
|
||||||
|
pyasn1_modules==0.4.2
|
||||||
|
pycparser==2.22
|
||||||
|
pydantic==2.11.7
|
||||||
|
pydantic_core==2.33.2
|
||||||
PyJWT==2.9.0
|
PyJWT==2.9.0
|
||||||
|
pyOpenSSL==25.1.0
|
||||||
python-decouple==3.8
|
python-decouple==3.8
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.2
|
||||||
redis==6.2.0
|
redis==6.2.0
|
||||||
|
requests==2.32.4
|
||||||
|
requests-toolbelt==1.0.0
|
||||||
|
service-identity==24.2.0
|
||||||
|
setuptools==80.9.0
|
||||||
|
sniffio==1.3.1
|
||||||
sqlparse==0.5.3
|
sqlparse==0.5.3
|
||||||
|
tenacity==9.1.2
|
||||||
|
Twisted==25.5.0
|
||||||
|
txaio==25.6.1
|
||||||
|
typing-inspection==0.4.1
|
||||||
|
typing_extensions==4.14.1
|
||||||
|
urllib3==2.5.0
|
||||||
virtualenv==20.31.2
|
virtualenv==20.31.2
|
||||||
|
zope.interface==7.2
|
||||||
|
zstandard==0.23.0
|
||||||
|
|||||||
Reference in New Issue
Block a user