From efc6ee9c5338907b81122b9a67e962f22b68c197 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 15:54:59 +0000 Subject: [PATCH] Add comprehensive scholarship management application This commit adds a complete local scholarship management system with the following features: - Database layer with SQLite for persistent storage - Models for Scholarships, Students, and Applications with full CRUD operations - Interactive CLI interface with menu-driven navigation - Application tracking with multiple status states - Search and filtering capabilities - Sample data loader for quick testing - Comprehensive documentation (README and Quick Start guide) - Basic test suite to verify functionality The application allows users to: - Manage scholarship listings with details, amounts, and deadlines - Maintain student/applicant records with academic information - Track scholarship applications with status updates - View statistics and reports - Search across all entities All files follow Python best practices with proper module structure, documentation, and error handling. --- .gitignore | 6 +- scholarship_app/QUICKSTART.md | 109 ++++ scholarship_app/README.md | 269 +++++++++ scholarship_app/__init__.py | 7 + scholarship_app/cli.py | 743 +++++++++++++++++++++++++ scholarship_app/database/__init__.py | 4 + scholarship_app/database/db_manager.py | 105 ++++ scholarship_app/main.py | 15 + scholarship_app/models/__init__.py | 6 + scholarship_app/models/application.py | 177 ++++++ scholarship_app/models/scholarship.py | 117 ++++ scholarship_app/models/student.py | 136 +++++ scholarship_app/requirements.txt | 1 + scholarship_app/tests/test_basic.py | 106 ++++ scholarship_app/utils/__init__.py | 1 + scholarship_app/utils/sample_data.py | 149 +++++ 16 files changed, 1950 insertions(+), 1 deletion(-) create mode 100644 scholarship_app/QUICKSTART.md create mode 100644 scholarship_app/README.md create mode 100644 scholarship_app/__init__.py create mode 100644 scholarship_app/cli.py create mode 100644 scholarship_app/database/__init__.py create mode 100644 scholarship_app/database/db_manager.py create mode 100644 scholarship_app/main.py create mode 100644 scholarship_app/models/__init__.py create mode 100644 scholarship_app/models/application.py create mode 100644 scholarship_app/models/scholarship.py create mode 100644 scholarship_app/models/student.py create mode 100644 scholarship_app/requirements.txt create mode 100644 scholarship_app/tests/test_basic.py create mode 100644 scholarship_app/utils/__init__.py create mode 100644 scholarship_app/utils/sample_data.py diff --git a/.gitignore b/.gitignore index ff6d83fa..a45bc1b3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,8 @@ **/.idea/ .ipynb_checkpoints/ **/.ipynb_checkpoints/ -**/.cache/ \ No newline at end of file +**/.cache/ +__pycache__/ +**/__pycache__/ +*.db +**/*.db \ No newline at end of file diff --git a/scholarship_app/QUICKSTART.md b/scholarship_app/QUICKSTART.md new file mode 100644 index 00000000..d35e3a05 --- /dev/null +++ b/scholarship_app/QUICKSTART.md @@ -0,0 +1,109 @@ +# Quick Start Guide + +## Get Started in 3 Steps + +### 1. Install Dependencies +```bash +cd scholarship_app +pip install -r requirements.txt +``` + +### 2. Load Sample Data (Optional) +```bash +python utils/sample_data.py +``` + +This will create: +- 5 sample scholarships +- 5 sample students +- 8 sample applications + +### 3. Run the Application +```bash +python main.py +``` + +or + +```bash +python cli.py +``` + +## First Time Usage + +When you first run the application, the menu will appear: + +``` +============================================================ +========== SCHOLARSHIP MANAGEMENT SYSTEM ================== +============================================================ + +[1] Manage Scholarships +[2] Manage Students +[3] Manage Applications +[4] View Statistics +[5] Search +[0] Exit +``` + +### Try These Common Tasks + +#### View All Scholarships +1. Select `[1] Manage Scholarships` +2. Select `[2] View All Scholarships` + +#### Submit an Application +1. Select `[3] Manage Applications` +2. Select `[1] Submit New Application` +3. Enter Student ID (e.g., 1) +4. Enter Scholarship ID (e.g., 1) +5. Add optional notes + +#### View Statistics +- Select `[4] View Statistics` from main menu + +#### Search for a Scholarship +1. Select `[1] Manage Scholarships` +2. Select `[6] Search Scholarships` +3. Enter keyword (e.g., "STEM") + +## Sample IDs (if you loaded sample data) + +**Students:** +- ID 1: Emily Johnson +- ID 2: Michael Chen +- ID 3: Sarah Williams +- ID 4: David Martinez +- ID 5: Jessica Taylor + +**Scholarships:** +- ID 1: Merit Excellence Scholarship ($5,000) +- ID 2: STEM Innovation Award ($7,500) +- ID 3: Community Service Scholarship ($3,000) +- ID 4: First Generation College Student Grant ($4,000) +- ID 5: Arts and Humanities Scholarship ($3,500) + +## Testing + +Run the test suite to verify everything is working: +```bash +python tests/test_basic.py +``` + +## Need Help? + +See the full [README.md](README.md) for detailed documentation. + +## Common Issues + +**Module not found error:** +- Make sure you're in the `scholarship_app` directory +- Verify dependencies are installed: `pip install -r requirements.txt` + +**Database locked error:** +- Close any other instances of the application +- Check if the database file exists: `ls database/scholarship.db` + +--- + +Enjoy managing scholarships! šŸŽ“ diff --git a/scholarship_app/README.md b/scholarship_app/README.md new file mode 100644 index 00000000..f0471dcc --- /dev/null +++ b/scholarship_app/README.md @@ -0,0 +1,269 @@ +# Scholarship Management System + +A comprehensive local application for managing scholarships, students, and scholarship applications. + +## Features + +### Scholarship Management +- Create, read, update, and delete scholarship records +- Track scholarship details including: + - Name and description + - Award amount + - Deadline + - Eligibility criteria + - Provider/organization +- Search scholarships by keyword +- Filter scholarships by amount range + +### Student Management +- Maintain student/applicant records +- Track student information: + - Personal details (name, email, phone, address) + - Academic information (GPA, graduation year) +- Search students by name or email +- Filter students by GPA range + +### Application Tracking +- Submit scholarship applications +- Track application status (pending, under review, approved, rejected, awarded) +- View applications by student +- View applications by scholarship +- Filter applications by status +- Add notes to applications +- Prevent duplicate applications + +### Statistics & Reporting +- View overall application statistics +- Track pending, approved, rejected, and awarded applications +- Monitor total students and scholarships + +## Installation + +### Prerequisites +- Python 3.7 or higher +- pip (Python package manager) + +### Setup + +1. Navigate to the scholarship_app directory: +```bash +cd scholarship_app +``` + +2. Install required dependencies: +```bash +pip install -r requirements.txt +``` + +3. (Optional) Load sample data: +```bash +python utils/sample_data.py +``` + +## Usage + +### Running the Application + +Start the application by running: +```bash +python main.py +``` + +Or alternatively: +```bash +python cli.py +``` + +### Main Menu Options + +The application provides an intuitive menu-driven interface: + +1. **Manage Scholarships** - Add, view, update, delete, and search scholarships +2. **Manage Students** - Add, view, update, delete, and search students +3. **Manage Applications** - Submit and track scholarship applications +4. **View Statistics** - See overall system statistics +5. **Search** - Quick search for scholarships or students +0. **Exit** - Close the application + +### Sample Workflows + +#### Adding a New Scholarship +1. Select "Manage Scholarships" from main menu +2. Choose "Add New Scholarship" +3. Enter scholarship details (name, amount, deadline, etc.) +4. Scholarship is saved to the database + +#### Submitting an Application +1. Select "Manage Applications" from main menu +2. Choose "Submit New Application" +3. Enter student ID and scholarship ID +4. Add optional notes +5. Application is created with 'pending' status + +#### Updating Application Status +1. Select "Manage Applications" from main menu +2. Choose "Update Application Status" +3. Enter application ID +4. Select new status (pending, under review, approved, rejected, awarded) +5. Add optional notes +6. Status is updated in the database + +## Database Structure + +The application uses SQLite database with three main tables: + +### scholarships +- id (Primary Key) +- name +- description +- amount +- deadline +- eligibility_criteria +- provider +- created_at +- updated_at + +### students +- id (Primary Key) +- first_name +- last_name +- email (Unique) +- phone +- address +- gpa +- graduation_year +- created_at +- updated_at + +### applications +- id (Primary Key) +- student_id (Foreign Key) +- scholarship_id (Foreign Key) +- application_date +- status +- notes +- created_at +- updated_at + +## Project Structure + +``` +scholarship_app/ +│ +ā”œā”€ā”€ database/ +│ ā”œā”€ā”€ __init__.py +│ ā”œā”€ā”€ db_manager.py # Database connection and schema +│ └── scholarship.db # SQLite database (created on first run) +│ +ā”œā”€ā”€ models/ +│ ā”œā”€ā”€ __init__.py +│ ā”œā”€ā”€ scholarship.py # Scholarship model and operations +│ ā”œā”€ā”€ student.py # Student model and operations +│ └── application.py # Application model and operations +│ +ā”œā”€ā”€ utils/ +│ ā”œā”€ā”€ __init__.py +│ └── sample_data.py # Sample data loader +│ +ā”œā”€ā”€ tests/ # Test directory (for future tests) +│ +ā”œā”€ā”€ __init__.py +ā”œā”€ā”€ main.py # Main entry point +ā”œā”€ā”€ cli.py # Command-line interface +ā”œā”€ā”€ requirements.txt # Python dependencies +└── README.md # This file +``` + +## Features Explained + +### CRUD Operations +- **Create**: Add new scholarships, students, and applications +- **Read**: View all records or individual details +- **Update**: Modify existing records +- **Delete**: Remove records (with confirmation) + +### Search Functionality +- Search scholarships by name, provider, or description +- Search students by name or email +- All searches are case-insensitive and support partial matches + +### Data Validation +- Email uniqueness for students +- Prevents duplicate applications (same student + scholarship) +- Foreign key constraints ensure data integrity + +### User Interface +- Clean, menu-driven CLI +- Tabular data display using tabulate library +- Clear prompts and confirmations +- Status indicators (āœ“ for success, āœ— for errors) + +## Sample Data + +The application includes a sample data loader that creates: +- 5 sample scholarships +- 5 sample students +- 8 sample applications + +To load sample data: +```bash +python utils/sample_data.py +``` + +## Tips + +1. **Start with Sample Data**: Load sample data to explore the application features +2. **Unique Emails**: Student emails must be unique +3. **Application Statuses**: Available statuses are: + - pending + - under review + - approved + - rejected + - awarded +4. **Date Format**: Use YYYY-MM-DD format for deadlines +5. **Backup**: The database file (scholarship.db) can be backed up by copying it + +## Future Enhancements + +Potential features for future versions: +- Web-based interface +- Email notifications for deadlines +- Document upload support +- Reporting and analytics dashboard +- Export to CSV/PDF +- Multi-user support with authentication +- Scholarship recommendation system + +## Technical Details + +- **Language**: Python 3.7+ +- **Database**: SQLite3 +- **UI Library**: tabulate (for table formatting) +- **Architecture**: Model-based with separated database layer + +## Troubleshooting + +### Database Locked Error +If you get a "database is locked" error, ensure no other instance of the application is running. + +### Import Errors +Make sure all dependencies are installed: +```bash +pip install -r requirements.txt +``` + +### Module Not Found +Ensure you're running the application from the correct directory (scholarship_app). + +## License + +This project is provided as-is for educational and local use. + +## Contact + +For questions or issues, please refer to the project repository. + +--- + +**Version**: 1.0.0 +**Last Updated**: November 2024 diff --git a/scholarship_app/__init__.py b/scholarship_app/__init__.py new file mode 100644 index 00000000..c48b3495 --- /dev/null +++ b/scholarship_app/__init__.py @@ -0,0 +1,7 @@ +""" +Scholarship Management Application +A local application to manage scholarships, students, and applications +""" + +__version__ = '1.0.0' +__author__ = 'Scholarship App Team' diff --git a/scholarship_app/cli.py b/scholarship_app/cli.py new file mode 100644 index 00000000..1ce0ef5d --- /dev/null +++ b/scholarship_app/cli.py @@ -0,0 +1,743 @@ +""" +Command Line Interface for Scholarship Management Application +""" + +import sys +import os +from datetime import datetime +from tabulate import tabulate + +# Add parent directory to path +parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, parent_dir) + +from scholarship_app.database import DatabaseManager +from scholarship_app.models import Scholarship, Student, Application + + +class ScholarshipCLI: + def __init__(self): + self.db = DatabaseManager() + self.db.create_tables() + + def clear_screen(self): + """Clear the terminal screen""" + os.system('clear' if os.name == 'posix' else 'cls') + + def display_menu(self): + """Display main menu""" + print("\n" + "="*60) + print(" SCHOLARSHIP MANAGEMENT SYSTEM ".center(60, "=")) + print("="*60) + print("\n[1] Manage Scholarships") + print("[2] Manage Students") + print("[3] Manage Applications") + print("[4] View Statistics") + print("[5] Search") + print("[0] Exit") + print("-"*60) + + def scholarship_menu(self): + """Scholarship management menu""" + while True: + print("\n" + "="*60) + print(" SCHOLARSHIP MANAGEMENT ".center(60, "=")) + print("="*60) + print("\n[1] Add New Scholarship") + print("[2] View All Scholarships") + print("[3] View Scholarship Details") + print("[4] Update Scholarship") + print("[5] Delete Scholarship") + print("[6] Search Scholarships") + print("[0] Back to Main Menu") + print("-"*60) + + choice = input("\nEnter your choice: ").strip() + + if choice == '1': + self.add_scholarship() + elif choice == '2': + self.view_all_scholarships() + elif choice == '3': + self.view_scholarship_details() + elif choice == '4': + self.update_scholarship() + elif choice == '5': + self.delete_scholarship() + elif choice == '6': + self.search_scholarships() + elif choice == '0': + break + else: + print("Invalid choice. Please try again.") + + def student_menu(self): + """Student management menu""" + while True: + print("\n" + "="*60) + print(" STUDENT MANAGEMENT ".center(60, "=")) + print("="*60) + print("\n[1] Add New Student") + print("[2] View All Students") + print("[3] View Student Details") + print("[4] Update Student") + print("[5] Delete Student") + print("[6] Search Students") + print("[0] Back to Main Menu") + print("-"*60) + + choice = input("\nEnter your choice: ").strip() + + if choice == '1': + self.add_student() + elif choice == '2': + self.view_all_students() + elif choice == '3': + self.view_student_details() + elif choice == '4': + self.update_student() + elif choice == '5': + self.delete_student() + elif choice == '6': + self.search_students() + elif choice == '0': + break + else: + print("Invalid choice. Please try again.") + + def application_menu(self): + """Application management menu""" + while True: + print("\n" + "="*60) + print(" APPLICATION MANAGEMENT ".center(60, "=")) + print("="*60) + print("\n[1] Submit New Application") + print("[2] View All Applications") + print("[3] View Application Details") + print("[4] Update Application Status") + print("[5] View Applications by Student") + print("[6] View Applications by Scholarship") + print("[7] View Applications by Status") + print("[8] Delete Application") + print("[0] Back to Main Menu") + print("-"*60) + + choice = input("\nEnter your choice: ").strip() + + if choice == '1': + self.submit_application() + elif choice == '2': + self.view_all_applications() + elif choice == '3': + self.view_application_details() + elif choice == '4': + self.update_application_status() + elif choice == '5': + self.view_applications_by_student() + elif choice == '6': + self.view_applications_by_scholarship() + elif choice == '7': + self.view_applications_by_status() + elif choice == '8': + self.delete_application() + elif choice == '0': + break + else: + print("Invalid choice. Please try again.") + + # ==================== Scholarship Methods ==================== + + def add_scholarship(self): + """Add a new scholarship""" + print("\n--- Add New Scholarship ---") + name = input("Scholarship Name: ").strip() + description = input("Description: ").strip() + amount = float(input("Amount ($): ")) + deadline = input("Deadline (YYYY-MM-DD): ").strip() + eligibility = input("Eligibility Criteria: ").strip() + provider = input("Provider/Organization: ").strip() + + scholarship_id = Scholarship.create( + name=name, + amount=amount, + deadline=deadline, + description=description, + eligibility_criteria=eligibility, + provider=provider + ) + + print(f"\nāœ“ Scholarship added successfully! (ID: {scholarship_id})") + + def view_all_scholarships(self): + """View all scholarships""" + scholarships = Scholarship.get_all() + + if not scholarships: + print("\nNo scholarships found.") + return + + print(f"\n--- All Scholarships (Total: {len(scholarships)}) ---") + table_data = [] + for s in scholarships: + table_data.append([ + s['id'], + s['name'][:30], + f"${s['amount']:.2f}", + s['deadline'], + s['provider'][:20] if s['provider'] else 'N/A' + ]) + + headers = ['ID', 'Name', 'Amount', 'Deadline', 'Provider'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + def view_scholarship_details(self): + """View detailed information about a scholarship""" + scholarship_id = int(input("\nEnter Scholarship ID: ")) + scholarship = Scholarship.get_by_id(scholarship_id) + + if not scholarship: + print(f"\nScholarship with ID {scholarship_id} not found.") + return + + print("\n--- Scholarship Details ---") + print(f"ID: {scholarship['id']}") + print(f"Name: {scholarship['name']}") + print(f"Description: {scholarship['description'] or 'N/A'}") + print(f"Amount: ${scholarship['amount']:.2f}") + print(f"Deadline: {scholarship['deadline']}") + print(f"Eligibility: {scholarship['eligibility_criteria'] or 'N/A'}") + print(f"Provider: {scholarship['provider'] or 'N/A'}") + + # Show applications for this scholarship + applications = Application.get_by_scholarship(scholarship_id) + print(f"\nTotal Applications: {len(applications)}") + + def update_scholarship(self): + """Update scholarship information""" + scholarship_id = int(input("\nEnter Scholarship ID to update: ")) + scholarship = Scholarship.get_by_id(scholarship_id) + + if not scholarship: + print(f"\nScholarship with ID {scholarship_id} not found.") + return + + print("\nLeave blank to keep current value") + name = input(f"Name [{scholarship['name']}]: ").strip() + description = input(f"Description [{scholarship['description']}]: ").strip() + amount = input(f"Amount [{scholarship['amount']}]: ").strip() + deadline = input(f"Deadline [{scholarship['deadline']}]: ").strip() + eligibility = input(f"Eligibility [{scholarship['eligibility_criteria']}]: ").strip() + provider = input(f"Provider [{scholarship['provider']}]: ").strip() + + update_data = {} + if name: + update_data['name'] = name + if description: + update_data['description'] = description + if amount: + update_data['amount'] = float(amount) + if deadline: + update_data['deadline'] = deadline + if eligibility: + update_data['eligibility_criteria'] = eligibility + if provider: + update_data['provider'] = provider + + if update_data: + Scholarship.update(scholarship_id, **update_data) + print("\nāœ“ Scholarship updated successfully!") + else: + print("\nNo changes made.") + + def delete_scholarship(self): + """Delete a scholarship""" + scholarship_id = int(input("\nEnter Scholarship ID to delete: ")) + scholarship = Scholarship.get_by_id(scholarship_id) + + if not scholarship: + print(f"\nScholarship with ID {scholarship_id} not found.") + return + + confirm = input(f"\nAre you sure you want to delete '{scholarship['name']}'? (yes/no): ") + if confirm.lower() == 'yes': + Scholarship.delete(scholarship_id) + print("\nāœ“ Scholarship deleted successfully!") + else: + print("\nDeletion cancelled.") + + def search_scholarships(self): + """Search scholarships""" + keyword = input("\nEnter search keyword: ").strip() + scholarships = Scholarship.search(keyword) + + if not scholarships: + print(f"\nNo scholarships found matching '{keyword}'.") + return + + print(f"\n--- Search Results (Found: {len(scholarships)}) ---") + table_data = [] + for s in scholarships: + table_data.append([ + s['id'], + s['name'][:30], + f"${s['amount']:.2f}", + s['deadline'], + s['provider'][:20] if s['provider'] else 'N/A' + ]) + + headers = ['ID', 'Name', 'Amount', 'Deadline', 'Provider'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + # ==================== Student Methods ==================== + + def add_student(self): + """Add a new student""" + print("\n--- Add New Student ---") + first_name = input("First Name: ").strip() + last_name = input("Last Name: ").strip() + email = input("Email: ").strip() + phone = input("Phone (optional): ").strip() + address = input("Address (optional): ").strip() + gpa = input("GPA (optional): ").strip() + graduation_year = input("Graduation Year (optional): ").strip() + + student_id = Student.create( + first_name=first_name, + last_name=last_name, + email=email, + phone=phone if phone else None, + address=address if address else None, + gpa=float(gpa) if gpa else None, + graduation_year=int(graduation_year) if graduation_year else None + ) + + if student_id: + print(f"\nāœ“ Student added successfully! (ID: {student_id})") + else: + print("\nāœ— Error adding student. Email might already exist.") + + def view_all_students(self): + """View all students""" + students = Student.get_all() + + if not students: + print("\nNo students found.") + return + + print(f"\n--- All Students (Total: {len(students)}) ---") + table_data = [] + for s in students: + table_data.append([ + s['id'], + f"{s['first_name']} {s['last_name']}", + s['email'], + s['gpa'] if s['gpa'] else 'N/A', + s['graduation_year'] if s['graduation_year'] else 'N/A' + ]) + + headers = ['ID', 'Name', 'Email', 'GPA', 'Grad Year'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + def view_student_details(self): + """View detailed information about a student""" + student_id = int(input("\nEnter Student ID: ")) + student = Student.get_by_id(student_id) + + if not student: + print(f"\nStudent with ID {student_id} not found.") + return + + print("\n--- Student Details ---") + print(f"ID: {student['id']}") + print(f"Name: {student['first_name']} {student['last_name']}") + print(f"Email: {student['email']}") + print(f"Phone: {student['phone'] or 'N/A'}") + print(f"Address: {student['address'] or 'N/A'}") + print(f"GPA: {student['gpa'] or 'N/A'}") + print(f"Graduation Year: {student['graduation_year'] or 'N/A'}") + + # Show applications by this student + applications = Application.get_by_student(student_id) + print(f"\nTotal Applications: {len(applications)}") + + def update_student(self): + """Update student information""" + student_id = int(input("\nEnter Student ID to update: ")) + student = Student.get_by_id(student_id) + + if not student: + print(f"\nStudent with ID {student_id} not found.") + return + + print("\nLeave blank to keep current value") + first_name = input(f"First Name [{student['first_name']}]: ").strip() + last_name = input(f"Last Name [{student['last_name']}]: ").strip() + email = input(f"Email [{student['email']}]: ").strip() + phone = input(f"Phone [{student['phone']}]: ").strip() + address = input(f"Address [{student['address']}]: ").strip() + gpa = input(f"GPA [{student['gpa']}]: ").strip() + graduation_year = input(f"Graduation Year [{student['graduation_year']}]: ").strip() + + update_data = {} + if first_name: + update_data['first_name'] = first_name + if last_name: + update_data['last_name'] = last_name + if email: + update_data['email'] = email + if phone: + update_data['phone'] = phone + if address: + update_data['address'] = address + if gpa: + update_data['gpa'] = float(gpa) + if graduation_year: + update_data['graduation_year'] = int(graduation_year) + + if update_data: + Student.update(student_id, **update_data) + print("\nāœ“ Student updated successfully!") + else: + print("\nNo changes made.") + + def delete_student(self): + """Delete a student""" + student_id = int(input("\nEnter Student ID to delete: ")) + student = Student.get_by_id(student_id) + + if not student: + print(f"\nStudent with ID {student_id} not found.") + return + + confirm = input(f"\nAre you sure you want to delete '{student['first_name']} {student['last_name']}'? (yes/no): ") + if confirm.lower() == 'yes': + Student.delete(student_id) + print("\nāœ“ Student deleted successfully!") + else: + print("\nDeletion cancelled.") + + def search_students(self): + """Search students""" + keyword = input("\nEnter search keyword: ").strip() + students = Student.search(keyword) + + if not students: + print(f"\nNo students found matching '{keyword}'.") + return + + print(f"\n--- Search Results (Found: {len(students)}) ---") + table_data = [] + for s in students: + table_data.append([ + s['id'], + f"{s['first_name']} {s['last_name']}", + s['email'], + s['gpa'] if s['gpa'] else 'N/A', + s['graduation_year'] if s['graduation_year'] else 'N/A' + ]) + + headers = ['ID', 'Name', 'Email', 'GPA', 'Grad Year'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + # ==================== Application Methods ==================== + + def submit_application(self): + """Submit a new scholarship application""" + print("\n--- Submit New Application ---") + + # Show available students + students = Student.get_all() + if not students: + print("No students found. Please add a student first.") + return + + # Show available scholarships + scholarships = Scholarship.get_all() + if not scholarships: + print("No scholarships found. Please add a scholarship first.") + return + + student_id = int(input("Enter Student ID: ")) + if not Student.get_by_id(student_id): + print("Invalid Student ID.") + return + + scholarship_id = int(input("Enter Scholarship ID: ")) + if not Scholarship.get_by_id(scholarship_id): + print("Invalid Scholarship ID.") + return + + notes = input("Notes (optional): ").strip() + + application_id = Application.create( + student_id=student_id, + scholarship_id=scholarship_id, + status='pending', + notes=notes if notes else None + ) + + if application_id: + print(f"\nāœ“ Application submitted successfully! (ID: {application_id})") + else: + print("\nāœ— Error submitting application. Application might already exist.") + + def view_all_applications(self): + """View all applications""" + applications = Application.get_all() + + if not applications: + print("\nNo applications found.") + return + + print(f"\n--- All Applications (Total: {len(applications)}) ---") + table_data = [] + for a in applications: + table_data.append([ + a['id'], + a['student_name'][:25], + a['scholarship_name'][:25], + f"${a['scholarship_amount']:.2f}", + a['status'], + a['application_date'][:10] + ]) + + headers = ['ID', 'Student', 'Scholarship', 'Amount', 'Status', 'Date'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + def view_application_details(self): + """View detailed information about an application""" + application_id = int(input("\nEnter Application ID: ")) + application = Application.get_by_id(application_id) + + if not application: + print(f"\nApplication with ID {application_id} not found.") + return + + print("\n--- Application Details ---") + print(f"ID: {application['id']}") + print(f"Student: {application['student_name']} ({application['student_email']})") + print(f"Scholarship: {application['scholarship_name']}") + print(f"Amount: ${application['scholarship_amount']:.2f}") + print(f"Status: {application['status'].upper()}") + print(f"Application Date: {application['application_date']}") + print(f"Notes: {application['notes'] or 'N/A'}") + + def update_application_status(self): + """Update application status""" + application_id = int(input("\nEnter Application ID: ")) + application = Application.get_by_id(application_id) + + if not application: + print(f"\nApplication with ID {application_id} not found.") + return + + print(f"\nCurrent Status: {application['status']}") + print("\nAvailable statuses:") + print("[1] Pending") + print("[2] Under Review") + print("[3] Approved") + print("[4] Rejected") + print("[5] Awarded") + + choice = input("\nSelect new status: ").strip() + + status_map = { + '1': 'pending', + '2': 'under review', + '3': 'approved', + '4': 'rejected', + '5': 'awarded' + } + + if choice in status_map: + new_status = status_map[choice] + notes = input("Additional notes (optional): ").strip() + + Application.update_status( + application_id, + new_status, + notes if notes else None + ) + print(f"\nāœ“ Application status updated to '{new_status}'!") + else: + print("\nInvalid choice.") + + def view_applications_by_student(self): + """View all applications for a student""" + student_id = int(input("\nEnter Student ID: ")) + student = Student.get_by_id(student_id) + + if not student: + print(f"\nStudent with ID {student_id} not found.") + return + + applications = Application.get_by_student(student_id) + + if not applications: + print(f"\nNo applications found for {student['first_name']} {student['last_name']}.") + return + + print(f"\n--- Applications by {student['first_name']} {student['last_name']} ---") + table_data = [] + for a in applications: + table_data.append([ + a['id'], + a['scholarship_name'][:30], + f"${a['scholarship_amount']:.2f}", + a['status'], + a['application_date'][:10] + ]) + + headers = ['ID', 'Scholarship', 'Amount', 'Status', 'Date'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + def view_applications_by_scholarship(self): + """View all applications for a scholarship""" + scholarship_id = int(input("\nEnter Scholarship ID: ")) + scholarship = Scholarship.get_by_id(scholarship_id) + + if not scholarship: + print(f"\nScholarship with ID {scholarship_id} not found.") + return + + applications = Application.get_by_scholarship(scholarship_id) + + if not applications: + print(f"\nNo applications found for {scholarship['name']}.") + return + + print(f"\n--- Applications for {scholarship['name']} ---") + table_data = [] + for a in applications: + table_data.append([ + a['id'], + a['student_name'][:30], + a['student_email'], + a['student_gpa'] if a['student_gpa'] else 'N/A', + a['status'], + a['application_date'][:10] + ]) + + headers = ['ID', 'Student', 'Email', 'GPA', 'Status', 'Date'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + def view_applications_by_status(self): + """View applications filtered by status""" + print("\nFilter by status:") + print("[1] Pending") + print("[2] Under Review") + print("[3] Approved") + print("[4] Rejected") + print("[5] Awarded") + + choice = input("\nSelect status: ").strip() + + status_map = { + '1': 'pending', + '2': 'under review', + '3': 'approved', + '4': 'rejected', + '5': 'awarded' + } + + if choice not in status_map: + print("\nInvalid choice.") + return + + status = status_map[choice] + applications = Application.get_by_status(status) + + if not applications: + print(f"\nNo applications found with status '{status}'.") + return + + print(f"\n--- Applications with Status: {status.upper()} ---") + table_data = [] + for a in applications: + table_data.append([ + a['id'], + a['student_name'][:25], + a['scholarship_name'][:25], + f"${a['scholarship_amount']:.2f}", + a['application_date'][:10] + ]) + + headers = ['ID', 'Student', 'Scholarship', 'Amount', 'Date'] + print(tabulate(table_data, headers=headers, tablefmt='grid')) + + def delete_application(self): + """Delete an application""" + application_id = int(input("\nEnter Application ID to delete: ")) + application = Application.get_by_id(application_id) + + if not application: + print(f"\nApplication with ID {application_id} not found.") + return + + confirm = input(f"\nAre you sure you want to delete this application? (yes/no): ") + if confirm.lower() == 'yes': + Application.delete(application_id) + print("\nāœ“ Application deleted successfully!") + else: + print("\nDeletion cancelled.") + + # ==================== Statistics ==================== + + def view_statistics(self): + """View application statistics""" + stats = Application.get_statistics() + + if not stats: + print("\nNo statistics available.") + return + + print("\n" + "="*60) + print(" APPLICATION STATISTICS ".center(60, "=")) + print("="*60) + print(f"\nTotal Applications: {stats['total_applications']}") + print(f"Pending: {stats['pending']}") + print(f"Approved: {stats['approved']}") + print(f"Rejected: {stats['rejected']}") + print(f"Awarded: {stats['awarded']}") + print("\nTotal Students: ", len(Student.get_all())) + print("Total Scholarships: ", len(Scholarship.get_all())) + + # ==================== Main Run ==================== + + def run(self): + """Main application loop""" + print("\n" + "="*60) + print(" WELCOME TO SCHOLARSHIP MANAGEMENT SYSTEM ".center(60, "=")) + print("="*60) + + while True: + self.display_menu() + choice = input("\nEnter your choice: ").strip() + + if choice == '1': + self.scholarship_menu() + elif choice == '2': + self.student_menu() + elif choice == '3': + self.application_menu() + elif choice == '4': + self.view_statistics() + elif choice == '5': + print("\n[1] Search Scholarships") + print("[2] Search Students") + search_choice = input("Enter your choice: ").strip() + if search_choice == '1': + self.search_scholarships() + elif search_choice == '2': + self.search_students() + elif choice == '0': + print("\nThank you for using Scholarship Management System!") + print("Goodbye!\n") + break + else: + print("\nInvalid choice. Please try again.") + + +if __name__ == '__main__': + app = ScholarshipCLI() + app.run() diff --git a/scholarship_app/database/__init__.py b/scholarship_app/database/__init__.py new file mode 100644 index 00000000..ce30a841 --- /dev/null +++ b/scholarship_app/database/__init__.py @@ -0,0 +1,4 @@ +"""Database package for scholarship application""" +from .db_manager import DatabaseManager + +__all__ = ['DatabaseManager'] diff --git a/scholarship_app/database/db_manager.py b/scholarship_app/database/db_manager.py new file mode 100644 index 00000000..2d43a733 --- /dev/null +++ b/scholarship_app/database/db_manager.py @@ -0,0 +1,105 @@ +""" +Database Manager for Scholarship Application +Handles SQLite database connection and schema creation +""" + +import sqlite3 +import os +from datetime import datetime + + +class DatabaseManager: + def __init__(self, db_name='scholarship.db'): + """Initialize database connection""" + self.db_path = os.path.join(os.path.dirname(__file__), db_name) + self.conn = None + self.cursor = None + + def connect(self): + """Establish database connection""" + self.conn = sqlite3.connect(self.db_path) + self.conn.row_factory = sqlite3.Row # Access columns by name + self.cursor = self.conn.cursor() + + def close(self): + """Close database connection""" + if self.conn: + self.conn.close() + + def create_tables(self): + """Create all required tables""" + self.connect() + + # Scholarships table + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS scholarships ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + amount REAL NOT NULL, + deadline TEXT NOT NULL, + eligibility_criteria TEXT, + provider TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + ''') + + # Students table + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS students ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL, + phone TEXT, + address TEXT, + gpa REAL, + graduation_year INTEGER, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + ''') + + # Applications table + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS applications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + student_id INTEGER NOT NULL, + scholarship_id INTEGER NOT NULL, + application_date TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + notes TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE, + FOREIGN KEY (scholarship_id) REFERENCES scholarships (id) ON DELETE CASCADE, + UNIQUE(student_id, scholarship_id) + ) + ''') + + self.conn.commit() + self.close() + + def execute_query(self, query, params=None): + """Execute a query and return results""" + self.connect() + if params: + self.cursor.execute(query, params) + else: + self.cursor.execute(query) + results = self.cursor.fetchall() + self.close() + return results + + def execute_update(self, query, params=None): + """Execute an update/insert/delete query""" + self.connect() + if params: + self.cursor.execute(query, params) + else: + self.cursor.execute(query) + self.conn.commit() + last_id = self.cursor.lastrowid + self.close() + return last_id diff --git a/scholarship_app/main.py b/scholarship_app/main.py new file mode 100644 index 00000000..3c21b498 --- /dev/null +++ b/scholarship_app/main.py @@ -0,0 +1,15 @@ +""" +Main entry point for the Scholarship Management Application +""" + +from cli import ScholarshipCLI + + +def main(): + """Run the application""" + app = ScholarshipCLI() + app.run() + + +if __name__ == '__main__': + main() diff --git a/scholarship_app/models/__init__.py b/scholarship_app/models/__init__.py new file mode 100644 index 00000000..eb74e461 --- /dev/null +++ b/scholarship_app/models/__init__.py @@ -0,0 +1,6 @@ +"""Models package for scholarship application""" +from .scholarship import Scholarship +from .student import Student +from .application import Application + +__all__ = ['Scholarship', 'Student', 'Application'] diff --git a/scholarship_app/models/application.py b/scholarship_app/models/application.py new file mode 100644 index 00000000..7a868473 --- /dev/null +++ b/scholarship_app/models/application.py @@ -0,0 +1,177 @@ +""" +Application Model +Handles scholarship application tracking +""" + +from datetime import datetime +from ..database import DatabaseManager + + +class Application: + def __init__(self, application_id=None, student_id=None, scholarship_id=None, + application_date=None, status='pending', notes=None): + self.id = application_id + self.student_id = student_id + self.scholarship_id = scholarship_id + self.application_date = application_date + self.status = status + self.notes = notes + self.db = DatabaseManager() + + @staticmethod + def create(student_id, scholarship_id, status='pending', notes=None): + """Create a new scholarship application""" + db = DatabaseManager() + now = datetime.now().isoformat() + + query = ''' + INSERT INTO applications + (student_id, scholarship_id, application_date, status, notes, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''' + params = (student_id, scholarship_id, now, status, notes, now, now) + + try: + application_id = db.execute_update(query, params) + return application_id + except Exception as e: + print(f"Error creating application: {e}") + return None + + @staticmethod + def get_all(): + """Get all applications with student and scholarship details""" + db = DatabaseManager() + query = ''' + SELECT + a.*, + s.first_name || ' ' || s.last_name as student_name, + s.email as student_email, + sc.name as scholarship_name, + sc.amount as scholarship_amount + FROM applications a + JOIN students s ON a.student_id = s.id + JOIN scholarships sc ON a.scholarship_id = sc.id + ORDER BY a.application_date DESC + ''' + results = db.execute_query(query) + return [dict(row) for row in results] + + @staticmethod + def get_by_id(application_id): + """Get application by ID with details""" + db = DatabaseManager() + query = ''' + SELECT + a.*, + s.first_name || ' ' || s.last_name as student_name, + s.email as student_email, + sc.name as scholarship_name, + sc.amount as scholarship_amount + FROM applications a + JOIN students s ON a.student_id = s.id + JOIN scholarships sc ON a.scholarship_id = sc.id + WHERE a.id = ? + ''' + results = db.execute_query(query, (application_id,)) + if results: + return dict(results[0]) + return None + + @staticmethod + def get_by_student(student_id): + """Get all applications for a specific student""" + db = DatabaseManager() + query = ''' + SELECT + a.*, + sc.name as scholarship_name, + sc.amount as scholarship_amount, + sc.deadline as scholarship_deadline + FROM applications a + JOIN scholarships sc ON a.scholarship_id = sc.id + WHERE a.student_id = ? + ORDER BY a.application_date DESC + ''' + results = db.execute_query(query, (student_id,)) + return [dict(row) for row in results] + + @staticmethod + def get_by_scholarship(scholarship_id): + """Get all applications for a specific scholarship""" + db = DatabaseManager() + query = ''' + SELECT + a.*, + s.first_name || ' ' || s.last_name as student_name, + s.email as student_email, + s.gpa as student_gpa + FROM applications a + JOIN students s ON a.student_id = s.id + WHERE a.scholarship_id = ? + ORDER BY a.application_date DESC + ''' + results = db.execute_query(query, (scholarship_id,)) + return [dict(row) for row in results] + + @staticmethod + def get_by_status(status): + """Get all applications with a specific status""" + db = DatabaseManager() + query = ''' + SELECT + a.*, + s.first_name || ' ' || s.last_name as student_name, + s.email as student_email, + sc.name as scholarship_name, + sc.amount as scholarship_amount + FROM applications a + JOIN students s ON a.student_id = s.id + JOIN scholarships sc ON a.scholarship_id = sc.id + WHERE a.status = ? + ORDER BY a.application_date DESC + ''' + results = db.execute_query(query, (status,)) + return [dict(row) for row in results] + + @staticmethod + def update_status(application_id, status, notes=None): + """Update application status""" + db = DatabaseManager() + now = datetime.now().isoformat() + + if notes: + query = 'UPDATE applications SET status = ?, notes = ?, updated_at = ? WHERE id = ?' + params = (status, notes, now, application_id) + else: + query = 'UPDATE applications SET status = ?, updated_at = ? WHERE id = ?' + params = (status, now, application_id) + + db.execute_update(query, params) + return True + + @staticmethod + def delete(application_id): + """Delete an application""" + db = DatabaseManager() + query = 'DELETE FROM applications WHERE id = ?' + db.execute_update(query, (application_id,)) + return True + + @staticmethod + def get_statistics(): + """Get application statistics""" + db = DatabaseManager() + query = ''' + SELECT + COUNT(*) as total_applications, + SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending, + SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as approved, + SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected, + SUM(CASE WHEN status = 'awarded' THEN 1 ELSE 0 END) as awarded + FROM applications + ''' + results = db.execute_query(query) + if results: + return dict(results[0]) + return None diff --git a/scholarship_app/models/scholarship.py b/scholarship_app/models/scholarship.py new file mode 100644 index 00000000..983e9250 --- /dev/null +++ b/scholarship_app/models/scholarship.py @@ -0,0 +1,117 @@ +""" +Scholarship Model +Handles all scholarship-related database operations +""" + +from datetime import datetime +from ..database import DatabaseManager + + +class Scholarship: + def __init__(self, scholarship_id=None, name=None, description=None, + amount=None, deadline=None, eligibility_criteria=None, provider=None): + self.id = scholarship_id + self.name = name + self.description = description + self.amount = amount + self.deadline = deadline + self.eligibility_criteria = eligibility_criteria + self.provider = provider + self.db = DatabaseManager() + + @staticmethod + def create(name, amount, deadline, description=None, + eligibility_criteria=None, provider=None): + """Create a new scholarship""" + db = DatabaseManager() + now = datetime.now().isoformat() + + query = ''' + INSERT INTO scholarships + (name, description, amount, deadline, eligibility_criteria, provider, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''' + params = (name, description, amount, deadline, eligibility_criteria, provider, now, now) + + scholarship_id = db.execute_update(query, params) + return scholarship_id + + @staticmethod + def get_all(): + """Get all scholarships""" + db = DatabaseManager() + query = 'SELECT * FROM scholarships ORDER BY deadline ASC' + results = db.execute_query(query) + return [dict(row) for row in results] + + @staticmethod + def get_by_id(scholarship_id): + """Get scholarship by ID""" + db = DatabaseManager() + query = 'SELECT * FROM scholarships WHERE id = ?' + results = db.execute_query(query, (scholarship_id,)) + if results: + return dict(results[0]) + return None + + @staticmethod + def update(scholarship_id, **kwargs): + """Update scholarship details""" + db = DatabaseManager() + now = datetime.now().isoformat() + + # Build dynamic update query based on provided fields + fields = [] + values = [] + + allowed_fields = ['name', 'description', 'amount', 'deadline', + 'eligibility_criteria', 'provider'] + + for field, value in kwargs.items(): + if field in allowed_fields: + fields.append(f'{field} = ?') + values.append(value) + + if not fields: + return False + + fields.append('updated_at = ?') + values.append(now) + values.append(scholarship_id) + + query = f"UPDATE scholarships SET {', '.join(fields)} WHERE id = ?" + db.execute_update(query, tuple(values)) + return True + + @staticmethod + def delete(scholarship_id): + """Delete a scholarship""" + db = DatabaseManager() + query = 'DELETE FROM scholarships WHERE id = ?' + db.execute_update(query, (scholarship_id,)) + return True + + @staticmethod + def search(keyword): + """Search scholarships by name or provider""" + db = DatabaseManager() + query = ''' + SELECT * FROM scholarships + WHERE name LIKE ? OR provider LIKE ? OR description LIKE ? + ORDER BY deadline ASC + ''' + search_term = f'%{keyword}%' + results = db.execute_query(query, (search_term, search_term, search_term)) + return [dict(row) for row in results] + + @staticmethod + def get_by_amount_range(min_amount, max_amount): + """Get scholarships within an amount range""" + db = DatabaseManager() + query = ''' + SELECT * FROM scholarships + WHERE amount BETWEEN ? AND ? + ORDER BY amount DESC + ''' + results = db.execute_query(query, (min_amount, max_amount)) + return [dict(row) for row in results] diff --git a/scholarship_app/models/student.py b/scholarship_app/models/student.py new file mode 100644 index 00000000..4f21fabe --- /dev/null +++ b/scholarship_app/models/student.py @@ -0,0 +1,136 @@ +""" +Student Model +Handles all student/applicant-related database operations +""" + +from datetime import datetime +from ..database import DatabaseManager + + +class Student: + def __init__(self, student_id=None, first_name=None, last_name=None, + email=None, phone=None, address=None, gpa=None, graduation_year=None): + self.id = student_id + self.first_name = first_name + self.last_name = last_name + self.email = email + self.phone = phone + self.address = address + self.gpa = gpa + self.graduation_year = graduation_year + self.db = DatabaseManager() + + @staticmethod + def create(first_name, last_name, email, phone=None, address=None, + gpa=None, graduation_year=None): + """Create a new student""" + db = DatabaseManager() + now = datetime.now().isoformat() + + query = ''' + INSERT INTO students + (first_name, last_name, email, phone, address, gpa, graduation_year, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + params = (first_name, last_name, email, phone, address, gpa, graduation_year, now, now) + + try: + student_id = db.execute_update(query, params) + return student_id + except Exception as e: + print(f"Error creating student: {e}") + return None + + @staticmethod + def get_all(): + """Get all students""" + db = DatabaseManager() + query = 'SELECT * FROM students ORDER BY last_name, first_name' + results = db.execute_query(query) + return [dict(row) for row in results] + + @staticmethod + def get_by_id(student_id): + """Get student by ID""" + db = DatabaseManager() + query = 'SELECT * FROM students WHERE id = ?' + results = db.execute_query(query, (student_id,)) + if results: + return dict(results[0]) + return None + + @staticmethod + def get_by_email(email): + """Get student by email""" + db = DatabaseManager() + query = 'SELECT * FROM students WHERE email = ?' + results = db.execute_query(query, (email,)) + if results: + return dict(results[0]) + return None + + @staticmethod + def update(student_id, **kwargs): + """Update student details""" + db = DatabaseManager() + now = datetime.now().isoformat() + + # Build dynamic update query based on provided fields + fields = [] + values = [] + + allowed_fields = ['first_name', 'last_name', 'email', 'phone', + 'address', 'gpa', 'graduation_year'] + + for field, value in kwargs.items(): + if field in allowed_fields: + fields.append(f'{field} = ?') + values.append(value) + + if not fields: + return False + + fields.append('updated_at = ?') + values.append(now) + values.append(student_id) + + query = f"UPDATE students SET {', '.join(fields)} WHERE id = ?" + try: + db.execute_update(query, tuple(values)) + return True + except Exception as e: + print(f"Error updating student: {e}") + return False + + @staticmethod + def delete(student_id): + """Delete a student""" + db = DatabaseManager() + query = 'DELETE FROM students WHERE id = ?' + db.execute_update(query, (student_id,)) + return True + + @staticmethod + def search(keyword): + """Search students by name or email""" + db = DatabaseManager() + query = ''' + SELECT * FROM students + WHERE first_name LIKE ? OR last_name LIKE ? OR email LIKE ? + ORDER BY last_name, first_name + ''' + search_term = f'%{keyword}%' + results = db.execute_query(query, (search_term, search_term, search_term)) + return [dict(row) for row in results] + + @staticmethod + def get_by_gpa_range(min_gpa, max_gpa): + """Get students within a GPA range""" + db = DatabaseManager() + query = ''' + SELECT * FROM students + WHERE gpa BETWEEN ? AND ? + ORDER BY gpa DESC + ''' + results = db.execute_query(query, (min_gpa, max_gpa)) + return [dict(row) for row in results] diff --git a/scholarship_app/requirements.txt b/scholarship_app/requirements.txt new file mode 100644 index 00000000..d6f44329 --- /dev/null +++ b/scholarship_app/requirements.txt @@ -0,0 +1 @@ +tabulate>=0.9.0 diff --git a/scholarship_app/tests/test_basic.py b/scholarship_app/tests/test_basic.py new file mode 100644 index 00000000..96cd96a3 --- /dev/null +++ b/scholarship_app/tests/test_basic.py @@ -0,0 +1,106 @@ +""" +Basic tests for scholarship app functionality +""" + +import sys +import os + +# Add parent directory to path +parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, parent_dir) + +from scholarship_app.models import Scholarship, Student, Application + + +def test_scholarships(): + """Test scholarship operations""" + print("Testing Scholarship operations...") + + # Get all scholarships + scholarships = Scholarship.get_all() + print(f" āœ“ Found {len(scholarships)} scholarships") + + # Get a specific scholarship + if scholarships: + s = Scholarship.get_by_id(1) + if s: + print(f" āœ“ Retrieved scholarship: {s['name']}") + + # Search scholarships + results = Scholarship.search("STEM") + print(f" āœ“ Search found {len(results)} STEM scholarships") + + return True + + +def test_students(): + """Test student operations""" + print("\nTesting Student operations...") + + # Get all students + students = Student.get_all() + print(f" āœ“ Found {len(students)} students") + + # Get a specific student + if students: + s = Student.get_by_id(1) + if s: + print(f" āœ“ Retrieved student: {s['first_name']} {s['last_name']}") + + # Search students + results = Student.search("Johnson") + print(f" āœ“ Search found {len(results)} students with 'Johnson'") + + return True + + +def test_applications(): + """Test application operations""" + print("\nTesting Application operations...") + + # Get all applications + applications = Application.get_all() + print(f" āœ“ Found {len(applications)} applications") + + # Get applications by status + pending = Application.get_by_status('pending') + print(f" āœ“ Found {len(pending)} pending applications") + + approved = Application.get_by_status('approved') + print(f" āœ“ Found {len(approved)} approved applications") + + # Get statistics + stats = Application.get_statistics() + if stats: + print(f" āœ“ Statistics:") + print(f" - Total: {stats['total_applications']}") + print(f" - Pending: {stats['pending']}") + print(f" - Approved: {stats['approved']}") + print(f" - Awarded: {stats['awarded']}") + + return True + + +def run_tests(): + """Run all tests""" + print("="*60) + print(" RUNNING TESTS ".center(60, "=")) + print("="*60) + + try: + test_scholarships() + test_students() + test_applications() + + print("\n" + "="*60) + print(" ALL TESTS PASSED ".center(60, "=")) + print("="*60) + + except Exception as e: + print(f"\nāœ— Test failed with error: {e}") + import traceback + traceback.print_exc() + + +if __name__ == '__main__': + run_tests() diff --git a/scholarship_app/utils/__init__.py b/scholarship_app/utils/__init__.py new file mode 100644 index 00000000..1d25cd94 --- /dev/null +++ b/scholarship_app/utils/__init__.py @@ -0,0 +1 @@ +"""Utility functions package""" diff --git a/scholarship_app/utils/sample_data.py b/scholarship_app/utils/sample_data.py new file mode 100644 index 00000000..b4765879 --- /dev/null +++ b/scholarship_app/utils/sample_data.py @@ -0,0 +1,149 @@ +""" +Sample data loader for demonstration purposes +""" + +import sys +import os + +# Add parent directory to path +parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, parent_dir) + +from scholarship_app.database import DatabaseManager +from scholarship_app.models import Scholarship, Student, Application + + +def load_sample_data(): + """Load sample scholarships, students, and applications""" + + # Initialize database + db = DatabaseManager() + db.create_tables() + + print("Loading sample data...") + + # Sample Scholarships + scholarships = [ + { + 'name': 'Merit Excellence Scholarship', + 'description': 'For students with outstanding academic performance', + 'amount': 5000.00, + 'deadline': '2024-06-30', + 'eligibility_criteria': 'GPA 3.5 or higher, Full-time student', + 'provider': 'Community Foundation' + }, + { + 'name': 'STEM Innovation Award', + 'description': 'Supporting students pursuing STEM fields', + 'amount': 7500.00, + 'deadline': '2024-07-15', + 'eligibility_criteria': 'STEM major, GPA 3.0 or higher', + 'provider': 'Tech Corp Foundation' + }, + { + 'name': 'Community Service Scholarship', + 'description': 'Recognizing students with strong community involvement', + 'amount': 3000.00, + 'deadline': '2024-08-01', + 'eligibility_criteria': '100+ hours of community service', + 'provider': 'Local Community Center' + }, + { + 'name': 'First Generation College Student Grant', + 'description': 'Supporting first-generation college students', + 'amount': 4000.00, + 'deadline': '2024-07-30', + 'eligibility_criteria': 'First generation college student', + 'provider': 'Education Alliance' + }, + { + 'name': 'Arts and Humanities Scholarship', + 'description': 'For students excelling in arts and humanities', + 'amount': 3500.00, + 'deadline': '2024-06-15', + 'eligibility_criteria': 'Arts or Humanities major, Portfolio required', + 'provider': 'Arts Council' + } + ] + + for s in scholarships: + Scholarship.create(**s) + print(f" āœ“ Added scholarship: {s['name']}") + + # Sample Students + students = [ + { + 'first_name': 'Emily', + 'last_name': 'Johnson', + 'email': 'emily.johnson@email.com', + 'phone': '555-0101', + 'address': '123 Main St, City, State 12345', + 'gpa': 3.8, + 'graduation_year': 2025 + }, + { + 'first_name': 'Michael', + 'last_name': 'Chen', + 'email': 'michael.chen@email.com', + 'phone': '555-0102', + 'address': '456 Oak Ave, City, State 12345', + 'gpa': 3.9, + 'graduation_year': 2024 + }, + { + 'first_name': 'Sarah', + 'last_name': 'Williams', + 'email': 'sarah.williams@email.com', + 'phone': '555-0103', + 'address': '789 Pine Rd, City, State 12345', + 'gpa': 3.6, + 'graduation_year': 2025 + }, + { + 'first_name': 'David', + 'last_name': 'Martinez', + 'email': 'david.martinez@email.com', + 'phone': '555-0104', + 'address': '321 Elm St, City, State 12345', + 'gpa': 3.7, + 'graduation_year': 2024 + }, + { + 'first_name': 'Jessica', + 'last_name': 'Taylor', + 'email': 'jessica.taylor@email.com', + 'phone': '555-0105', + 'address': '654 Maple Dr, City, State 12345', + 'gpa': 3.5, + 'graduation_year': 2026 + } + ] + + for st in students: + Student.create(**st) + print(f" āœ“ Added student: {st['first_name']} {st['last_name']}") + + # Sample Applications + applications = [ + {'student_id': 1, 'scholarship_id': 1, 'status': 'approved', 'notes': 'Excellent application'}, + {'student_id': 1, 'scholarship_id': 2, 'status': 'pending', 'notes': None}, + {'student_id': 2, 'scholarship_id': 2, 'status': 'awarded', 'notes': 'Outstanding STEM project'}, + {'student_id': 2, 'scholarship_id': 1, 'status': 'approved', 'notes': None}, + {'student_id': 3, 'scholarship_id': 3, 'status': 'under review', 'notes': 'Impressive volunteer work'}, + {'student_id': 3, 'scholarship_id': 5, 'status': 'pending', 'notes': None}, + {'student_id': 4, 'scholarship_id': 4, 'status': 'approved', 'notes': 'Strong background'}, + {'student_id': 5, 'scholarship_id': 1, 'status': 'pending', 'notes': None}, + ] + + for app in applications: + Application.create(**app) + print(f" āœ“ Added application: Student {app['student_id']} -> Scholarship {app['scholarship_id']}") + + print("\nāœ“ Sample data loaded successfully!") + print(f" - {len(scholarships)} scholarships") + print(f" - {len(students)} students") + print(f" - {len(applications)} applications") + + +if __name__ == '__main__': + load_sample_data()