diff --git a/server/manage.py b/server/manage.py index f1b8264..0f99810 100644 --- a/server/manage.py +++ b/server/manage.py @@ -1,10 +1,12 @@ import csv from flask.cli import FlaskGroup from project import create_app, db -from project.models.timezone import Timezone +from project.models.timezone import Timezone, create_timezone from project.models.user import add_user import unittest import sys +from typing import List +import datetime as dt cli = FlaskGroup(create_app=create_app) @@ -14,20 +16,25 @@ def create_db(): print("Recreating db") db.drop_all() db.create_all() + seed_timezones('project/db/timezones.csv') db.session.commit() -@cli.command('seed_db') -def seed_timezones(): - """Creates the timezone table and populates it.""" - with open('project/db/timezones.csv') as csv_file: +def seed_timezones(path: str): + """ + Seeds the timezones table with the timezones from path returns the array of + seeded timezones. + :param path: str path to timezones.csv + :return: array of Timezone objects + """ + timezones: List[Timezone] = [] + with open(path) as csv_file: csv_reader = csv.reader(csv_file) - next(csv_reader) + next(csv_reader) # skip the header row for row in csv_reader: - timezone = Timezone(*row) - db.session.add(timezone) - db.session.commit() - return + timezones.append(create_timezone(*row)) + return timezones + def seed_db(): user1 = add_user(name="Brian", email="Brian@email.com", public_id="Brianh") @@ -35,6 +42,7 @@ def seed_db(): user3 = add_user(name="Kenneth", email="kenn@email.com", public_id="kennethc") db.commit() + @cli.command() def test(): """Runs the tests without code coverage""" diff --git a/server/project/__init__.py b/server/project/__init__.py index 7a60f97..291caa7 100644 --- a/server/project/__init__.py +++ b/server/project/__init__.py @@ -40,6 +40,9 @@ def create_app(script_info=None): from project.api.events_handler import events_blueprint app.register_blueprint(events_blueprint) + from project.api.timezone_handler import timezones_blueprint + app.register_blueprint(timezones_blueprint) + from project.api.calendar_handler import calendar_blueprint app.register_blueprint(calendar_blueprint) diff --git a/server/project/api/timezone_handler.py b/server/project/api/timezone_handler.py new file mode 100644 index 0000000..7e74019 --- /dev/null +++ b/server/project/api/timezone_handler.py @@ -0,0 +1,45 @@ +from flask import Blueprint +from flask_restx import Resource, fields +from project import api, db +from project.models.timezone import Timezone, hours_minutes + +timezones_blueprint = Blueprint('timezone', __name__) + + +class OffsetHours(fields.Raw): + """A field that converts a timedelta to an integer hour.""" + def format(self, value): + return hours_minutes(value)[0] + + +class OffsetMinutes(fields.Raw): + """A field that converts a timedelta to an integer minute.""" + def format(self, value): + return hours_minutes(value)[1] + + +timezones_model = api.model( + 'Timezone', { + 'name': fields.String(description='The name of the timezone', + example='America/Toronto'), + 'offset hours': OffsetHours(description='The UTC offset hours only.', + attribute='offset', required=True, + example=-5), + 'offset minutes': OffsetMinutes(description='The UTC offset minutes ' + 'only.', + attribute='offset', required=True, + example=0), + 'dst offset hours': OffsetHours(description='The daylight savings time ' + 'UTC offset hours only.', + attribute='dst_offset', example=-4), + 'dst offset minutes': OffsetMinutes(description='The UTC offset minutes' + ' only.', + attribute='dst_offset', example=0)}) + + +@api.route('/timezones') +class Timezones(Resource): + @api.marshal_with(timezones_model) + def get(self): + """Returns a list of all of the timezones.""" + return Timezone.query.all(), 200 diff --git a/server/project/models/timezone.py b/server/project/models/timezone.py index d2e20e2..2065ccf 100644 --- a/server/project/models/timezone.py +++ b/server/project/models/timezone.py @@ -1,4 +1,6 @@ from project import db +import datetime as dt +from typing import Tuple class Timezone(db.Model): @@ -6,14 +8,33 @@ class Timezone(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True, nullable=False) - hours = db.Column(db.Integer, nullable=False) - minutes = db.Column(db.Integer) - dst_hours = db.Column(db.Integer, nullable=False) - dst_minutes = db.Column(db.Integer) + offset = db.Column(db.Interval, nullable=False) + dst_offset = db.Column(db.Interval) - def __init__(self, name, hours, minutes, dst_hours, dst_minutes): - self.name = name - self.hours = hours - self.minutes = minutes - self.dst_hours = dst_hours - self.dst_minutes = dst_minutes + +def create_timezone(name: str, hours: str, minutes: str, dst_hours: str, + dst_minutes: str) -> Timezone: + """ + Creates and adds a timezone object returns the created timezone + :param name: the name of the timezone + :param hours: hours portion of the offset + :param minutes: minutes portion of the offset + :param dst_hours: daylight savings hours + :param dst_minutes: daylight savings minutes + :return: Timezone object + """ + timezone = Timezone( + name=name, + offset=dt.timedelta(hours=int(hours), minutes=int(minutes)), + dst_offset=dt.timedelta(hours=int(dst_hours), minutes=int(dst_minutes))) + db.session.add(timezone) + return timezone + + +def hours_minutes(td: dt.timedelta) -> Tuple[int, int]: + """ + Converts a timedelta to hours and minutes + :param td: timedelta + :return: tuple of integers + """ + return td.days * 24 + td.seconds // 3600, (td.seconds//60) % 60 diff --git a/server/project/test/timezone_longtest.py b/server/project/test/timezone_longtest.py index 9057438..0f15971 100644 --- a/server/project/test/timezone_longtest.py +++ b/server/project/test/timezone_longtest.py @@ -1,15 +1,48 @@ -import csv -from project.models.timezone import Timezone +from project.models.timezone import Timezone, hours_minutes from project.test.timezone_test_base import TimezoneTestBase +import json class TimezoneModelTest(TimezoneTestBase): - def test_timezone_table(self): + def test_timezone_model(self): """Tests whether the timezone table was created successfully.""" - with open('project/db/timezones.csv') as csv_file: - csv_reader = csv.reader(csv_file) - next(csv_reader) - counter = 0 - for row in csv_reader: - counter += 1 - self.assertEqual(Timezone.query.count(), counter) + eastern = 'America/Toronto' + pacific = 'America/Los_Angeles' + est = Timezone.query.filter_by(name=eastern).first() + pst = Timezone.query.filter_by(name=pacific).first() + + self.assertEqual(hours_minutes(est.offset)[0], -5) + self.assertEqual(hours_minutes(est.dst_offset)[0], -4) + self.assertEqual(hours_minutes(pst.offset)[0], -8) + self.assertEqual(hours_minutes(pst.dst_offset)[0], -7) + + +class TimezoneGetTest(TimezoneTestBase): + def test_all_timezones(self): + """Tests whether the format of the returned timezone is correct.""" + response = self.api.get('/timezones', content_type='application/json') + data = json.loads(response.data.decode()) + + self.assertEqual(len(data), Timezone.query.count()) + + def test_timezone_marshal(self): + """Tests whether the format of the returned timezone is correct.""" + response = self.api.get('/timezones', content_type='application/json') + data = json.loads(response.data.decode()) + eastern_response = None # get rid of warning + + for row in data: + if row['name'] == 'America/Toronto': + eastern_response = row + break + + eastern_query = Timezone.query.filter_by(name='America/Toronto').first() + + self.assertEqual(eastern_response['offset hours'], + hours_minutes(eastern_query.offset)[0]) + self.assertEqual(eastern_response['offset minutes'], + hours_minutes(eastern_query.offset)[1]) + self.assertEqual(eastern_response['dst offset hours'], + hours_minutes(eastern_query.dst_offset)[0]) + self.assertEqual(eastern_response['dst offset minutes'], + hours_minutes(eastern_query.dst_offset)[1]) diff --git a/server/project/test/timezone_test_base.py b/server/project/test/timezone_test_base.py index 71c5254..64cf719 100644 --- a/server/project/test/timezone_test_base.py +++ b/server/project/test/timezone_test_base.py @@ -1,25 +1,10 @@ -import csv -from project.models.timezone import Timezone import unittest from project import create_app, db - -# Import this test base if you need to use Timezones db +from manage import seed_timezones app = create_app() -def seed_timezones(): - """Creates the timezone table and populates it.""" - with open('project/db/timezones.csv') as csv_file: - csv_reader = csv.reader(csv_file) - next(csv_reader) - for row in csv_reader: - timezone = Timezone(*row) - db.session.add(timezone) - db.session.commit() - return - - class TimezoneTestBase(unittest.TestCase): """Test base to be used whenever timezones are needed. This test base will populate the timezones table and will significantly add to the time @@ -31,8 +16,8 @@ def create_app(self): def setUp(self): self.api = app.test_client() db.create_all() + seed_timezones('project/db/timezones.csv') db.session.commit() - seed_timezones() def tearDown(self): db.session.remove()