Skip to content

Commit

Permalink
📊 penndata typing errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ashleyzhang01 committed Nov 13, 2024
1 parent a92e767 commit 2bec944
Show file tree
Hide file tree
Showing 21 changed files with 483 additions and 380 deletions.
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ repos:
- django-stubs
- djangorestframework-stubs
- types-PyYAML
- types-redis
- types-pytz
files: ^backend/
exclude: ^backend/.*/migrations/.*$
args: [
--ignore-missing-imports,
--disallow-untyped-defs,
Expand Down
2 changes: 2 additions & 0 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ webdriver-manager = "*"
pre-commit = "*"
alt-profanity-check = "*"
inflection = "*"
types-redis = "*"
types-pytz = "*"

[requires]
python_version = "3.11"
240 changes: 141 additions & 99 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

47 changes: 21 additions & 26 deletions backend/dining/models.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
from django.db import models
from django.db.models import (
CharField,
DateField,
DateTimeField,
ForeignKey,
IntegerField,
ManyToManyField,
URLField,
)
from django.utils import timezone


class Venue(models.Model):
venue_id: IntegerField = models.IntegerField(primary_key=True)
name: CharField = models.CharField(max_length=255, null=True)
image_url: URLField = models.URLField()
venue_id: models.IntegerField = models.IntegerField(primary_key=True)
name: models.CharField = models.CharField(max_length=255, null=True)
image_url: models.URLField = models.URLField()

def __str__(self) -> str:
return f"{self.name}-{str(self.venue_id)}"


class DiningItem(models.Model):
item_id: IntegerField = models.IntegerField(primary_key=True)
name: CharField = models.CharField(max_length=255)
description: CharField = models.CharField(max_length=1000, blank=True)
ingredients: CharField = models.CharField(max_length=1000, blank=True) # comma separated list
allergens: CharField = models.CharField(max_length=1000, blank=True) # comma separated list
nutrition_info: CharField = models.CharField(max_length=1000, blank=True) # json string.
item_id: models.IntegerField = models.IntegerField(primary_key=True)
name: models.CharField = models.CharField(max_length=255)
description: models.CharField = models.CharField(max_length=1000, blank=True)
ingredients: models.CharField = models.CharField(
max_length=1000, blank=True
) # comma separated list
allergens: models.CharField = models.CharField(
max_length=1000, blank=True
) # comma separated list
nutrition_info: models.CharField = models.CharField(max_length=1000, blank=True) # json string.
# Technically, postgres supports json fields but that involves local postgres
# instead of sqlite AND we don't need to query on this field

Expand All @@ -35,16 +30,16 @@ def __str__(self) -> str:


class DiningStation(models.Model):
name: CharField = models.CharField(max_length=255)
items: ManyToManyField = models.ManyToManyField(DiningItem)
menu: ForeignKey = models.ForeignKey(
name: models.CharField = models.CharField(max_length=255)
items: models.ManyToManyField = models.ManyToManyField(DiningItem)
menu: models.ForeignKey = models.ForeignKey(
"DiningMenu", on_delete=models.CASCADE, related_name="stations"
)


class DiningMenu(models.Model):
venue: ForeignKey = models.ForeignKey(Venue, on_delete=models.CASCADE)
date: DateField = models.DateField(default=timezone.now)
start_time: DateTimeField = models.DateTimeField()
end_time: DateTimeField = models.DateTimeField()
service: CharField = models.CharField(max_length=255)
venue: models.ForeignKey = models.ForeignKey(Venue, on_delete=models.CASCADE)
date: models.DateField = models.DateField(default=timezone.now)
start_time: models.DateTimeField = models.DateTimeField()
end_time: models.DateTimeField = models.DateTimeField()
service: models.CharField = models.CharField(max_length=255)
36 changes: 24 additions & 12 deletions backend/laundry/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import calendar
import datetime
from typing import Any, cast

from django.core.cache import cache
from django.db.models import Q, QuerySet
from django.db.models import Manager, Q, QuerySet
from django.shortcuts import get_object_or_404
from django.utils import timezone
from requests.exceptions import HTTPError
Expand All @@ -16,6 +17,7 @@
from laundry.serializers import LaundryRoomSerializer
from pennmobile.analytics import Metric, record_analytics
from utils.cache import Cache
from utils.types import get_user


class Ids(APIView):
Expand Down Expand Up @@ -46,15 +48,15 @@ class MultipleHallInfo(APIView):

def get(self, request: Request, hall_ids: str) -> Response:
halls = [int(x) for x in hall_ids.split(",")]
output = {"rooms": []}
output: dict[str, Any] = {"rooms": []}

for hall_id in halls:
hall_data = hall_status(get_object_or_404(LaundryRoom, hall_id=hall_id))
hall_data["id"] = hall_id
hall_data["usage_data"] = HallUsage.compute_usage(hall_id)
output["rooms"].append(hall_data)

record_analytics(Metric.LAUNDRY_VIEWED, request.user.username)
record_analytics(Metric.LAUNDRY_VIEWED, get_user(request).username)

return Response(output)

Expand All @@ -64,10 +66,16 @@ class HallUsage(APIView):
GET: returns usage data for dryers and washers of a particular hall
"""

@staticmethod
def safe_division(a: int | None, b: int | None) -> float | None:
return round(a / float(b), 3) if b > 0 else 0

def get_snapshot_info(hall_id: int) -> tuple[LaundryRoom, QuerySet[LaundrySnapshot]]:
if a is None or b is None or b <= 0:
return 0.0
return round(a / float(b), 3)

@staticmethod
def get_snapshot_info(
hall_id: int,
) -> tuple[LaundryRoom, QuerySet[LaundrySnapshot, Manager[LaundrySnapshot]]]:
# filters for LaundrySnapshots within timeframe
room = get_object_or_404(LaundryRoom, hall_id=hall_id)

Expand All @@ -84,7 +92,8 @@ def get_snapshot_info(hall_id: int) -> tuple[LaundryRoom, QuerySet[LaundrySnapsh
snapshots = LaundrySnapshot.objects.filter(filter).order_by("-date")
return (room, snapshots)

def compute_usage(hall_id: int) -> Response:
@staticmethod
def compute_usage(hall_id: int) -> dict[str, Any] | Response:
try:
(room, snapshots) = HallUsage.get_snapshot_info(hall_id)
except ValueError:
Expand All @@ -97,7 +106,8 @@ def compute_usage(hall_id: int) -> Response:
min_date = timezone.localtime()
max_date = timezone.localtime() - datetime.timedelta(days=30)

for snapshot in snapshots:
for snapshot_obj in snapshots.iterator():
snapshot = cast(LaundrySnapshot, snapshot_obj)
date = snapshot.date.astimezone()
min_date = min(min_date, date)
max_date = max(max_date, date)
Expand Down Expand Up @@ -152,18 +162,20 @@ class Preferences(APIView):
key = "laundry_preferences:{user_id}"

def get(self, request: Request) -> Response:
key = self.key.format(user_id=request.user.id)
user = get_user(request)
key = self.key.format(user_id=user.id)
cached_preferences = cache.get(key)
if cached_preferences is None:
preferences = request.user.profile.laundry_preferences.all()
preferences = user.profile.laundry_preferences.all()
cached_preferences = preferences.values_list("hall_id", flat=True)
cache.set(key, cached_preferences, Cache.MONTH)

return Response({"rooms": cached_preferences})

def post(self, request: Request) -> Response:
key = self.key.format(user_id=request.user.id)
profile = request.user.profile
user = get_user(request)
key = self.key.format(user_id=user.id)
profile = user.profile
preferences = profile.laundry_preferences
if "rooms" not in request.data:
return Response({"success": False, "error": "No rooms provided"}, status=400)
Expand Down
6 changes: 3 additions & 3 deletions backend/penndata/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib import admin
from django.utils.html import escape, mark_safe
from django.utils.safestring import SafeText
from django.utils.html import escape
from django.utils.safestring import SafeText, mark_safe

from penndata.models import (
AnalyticsEvent,
Expand All @@ -16,7 +16,7 @@ class FitnessRoomAdmin(admin.ModelAdmin):
def image_tag(self, instance: FitnessRoom) -> SafeText:
return mark_safe('<img src="%s" height="300" />' % escape(instance.image_url))

image_tag.short_description = "Fitness Room Image"
image_tag.short_description = "Fitness Room Image" # type: ignore[attr-defined]
readonly_fields = ("image_tag",)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def scrape_details(self, event_url: str) -> tuple[
resp = requests.get(event_url)
except ConnectionError:
print("Error:", ConnectionError)
return None
return None, None, None, None, None
soup = BeautifulSoup(resp.text, "html.parser")

location = (
Expand Down
16 changes: 11 additions & 5 deletions backend/penndata/management/commands/get_fitness_snapshot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import Any
from typing import Any, Optional

import requests
from bs4 import BeautifulSoup
Expand All @@ -14,7 +14,7 @@ def cap_string(s: str) -> str:
return " ".join([word[0].upper() + word[1:] for word in s.split()])


def get_usages() -> tuple[dict[str, dict[str, int | float]], datetime.datetime]:
def get_usages() -> tuple[Optional[dict[str, dict[str, int | float]]], datetime.datetime]:

# count/capacities default to 0 since spreadsheet number appears blank if no one there
locations = [
Expand All @@ -28,7 +28,9 @@ def get_usages() -> tuple[dict[str, dict[str, int | float]], datetime.datetime]:
"Pool-Shallow",
"Pool-Deep",
]
usages = {location: {"count": 0, "capacity": 0} for location in locations}
usages: dict[str, dict[str, int | float]] = {
location: {"count": 0, "capacity": 0} for location in locations
}

date = timezone.localtime() # default if can't get date from spreadsheet

Expand All @@ -42,12 +44,12 @@ def get_usages() -> tuple[dict[str, dict[str, int | float]], datetime.datetime]:
)
)
except ConnectionError:
return None
return None, date

html = resp.content.decode("utf8")
soup = BeautifulSoup(html, "html5lib")
if not (embedded_spreadsheet := soup.find("tbody")):
return None
return None, date

table_rows = embedded_spreadsheet.findChildren("tr")
for i, row in enumerate(table_rows):
Expand Down Expand Up @@ -77,6 +79,10 @@ def handle(self, *args: Any, **kwargs: Any) -> None:
self.stdout.write("FitnessSnapshots already exist for this date!")
return

if not usage_by_location:
self.stdout.write("Failed to get usages from spreadsheet!")
return

FitnessSnapshot.objects.bulk_create(
[
FitnessSnapshot(
Expand Down
Loading

0 comments on commit 2bec944

Please sign in to comment.