diff --git a/backend/market/migrations/0005_sublet_true_latitude_sublet_true_longitude.py b/backend/market/migrations/0005_sublet_true_latitude_sublet_true_longitude.py new file mode 100644 index 0000000..c9f6a12 --- /dev/null +++ b/backend/market/migrations/0005_sublet_true_latitude_sublet_true_longitude.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.2 on 2026-02-13 23:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("market", "0004_rename_address_sublet_street_address_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="sublet", + name="latitude", + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name="sublet", + name="longitude", + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/backend/market/models.py b/backend/market/models.py index f0ad0c9..5c50ca3 100644 --- a/backend/market/models.py +++ b/backend/market/models.py @@ -1,3 +1,6 @@ +import hashlib +import math + from django.contrib.auth.models import AbstractUser from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator @@ -42,7 +45,6 @@ class Meta: def __str__(self): return f"Offer for {self.listing} made by {self.user}" - class Category(models.Model): name = models.CharField(max_length=100, unique=True) @@ -130,12 +132,42 @@ class Sublet(Listing): baths = models.PositiveIntegerField() start_date = models.DateField() end_date = models.DateField() + latitude = models.FloatField(null=True, blank=True) + longitude = models.FloatField(null=True, blank=True) def clean(self): super().clean() if self.start_date and self.end_date and self.start_date >= self.end_date: raise ValidationError({"end_date": "End date must be after start date"}) + def _calculate_approximate_location(self, latitude, longitude): + if latitude is None or longitude is None: + return None, None + + lat_str = f"{float(latitude):.9f}" + lon_str = f"{float(longitude):.9f}" + seed = hashlib.md5(f"{lat_str}{lon_str}".encode()).hexdigest() + + offset_factor = int(seed[:8], 16) / 0xFFFFFFFF + + offset_distance = 0.0005 + (offset_factor * 0.0013) + angle = offset_factor * 2 * math.pi + + lat_offset = offset_distance * math.sin(angle) + lon_offset = offset_distance * math.cos(angle) + + approx_lat = float(latitude) + lat_offset + approx_lon = float(longitude) + lon_offset + return approx_lat, approx_lon + + @property + def approximate_location(self): + if self.latitude is not None and self.longitude is not None: + approximate_location = self._calculate_approximate_location( + self.latitude, self.longitude) + return approximate_location + return None, None + def save(self, *args, **kwargs): self.full_clean() super().save(*args, **kwargs) diff --git a/backend/market/serializers.py b/backend/market/serializers.py index 93ab683..7230532 100644 --- a/backend/market/serializers.py +++ b/backend/market/serializers.py @@ -1,3 +1,4 @@ + from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError as ModelValidationError from profanity_check import predict @@ -98,10 +99,23 @@ def get_condition(self, obj): class SubletDataSerializer(ModelSerializer): + latitude = SerializerMethodField() + longitude = SerializerMethodField() + class Meta: model = Sublet - fields = ["street_address", "beds", "baths", "start_date", "end_date"] + fields = ["street_address", "beds", "baths", "start_date", "end_date", + "latitude", "longitude"] + def get_latitude(self, obj): + if obj.approximate_location is not None: + return float(obj.approximate_location[0]) + return None + + def get_longitude(self, obj): + if obj.approximate_location is not None: + return float(obj.approximate_location[1]) + return None # Unified serializer for all listing types (Items and Sublets); used for CRUD operations class ListingSerializer(ListingTypeMixin, ModelSerializer): @@ -265,12 +279,23 @@ def _create_item(self, validated_data, additional_data): def _create_sublet(self, validated_data, additional_data): tags = validated_data.pop("tags", None) + latitude = additional_data.get("latitude") + longitude = additional_data.get("longitude") + + + if latitude is not None: + latitude = float(latitude) + if longitude is not None: + longitude = float(longitude) + sublet = Sublet.objects.create( street_address=additional_data.get("street_address"), beds=additional_data.get("beds"), baths=additional_data.get("baths"), start_date=additional_data.get("start_date"), end_date=additional_data.get("end_date"), + latitude=latitude, + longitude=longitude, **validated_data, ) @@ -323,10 +348,16 @@ def _update_item(self, instance, additional_data): def _update_sublet(self, instance, additional_data): sublet = instance.sublet - sublet_fields = ["street_address", "beds", "baths", "start_date", "end_date"] - for field in sublet_fields: + str_fields = ["street_address", "beds", "baths", "start_date", "end_date"] + float_fields = ["latitude", "longitude"] + for field in str_fields: if field in additional_data: setattr(sublet, field, additional_data[field]) + + for field in float_fields: + if field in additional_data: + value = additional_data[field] + setattr(sublet, field, float(value) if value is not None else None) sublet.full_clean() sublet.save()