student-feedback-system 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- student_feedback_system-1.0.0/MANIFEST.in +4 -0
- student_feedback_system-1.0.0/PKG-INFO +140 -0
- student_feedback_system-1.0.0/README.md +117 -0
- student_feedback_system-1.0.0/feedback_system/__init__.py +67 -0
- student_feedback_system-1.0.0/feedback_system/cli.py +100 -0
- student_feedback_system-1.0.0/feedback_system/config.py +34 -0
- student_feedback_system-1.0.0/feedback_system/database.py +126 -0
- student_feedback_system-1.0.0/feedback_system/routes.py +112 -0
- student_feedback_system-1.0.0/feedback_system/static/style.css +255 -0
- student_feedback_system-1.0.0/feedback_system/templates/edit.html +109 -0
- student_feedback_system-1.0.0/feedback_system/templates/feedback.html +111 -0
- student_feedback_system-1.0.0/feedback_system/templates/index.html +186 -0
- student_feedback_system-1.0.0/pyproject.toml +49 -0
- student_feedback_system-1.0.0/setup.cfg +4 -0
- student_feedback_system-1.0.0/student_feedback_system.egg-info/PKG-INFO +140 -0
- student_feedback_system-1.0.0/student_feedback_system.egg-info/SOURCES.txt +18 -0
- student_feedback_system-1.0.0/student_feedback_system.egg-info/dependency_links.txt +1 -0
- student_feedback_system-1.0.0/student_feedback_system.egg-info/entry_points.txt +2 -0
- student_feedback_system-1.0.0/student_feedback_system.egg-info/requires.txt +3 -0
- student_feedback_system-1.0.0/student_feedback_system.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: student-feedback-system
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A plug-and-play student feedback web application built with Flask
|
|
5
|
+
Author: Student DevOps Lab
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PAWAN1118/Student-Feedback-Flask-DEVops
|
|
8
|
+
Project-URL: Repository, https://github.com/PAWAN1118/Student-Feedback-Flask-DEVops
|
|
9
|
+
Keywords: flask,feedback,education,web,devops
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: Flask
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: flask>=3.0.0
|
|
21
|
+
Requires-Dist: gunicorn>=22.0.0
|
|
22
|
+
Requires-Dist: click>=8.1.0
|
|
23
|
+
|
|
24
|
+
# Student Feedback System
|
|
25
|
+
|
|
26
|
+
A plug-and-play student feedback web application built with Flask.
|
|
27
|
+
Install it, run one command, and you have a fully working feedback portal.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install student-feedback-system
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
feedback-system run
|
|
39
|
+
# Open http://localhost:5000
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## CLI Commands
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
feedback-system run # Start app (default port 5000)
|
|
46
|
+
feedback-system run --port 8080 # Custom port
|
|
47
|
+
feedback-system run --debug # Debug mode
|
|
48
|
+
feedback-system run --config development
|
|
49
|
+
|
|
50
|
+
feedback-system init-db # Create database tables
|
|
51
|
+
feedback-system export-csv # Export all feedback to feedback.csv
|
|
52
|
+
feedback-system reset-db # ⚠ Delete all data and recreate DB
|
|
53
|
+
feedback-system info # Show current configuration
|
|
54
|
+
feedback-system --version # Show version
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration via Environment Variables
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
export DB_PATH=/var/data/feedback.db
|
|
61
|
+
export SECRET_KEY=your-production-secret
|
|
62
|
+
export PORT=8080
|
|
63
|
+
export PER_PAGE=10
|
|
64
|
+
export APP_NAME="My University Feedback"
|
|
65
|
+
|
|
66
|
+
feedback-system run
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration via Code
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from feedback_system import create_app
|
|
73
|
+
from feedback_system.config import Config
|
|
74
|
+
|
|
75
|
+
class MyConfig(Config):
|
|
76
|
+
DB_PATH = '/var/data/feedback.db'
|
|
77
|
+
SECRET_KEY = 'super-secret-key'
|
|
78
|
+
PER_PAGE = 10
|
|
79
|
+
APP_NAME = 'My University Feedback'
|
|
80
|
+
APP_TAGLINE= 'Helping Faculty Improve Every Semester'
|
|
81
|
+
|
|
82
|
+
app = create_app(config=MyConfig)
|
|
83
|
+
app.run(host='0.0.0.0', port=8080)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Embed in an Existing Flask App
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from flask import Flask
|
|
90
|
+
from feedback_system.routes import feedback_bp
|
|
91
|
+
from feedback_system.database import init_db
|
|
92
|
+
|
|
93
|
+
app = Flask(__name__)
|
|
94
|
+
app.config['DB_PATH'] = './feedback.db'
|
|
95
|
+
app.config['PER_PAGE'] = 6
|
|
96
|
+
app.secret_key = 'your-secret'
|
|
97
|
+
|
|
98
|
+
# Mount at /feedback prefix
|
|
99
|
+
app.register_blueprint(feedback_bp, url_prefix='/feedback')
|
|
100
|
+
|
|
101
|
+
with app.app_context():
|
|
102
|
+
init_db(app.config['DB_PATH'])
|
|
103
|
+
|
|
104
|
+
# Dashboard → http://localhost:5000/feedback/
|
|
105
|
+
# Submit → http://localhost:5000/feedback/submit
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Use the Database Layer Directly
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from feedback_system.database import init_db, add_feedback, get_stats, export_csv
|
|
112
|
+
|
|
113
|
+
DB = './feedback.db'
|
|
114
|
+
init_db(DB)
|
|
115
|
+
add_feedback(DB, 'Alice', 'DevOps Lab', '5', 'Best lab ever!')
|
|
116
|
+
print(get_stats(DB))
|
|
117
|
+
|
|
118
|
+
csv_data = export_csv(DB)
|
|
119
|
+
with open('backup.csv', 'w') as f:
|
|
120
|
+
f.write(csv_data)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Features
|
|
124
|
+
|
|
125
|
+
- Submit, view, edit, delete feedback
|
|
126
|
+
- Star ratings (1–5)
|
|
127
|
+
- Search and filter
|
|
128
|
+
- Pagination (configurable per page)
|
|
129
|
+
- Rating distribution chart
|
|
130
|
+
- CSV export via CLI and HTTP
|
|
131
|
+
- Professional corporate UI (navy/gold theme)
|
|
132
|
+
- Health check endpoint at `/health`
|
|
133
|
+
|
|
134
|
+
## Tech Stack
|
|
135
|
+
|
|
136
|
+
Flask · SQLite · Gunicorn · Chart.js · Click
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Student Feedback System
|
|
2
|
+
|
|
3
|
+
A plug-and-play student feedback web application built with Flask.
|
|
4
|
+
Install it, run one command, and you have a fully working feedback portal.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install student-feedback-system
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
feedback-system run
|
|
16
|
+
# Open http://localhost:5000
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## CLI Commands
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
feedback-system run # Start app (default port 5000)
|
|
23
|
+
feedback-system run --port 8080 # Custom port
|
|
24
|
+
feedback-system run --debug # Debug mode
|
|
25
|
+
feedback-system run --config development
|
|
26
|
+
|
|
27
|
+
feedback-system init-db # Create database tables
|
|
28
|
+
feedback-system export-csv # Export all feedback to feedback.csv
|
|
29
|
+
feedback-system reset-db # ⚠ Delete all data and recreate DB
|
|
30
|
+
feedback-system info # Show current configuration
|
|
31
|
+
feedback-system --version # Show version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration via Environment Variables
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
export DB_PATH=/var/data/feedback.db
|
|
38
|
+
export SECRET_KEY=your-production-secret
|
|
39
|
+
export PORT=8080
|
|
40
|
+
export PER_PAGE=10
|
|
41
|
+
export APP_NAME="My University Feedback"
|
|
42
|
+
|
|
43
|
+
feedback-system run
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration via Code
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from feedback_system import create_app
|
|
50
|
+
from feedback_system.config import Config
|
|
51
|
+
|
|
52
|
+
class MyConfig(Config):
|
|
53
|
+
DB_PATH = '/var/data/feedback.db'
|
|
54
|
+
SECRET_KEY = 'super-secret-key'
|
|
55
|
+
PER_PAGE = 10
|
|
56
|
+
APP_NAME = 'My University Feedback'
|
|
57
|
+
APP_TAGLINE= 'Helping Faculty Improve Every Semester'
|
|
58
|
+
|
|
59
|
+
app = create_app(config=MyConfig)
|
|
60
|
+
app.run(host='0.0.0.0', port=8080)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Embed in an Existing Flask App
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from flask import Flask
|
|
67
|
+
from feedback_system.routes import feedback_bp
|
|
68
|
+
from feedback_system.database import init_db
|
|
69
|
+
|
|
70
|
+
app = Flask(__name__)
|
|
71
|
+
app.config['DB_PATH'] = './feedback.db'
|
|
72
|
+
app.config['PER_PAGE'] = 6
|
|
73
|
+
app.secret_key = 'your-secret'
|
|
74
|
+
|
|
75
|
+
# Mount at /feedback prefix
|
|
76
|
+
app.register_blueprint(feedback_bp, url_prefix='/feedback')
|
|
77
|
+
|
|
78
|
+
with app.app_context():
|
|
79
|
+
init_db(app.config['DB_PATH'])
|
|
80
|
+
|
|
81
|
+
# Dashboard → http://localhost:5000/feedback/
|
|
82
|
+
# Submit → http://localhost:5000/feedback/submit
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Use the Database Layer Directly
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from feedback_system.database import init_db, add_feedback, get_stats, export_csv
|
|
89
|
+
|
|
90
|
+
DB = './feedback.db'
|
|
91
|
+
init_db(DB)
|
|
92
|
+
add_feedback(DB, 'Alice', 'DevOps Lab', '5', 'Best lab ever!')
|
|
93
|
+
print(get_stats(DB))
|
|
94
|
+
|
|
95
|
+
csv_data = export_csv(DB)
|
|
96
|
+
with open('backup.csv', 'w') as f:
|
|
97
|
+
f.write(csv_data)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
- Submit, view, edit, delete feedback
|
|
103
|
+
- Star ratings (1–5)
|
|
104
|
+
- Search and filter
|
|
105
|
+
- Pagination (configurable per page)
|
|
106
|
+
- Rating distribution chart
|
|
107
|
+
- CSV export via CLI and HTTP
|
|
108
|
+
- Professional corporate UI (navy/gold theme)
|
|
109
|
+
- Health check endpoint at `/health`
|
|
110
|
+
|
|
111
|
+
## Tech Stack
|
|
112
|
+
|
|
113
|
+
Flask · SQLite · Gunicorn · Chart.js · Click
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
feedback_system
|
|
3
|
+
~~~~~~~~~~~~~~~
|
|
4
|
+
A plug-and-play Student Feedback web application built with Flask.
|
|
5
|
+
|
|
6
|
+
Basic usage:
|
|
7
|
+
from feedback_system import create_app
|
|
8
|
+
app = create_app()
|
|
9
|
+
app.run()
|
|
10
|
+
|
|
11
|
+
Custom config:
|
|
12
|
+
from feedback_system import create_app
|
|
13
|
+
from feedback_system.config import Config
|
|
14
|
+
|
|
15
|
+
class MyConfig(Config):
|
|
16
|
+
DB_PATH = '/var/data/feedback.db'
|
|
17
|
+
SECRET_KEY = 'super-secret-key'
|
|
18
|
+
APP_NAME = 'My University Feedback'
|
|
19
|
+
|
|
20
|
+
app = create_app(config=MyConfig)
|
|
21
|
+
|
|
22
|
+
String shortcuts:
|
|
23
|
+
app = create_app(config='production')
|
|
24
|
+
app = create_app(config='development')
|
|
25
|
+
app = create_app(config='testing')
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from flask import Flask
|
|
29
|
+
from .config import Config, config_map
|
|
30
|
+
from .database import init_db
|
|
31
|
+
from .routes import feedback_bp
|
|
32
|
+
|
|
33
|
+
__version__ = '1.0.0'
|
|
34
|
+
__author__ = 'Student DevOps Lab'
|
|
35
|
+
__all__ = ['create_app', 'feedback_bp', '__version__']
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_app(config=None) -> Flask:
|
|
39
|
+
"""
|
|
40
|
+
Application factory — creates and returns a configured Flask app.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
config: None | Config subclass | 'production' | 'development' | 'testing'
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Configured Flask application instance.
|
|
47
|
+
"""
|
|
48
|
+
app = Flask(__name__, template_folder='templates', static_folder='static')
|
|
49
|
+
|
|
50
|
+
if config is None:
|
|
51
|
+
app.config.from_object(Config)
|
|
52
|
+
elif isinstance(config, str):
|
|
53
|
+
cfg_class = config_map.get(config)
|
|
54
|
+
if not cfg_class:
|
|
55
|
+
raise ValueError(f"Unknown config '{config}'. Choose from: {list(config_map.keys())}")
|
|
56
|
+
app.config.from_object(cfg_class)
|
|
57
|
+
else:
|
|
58
|
+
app.config.from_object(config)
|
|
59
|
+
|
|
60
|
+
app.secret_key = app.config['SECRET_KEY']
|
|
61
|
+
|
|
62
|
+
with app.app_context():
|
|
63
|
+
init_db(app.config['DB_PATH'])
|
|
64
|
+
|
|
65
|
+
app.register_blueprint(feedback_bp)
|
|
66
|
+
|
|
67
|
+
return app
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import click
|
|
3
|
+
from . import create_app, __version__
|
|
4
|
+
from .config import Config
|
|
5
|
+
from .database import init_db, export_csv, reset_db
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _env_config():
|
|
9
|
+
env = os.environ.get('FLASK_ENV', 'production')
|
|
10
|
+
return env if env in ('development', 'production', 'testing') else 'default'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
@click.version_option(version=__version__, prog_name='feedback-system')
|
|
15
|
+
def cli():
|
|
16
|
+
"""Student Feedback System CLI."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@cli.command()
|
|
21
|
+
@click.option('--host', default=None, help='Host to bind (default: 0.0.0.0)')
|
|
22
|
+
@click.option('--port', default=None, type=int, help='Port (default: 5000)')
|
|
23
|
+
@click.option('--debug', is_flag=True, default=False, help='Enable debug mode')
|
|
24
|
+
@click.option('--config', 'config_name', default=None, help='development | production | testing')
|
|
25
|
+
def run(host, port, debug, config_name):
|
|
26
|
+
"""Start the Student Feedback web application."""
|
|
27
|
+
cfg = config_name or _env_config()
|
|
28
|
+
app = create_app(config=cfg)
|
|
29
|
+
_host = host or app.config.get('HOST', '0.0.0.0')
|
|
30
|
+
_port = port or app.config.get('PORT', 5000)
|
|
31
|
+
_dbg = debug or app.config.get('DEBUG', False)
|
|
32
|
+
|
|
33
|
+
click.echo(click.style(f'''
|
|
34
|
+
╔══════════════════════════════════════════╗
|
|
35
|
+
║ Student Feedback System v{__version__:<13} ║
|
|
36
|
+
╠══════════════════════════════════════════╣
|
|
37
|
+
║ URL : http://{_host}:{_port}
|
|
38
|
+
║ DB : {app.config["DB_PATH"]}
|
|
39
|
+
║ Config : {cfg}
|
|
40
|
+
╚══════════════════════════════════════════╝
|
|
41
|
+
''', fg='green'))
|
|
42
|
+
|
|
43
|
+
app.run(host=_host, port=_port, debug=_dbg)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@cli.command('init-db')
|
|
47
|
+
@click.option('--db-path', default=None, help='Path to the SQLite database')
|
|
48
|
+
def init_db_cmd(db_path):
|
|
49
|
+
"""Initialise the database."""
|
|
50
|
+
path = db_path or os.environ.get('DB_PATH', Config.DB_PATH)
|
|
51
|
+
init_db(path)
|
|
52
|
+
click.echo(click.style(f'✓ Database initialised at: {path}', fg='green'))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@cli.command('export-csv')
|
|
56
|
+
@click.option('--db-path', default=None)
|
|
57
|
+
@click.option('--output', default='feedback.csv')
|
|
58
|
+
def export_cmd(db_path, output):
|
|
59
|
+
"""Export all feedback to a CSV file."""
|
|
60
|
+
path = db_path or os.environ.get('DB_PATH', Config.DB_PATH)
|
|
61
|
+
data = export_csv(path)
|
|
62
|
+
with open(output, 'w', newline='', encoding='utf-8') as f:
|
|
63
|
+
f.write(data)
|
|
64
|
+
lines = len(data.strip().split('\n')) - 1
|
|
65
|
+
click.echo(click.style(f'✓ Exported {lines} records to: {output}', fg='green'))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@cli.command('reset-db')
|
|
69
|
+
@click.option('--db-path', default=None)
|
|
70
|
+
@click.confirmation_option(prompt='⚠ This will DELETE all feedback data. Continue?')
|
|
71
|
+
def reset_db_cmd(db_path):
|
|
72
|
+
"""Drop and recreate the database. ALL DATA WILL BE LOST."""
|
|
73
|
+
path = db_path or os.environ.get('DB_PATH', Config.DB_PATH)
|
|
74
|
+
reset_db(path)
|
|
75
|
+
click.echo(click.style(f'✓ Database reset at: {path}', fg='yellow'))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@cli.command()
|
|
79
|
+
def info():
|
|
80
|
+
"""Show current configuration."""
|
|
81
|
+
cfg = Config()
|
|
82
|
+
click.echo(f'''
|
|
83
|
+
Student Feedback System v{__version__}
|
|
84
|
+
{"─" * 38}
|
|
85
|
+
DB_PATH : {cfg.DB_PATH}
|
|
86
|
+
HOST : {cfg.HOST}
|
|
87
|
+
PORT : {cfg.PORT}
|
|
88
|
+
DEBUG : {cfg.DEBUG}
|
|
89
|
+
PER_PAGE : {cfg.PER_PAGE}
|
|
90
|
+
APP_NAME : {cfg.APP_NAME}
|
|
91
|
+
SECRET_KEY : {'*** (set)' if cfg.SECRET_KEY != 'dev-secret-change-in-production' else '⚠ using default!'}
|
|
92
|
+
''')
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def main():
|
|
96
|
+
cli()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == '__main__':
|
|
100
|
+
main()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Config:
|
|
5
|
+
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-change-in-production')
|
|
6
|
+
DB_PATH = os.environ.get('DB_PATH', os.path.join(os.getcwd(), 'feedback.db'))
|
|
7
|
+
PER_PAGE = int(os.environ.get('PER_PAGE', 6))
|
|
8
|
+
HOST = os.environ.get('HOST', '0.0.0.0')
|
|
9
|
+
PORT = int(os.environ.get('PORT', 5000))
|
|
10
|
+
DEBUG = os.environ.get('DEBUG', 'false').lower() == 'true'
|
|
11
|
+
APP_NAME = os.environ.get('APP_NAME', 'Student Feedback System')
|
|
12
|
+
APP_TAGLINE = os.environ.get('APP_TAGLINE', 'Academic Excellence Portal')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DevelopmentConfig(Config):
|
|
16
|
+
DEBUG = True
|
|
17
|
+
DB_PATH = os.path.join(os.getcwd(), 'feedback_dev.db')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ProductionConfig(Config):
|
|
21
|
+
DEBUG = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestingConfig(Config):
|
|
25
|
+
TESTING = True
|
|
26
|
+
DB_PATH = ':memory:'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
config_map = {
|
|
30
|
+
'development': DevelopmentConfig,
|
|
31
|
+
'production': ProductionConfig,
|
|
32
|
+
'testing': TestingConfig,
|
|
33
|
+
'default': Config,
|
|
34
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import os
|
|
3
|
+
import csv
|
|
4
|
+
import io
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _connect(db_path: str) -> sqlite3.Connection:
|
|
9
|
+
if db_path != ':memory:':
|
|
10
|
+
os.makedirs(os.path.dirname(os.path.abspath(db_path)), exist_ok=True)
|
|
11
|
+
conn = sqlite3.connect(db_path)
|
|
12
|
+
conn.row_factory = sqlite3.Row
|
|
13
|
+
return conn
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def init_db(db_path: str) -> None:
|
|
17
|
+
conn = _connect(db_path)
|
|
18
|
+
conn.execute('''
|
|
19
|
+
CREATE TABLE IF NOT EXISTS feedback (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
name TEXT NOT NULL,
|
|
22
|
+
course TEXT,
|
|
23
|
+
rating TEXT,
|
|
24
|
+
message TEXT NOT NULL,
|
|
25
|
+
submitted_at TEXT DEFAULT (datetime('now'))
|
|
26
|
+
)
|
|
27
|
+
''')
|
|
28
|
+
conn.commit()
|
|
29
|
+
conn.close()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def add_feedback(db_path, name, course, rating, message):
|
|
33
|
+
conn = _connect(db_path)
|
|
34
|
+
conn.execute(
|
|
35
|
+
'INSERT INTO feedback (name, course, rating, message, submitted_at) VALUES (?,?,?,?,?)',
|
|
36
|
+
(name, course, rating, message, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
|
37
|
+
)
|
|
38
|
+
conn.commit()
|
|
39
|
+
conn.close()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_paginated_feedback(db_path, page, per_page):
|
|
43
|
+
conn = _connect(db_path)
|
|
44
|
+
total = conn.execute('SELECT COUNT(*) FROM feedback').fetchone()[0]
|
|
45
|
+
offset = (page - 1) * per_page
|
|
46
|
+
rows = conn.execute(
|
|
47
|
+
'SELECT * FROM feedback ORDER BY submitted_at DESC LIMIT ? OFFSET ?',
|
|
48
|
+
(per_page, offset)
|
|
49
|
+
).fetchall()
|
|
50
|
+
conn.close()
|
|
51
|
+
return [dict(r) for r in rows], total
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_all_feedback(db_path):
|
|
55
|
+
conn = _connect(db_path)
|
|
56
|
+
rows = conn.execute('SELECT * FROM feedback ORDER BY submitted_at DESC').fetchall()
|
|
57
|
+
conn.close()
|
|
58
|
+
return [dict(r) for r in rows]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_feedback_by_id(db_path, feedback_id):
|
|
62
|
+
conn = _connect(db_path)
|
|
63
|
+
row = conn.execute('SELECT * FROM feedback WHERE id = ?', (feedback_id,)).fetchone()
|
|
64
|
+
conn.close()
|
|
65
|
+
return dict(row) if row else None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def update_feedback(db_path, feedback_id, name, course, rating, message):
|
|
69
|
+
conn = _connect(db_path)
|
|
70
|
+
conn.execute(
|
|
71
|
+
'UPDATE feedback SET name=?, course=?, rating=?, message=? WHERE id=?',
|
|
72
|
+
(name, course, rating, message, feedback_id)
|
|
73
|
+
)
|
|
74
|
+
conn.commit()
|
|
75
|
+
conn.close()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def delete_feedback(db_path, feedback_id):
|
|
79
|
+
conn = _connect(db_path)
|
|
80
|
+
conn.execute('DELETE FROM feedback WHERE id = ?', (feedback_id,))
|
|
81
|
+
conn.commit()
|
|
82
|
+
conn.close()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def search_feedback(db_path, query):
|
|
86
|
+
conn = _connect(db_path)
|
|
87
|
+
q = f'%{query}%'
|
|
88
|
+
rows = conn.execute(
|
|
89
|
+
'SELECT * FROM feedback WHERE name LIKE ? OR course LIKE ? OR message LIKE ? ORDER BY submitted_at DESC',
|
|
90
|
+
(q, q, q)
|
|
91
|
+
).fetchall()
|
|
92
|
+
conn.close()
|
|
93
|
+
return [dict(r) for r in rows]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_stats(db_path):
|
|
97
|
+
conn = _connect(db_path)
|
|
98
|
+
total = conn.execute('SELECT COUNT(*) FROM feedback').fetchone()[0]
|
|
99
|
+
avg = conn.execute(
|
|
100
|
+
"SELECT ROUND(AVG(CAST(rating AS REAL)),1) FROM feedback WHERE rating != '' AND rating IS NOT NULL"
|
|
101
|
+
).fetchone()[0]
|
|
102
|
+
dist = conn.execute(
|
|
103
|
+
"SELECT rating, COUNT(*) as count FROM feedback WHERE rating != '' AND rating IS NOT NULL GROUP BY rating ORDER BY rating"
|
|
104
|
+
).fetchall()
|
|
105
|
+
recent = conn.execute(
|
|
106
|
+
"SELECT COUNT(*) FROM feedback WHERE submitted_at >= datetime('now', '-7 days')"
|
|
107
|
+
).fetchone()[0]
|
|
108
|
+
conn.close()
|
|
109
|
+
return {'total': total, 'avg_rating': avg or 0, 'distribution': [dict(r) for r in dist], 'recent': recent}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def export_csv(db_path):
|
|
113
|
+
rows = get_all_feedback(db_path)
|
|
114
|
+
output = io.StringIO()
|
|
115
|
+
writer = csv.DictWriter(output, fieldnames=['id','name','course','rating','message','submitted_at'])
|
|
116
|
+
writer.writeheader()
|
|
117
|
+
writer.writerows(rows)
|
|
118
|
+
return output.getvalue()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def reset_db(db_path):
|
|
122
|
+
conn = _connect(db_path)
|
|
123
|
+
conn.execute('DROP TABLE IF EXISTS feedback')
|
|
124
|
+
conn.commit()
|
|
125
|
+
conn.close()
|
|
126
|
+
init_db(db_path)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, Response, jsonify
|
|
2
|
+
from .database import (
|
|
3
|
+
add_feedback, get_feedback_by_id, update_feedback,
|
|
4
|
+
delete_feedback, search_feedback, get_stats,
|
|
5
|
+
get_paginated_feedback, export_csv
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
feedback_bp = Blueprint(
|
|
9
|
+
'feedback', __name__,
|
|
10
|
+
template_folder='templates',
|
|
11
|
+
static_folder='static',
|
|
12
|
+
static_url_path='/feedback/static'
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _db():
|
|
17
|
+
return current_app.config['DB_PATH']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _per_page():
|
|
21
|
+
return current_app.config.get('PER_PAGE', 6)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@feedback_bp.route('/')
|
|
25
|
+
def index():
|
|
26
|
+
page = request.args.get('page', 1, type=int)
|
|
27
|
+
query = request.args.get('q', '').strip()
|
|
28
|
+
per_page = _per_page()
|
|
29
|
+
|
|
30
|
+
if query:
|
|
31
|
+
feedbacks = search_feedback(_db(), query)
|
|
32
|
+
total = len(feedbacks)
|
|
33
|
+
start = (page - 1) * per_page
|
|
34
|
+
feedbacks = feedbacks[start:start + per_page]
|
|
35
|
+
else:
|
|
36
|
+
feedbacks, total = get_paginated_feedback(_db(), page, per_page)
|
|
37
|
+
|
|
38
|
+
total_pages = max(1, (total + per_page - 1) // per_page)
|
|
39
|
+
|
|
40
|
+
return render_template(
|
|
41
|
+
'index.html',
|
|
42
|
+
feedbacks=feedbacks,
|
|
43
|
+
stats=get_stats(_db()),
|
|
44
|
+
page=page,
|
|
45
|
+
total_pages=total_pages,
|
|
46
|
+
total=total,
|
|
47
|
+
query=query,
|
|
48
|
+
app_name=current_app.config.get('APP_NAME', 'Student Feedback System'),
|
|
49
|
+
app_tagline=current_app.config.get('APP_TAGLINE', 'Academic Excellence Portal'),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@feedback_bp.route('/submit', methods=['GET', 'POST'])
|
|
54
|
+
def submit():
|
|
55
|
+
if request.method == 'POST':
|
|
56
|
+
name = request.form.get('name', '').strip()
|
|
57
|
+
course = request.form.get('course', '').strip()
|
|
58
|
+
rating = request.form.get('rating', '').strip()
|
|
59
|
+
message = request.form.get('message', '').strip()
|
|
60
|
+
if not name or not message:
|
|
61
|
+
flash('Name and feedback message are required!', 'error')
|
|
62
|
+
return render_template('feedback.html', form_data=request.form)
|
|
63
|
+
add_feedback(_db(), name, course, rating, message)
|
|
64
|
+
flash('Feedback submitted successfully!', 'success')
|
|
65
|
+
return redirect(url_for('feedback.index'))
|
|
66
|
+
return render_template('feedback.html', form_data={})
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@feedback_bp.route('/edit/<int:feedback_id>', methods=['GET', 'POST'])
|
|
70
|
+
def edit(feedback_id):
|
|
71
|
+
fb = get_feedback_by_id(_db(), feedback_id)
|
|
72
|
+
if not fb:
|
|
73
|
+
flash('Feedback not found.', 'error')
|
|
74
|
+
return redirect(url_for('feedback.index'))
|
|
75
|
+
if request.method == 'POST':
|
|
76
|
+
name = request.form.get('name', '').strip()
|
|
77
|
+
course = request.form.get('course', '').strip()
|
|
78
|
+
rating = request.form.get('rating', '').strip()
|
|
79
|
+
message = request.form.get('message', '').strip()
|
|
80
|
+
if not name or not message:
|
|
81
|
+
flash('Name and feedback message are required!', 'error')
|
|
82
|
+
return render_template('edit.html', fb=fb)
|
|
83
|
+
update_feedback(_db(), feedback_id, name, course, rating, message)
|
|
84
|
+
flash('Feedback updated successfully!', 'success')
|
|
85
|
+
return redirect(url_for('feedback.index'))
|
|
86
|
+
return render_template('edit.html', fb=fb)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@feedback_bp.route('/delete/<int:feedback_id>', methods=['POST'])
|
|
90
|
+
def delete(feedback_id):
|
|
91
|
+
delete_feedback(_db(), feedback_id)
|
|
92
|
+
flash('Feedback deleted.', 'success')
|
|
93
|
+
return redirect(url_for('feedback.index'))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@feedback_bp.route('/api/stats')
|
|
97
|
+
def api_stats():
|
|
98
|
+
return jsonify(get_stats(_db()))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@feedback_bp.route('/api/export.csv')
|
|
102
|
+
def api_export():
|
|
103
|
+
return Response(
|
|
104
|
+
export_csv(_db()),
|
|
105
|
+
mimetype='text/csv',
|
|
106
|
+
headers={'Content-Disposition': 'attachment; filename=feedback.csv'}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@feedback_bp.route('/health')
|
|
111
|
+
def health():
|
|
112
|
+
return jsonify({'status': 'ok', 'message': 'Feedback System running'}), 200
|