-
Notifications
You must be signed in to change notification settings - Fork 146
Lilian Mora tigers #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Lilian Mora tigers #131
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| web: gunicorn 'app:create_app()' |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from flask import Flask | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from flask_sqlalchemy import SQLAlchemy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from flask_migrate import Migrate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from dotenv import load_dotenv | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db = SQLAlchemy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| migrate = Migrate() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| load_dotenv() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def create_app(test_config=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app = Flask(__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if test_config == None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_DATABASE_URI") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.config["TESTING"] = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from app.models.task import Task | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from app.models.goal import Goal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db.init_app(app) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| migrate.init_app(app, db) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from .routes import tasks_bp, goals_bp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from .routes import tasks_bp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from .routes import goals_bp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.register_blueprint(tasks_bp) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.register_blueprint(goals_bp) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return app | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -1,5 +1,24 @@ | ||||
| from app import db | ||||
| from flask import current_app, jsonify | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see anywhere we use these in the Goal model. Let's remove this line.
Suggested change
|
||||
| from sqlalchemy.orm import backref | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we shouldn't need to import this explicitly. It is already accessible through the
Suggested change
|
||||
|
|
||||
|
|
||||
| class Goal(db.Model): | ||||
| goal_id = db.Column(db.Integer, primary_key=True) | ||||
| title = db.Column(db.String) | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See |
||||
| tasks = db.relationship("Task", backref= "goal") | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As much as I love |
||||
|
|
||||
| def goal_dict(self): | ||||
| return{ | ||||
| "id":self.goal_id, | ||||
| "title":self.title | ||||
| } | ||||
| @classmethod | ||||
| def goal_arguments(cls,title_from_url): | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can make this method name a little more description. A model might have several arguments that will do different things. This looks to be filtering data, so perhaps we can call it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see this being used anywhere. We filter by ascending in one of the routes, that would be a perfect time to modify this and use it! |
||||
| if title_from_url: | ||||
| goals = Goal.query.filter_by(title=title_from_url).all() | ||||
| if not goals: | ||||
| goals = Goal.query.filter(Goal.title.contains(title_from_url)) | ||||
| else: | ||||
| goals = Goal.query.all() | ||||
| return goals | ||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,19 @@ | ||||||||
| from app import db | ||||||||
| from flask import current_app,request | ||||||||
| from sqlalchemy import desc,asc | ||||||||
| from dotenv import load_dotenv | ||||||||
|
Comment on lines
+2
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Always be aware of your imports and if you need them. Sometimes when typing, VSCode will try and help us out an auto-complete. But if we don't need it, don't see it used in our resources on Learn or in previous projects, we shouldn't keep it in.
Suggested change
|
||||||||
|
|
||||||||
|
|
||||||||
| class Task(db.Model): | ||||||||
| task_id = db.Column(db.Integer, primary_key=True) | ||||||||
| task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||||||||
| title = db.Column(db.String) | ||||||||
| description = db.Column(db.String) | ||||||||
| completed_at = db.Column(db.DateTime, nullable = True) | ||||||||
|
Comment on lines
+9
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It turns out that The way the project emphasized that |
||||||||
| goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id"), nullable = True) | ||||||||
|
|
||||||||
| def task_dict(self): | ||||||||
| task_dict = {"id": self.task_id, "title": self.title, "description": self.description, "is_complete": False if self.completed_at != None else True} | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good job using a ternary operator for task_dict = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": False if self.completed_at != None else True
} |
||||||||
|
|
||||||||
| return task_dict | ||||||||
|
|
||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -1 +1,190 @@ | ||||
| from flask import Blueprint | ||||
| from flask import Blueprint, request, jsonify, make_response, abort | ||||
| from app.models.task import Task | ||||
| from app import db | ||||
| from app.models.goal import Goal | ||||
| from sqlalchemy import desc | ||||
| from datetime import datetime | ||||
| import requests | ||||
| import os | ||||
|
|
||||
| tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||||
| goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||||
|
|
||||
| @tasks_bp.route("", methods=["POST"]) | ||||
| def new_task(): | ||||
|
|
||||
| request_body = request.get_json() | ||||
| if 'title' not in request_body or 'description' \ | ||||
| not in request_body or 'completed_at' not in request_body: | ||||
| return make_response({"details": "Invalid data"}, 400) | ||||
| else: | ||||
|
|
||||
| new_task = Task(title=request_body["title"], | ||||
| description=request_body["description"], | ||||
| completed_at=request_body["completed_at"]) | ||||
|
Comment on lines
+16
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would make a good helper method inside the model class. |
||||
| db.session.add(new_task) | ||||
| db.session.commit() | ||||
| is_complete = new_task.completed_at != None | ||||
| return make_response({"task": {"id": new_task.task_id, "title": new_task.title, "description": new_task.description, "is_complete": is_complete}}, 201) | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works, but it is a little difficult to read the response body and quickly ascertain where one argument ends and another begins. Maybe turn this into a separate variable that is passed in. |
||||
|
|
||||
| @tasks_bp.route("", methods=["GET"]) | ||||
| def all_tasks(): | ||||
| title_query = request.args.get('title') | ||||
| order_by_query = request.args.get('sort') | ||||
| if title_query: | ||||
| tasks = Task.query.filter_by(title = title_query) | ||||
| elif order_by_query == 'asc': | ||||
| tasks = Task.query.order_by(Task.title).all() | ||||
|
|
||||
| elif order_by_query == 'desc': | ||||
| tasks = Task.query.order_by(desc(Task.title)).all() | ||||
| else: | ||||
| tasks = Task.query.order_by(Task.title).all() | ||||
|
Comment on lines
+32
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like you have a helper method for filtering in the |
||||
|
|
||||
| task_response = [] | ||||
| for task in tasks: | ||||
| is_complete = task.completed_at != None | ||||
| task_response.append({'id': task.task_id, 'title': task.title, 'description': task.description,'is_complete': is_complete}) | ||||
|
Comment on lines
+44
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would also make another good helper method. |
||||
|
|
||||
| return jsonify(task_response), 200 | ||||
|
|
||||
| @tasks_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE", "PATCH"]) | ||||
| def one_task(task_id): | ||||
| task = Task.query.get(task_id) | ||||
| if task == None: | ||||
| abort(404) | ||||
| is_complete = task.completed_at != None | ||||
| if request.method == "GET": | ||||
| if task.goal_id: | ||||
| return make_response({"task": {"id": task.task_id,"title": task.title, "description": task.description, "goal_id": task.goal_id, "is_complete": is_complete}}, 200) | ||||
| else: | ||||
| return make_response({"task": {"id": task.task_id, "title": task.title, "description": task.description, "is_complete": is_complete}}, 200) | ||||
| elif request.method == "PUT": | ||||
| form_data = request.get_json() | ||||
| task.title = form_data["title"] | ||||
| task.description = form_data["description"] | ||||
| task.completed_at = task.completed_at | ||||
|
Comment on lines
+64
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above! We can turn this into an "update" helper method in the Task model and get it out of the route |
||||
|
|
||||
| db.session.commit() | ||||
|
|
||||
| return make_response({"task": {"id": task.task_id, "title": task.title, "description": task.description, "is_complete": is_complete}}, 200) | ||||
|
|
||||
| elif request.method == "PATCH": | ||||
| form_data = request.get_json() | ||||
| if "title" in form_data: | ||||
| task.title = form_data["title"] | ||||
| if "description" in form_data: | ||||
| task.description = form_data["description"] | ||||
|
|
||||
| db.session.commit() | ||||
| return make_response({"task": {"id": task.task_id, "title": task.title, "description": task.description, "is_complete": is_complete}}, 200) | ||||
| elif request.method == "DELETE": | ||||
| db.session.delete(task) | ||||
| db.session.commit() | ||||
| return make_response({'details':f'Task {task.task_id} "{task.title}" successfully deleted'}, 200) | ||||
|
|
||||
| @tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||||
| def task_complete(task_id): | ||||
|
|
||||
| time_stamp = datetime.now() | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's save ourselves a line of code here and create the timestamp down below where it's inevitably assigned.
Suggested change
|
||||
| task = Task.query.get(task_id) | ||||
| if task == None: | ||||
| abort(404) | ||||
| else: | ||||
| task.completed_at = time_stamp | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now let's turn this into: |
||||
| db.session.commit() | ||||
| is_complete = task.completed_at != None | ||||
|
|
||||
| try: | ||||
| header= {"Authorization"} | ||||
| post_body = requests.post(header) | ||||
| except TypeError: | ||||
| pass | ||||
|
Comment on lines
+98
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see you started implementing the slack bot API here. When you get time, maybe over winter break, I highly recommend trying to finish it. Working with third-party APIs like this one will be something you'll be doing during your capstone. |
||||
| return make_response({"task": {"id": task.task_id, "title": task.title, "description": task.description, "is_complete": is_complete}}, 200) | ||||
|
|
||||
| @tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||||
| def task_not_complete(task_id): | ||||
| task = Task.query.get(task_id) | ||||
| if task == None: | ||||
| abort(404) | ||||
| else: | ||||
| task.completed_at = None | ||||
| db.session.commit() | ||||
| is_complete = task.completed_at != None | ||||
| return make_response({"task": {"id": task.task_id, "title": task.title, "description": task.description, "is_complete": is_complete}}, 200) | ||||
|
|
||||
| @goals_bp.route("", methods=["POST"]) | ||||
| def post_goal(): | ||||
| request_body = request.get_json() | ||||
| if 'title' not in request_body : | ||||
| return make_response({"details": "Invalid data"}, 400) | ||||
| else: | ||||
| new_goal = Goal(title=request_body["title"]) | ||||
|
|
||||
| db.session.add(new_goal) | ||||
| db.session.commit() | ||||
| return make_response({"goal": {"id": new_goal.goal_id, "title": new_goal.title}}, 201) | ||||
|
|
||||
| @goals_bp.route("", methods=["GET"]) | ||||
| def all_goals(): | ||||
| title_query = request.args.get('title') | ||||
| order_by_query = request.args.get('sort') | ||||
| if title_query: | ||||
| goals = Goal.query.filter_by(title = title_query) | ||||
| elif order_by_query == 'asc': | ||||
| goals = Goal.query.order_by(Goal.title).all() | ||||
| elif order_by_query == 'desc': | ||||
| goals = Goal.query.order_by(desc(Goal.title)).all() | ||||
| else: | ||||
| goals = Goal.query.order_by(Goal.title).all() | ||||
|
|
||||
| goal_response = [] | ||||
| for goal in goals: | ||||
| goal_response.append({'id': goal.goal_id, 'title': goal.title}) | ||||
|
Comment on lines
+141
to
+143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would make a good helper method as well in the Goal model |
||||
| return jsonify(goal_response), 200 | ||||
|
|
||||
| @goals_bp.route("/<goal_id>", methods=["GET", "PUT", "DELETE"]) | ||||
| def one_goal(goal_id): | ||||
| goal = Goal.query.get(goal_id) | ||||
| if goal == None: | ||||
| abort(404) | ||||
| if request.method == "GET": | ||||
| return make_response({"goal": {"id": goal.goal_id, "title": goal.title}}, 200) | ||||
| elif request.method == "PUT": | ||||
| form_data = request.get_json() | ||||
| goal.title = form_data["title"] | ||||
| db.session.commit() | ||||
|
|
||||
| return make_response({"goal": {"id": goal.goal_id, "title": goal.title}}, 200) | ||||
| elif request.method == "DELETE": | ||||
| db.session.delete(goal) | ||||
| db.session.commit() | ||||
| return make_response({'details': | ||||
| f'Goal {goal.goal_id} "{goal.title}" successfully deleted'}, 200) | ||||
|
|
||||
| @goals_bp.route("/<goal_id>/tasks", methods=["POST", "GET"]) | ||||
| def task_and_goal(goal_id): | ||||
| request_body = request.get_json() | ||||
| tasks = Task.query.all() | ||||
| goal = Goal.query.get(goal_id) | ||||
| if goal == None: | ||||
| abort(404) | ||||
|
Comment on lines
+169
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is another place we can create a helper function. Let's validate the parameter passed in through the route to check if it's the right data type, exists in our db, etc. We should do this for bouth our models. Here's a class resource we made about this from Flasky |
||||
| if request.method == "POST": | ||||
| for task in tasks: | ||||
| if task.task_id in request_body["task_ids"]: | ||||
| task.goal_id = int(goal_id) | ||||
| db.session.commit() | ||||
| return make_response({"id": int(goal_id), "task_ids": request_body["task_ids"]}, 200) | ||||
| elif request.method == "GET": | ||||
| goals_tasks = Goal.query.get(goal_id).tasks | ||||
|
|
||||
| task_response = [] | ||||
| for task in goals_tasks: | ||||
| is_complete = task.completed_at != None | ||||
| task_response.append({'id': task.task_id, | ||||
| 'goal_id': task.goal_id, | ||||
| 'title': task.title, | ||||
| 'description': task.description, | ||||
| 'is_complete': is_complete}) | ||||
|
Comment on lines
+182
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another good candidate for a helper method in our Task model |
||||
|
|
||||
| return make_response({"id": goal.goal_id, "title": goal.title, "tasks": task_response}, 200) | ||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,21 @@ | ||
| alembic==1.5.4 | ||
| attrs==20.3.0 | ||
| autopep8==1.5.5 | ||
| blinker==1.4 | ||
| certifi==2020.12.5 | ||
| chardet==4.0.0 | ||
| click==7.1.2 | ||
| Flask==1.1.2 | ||
| Flask-Migrate==2.6.0 | ||
| Flask-SQLAlchemy==2.4.4 | ||
| alembic==1.8.1 | ||
| attrs==21.4.0 | ||
| click==8.1.3 | ||
| Flask==2.2.2 | ||
| Flask-Migrate==4.0.0 | ||
| Flask-SQLAlchemy==3.0.2 | ||
| gunicorn==20.1.0 | ||
| idna==2.10 | ||
| iniconfig==1.1.1 | ||
| itsdangerous==1.1.0 | ||
| Jinja2==2.11.3 | ||
| Mako==1.1.4 | ||
| MarkupSafe==1.1.1 | ||
| packaging==20.9 | ||
| pluggy==0.13.1 | ||
| psycopg2-binary==2.9.4 | ||
| py==1.10.0 | ||
| pycodestyle==2.6.0 | ||
| pyparsing==2.4.7 | ||
| itsdangerous==2.1.2 | ||
| Jinja2==3.1.2 | ||
| Mako==1.2.3 | ||
| MarkupSafe==2.1.1 | ||
| packaging==21.3 | ||
| pluggy==1.0.0 | ||
| py==1.11.0 | ||
| pyparsing==3.0.7 | ||
| pytest==7.1.1 | ||
| pytest-cov==2.12.1 | ||
| python-dateutil==2.8.1 | ||
| python-dotenv==0.15.0 | ||
| python-editor==1.0.4 | ||
| requests==2.25.1 | ||
| six==1.15.0 | ||
| SQLAlchemy==1.3.23 | ||
| toml==0.10.2 | ||
| urllib3==1.26.5 | ||
| Werkzeug==1.0.1 | ||
|
Comment on lines
-1
to
-34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm I'm not sure what happened here, but somehow your list of packages got erased. I would recommend recommitting this file with the original list of packages |
||
| python-dotenv==0.21.0 | ||
| SQLAlchemy==1.4.44 | ||
| tomli==2.0.1 | ||
| Werkzeug==2.2.2 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All this information stays in the root
__init__.pyfile. Refer back to Flasky, Solar System API, and Book Review. This file should be completely empty.