diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..36f8436 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,70 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.0.6] - 2024-04-28 + +### Added + +1. This changelog file. Hooray! +2. Nix shell: + - Retext now available to easily view markdown files +3. Makefile targets: + - view-readme now available for convenient README.md viewing. + - view-changelog now available for convenient CHANGELOG.md viewing. +4. Models subpackage: + - Status class + - Dog subpackage: + * BreedSize class + * Dog class: + + age column + + breed_size column + + created_at column + + status column + + updated_at column + - User subpackage: + * User class: + + created_at column + + name column + + updated_at column + - Visit subpackage: + * VisitType class + * Visit class: + + created_at column + + updated_at column + + visit_type column + + get_date method + + get_time method +5. Tests: + - Test for User db model class (test_user_model.py) + - testing_app.py: + * added additional user (Real Person) +6. Jinja templates: + - User dashboard: + * Pets overview table [subtemplate](/accounts/templates/users/dashboard/pets_overview.html) + +### Changed + +1. Models subpackage: + - back_populates replaced backref parameter to explicitly define table relationships + - Dog class moved to its own [subpackage](/accounts/models/dog/__init__py) of models + - Visit class moved to its own [subpackage](/accounts/models/visit/__init__.py)) of models +2. Tests: + - testing_app.py: + * updated models to use new columns in database seeding function + * seeded db data changed to be more unique +3. Jinja templates: + - User dashboard: + * User visit overview [renamed](/accounts/templates/users/dashboard/visits_overview.html) + +### Removed + +1. Nix development shell: + - Inlyne markdown viewer (replaced by Retext) +2. Jinja templates: + - User Dashboard: + * User details no longer displayed + diff --git a/Makefile b/Makefile index a927509..c6cc5c9 100644 --- a/Makefile +++ b/Makefile @@ -9,3 +9,11 @@ development-server: # Start a development server flask run --debug +.PHONY: view-readme +view-readme: # View README.md for project + retext --preview README.md + +.PHONY: view-changelog +view-changelog: # View CHANGELOG.md for project + retext --preview CHANGELOG.md + diff --git a/README.md b/README.md index 82903a4..75669b2 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,17 @@ Not much, at the moment. Routes handled by this blueprint. + ### /accounts/register Account registration and creation. + ### /accounts/login User login and authentication. + ### /accounts/users//dashboard A central point for the user to get an overview of their account. diff --git a/accounts/models/dog.py b/accounts/models/dog.py deleted file mode 100644 index 42745c0..0000000 --- a/accounts/models/dog.py +++ /dev/null @@ -1,21 +0,0 @@ -from accounts.models import db - -class Dog(db.Model): - __tablename__ = 'dogs' - - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - name = db.Column(db.String, nullable=False) - breed = db.Column(db.String, nullable=False) - owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) - owner = db.relationship('User', backref='dogs') - - def __repr__(self): - return f'{self.name}' - - def to_dict(self): - return { - 'name': self.name, - 'breed': self.breed, - 'owner': self.owner - } - diff --git a/accounts/models/dog/__init__.py b/accounts/models/dog/__init__.py new file mode 100644 index 0000000..3cdefd0 --- /dev/null +++ b/accounts/models/dog/__init__.py @@ -0,0 +1,35 @@ +# Standard library imports +from datetime import datetime + +from sqlalchemy import Enum + +# Import db +from accounts.models import db + +from accounts.models.dog.breed_size import BreedSize +from accounts.models.status import Status + +class Dog(db.Model): + __tablename__ = 'dogs' + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + name = db.Column(db.String, nullable=False) + breed_size = db.Column(Enum(BreedSize), default=BreedSize.MEDIUM) + age = db.Column(db.DateTime, default=datetime(1970, 1, 1, 0, 0, 0)) + owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + owner = db.relationship('User', back_populates='dogs') + visits = db.relationship('Visit', back_populates='dogs') + status = db.Column(Enum(Status), default=Status.ACTIVE) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow) + + def __repr__(self): + return f'{self.name}' + + def to_dict(self): + return { + 'name': self.name, + 'breed': self.breed, + 'owner': self.owner + } + diff --git a/accounts/models/dog/breed_size.py b/accounts/models/dog/breed_size.py new file mode 100644 index 0000000..adb56e4 --- /dev/null +++ b/accounts/models/dog/breed_size.py @@ -0,0 +1,8 @@ +from enum import Enum +from enum import auto + +class BreedSize(Enum): + SMALL = auto() + MEDIUM = auto() + LARGE = auto() + diff --git a/accounts/models/status.py b/accounts/models/status.py new file mode 100644 index 0000000..c34d6e0 --- /dev/null +++ b/accounts/models/status.py @@ -0,0 +1,7 @@ +from enum import Enum +from enum import auto + +class Status(Enum): + INACTIVE = auto() + ACTIVE = auto() + diff --git a/accounts/models/user.py b/accounts/models/user.py index d0bebfd..2cd15d7 100644 --- a/accounts/models/user.py +++ b/accounts/models/user.py @@ -1,3 +1,5 @@ +from datetime import datetime + # Import db from accounts.models import db @@ -5,11 +7,15 @@ class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True, autoincrement=True) + name = db.Column(db.String, nullable=False) username = db.Column(db.String, unique=True, nullable=False) email = db.Column(db.String, nullable=False) address = db.Column(db.String, nullable=False) password = db.Column(db.String, nullable=False) + dogs = db.relationship('Dog', back_populates='owner') visits = db.relationship('Visit', back_populates='owner') + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow) def __repr__(self): return f'{self.username}' diff --git a/accounts/models/visit.py b/accounts/models/visit.py deleted file mode 100644 index 1e652c5..0000000 --- a/accounts/models/visit.py +++ /dev/null @@ -1,21 +0,0 @@ -# Import db -from accounts.models import db - -class Visit(db.Model): - __tablename__ = 'visits' - - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - date_time = db.Column(db.DateTime, nullable=False) - owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) - owner = db.relationship('User', back_populates='visits') - dog_id = db.Column(db.Integer, db.ForeignKey('dogs.id'), nullable=False) - dog = db.relationship('Dog', backref='visits') - - def to_dict(self): - return { - 'ID': self.id, - 'Date-Time': self.date_time, - 'Owner-ID': self.owner_id, - 'Dog-ID': self.dog_id - } - diff --git a/accounts/models/visit/__init__.py b/accounts/models/visit/__init__.py new file mode 100644 index 0000000..4179cc2 --- /dev/null +++ b/accounts/models/visit/__init__.py @@ -0,0 +1,50 @@ +from datetime import datetime + +# Import SQLAlchemy enum type +from sqlalchemy import Enum + +# Import db +from accounts.models import db + +# Import custom enum db column +from accounts.models.status import Status +from accounts.models.visit.visit_type import VisitType + +class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + date_time = db.Column(db.DateTime, nullable=False) + owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + owner = db.relationship('User', back_populates='visits') + visit_type = db.Column(Enum(VisitType), default=VisitType.MEDIUM_MINUTE_WALK) + dog_id = db.Column(db.Integer, db.ForeignKey('dogs.id'), nullable=False) + dogs = db.relationship('Dog', back_populates='visits') + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow) + + def get_date(self): + year = self.date_time.year + month = self.date_time.month + day = self.date_time.month + + return f'{year}-{month}-{day}' + + def get_time(self): + hour = self.date_time.hour + minute = self.date_time.minute + + if hour == 0 and minute == 0: + return 0 + + else: + return f'{hour}:{minute}' + + def to_dict(self): + return { + 'ID': self.id, + 'Date-Time': self.date_time, + 'Owner-ID': self.owner_id, + 'Dog-ID': self.dog_id + } + diff --git a/accounts/models/visit/visit_type.py b/accounts/models/visit/visit_type.py new file mode 100644 index 0000000..a373941 --- /dev/null +++ b/accounts/models/visit/visit_type.py @@ -0,0 +1,9 @@ +from enum import Enum +from enum import auto + +class VisitType(Enum): + SMALL_MINUTE_WALK = auto() + MEDIUM_MINUTE_WALK = auto() + LARGE_MINUTE_WALK = auto() + HOUSE_SITTING = auto() + diff --git a/accounts/templates/users/dashboard/base.html b/accounts/templates/users/dashboard/base.html index 073b796..5f22aad 100644 --- a/accounts/templates/users/dashboard/base.html +++ b/accounts/templates/users/dashboard/base.html @@ -8,12 +8,12 @@
-

