Passed
Push — master ( 092f6c...a595fa )
by Markus
02:24
created

chaoswg   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 149
dl 0
loc 241
rs 8.3999
c 0
b 0
f 0
wmc 38

17 Functions

Rating   Name   Duplication   Size   Complexity  
A set_task_state() 0 13 3
B get_tasks() 0 16 7
A get_history_user() 0 5 1
A get_history() 0 4 1
A get_users() 0 8 2
A unauthorized() 0 3 1
A load_user() 0 3 1
A start_task_scheduler() 0 4 1
A register() 0 16 4
A get_history_user_json() 0 4 1
A do_custom_task() 0 11 2
A login() 0 11 4
A get_users_json() 0 4 1
A create_task() 0 17 3
A logout() 0 5 1
B get_history_json() 0 25 4
A index() 0 3 1
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 RegisterForm, 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
# from test.testdata import insert_testdata
15
16
# init app and load config
17
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...
18
# read ../default-config.py
19
app.config.from_pyfile('../default-config.py')
20
# overwrite with custom config in ../custom-config.py
21
app.config.from_pyfile('../custom-config.py', silent=True)
22
23
# init DB
24
database = init_database(app)
0 ignored issues
show
Coding Style Naming introduced by
The name database 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...
25
create_tables()
26
# Comment out in production
27
# insert_testdata(database)
28
29
# init login manager
30
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...
31
login_manager.init_app(app)
32
33
# Enable bootstrap support
34
Bootstrap(app)
35
# jQuery 3 instead of 1
36
app.extensions['bootstrap']['cdns']['jquery'] = WebCDN(
37
    '//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/'
38
)
39
40
# Enable babel support
41
app.babel = Babel(app, default_timezone='Europe/Berlin')
42
# set datetime filter for jinja2
43
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...
44
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...
45
46
# init admin interface
47
init_admin(app)
48
49
# Create the task scheduler thread
50
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...
51
52
53
@app.before_first_request
54
def start_task_scheduler():
55
    # Start the task scheduler thread only once even if app is in debug mode
56
    task_scheduler.start()
57
58
59
@app.route('/')
60
def index():
61
    return render_template('index.html', usernames=User.get_usernames())
62
63
64
@app.route('/register', methods=['GET', 'POST'])
65
def register():
66
    form = RegisterForm()
67
68
    if form.validate_on_submit():
69
        if app.config['INVITE_KEY'] != form.invite_key.data:
70
            # TODO return error message, wrong invite key
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
71
            return '', 403
72
        if User.register(form.name.data, form.password.data):
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
73
            # TODO return success message
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
74
            return redirect('/login')
75
        else:
76
            # TODO return error message, user exists
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
77
            return '', 403
78
79
    return render_template('register.html', form=form)
80
81
82
@app.route('/login', methods=['GET', 'POST'])
83
def login():
84
    form = LoginForm()
85
86
    if form.validate_on_submit():
87
        user = User.get_by_name(form.name.data)
88
        if user and user.check_password(form.password.data):
89
            login_user(user, remember=True)
90
            return redirect('/tasks')
91
92
    return render_template('login.html', form=form)
93
94
95
@login_manager.user_loader
96
def load_user(user_id):
97
    return User.get(User.id == user_id)
98
99
100
@login_manager.unauthorized_handler
101
def unauthorized():
102
    return redirect('/login')
103
104
105
@app.route('/logout')
106
@login_required
107
def logout():
108
    logout_user()
109
    return redirect('/')
110
111
112
@app.route('/users')
113
@login_required
114
def get_users():
115
    users = User.get_all()
116
    usernames = set()
117
    for user in users:
118
        usernames.add(user['username'])
119
    return render_template('users.html', users=users, usernames=usernames)
120
121
122
@app.route('/history')
123
@login_required
124
def get_history():
125
    return render_template('history.html', usernames=User.get_usernames())
126
127
128
@app.route('/history/<username>')
129
@login_required
130
def get_history_user(username):
131
    return render_template('history_user.html', userhist=History.get_user_history(username), username=username,
132
                           usernames=User.get_usernames())
133
134
135
@app.route('/create_task', methods=['GET', 'POST'])
136
@login_required
137
def create_task():
138
    form = CreateTaskForm()
139
140
    if form.validate_on_submit():
141
        task, created = Task.get_or_create(task=form.task.data, defaults={'base_points': form.base_points.data,
142
                                                                          'time_factor': form.time_factor.data,
143
                                                                          'schedule_days': form.schedule_days.data})
144
        if not created:
145
            # TODO return error message, task exists
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
146
            return '', 403
147
148
        # TODO return success message
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
149
        return redirect('/tasks')
150
151
    return render_template('create_task.html', form=form)
152
153
154
@app.route('/do_custom_task', methods=['GET', 'POST'])
155
@login_required
156
def do_custom_task():
157
    form = CustomTaskForm()
158
159
    if form.validate_on_submit():
160
        # do custom onetime task
161
        Task.do_custom_task(form.task.data, form.points.data, current_user.get_id())
162
        return redirect('/tasks')
163
164
    return render_template('do_custom_task.html', form=form)
165
166
167
@app.route('/tasks')
168
@login_required
169
def get_tasks():
170
    tasks = Task.get_all()
171
    backlog = [t for t in tasks if t.state == Task.BACKLOG]
172
    todo = [t for t in tasks if t.state == Task.TODO]
173
    done = [t for t in tasks if t.state == Task.DONE]
174
175
    progress = {
176
        'backlog': len(backlog) / len(tasks) * 100,
177
        'todo': len(todo) / len(tasks) * 100,
178
        'done': len(done) / len(tasks) * 100
179
    }
180
181
    return render_template('tasks.html', backlog=backlog, todo=todo, done=done, progress=progress,
182
                           usernames=User.get_usernames())
183
184
185
@app.route('/set_task_state', methods=['POST'])
186
@login_required
187
def set_task_state():
188
    user_id = current_user.get_id()
189
    task_id = request.form.get('id', type=int)
190
    state = request.form.get('state', type=int)
191
    if None not in (task_id, state, user_id) and state in (0, 1, 2):
192
        Task.set_state(task_id, state, user_id)
193
    else:
194
        # TODO message
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
195
        return '', 403
196
    # New state was set
197
    return '', 204
198
199
200
#########################
201
# JSON endpoints for JS #
202
#########################
203
204
@app.route('/json/history/<username>')
205
@login_required
206
def get_history_user_json(username):
207
    return jsonify(History.get_user_history(username))
208
209
210
@app.route('/json/users')
211
@login_required
212
def get_users_json():
213
    return jsonify(User.get_all())
214
215
216
@app.route('/json/history')
217
@login_required
218
def get_history_json():
219
    hist = History.get_full_history()
220
    result = {}
221
    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...
222
        if h['username'] not in result:
223
            # initial 0 points
224
            result[h['username']] = [{
225
                'time': h['time'],
226
                'points': 0
227
            }]
228
        result[h['username']].append({
229
            'time': h['time'],
230
            'points': h['points']
231
        })
232
233
    # same point count till today
234
    for user in result:
235
        result[user].append({
236
            'time': datetime.utcnow(),
237
            'points': 0
238
        })
239
240
    return jsonify(result)
241