Passed
Pull Request — master (#6)
by Markus
02:49
created

chaoswg.start_task_scheduler()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
from datetime import datetime
2
3
from flask import Flask, render_template, request, redirect, jsonify
4
from flask_babel import Babel
5
from flask_bootstrap import Bootstrap, WebCDN
6
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
7
8
from chaoswg.admin import init_admin
9
from chaoswg.forms import LoginForm, CreateTaskForm, CustomTaskForm
10
from chaoswg.helpers import format_datetime_custom, format_timedelta_custom
11
from chaoswg.models import init_database, create_tables, User, Task, History
12
from chaoswg.scheduler import TaskScheduler
13
14
# init app and load config
15
app = Flask(__name__)
0 ignored issues
show
Coding Style Naming introduced by
The name app does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
16
# read config.py
17
app.config.from_pyfile('../config.py')
18
19
# init DB
20
init_database(app)
21
create_tables()
22
# Comment out in production
23
# insert_testdata(database)
24
25
# init login manager
26
login_manager = LoginManager()
0 ignored issues
show
Coding Style Naming introduced by
The name login_manager does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
27
login_manager.init_app(app)
28
29
# Enable bootstrap support
30
Bootstrap(app)
31
# jQuery 3 instead of 1
32
app.extensions['bootstrap']['cdns']['jquery'] = WebCDN(
33
    '//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/'
34
)
35
36
# Enable babel support
37
app.babel = Babel(app, default_timezone='Europe/Berlin')
38
# set datetime filter for jinja2
39
app.jinja_env.filters['datetime'] = format_datetime_custom
0 ignored issues
show
Bug introduced by
The Method jinja_env does not seem to have a member named filters.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
40
app.jinja_env.filters['timedelta'] = format_timedelta_custom
0 ignored issues
show
Bug introduced by
The Method jinja_env does not seem to have a member named filters.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
41
42
# init admin interface
43
init_admin(app)
44
45
# Create the task scheduler thread
46
task_scheduler = TaskScheduler()
0 ignored issues
show
Coding Style Naming introduced by
The name task_scheduler does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
47
48
49
@app.before_first_request
50
def start_task_scheduler():
51
    # Start the task scheduler thread only once even if app is in debug mode
52
    task_scheduler.start()
53
54
55
@app.route('/')
56
def index():
57
    return render_template('index.html', usernames=User.get_usernames())
58
59
60
@app.route('/login', methods=['GET', 'POST'])
61
def login():
62
    form = LoginForm()
63
64
    if form.validate_on_submit():
65
        user = User.get_by_name(form.name.data)
66
        if user and user.check_password(form.password.data):
67
            login_user(user, remember=True)
68
            return redirect('/tasks')
69
70
    return render_template('login.html', form=form)
71
72
73
@login_manager.user_loader
74
def load_user(user_id):
75
    return User.get(User.id == user_id)
76
77
78
@login_manager.unauthorized_handler
79
def unauthorized():
80
    return redirect('/login')
81
82
83
@app.route('/logout')
84
@login_required
85
def logout():
86
    logout_user()
87
    return redirect('/')
88
89
90
@app.route('/users')
91
@login_required
92
def get_users():
93
    users = User.get_all()
94
    usernames = set()
95
    for user in users:
96
        usernames.add(user['username'])
97
    return render_template('users.html', users=users, usernames=usernames)
98
99
100
@app.route('/history')
101
@login_required
102
def get_history():
103
    return render_template('history.html', usernames=User.get_usernames())
104
105
106
@app.route('/history/<username>')
107
@login_required
108
def get_history_user(username):
109
    return render_template('history_user.html', userhist=History.get_user_history(username), username=username,
110
                           usernames=User.get_usernames())
111
112
113
@app.route('/create_task', methods=['GET', 'POST'])
114
@login_required
115
def create_task():
116
    form = CreateTaskForm()
117
118
    if form.validate_on_submit():
119
        task, created = Task.get_or_create(task=form.task.data, defaults={'base_points': form.base_points.data,
120
                                                                          'time_factor': form.time_factor.data,
121
                                                                          'schedule_days': form.schedule_days.data})
122
        if not created:
123
            # TODO return error message, task exists
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
124
            return '', 403
125
126
        # TODO return success message
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
127
        return redirect('/tasks')
128
129
    return render_template('create_task.html', form=form)
130
131
132
@app.route('/do_custom_task', methods=['GET', 'POST'])
133
@login_required
134
def do_custom_task():
135
    form = CustomTaskForm()
136
137
    if form.validate_on_submit():
138
        # do custom onetime task
139
        Task.do_custom_task(form.task.data, form.points.data, current_user.get_id())
140
        return redirect('/tasks')
141
142
    return render_template('do_custom_task.html', form=form)
143
144
145
@app.route('/tasks')
146
@login_required
147
def get_tasks():
148
    tasks = Task.get_all()
149
    backlog = [t for t in tasks if t.state == Task.BACKLOG]
150
    todo = [t for t in tasks if t.state == Task.TODO]
151
    done = [t for t in tasks if t.state == Task.DONE]
152
153
    progress = {
154
        'backlog': len(backlog) / len(tasks) * 100,
155
        'todo': len(todo) / len(tasks) * 100,
156
        'done': len(done) / len(tasks) * 100
157
    }
158
159
    return render_template('tasks.html', backlog=backlog, todo=todo, done=done, progress=progress,
160
                           usernames=User.get_usernames())
161
162
163
@app.route('/set_task_state', methods=['POST'])
164
@login_required
165
def set_task_state():
166
    user_id = current_user.get_id()
167
    task_id = request.form.get('id', type=int)
168
    state = request.form.get('state', type=int)
169
    if None not in (task_id, state, user_id) and state in (0, 1, 2):
170
        Task.set_state(task_id, state, user_id)
171
    else:
172
        # TODO message
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
173
        return '', 403
174
    # New state was set
175
    return '', 204
176
177
178
#########################
179
# JSON endpoints for JS #
180
#########################
181
182
@app.route('/json/history/<username>')
183
@login_required
184
def get_history_user_json(username):
185
    return jsonify(History.get_user_history(username))
186
187
188
@app.route('/json/users')
189
@login_required
190
def get_users_json():
191
    return jsonify(User.get_all())
192
193
194
@app.route('/json/history')
195
@login_required
196
def get_history_json():
197
    hist = History.get_full_history()
198
    result = {}
199
    for h in hist:
0 ignored issues
show
Coding Style Naming introduced by
The name h does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
200
        if h['username'] not in result:
201
            # initial 0 points
202
            result[h['username']] = [{
203
                'time': h['time'],
204
                'points': 0
205
            }]
206
        result[h['username']].append({
207
            'time': h['time'],
208
            'points': h['points']
209
        })
210
211
    # same point count till today
212
    for user in result:
213
        result[user].append({
214
            'time': datetime.utcnow(),
215
            'points': 0
216
        })
217
218
    return jsonify(result)
219