diff --git a/api/accounts/views.py b/api/accounts/views.py index c88991d..83899ee 100644 --- a/api/accounts/views.py +++ b/api/accounts/views.py @@ -137,9 +137,12 @@ def activate(request, uidb64, token): @api_view(['GET']) @permission_classes([permissions.IsAuthenticated]) def check_login(request: Request): + print('😼', request.user) try: profile = UserProfile.objects.get(user=request.user) + except UserProfile.DoesNotExist: + print("missing") return Response({'details': 'not logged in'}, status=status.HTTP_403_FORBIDDEN) if profile.approved: return Response(UserProfileSerializer(profile).data) diff --git a/api/housecat/settings.py b/api/housecat/settings.py index bf6ad22..14d0bb2 100644 --- a/api/housecat/settings.py +++ b/api/housecat/settings.py @@ -25,7 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.environ.get('DEBUG', 'False').lower() in ['true', '1'] -ALLOWED_HOSTS = json.loads(os.environ.get('ALLOWED_HOSTS', '["www.housecatpgh.org", "localhost"]')) +ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] # sjson.loads(os.environ.get('ALLOWED_HOSTS', '["www.housecatpgh.org", "localhost", "127.0.0.1"]')) # Application definition INSTALLED_APPS = [ @@ -38,6 +38,7 @@ 'django.contrib.staticfiles', 'rest_framework', 'rest_framework_gis', + 'rest_framework.authtoken', 'housing_data', 'accounts' @@ -155,7 +156,9 @@ REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', + ], 'DEFAULT_RENDERER_CLASSES': ( diff --git a/api/housecat/urls.py b/api/housecat/urls.py index 012ad0f..d43ca43 100644 --- a/api/housecat/urls.py +++ b/api/housecat/urls.py @@ -17,13 +17,17 @@ from django.contrib import admin from django.urls import path, include from django.conf import settings - +from rest_framework.authtoken.views import obtain_auth_token urlpatterns = [ path('admin/', admin.site.urls), + path("auth/", include("django.contrib.auth.urls")), path('data/', include('housing_data.urls')), - path('accounts/', include('accounts.urls')) + + path('accounts/', include('accounts.urls')), + + path('api-token-auth/', obtain_auth_token) ] if settings.BASE_URL_PREFIX: diff --git a/api/housing_data/management/commands/connect_datastore.py b/api/housing_data/management/commands/connect_datastore.py index c424e5d..4065d44 100644 --- a/api/housing_data/management/commands/connect_datastore.py +++ b/api/housing_data/management/commands/connect_datastore.py @@ -115,7 +115,9 @@ def handle(self, *args, **options): "fcb7cd5d-71f6-4f38-bb78-a3002be47ed6", "a6b93b7b-e04e-42c9-96f9-ee788e4f0978", "afba564f-cc68-42f0-b37b-fc8d70e8ef47", - "ecdbf89a-bbe7-4e82-b902-d6b774758d3a" + "ecdbf89a-bbe7-4e82-b902-d6b774758d3a", + "0b6a109e-b1f1-4064-8f42-eeb5355dc9df", + "e82411f8-623d-4336-b714-ecdded80703d" ) FROM SERVER datastore INTO datastore; diff --git a/api/housing_data/migrations/0003_groupindex_parcelindex.py b/api/housing_data/migrations/0003_groupindex_parcelindex.py new file mode 100644 index 0000000..defa88d --- /dev/null +++ b/api/housing_data/migrations/0003_groupindex_parcelindex.py @@ -0,0 +1,37 @@ +# Generated by Django 5.1.4 on 2025-01-13 13:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('housing_data', '0002_fhlbfundingid_fhlbrentalprojects_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='GroupIndex', + fields=[ + ('id', models.IntegerField(db_column='_id', primary_key=True, serialize=False)), + ('house_cat_id', models.CharField(max_length=100)), + ('group_id', models.CharField(max_length=100)), + ], + options={ + 'db_table': '0b6a109e-b1f1-4064-8f42-eeb5355dc9df', + 'managed': False, + }, + ), + migrations.CreateModel( + name='ParcelIndex', + fields=[ + ('id', models.IntegerField(db_column='_id', primary_key=True, serialize=False)), + ('parcel_id', models.CharField(max_length=100)), + ('group_id', models.CharField(max_length=100)), + ], + options={ + 'db_table': 'e82411f8-623d-4336-b714-ecdded80703d', + 'managed': False, + }, + ), + ] diff --git a/api/housing_data/models.py b/api/housing_data/models.py index d92bcd8..2c9642b 100644 --- a/api/housing_data/models.py +++ b/api/housing_data/models.py @@ -5,8 +5,9 @@ from django.contrib.auth.models import User from django.contrib.gis.db import models from django.contrib.gis.geos import Point -from django.contrib.postgres.fields import ArrayField +from django.db import connections from django.db.models import QuerySet, Q +from django.db.models.fields import CharField from housecat.abstract_models import DatastoreDataset, Described from housing_data.housing_datasets import ( @@ -76,6 +77,24 @@ def get_fkeys(model: Type['LookupTable'], origin_id: int): TODAY = datetime.now().date() +class GroupIndex(DatastoreDataset): + house_cat_id = CharField(max_length=100) + group_id = CharField(max_length=100) + + class Meta: + managed = False + db_table = '0b6a109e-b1f1-4064-8f42-eeb5355dc9df' + + +class ParcelIndex(DatastoreDataset): + parcel_id = CharField(max_length=100) + group_id = CharField(max_length=100) + + class Meta: + managed = False + db_table = "e82411f8-623d-4336-b714-ecdded80703d" + + class ProjectIndex(DatastoreDataset): """ Index of projects. @@ -112,6 +131,14 @@ class Meta: managed = False db_table = '1885161c-65f3-4cb2-98aa-4402487ae888' + @property + def groups(self) -> list[str]: + return [g.group_id for g in GroupIndex.objects.filter(house_cat_id=self.house_cat_id)] + + @property + def parcels(self) -> list[str]: + return [p.parcel_id for p in ParcelIndex.objects.filter(group_id__in=self.groups)] + @property def subsidy_expiration_date(self): """ Returns the earliest expiration date found to be associated with project """ @@ -153,6 +180,55 @@ def reac_scores(self): return None + def has_parcel(self, parcel_ids: list[str]) -> bool: + """Checks if this project has a parcel in the set of parcels in `parcel_ids` """ + project_parcels = set(self.parcels) + search_parcels = set(parcel_ids) + result = project_parcels.intersection(search_parcels) + return bool(len(result)) + + @staticmethod + def filter_by_recent_sale(queryset: QuerySet['ProjectIndex'], months=6): + """ Finds projects that have had a parcel sell within the past `months` months """ + conn = connections['datastore'] + with conn.cursor() as cursor: + cursor.execute( + f"""SELECT "PARID" FROM "5bbe6c55-bce6-4edb-9d04-68edeb6bf7b1" WHERE "SALEDATE" > ('now'::timestamp - '{months} month'::interval)""") + parcels = [row[0] for row in cursor.fetchall()] + + groups = [p.group_id for p in ParcelIndex.objects.filter(parcel_id__in=parcels)] + housecat_ids = [g.house_cat_id for g in GroupIndex.objects.filter(group_id__in=groups)] + + return queryset.filter(house_cat_id__in=housecat_ids) + + @staticmethod + def filter_by_recent_foreclosure(queryset: QuerySet['ProjectIndex'], months=6): + """ Finds projects that have had a foreclosure within the past `months` months """ + conn = connections['datastore'] + with conn.cursor() as cursor: + cursor.execute( + f"""SELECT "pin" FROM "859bccfd-0e12-4161-a348-313d734f25fd" WHERE "filing_date" > ('now'::timestamp - '{months} month'::interval)""") + parcels = [row[0] for row in cursor.fetchall()] + + groups = [p.group_id for p in ParcelIndex.objects.filter(parcel_id__in=parcels)] + housecat_ids = [g.house_cat_id for g in GroupIndex.objects.filter(group_id__in=groups)] + + return queryset.filter(house_cat_id__in=housecat_ids) + + @staticmethod + def filter_by_recent_code_violation(queryset: QuerySet['ProjectIndex'], months=6): + """ Finds projects that have had a PLI violation within the past `months` months """ + conn = connections['datastore'] + with conn.cursor() as cursor: + cursor.execute( + f"""SELECT "parcel_id" FROM "70c06278-92c5-4040-ab28-17671866f81c" WHERE "investigation_date" > ('now'::timestamp - '{months} month'::interval)""") + parcels = [row[0] for row in cursor.fetchall()] + + groups = [p.group_id for p in ParcelIndex.objects.filter(parcel_id__in=parcels)] + housecat_ids = [g.house_cat_id for g in GroupIndex.objects.filter(group_id__in=groups)] + + return queryset.filter(house_cat_id__in=housecat_ids) + @staticmethod def filter_by_risk_level( queryset: QuerySet['ProjectIndex'], @@ -487,5 +563,3 @@ def fhlb_projects(self): def __str__(self): return f'{self.id}: {self.hud_property_name}' - - diff --git a/api/housing_data/serializers.py b/api/housing_data/serializers.py index 831e5d1..577d32e 100644 --- a/api/housing_data/serializers.py +++ b/api/housing_data/serializers.py @@ -502,7 +502,6 @@ class Meta: ) - class FHLBRentalProjectsSerializer(serializers.ModelSerializer): class Meta: model = FHLBRentalProjects @@ -554,7 +553,6 @@ class ProjectIndexSerializer(serializers.ModelSerializer): fhlb_properties = FHLBRentalPropertiesSerializer(many=True) fhlb_projects = FHLBRentalProjectsSerializer(many=True) - class Meta: model = ProjectIndex fields = ( @@ -601,7 +599,13 @@ class Meta: 'subsidy_info', 'fhlb_properties', - 'fhlb_projects' + 'fhlb_projects', + + 'groups', + 'parcels', + + "longitude", + "latitude", ) diff --git a/api/housing_data/views.py b/api/housing_data/views.py index d412af5..95c1e25 100644 --- a/api/housing_data/views.py +++ b/api/housing_data/views.py @@ -30,7 +30,10 @@ def get_filtered_project_indices(request: Request) -> QuerySet[ProjectIndex]: .all() # Match filter form items from app's map page - watchlist = request.query_params.get('watchlist') + recent_sale = request.query_params.get('recent-sale') + recent_foreclosure = request.query_params.get('recent-foreclosure') + recent_code_violation = request.query_params.get('recent-code-violation') + risk_level = request.query_params.get('risk-level') lihtc_compliance = request.query_params.get('lihtc-compliance') reac_score = request.query_params.get('reac-score') @@ -38,10 +41,16 @@ def get_filtered_project_indices(request: Request) -> QuerySet[ProjectIndex]: funding_category = request.query_params.get('funding-category') status = request.query_params.get('status') + print(recent_sale) + # run all the filters - if watchlist: - wl = Watchlist.objects.get(slug=watchlist) - queryset = ProjectIndex.objects.filter(property_id__in=wl.items) + if recent_sale: + queryset = ProjectIndex.filter_by_recent_sale(queryset, months=int(recent_sale)) + if recent_foreclosure: + queryset = ProjectIndex.filter_by_recent_foreclosure(queryset, months=int(recent_foreclosure)) + if recent_code_violation: + queryset = ProjectIndex.filter_by_recent_code_violation(queryset, months=int(recent_code_violation)) + if risk_level: queryset = ProjectIndex.filter_by_risk_level(queryset, lvl=risk_level) if lihtc_compliance: diff --git a/explorer/pages/index.tsx b/explorer/pages/index.tsx index f6613c6..e293f0d 100644 --- a/explorer/pages/index.tsx +++ b/explorer/pages/index.tsx @@ -34,14 +34,6 @@ function HousecatHome() { - {/**/} - {/* */} - {/**/}