Add files via upload
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import os
|
||||
|
||||
from flask import Flask, send_from_directory
|
||||
from sqlalchemy import text
|
||||
|
||||
from .extensions import db, migrate, cors
|
||||
from config import config
|
||||
|
||||
|
||||
def create_app(config_name=None):
|
||||
if config_name is None:
|
||||
config_name = os.environ.get('FLASK_ENV', 'production')
|
||||
@@ -17,6 +20,7 @@ def create_app(config_name=None):
|
||||
|
||||
from .routes.projects import projects_bp
|
||||
from .routes.deliverables import deliverables_bp
|
||||
|
||||
app.register_blueprint(projects_bp, url_prefix='/api')
|
||||
app.register_blueprint(deliverables_bp, url_prefix='/api')
|
||||
|
||||
@@ -43,7 +47,9 @@ def _run_migrations():
|
||||
"""
|
||||
migrations = [
|
||||
'ALTER TABLE projects ADD COLUMN drive_url VARCHAR(500)',
|
||||
'ALTER TABLE projects ADD COLUMN archived_at DATETIME',
|
||||
]
|
||||
|
||||
with db.engine.connect() as conn:
|
||||
for stmt in migrations:
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .extensions import db
|
||||
from datetime import datetime, date
|
||||
|
||||
|
||||
class Project(db.Model):
|
||||
__tablename__ = 'projects'
|
||||
|
||||
@@ -9,11 +10,14 @@ class Project(db.Model):
|
||||
color = db.Column(db.String(7), nullable=False, default='#C9A84C')
|
||||
description = db.Column(db.Text)
|
||||
drive_url = db.Column(db.String(500))
|
||||
archived_at = db.Column(db.DateTime, nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
deliverables = db.relationship(
|
||||
'Deliverable', backref='project',
|
||||
cascade='all, delete-orphan', lazy=True
|
||||
'Deliverable',
|
||||
backref='project',
|
||||
cascade='all, delete-orphan',
|
||||
lazy=True,
|
||||
)
|
||||
|
||||
def to_dict(self, include_deliverables=True):
|
||||
@@ -23,6 +27,7 @@ class Project(db.Model):
|
||||
'color': self.color,
|
||||
'description': self.description,
|
||||
'drive_url': self.drive_url,
|
||||
'archived_at': self.archived_at.isoformat() if self.archived_at else None,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
if include_deliverables:
|
||||
@@ -36,7 +41,11 @@ class Deliverable(db.Model):
|
||||
__tablename__ = 'deliverables'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
project_id = db.Column(db.Integer, db.ForeignKey('projects.id', ondelete='CASCADE'), nullable=False)
|
||||
project_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('projects.id', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
)
|
||||
title = db.Column(db.String(300), nullable=False)
|
||||
due_date = db.Column(db.Date, nullable=False)
|
||||
status = db.Column(db.String(20), nullable=False, default='upcoming')
|
||||
@@ -44,9 +53,8 @@ class Deliverable(db.Model):
|
||||
|
||||
def effective_status(self):
|
||||
"""
|
||||
Returns 'overdue' if the due date has passed and the deliverable
|
||||
has not been marked completed. Completed deliverables are never
|
||||
auto-downgraded regardless of date.
|
||||
Returns 'overdue' if the due date has passed and the deliverable has not been
|
||||
marked completed. Completed deliverables are never auto-downgraded.
|
||||
"""
|
||||
if self.status != 'completed' and self.due_date < date.today():
|
||||
return 'overdue'
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from ..models import Project, Deliverable
|
||||
from ..extensions import db
|
||||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
|
||||
projects_bp = Blueprint('projects', __name__)
|
||||
|
||||
|
||||
@projects_bp.route('/projects', methods=['GET'])
|
||||
def get_projects():
|
||||
projects = Project.query.order_by(Project.created_at.desc()).all()
|
||||
return jsonify([p.to_dict() for p in projects])
|
||||
|
||||
|
||||
@projects_bp.route('/projects/<int:id>', methods=['GET'])
|
||||
def get_project(id):
|
||||
project = Project.query.get_or_404(id)
|
||||
return jsonify(project.to_dict())
|
||||
|
||||
|
||||
@projects_bp.route('/projects', methods=['POST'])
|
||||
def create_project():
|
||||
data = request.get_json()
|
||||
@@ -26,6 +29,7 @@ def create_project():
|
||||
)
|
||||
db.session.add(project)
|
||||
db.session.flush()
|
||||
|
||||
for d in data.get('deliverables', []):
|
||||
if d.get('title') and d.get('due_date'):
|
||||
db.session.add(Deliverable(
|
||||
@@ -34,9 +38,11 @@ def create_project():
|
||||
due_date=date.fromisoformat(d['due_date']),
|
||||
status=d.get('status', 'upcoming'),
|
||||
))
|
||||
|
||||
db.session.commit()
|
||||
return jsonify(project.to_dict()), 201
|
||||
|
||||
|
||||
@projects_bp.route('/projects/<int:id>', methods=['PATCH'])
|
||||
def update_project(id):
|
||||
project = Project.query.get_or_404(id)
|
||||
@@ -47,6 +53,23 @@ def update_project(id):
|
||||
db.session.commit()
|
||||
return jsonify(project.to_dict())
|
||||
|
||||
|
||||
@projects_bp.route('/projects/<int:id>/archive', methods=['PATCH'])
|
||||
def archive_project(id):
|
||||
project = Project.query.get_or_404(id)
|
||||
project.archived_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
return jsonify(project.to_dict())
|
||||
|
||||
|
||||
@projects_bp.route('/projects/<int:id>/unarchive', methods=['PATCH'])
|
||||
def unarchive_project(id):
|
||||
project = Project.query.get_or_404(id)
|
||||
project.archived_at = None
|
||||
db.session.commit()
|
||||
return jsonify(project.to_dict())
|
||||
|
||||
|
||||
@projects_bp.route('/projects/<int:id>', methods=['DELETE'])
|
||||
def delete_project(id):
|
||||
project = Project.query.get_or_404(id)
|
||||
|
||||
Reference in New Issue
Block a user