Skip to content

Commit

Permalink
Better test coverage, fixed a few model bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
brystmar committed Aug 30, 2020
1 parent aeea294 commit 0253d0c
Show file tree
Hide file tree
Showing 5 changed files with 440 additions and 97 deletions.
5 changes: 5 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 41 additions & 23 deletions backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from backend.config import Config, local
from backend.functions import generate_new_id
from datetime import datetime, timedelta
from operator import attrgetter
from pynamodb.models import Model
from pynamodb.attributes import UnicodeAttribute, UTCDateTimeAttribute, NumberAttribute, \
MapAttribute, ListAttribute, BooleanAttribute
import json


class Step(MapAttribute):
Expand Down Expand Up @@ -36,10 +36,6 @@ def to_dict(self) -> dict:
"note": self.note.__str__() if self.note else None
}

def to_json(self) -> str:
"""Converts output from the to_dict() method to a JSON-serialized string."""
return json.dumps(self.to_dict(), ensure_ascii=True)

def __init__(self, **kwargs):
super().__init__(**kwargs)

Expand Down Expand Up @@ -75,7 +71,7 @@ class Meta:
length = NumberAttribute(default=0)

# Steps (`list`): a list of dictionaries / "maps"
steps = ListAttribute(of=Step, null=True)
steps = ListAttribute(of=Step, default=[], null=True)

## Datetime attributes ##
# Stored as UTC timestamp in the db, operates as datetime here, exported as string or epoch
Expand Down Expand Up @@ -115,11 +111,15 @@ def update_length(self, save=True):
# logger.debug(f"Calculated length: {length}, original length: {original_length}")
self.length = length

if length != original_length and save:
# Update the database if the length changed
logger.debug(f"Attempting to save Recipe...")
self.save()
logger.info(f"Updated recipe {self.name} to reflect its new length: {self.length}.")
if length != original_length:
# Always update last_modified
logger.debug(f"Updating last_modified (was {self.last_modified}).")
self.last_modified = datetime.utcnow()
if save:
# User specifies if they want changes to be saved to the db
logger.debug(f"Attempting to save {self.__repr__()}")
self.save()
logger.info(f"Updated recipe {self.name} to reflect its new length: {self.length}.")

# logger.debug("End of Recipe.update_length()")

Expand Down Expand Up @@ -148,7 +148,7 @@ def to_dict(self, dates_as_epoch=False) -> dict:
"source": self.source.__str__() if self.source else None,
"url": self.url.__str__() if self.url else None,
"difficulty": self.difficulty.__str__(),
"solve_for_start": self.solve_for_start if self.solve_for_start else True,
"solve_for_start": self.solve_for_start,
"length": int(self.length),
"date_added": self.date_added.timestamp() * 1000, # JS timestamps are in ms
"start_time": self.start_time.timestamp() * 1000,
Expand All @@ -163,22 +163,24 @@ def to_dict(self, dates_as_epoch=False) -> dict:

return output

def to_json(self, dates_as_epoch=False) -> json:
"""Convert output from the to_dict() method to a JSON-serialized string."""
return json.dumps(self.to_dict(dates_as_epoch=dates_as_epoch), ensure_ascii=True)

def __init__(self, **kwargs):
super().__init__(**kwargs)

# Generate a unique id if the recipe doesn't have one yet
if 'id' not in kwargs:
self.id = generate_new_id(short=True)

# Validate URLs
if 'url' in kwargs:
if kwargs['url'] is None or kwargs['url'][:4] != 'http' or '://' not in kwargs['url']:
logger.debug(f"Invalid input for URL: {kwargs['url']}")
self.url = ""

# Convert any provided epoch dates/times to datetime
if 'date_added' in kwargs:
if not kwargs['date_added'] or kwargs['date_added'].__str__().lower() in \
("none", "null", "nan"):
kwargs['date_added'] = datetime.utcnow()
self.date_added = datetime.utcnow()
else:
if isinstance(self.date_added, (int, float)):
# Convert from JS milliseconds to seconds
Expand All @@ -187,7 +189,7 @@ def __init__(self, **kwargs):
if 'start_time' in kwargs:
if not kwargs['start_time'] or kwargs['start_time'].__str__().lower() in \
("none", "null", "nan"):
kwargs['start_time'] = self.date_added
self.start_time = self.date_added
else:
if isinstance(self.start_time, (int, float)):
# Convert from JS milliseconds to seconds
Expand All @@ -196,12 +198,32 @@ def __init__(self, **kwargs):
if 'last_modified' in kwargs:
if not kwargs['last_modified'] or kwargs['last_modified'].__str__().lower() in \
("none", "null", "nan"):
kwargs['last_modified'] = self.date_added
self.last_modified = self.date_added
else:
if isinstance(self.last_modified, (int, float)):
# Convert from JS milliseconds to seconds
self.last_modified = datetime.utcfromtimestamp(kwargs['last_modified'] / 1000)

def __setattr__(self, name, value):
"""Apply validation when values are changed."""
# URL format validation
if name.lower() == "url":
if value not in ("", None):
if value[:4] != "http" or "://" not in value:
raise ValueError("Invalid URL format.")

# TODO: Troubleshoot this logic
# Update length when modifying steps
# if name.lower() == "steps":
# super().__setattr__(name, value)
# self.update_length(save=True)

# Calculate the length instead of accepting a new value for length
# if name.lower() == "length" and self.length != value:
# self.update_length(save=True)

super().__setattr__(name, value)

def __repr__(self) -> str:
return f'<Recipe | id: {self.id}, name: {self.name}, length: {self.length}, ' \
f'steps: {len(self.steps) if self.steps else 0}>'
Expand All @@ -225,10 +247,6 @@ def to_dict(self) -> dict:
"new": self.new.__str__()
}

def to_json(self) -> str:
"""Converts output from the to_dict() method to a JSON-serialized string."""
return json.dumps(self.to_dict(), ensure_ascii=True)

def __init__(self, **kwargs):
super().__init__(**kwargs)

Expand Down
Loading

0 comments on commit 0253d0c

Please sign in to comment.