User Details

-{% include 'users/dashboard/user_details_table.html' %} +

Pets

+{% include 'users/dashboard/pets_overview.html' %}

Booking History

-{% include 'users/dashboard/user_history_table.html' %} +{% include 'users/dashboard/visits_overview.html' %} {% endblock %} diff --git a/accounts/templates/users/dashboard/pets_overview.html b/accounts/templates/users/dashboard/pets_overview.html new file mode 100644 index 0000000..61e4dbe --- /dev/null +++ b/accounts/templates/users/dashboard/pets_overview.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + +
NameBreed
+ diff --git a/accounts/templates/users/dashboard/user_details_table.html b/accounts/templates/users/dashboard/user_details_table.html deleted file mode 100644 index f1428a1..0000000 --- a/accounts/templates/users/dashboard/user_details_table.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - -
Username:{{ user.username }}
Email Address:{{ user.email }}
Dogs:{{ user_dogs }}
- diff --git a/accounts/templates/users/dashboard/user_history_table.html b/accounts/templates/users/dashboard/user_history_table.html deleted file mode 100644 index 569f041..0000000 --- a/accounts/templates/users/dashboard/user_history_table.html +++ /dev/null @@ -1,4 +0,0 @@ - -{{ user_book_history }} -
- diff --git a/accounts/templates/users/dashboard/visits_overview.html b/accounts/templates/users/dashboard/visits_overview.html new file mode 100644 index 0000000..07f954a --- /dev/null +++ b/accounts/templates/users/dashboard/visits_overview.html @@ -0,0 +1,18 @@ + + + + + + + + + {% for visit in user.visits %} + + + + + + + {% endfor %} +
TypeDateTimeDog
{{ visit.type }}{{ visit.get_date() }}{{ visit.get_time() }}{{ visit.dog }}
+ diff --git a/shell.nix b/shell.nix index 3204059..9d0000d 100644 --- a/shell.nix +++ b/shell.nix @@ -4,7 +4,7 @@ with pkgs; pkgs.mkShell { nativeBuildInputs = [ - inlyne # markdown viewer + retext # markdown viewer # Python development environment (python3.withPackages(ps: with ps; [ diff --git a/tests/test_user_model.py b/tests/test_user_model.py new file mode 100644 index 0000000..b5e503e --- /dev/null +++ b/tests/test_user_model.py @@ -0,0 +1,43 @@ +# Import libraries +import pytest + +# Import db +from accounts.models import db + +# Import db models +from accounts.models.dog import Dog +from accounts.models.dog.breed_size import BreedSize +from accounts.models.user import User +from accounts.models.visit.visit_type import VisitType + +# Import flask app factory function +from tests.testing_app import create_app +from tests.testing_app import create_app + +def test_db(): + app = create_app() + + with app.app_context(): + man_man = db.session.execute(db.select(User).where(User.name == 'Man Man')).scalar_one() + man_man_bookings = man_man.get_bookings() + + real_person = db.session.execute(db.select(User).where(User.name == 'Real Person')).scalar_one() + real_person_bookings = real_person.get_bookings() + + # Assert something + assert man_man.id == 1 + assert man_man.name == 'Man Man' + assert man_man.username == 'mamanman' + assert man_man.email == 'man_man@email.com' + assert man_man.address == '123 Home Ln. City, AA 11223' + assert man_man.password == 'manword' + assert len(man_man_bookings) == 2 + + assert real_person.id == 2 + assert real_person.name == 'Real Person' + assert real_person.username == 'imarealperson' + assert real_person.email == 'real_person@email.com' + assert real_person.address == '113 Park St. City, AA 13433' + assert real_person.password == 'realpassword' + assert len(real_person_bookings) == 1 + diff --git a/tests/testing_app.py b/tests/testing_app.py index bc3a360..e9f6fb5 100755 --- a/tests/testing_app.py +++ b/tests/testing_app.py @@ -13,8 +13,10 @@ from accounts.models import db # Import db models from accounts.models.dog import Dog +from accounts.models.dog.breed_size import BreedSize from accounts.models.user import User from accounts.models.visit import Visit +from accounts.models.visit.visit_type import VisitType def create_app(): # Initialize flask app @@ -39,39 +41,72 @@ def create_app(): def add_test_data(db): # Create test users - test_user1 = User(username='test_user1', - email='testing@email.com', - address='123 Home Ln. Baltimore,MD 12345', - password='12345') + man_man = User(name='Man Man', + username='mamanman', + email='man_man@email.com', + address='123 Home Ln. City, AA 11223', + password='manword') + + real_person = User(name='Real Person', + username='imarealperson', + email='real_person@email.com', + address='113 Park St. City, AA 13433', + password='realpassword') # Add test users to db session - db.session.add(test_user1) + db.session.add(man_man) + db.session.add(real_person) # Commit test users to db db.session.commit() # Create test dogs - test_dog1 = Dog(name='Lassey', - breed='Golden Retreiver', - owner_id=test_user1.id) - test_dog2 = Dog(name='Rufus', - breed='Basset Hound', - owner_id=test_user1.id) + lassey = Dog(name='Lassey', + breed_size=BreedSize.LARGE, + owner_id=man_man.id) + + rufus = Dog(name='Rufus', + breed_size=BreedSize.SMALL, + owner_id=man_man.id) + + air_bud = Dog(name='Air Bud', + owner_id=real_person.id) # Add test dogs to db session - db.session.add(test_dog1) - db.session.add(test_dog2) + db.session.add(lassey) + db.session.add(rufus) + db.session.add(air_bud) # Commit test dogs to db db.session.commit() # Create test visits + + # Visit with the date in the past test_visit1 = Visit(date_time=datetime(2023,12,5,10,30), - owner_id=test_user1.id, - dog_id=test_dog2.id) + owner_id=man_man.id, + dog_id=rufus.id) + + # Visit with the date in the future + test_visit2 = Visit(date_time=datetime(2034,12,5,10,30), + owner_id=man_man.id, + dog_id=lassey.id) + + # Visit with non-default visit type + test_visit3 = Visit(date_time=datetime(2034,12,5,10,30), + owner_id=real_person.id, + visit_type=VisitType.HOUSE_SITTING, + dog_id=air_bud.id) + + # Visit with more than one dog + #test_visit3 = Visit(date_time=datetime(2034,12,5,10,30), + # owner_id=man_man.id, + # dog_id=[lassey.id, rufus.id]) # Add test visits to db session db.session.add(test_visit1) + db.session.add(test_visit2) + db.session.add(test_visit3) # Commit test visits to db db.session.commit()