Blame view

app/main/routes.py 21.8 KB
d568e561   hitier   New generic rest ...
1
2
import xlsxwriter
import io
3c53ad45   hitier   Project edit work...
3

d568e561   hitier   New generic rest ...
4
from flask import render_template, make_response, current_app, redirect, url_for, request, flash, abort, send_file
3b0d5feb   hitier   New Site_Login ca...
5
from flask_login import login_required, current_user
8bd0f3cb   hitier   Fix #25: Flask Bl...
6
7
8

from . import bp

1e327d7a   hitier   New categories page
9
from app.models import Agent, Project, Service, Capacity, Period, db, Company, AgentGrade, AgentStatus, AgentBap, \
79f0ebb0   hitier   Added projects st...
10
    Charge, Category, Label, AgentResponsability, AgentSkill, EmploymentType, ProjectStatus
dcafa5d0   hitier   Protect routes by...
11
12
from app import db_mgr
from app.auth.routes import role_required
d6836d5e   hitier   Agents list
13

5362015f   hitier   Refactored routes...
14
15
16
updated_message = "mis à jour"
added_message = "ajouté"

8bd0f3cb   hitier   Fix #25: Flask Bl...
17

ba0225ff   hitier   New generic form_...
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def form_manager(object_class,
                 object_id_key,
                 object_id,
                 form_template,
                 form_struct,
                 urlfor_exist_dict,
                 urlfor_done_dict):
    """
        Generic form manager, holds etheir POST or GET method.
        Allows to modify existing object, or create new one.
        Form template has to deal with 'object_struct'.
        Class needs to have 'name' and 'id' attr.

    :param object_class:  The object class we will deal with. Needs 'name' attr.
    :param object_id_key: The string to access get the object id from the form.
    :param object_id: Initial object id if any.
    :param form_template: Form template name.
    :param form_struct:  Struct to pass to template for rendering.
    :param urlfor_exist_dict: Where to redirect if object already exist when creating new.
    :param urlfor_done_dict: Where do redirect when done.
    :return:
    """
    if request.method == 'GET':
        if object_id:
            this_object = object_class.query.get(int(object_id))
        else:
            this_object = object_class()
        object_struct = this_object.to_struct()
        return render_template(form_template, object_struct=object_struct,
                               **form_struct)
    elif request.method == 'POST':
        try:
            object_id = request.form[object_id_key]
        except KeyError:
            object_id = None
        if object_id:
            # then update existing
            this_object = object_class.query.get(int(object_id))
            done_string = updated_message + "."
        else:
            # check name doesnt exist yet
            existing_object = object_class.query.filter(object_class.name == request.form.get('name')).one_or_none()
            if existing_object:
d1067150   hitier   Form_manager() ap...
61
                flash(f">{existing_object.name}< existe déjà.", 'warning')
ba0225ff   hitier   New generic form_...
62
63
64
65
66
67
68
69
70
                return redirect(url_for(**urlfor_exist_dict))
            # or create from scratch
            this_object = object_class()
            done_string = added_message + "."
        # fill orm with form and write to db
        this_object.from_request(request)
        db.session.add(this_object)
        db.session.commit()
        # we're done
d1067150   hitier   Form_manager() ap...
71
        flash(f"{this_object.name} (#{this_object.id}) " + done_string)
ba0225ff   hitier   New generic form_...
72
73
74
75
        urlfor_done_dict[object_id_key] = this_object.id
        return redirect(url_for(**urlfor_done_dict))


3b0d5feb   hitier   New Site_Login ca...
76
77
78
79
80
81
82
83
84
85
@bp.before_request
def site_login():
    try:
        if current_app.config['SITE_LOGIN'] \
                and not current_user.is_authenticated:
            return redirect(url_for('auth.login'))
    except KeyError:
        # no such config, juste ignore
        pass

1b604951   hitier   Rearrange code
86

3b0d5feb   hitier   New Site_Login ca...
87
88
@bp.before_request
def catch_all_route():
1b604951   hitier   Rearrange code
89
    current_app.logger.debug(f"{request.method} {request.path}")
3b0d5feb   hitier   New Site_Login ca...
90
91


8bd0f3cb   hitier   Fix #25: Flask Bl...
92
93
@bp.route('/')
def index():
a7e64a99   hitier   Services list
94
95
96
97
    return render_template('index.html', subtitle="Page d'accueil")


@bp.route('/services')
dcafa5d0   hitier   Protect routes by...
98
@role_required('service')
a7e64a99   hitier   Services list
99
100
101
102
103
def services():
    # get services list
    all_services = Service.query.order_by(Service.name).all()
    num_services = len(all_services)
    # pass to template
6cbf4b75   hitier   Periods list
104
    return render_template('services.html', subtitle="Liste des services ({})".format(num_services),
a7e64a99   hitier   Services list
105
                           services=all_services)
d6836d5e   hitier   Agents list
106
107


3dae0d18   hitier   Projects list
108
@bp.route('/projects')
dcafa5d0   hitier   Protect routes by...
109
@role_required('project')
3dae0d18   hitier   Projects list
110
111
def projects():
    # get projects list
f8e1465a   hitier   New projects list...
112
    all_projects = db_mgr.projects()
3dae0d18   hitier   Projects list
113
114
    num_projects = len(all_projects)
    # pass to template
a7e64a99   hitier   Services list
115
    return render_template('projects.html', subtitle="Liste des projets ({})".format(num_projects),
3dae0d18   hitier   Projects list
116
117
                           projects=all_projects)

a7e64a99   hitier   Services list
118

5362015f   hitier   Refactored routes...
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@bp.route('/project/<project_id>')
@role_required('project')
def project(project_id):
    # TODO: am i the project manager ?
    this_project = Project.query.get(int(project_id))
    charges_table = db_mgr.charges_by_project(project_id)
    return render_template('project.html',
                           project=this_project.to_struct(),
                           charges=charges_table,
                           subtitle="{}".format(this_project.name))


@bp.route('/project/create', methods=('POST', 'GET'))
@bp.route('/project/<project_id>/edit', methods=('POST', 'GET'))
@role_required('service')
def project_edit(project_id=None):
d1067150   hitier   Form_manager() ap...
135
    _form_struct = {'categories': db_mgr.category_labels()}
ba0225ff   hitier   New generic form_...
136
137
138
139
    return form_manager(object_class=Project,
                        object_id_key='project_id',
                        object_id=project_id,
                        form_template='project_form.html',
d1067150   hitier   Form_manager() ap...
140
                        form_struct=_form_struct,
ba0225ff   hitier   New generic form_...
141
142
                        urlfor_exist_dict={'endpoint': 'main.project_edit'},
                        urlfor_done_dict={'endpoint': 'main.project', 'project_id': None})
5362015f   hitier   Refactored routes...
143
144
145
146
147
148
149
150
151
152
153


@bp.route('/project/<project_id>/delete', methods=('POST', 'GET'))
@role_required('admin')
def project_delete(project_id=None):
    this_project = Project.query.get(int(project_id))
    flash("Suppression du Projet pas encore implémentée", 'warning')
    # flash(f" {this_project.name} effacé")
    return redirect(url_for('main.project', project_id=this_project.id))


55d58635   hitier   New projects stat...
154
155
156
157
@bp.route('/projects/stats')
@role_required('project')
def projects_stats():
    num_projects = len(Project.query.all())
c8678176   hitier   New stacked table...
158
159
160
    this_categories = Category.query.all()
    return render_template('projects_stats.html', subtitle="Statistiques des projets ({})".format(num_projects),
                           categories=this_categories)
55d58635   hitier   New projects stat...
161
162


d6836d5e   hitier   Agents list
163
@bp.route('/agents')
dcafa5d0   hitier   Protect routes by...
164
@role_required('project')
d6836d5e   hitier   Agents list
165
def agents():
d6836d5e   hitier   Agents list
166
    # get agents list
2cb0a345   hitier   Add 2 columns in ...
167
    all_agents = db_mgr.agents()
d6836d5e   hitier   Agents list
168
169
    num_agents = len(all_agents)
    # pass to template
a7e64a99   hitier   Services list
170
    return render_template('agents.html', subtitle="Liste des agents ({})".format(num_agents),
d6836d5e   hitier   Agents list
171
                           agents=all_agents)
980708a2   hitier   Capacities list
172

8b3ab81d   hitier   All charges now i...
173

5362015f   hitier   Refactored routes...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
@bp.route('/agent/<agent_id>')
@role_required('agent')
def agent(agent_id):
    # TODO: am i the agent, the service or project manager , or the admin ?
    this_agent = Agent.query.get(int(agent_id))
    charges_table = db_mgr.charges_by_agent_tabled(agent_id)
    return render_template('agent.html',
                           agent=this_agent,
                           charges=charges_table,
                           subtitle=f"{this_agent.fullname}")


@bp.route('/agent/create', methods=('POST', 'GET'))
@bp.route('/agent/<agent_id>/edit', methods=('POST', 'GET'))
@role_required('service')
def agent_edit(agent_id=None):
d1067150   hitier   Form_manager() ap...
190
191
192
193
194
195
196
197
198
199
200
    _form_struct = {'companies': Company.query.all(),
                    'grades': AgentGrade.query.all(),
                    'statuses': AgentStatus.query.all(),
                    'baps': AgentBap.query.all()}
    return form_manager(object_class=Agent,
                        object_id_key='agent_id',
                        object_id=agent_id,
                        form_template='agent_form.html',
                        form_struct=_form_struct,
                        urlfor_exist_dict={'endpoint': 'main.agent_edit'},
                        urlfor_done_dict={'endpoint': 'main.agent', 'agent_id': None})
5362015f   hitier   Refactored routes...
201
202
203
204
205
206
207
208
209
210
211


@bp.route('/agent/<agent_id>/delete', methods=('POST', 'GET'))
@role_required('admin')
def agent_delete(agent_id=None):
    this_agent = Agent.query.get(int(agent_id))
    flash("Suppression de l'Agent pas encore implémentée", 'warning')
    # flash(f"Agent {this_agent.name} effacé")
    return redirect(url_for('main.agent', agent_id=this_agent.id))


730521e0   hitier   New agents_stats ...
212
213
214
215
@bp.route('/agents/stats')
@role_required('project')
def agents_stats():
    num_agents = len(Agent.query.all())
9765d07f   hitier   New period widget...
216
217
218
    all_periods = Period.query.order_by(Period.name).all()
    return render_template('agents_stats.html', subtitle="Statistiques des agents ({})".format(num_agents),
                           periods=all_periods)
730521e0   hitier   New agents_stats ...
219
220


0f1c3228   hitier   Added responsabil...
221
@bp.route('/agents/responsabilities')
bae9b898   hitier   New routes: Respo...
222
@role_required('agent')
0f1c3228   hitier   Added responsabil...
223
224
225
226
227
228
229
def responsabilities():
    num_resp = len(AgentResponsability.query.all())
    all_resp = AgentResponsability.query.order_by(AgentResponsability.name).all()
    return render_template('responsabilities.html', subtitle="Liste des Responsabilités ({})".format(num_resp),
                           responsabilities=all_resp)


bae9b898   hitier   New routes: Respo...
230
231
232
233
234
235
236
237
238
239
240
241
@bp.route('/responsability/<responsability_id>')
@role_required('agent')
def responsability(responsability_id):
    this_responsability = AgentResponsability.query.get(int(responsability_id))
    return render_template('responsability.html', subtitle=f"{this_responsability.name}",
                           responsability=this_responsability)


@bp.route('/responsability/create', methods=('POST', 'GET'))
@bp.route('/responsability/<responsability_id>/edit', methods=('POST', 'GET'))
@role_required('admin')
def responsability_edit(responsability_id=None):
7bd625a0   hitier   Form_manager() ap...
242
243
244
245
246
247
248
    return form_manager(object_class=AgentResponsability,
                        object_id_key='responsability_id',
                        object_id=responsability_id,
                        form_template='responsability_form.html',
                        form_struct={},
                        urlfor_exist_dict={'endpoint': 'main.responsability_edit'},
                        urlfor_done_dict={'endpoint': 'main.responsability', 'agent_id': None})
bae9b898   hitier   New routes: Respo...
249
250
251
252
253
254
255
256
257
258
259


@bp.route('/responsability/<responsability_id>/delete', methods=('POST', 'GET'))
@role_required('admin')
def responsability_delete(responsability_id=None):
    this_responsability = AgentResponsability.query.get(int(responsability_id))
    flash("Suppression de la Responsabilité pas encore implémentée", 'warning')
    # flash(f"Statut de Projet {this_status.name} effacé")
    return redirect(url_for('main.responsability', responsability_id=this_responsability.id))


ee193a09   hitier   Added employments
260
261
262
263
264
265
266
267
268
269
@bp.route('/employments')
@login_required
def employments():
    # get employments list
    all_employments = EmploymentType.query.order_by(EmploymentType.name).all()
    num_employments = len(all_employments)
    # pass to template
    return render_template('employments.html', subtitle="Liste des Emplois Types ({})".format(num_employments),
                           employments=all_employments)

79f0ebb0   hitier   Added projects st...
270

48c6f9a0   hitier   Added skills
271
272
273
274
275
276
277
278
279
280
281
@bp.route('/skills')
@login_required
def skills():
    # get skills list
    all_skills = AgentSkill.query.order_by(AgentSkill.name).all()
    num_skills = len(all_skills)
    # pass to template
    return render_template('skills.html', subtitle="Liste des fonctions ({})".format(num_skills),
                           skills=all_skills)


79f0ebb0   hitier   Added projects st...
282
283
284
285
286
287
288
289
290
291
292
@bp.route('/project_statuses')
@login_required
def project_statuses():
    # get project_statuses list
    all_project_statuses = ProjectStatus.query.order_by(ProjectStatus.name).all()
    num_project_statuses = len(all_project_statuses)
    # pass to template
    return render_template('project_statuses.html', subtitle="Statuts de projets ({})".format(num_project_statuses),
                           project_statuses=all_project_statuses)


5033b855   hitier   New routes: Proje...
293
294
295
296
297
298
299
300
301
302
@bp.route('/project_status/<status_id>')
@role_required('admin')
def project_status(status_id):
    # get label
    this_status = ProjectStatus.query.get(int(status_id))
    # pass to template
    return render_template('project_status.html', subtitle=f"{this_status.name}",
                           status=this_status)


5362015f   hitier   Refactored routes...
303
304
305
306
@bp.route('/project_status/create', methods=('POST', 'GET'))
@bp.route('/project_status/<status_id>/edit', methods=('POST', 'GET'))
@role_required('admin')
def project_status_edit(status_id=None):
e9c0b177   hitier   Form_manager() ap...
307
308
309
310
311
312
313
    return form_manager(object_class=ProjectStatus,
                        object_id_key='status_id',
                        object_id=status_id,
                        form_template='project_status_form.html',
                        form_struct={},
                        urlfor_exist_dict={'endpoint': 'main.project_status_edit'},
                        urlfor_done_dict={'endpoint': 'main.project_status', 'status_id': None})
5362015f   hitier   Refactored routes...
314
315
316
317
318
319
320
321
322
323
324


@bp.route('/project_status/<status_id>/delete', methods=('POST', 'GET'))
@role_required('admin')
def project_status_delete(status_id=None):
    this_status = ProjectStatus.query.get(int(status_id))
    flash("Suppression du Statut de Projet pas encore implémentée", 'warning')
    # flash(f"Statut de Projet {this_status.name} effacé")
    return redirect(url_for('main.project_status', status_id=this_status.id))


980708a2   hitier   Capacities list
325
@bp.route('/capacities')
dcafa5d0   hitier   Protect routes by...
326
@login_required
980708a2   hitier   Capacities list
327
328
329
330
331
332
333
def capacities():
    # get capacities list
    all_capacities = Capacity.query.order_by(Capacity.name).all()
    num_capacities = len(all_capacities)
    # pass to template
    return render_template('capacities.html', subtitle="Liste des fonctions ({})".format(num_capacities),
                           capacities=all_capacities)
6cbf4b75   hitier   Periods list
334

8b3ab81d   hitier   All charges now i...
335

1f54a207   hitier   New routes and tp...
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
@bp.route('/capacity/<capacity_id>')
@role_required('admin')
def capacity(capacity_id):
    # get capacity
    this_capacity = Capacity.query.get(int(capacity_id))
    # pass to template
    return render_template('capacity.html', subtitle=f"{this_capacity.name}",
                           capacity=this_capacity)


@bp.route('/capacity/create', methods=('POST', 'GET'))
@bp.route('/capacity/<capacity_id>/edit', methods=('POST', 'GET'))
@role_required('admin')
def capacity_edit(capacity_id=None):
    return form_manager(object_class=Capacity,
                        object_id_key='capacity_id',
                        object_id=capacity_id,
                        form_template='capacity_form.html',
                        form_struct={},
                        urlfor_exist_dict={'endpoint': 'main.capacity_edit'},
                        urlfor_done_dict={'endpoint': 'main.capacity', 'capacity_id': None})


@bp.route('/capacity/<capacity_id>/delete', methods=('POST', 'GET'))
@role_required('admin')
def capacity_delete(capacity_id=None):
    flash("Suppression du Capacity pas encore implémentée", 'warning')
    # this_label = Label.query.get(int(label_id))
    # flash(f"Label {this_label.name} effacé")
    return redirect(url_for('main.capacities'))


ec68fbf2   hitier   New labels page
368
369
370
371
372
373
374
375
376
377
378
@bp.route('/labels')
@login_required
def labels():
    # get labels list
    all_labels = Label.query.order_by(Label.name).all()
    num_labels = len(all_labels)
    # pass to template
    return render_template('labels.html', subtitle="Liste des labels ({})".format(num_labels),
                           labels=all_labels)


1a11a6df   hitier   New label route/page
379
380
381
382
383
@bp.route('/label/<label_id>')
@role_required('admin')
def label(label_id):
    # get label
    this_label = Label.query.get(int(label_id))
1a11a6df   hitier   New label route/page
384
385
    # pass to template
    return render_template('label.html', subtitle=f"{this_label.name}",
8be2494f   hitier   Remove list param...
386
                           label=this_label)
1a11a6df   hitier   New label route/page
387

9c5c9883   hitier   New label.categor...
388

61e8ddf2   hitier   New labels and ca...
389
390
391
392
@bp.route('/label/create', methods=('POST', 'GET'))
@bp.route('/label/<label_id>/edit', methods=('POST', 'GET'))
@role_required('admin')
def label_edit(label_id=None):
2a7414df   hitier   Form_manager() ap...
393
394
395
396
397
398
399
400
    _form_struct = {'categories': Category.query.order_by(Category.name).all()}
    return form_manager(object_class=Label,
                        object_id_key='label_id',
                        object_id=label_id,
                        form_template='label_form.html',
                        form_struct=_form_struct,
                        urlfor_exist_dict={'endpoint': 'main.label_edit'},
                        urlfor_done_dict={'endpoint': 'main.label', 'label_id': None})
61e8ddf2   hitier   New labels and ca...
401
402


5362015f   hitier   Refactored routes...
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
@bp.route('/label/<label_id>/delete', methods=('POST', 'GET'))
@role_required('admin')
def label_delete(label_id=None):
    flash("Suppression du Label pas encore implémentée", 'warning')
    # this_label = Label.query.get(int(label_id))
    # flash(f"Label {this_label.name} effacé")
    return redirect(url_for('main.labels'))


@bp.route('/categories')
@login_required
def categories():
    # get categories list
    all_categories = Category.query.order_by(Category.name).all()
    num_categories = len(all_categories)
    # pass to template
    return render_template('categories.html', subtitle="Liste des categories ({})".format(num_categories),
                           categories=all_categories)


@bp.route('/category/<category_id>')
@role_required('admin')
def category(category_id):
    # get category
    this_category = Category.query.get(int(category_id))
    # pass to template
    return render_template('category.html', subtitle=f"{this_category.name}",
                           category=this_category)


61e8ddf2   hitier   New labels and ca...
433
@bp.route('/category/create', methods=('POST', 'GET'))
fc06cbe5   hitier   Category edit for...
434
@bp.route('/category/<category_id>/edit', methods=('POST', 'GET'))
61e8ddf2   hitier   New labels and ca...
435
436
@role_required('admin')
def category_edit(category_id=None):
23aab032   hitier   Form_manager() ap...
437
438
439
440
441
442
443
444
    _form_struct = {'labels': Label.query.order_by(Label.name).all()}
    return form_manager(object_class=Category,
                        object_id_key='category_id',
                        object_id=category_id,
                        form_template='category_form.html',
                        form_struct=_form_struct,
                        urlfor_exist_dict={'endpoint': 'main.category_edit'},
                        urlfor_done_dict={'endpoint': 'main.category', 'category_id': None})
61e8ddf2   hitier   New labels and ca...
445

12de0b1d   hitier   Now charge add ro...
446

5362015f   hitier   Refactored routes...
447
448
449
450
451
452
453
@bp.route('/category/<category_id>/delete', methods=('POST', 'GET'))
@role_required('admin')
def category_delete(category_id=None):
    flash("Suppression de la Catégorie pas encore implémentée", 'warning')
    # this_category = Category.query.get(int(category_id))
    # flash(f"Category {this_category.name} effacé")
    return redirect(url_for('main.categories'))
a540c9b1   hitier   New project form ...
454
455


5362015f   hitier   Refactored routes...
456
457
458
459
460
461
462
463
464
@bp.route('/periods')
@login_required
def periods():
    # get categories list
    all_periods = Period.query.order_by(Period.name).all()
    num_periods = len(all_periods)
    # pass to template
    return render_template('periods.html', subtitle="Liste des périodes ({})".format(num_periods),
                           periods=all_periods)
12de0b1d   hitier   Now charge add ro...
465
466
467
468
469
470


@bp.route('/charge/add', methods=('POST', 'GET'))
@role_required('service')
def charge_add():
    if request.method == 'GET':
09551925   hitier   New charge add pa...
471
472
473
474
475
476
477
478
        try:
            this_agent = Agent.query.get(int(request.args['agent_id']))
        except KeyError:
            this_agent = None
        try:
            this_project = Project.query.get(int(request.args['project_id']))
        except KeyError:
            this_project = None
12de0b1d   hitier   Now charge add ro...
479
480
481
482
483
        this_agents = Agent.query.order_by(Agent.firstname).all()
        this_projects = Project.query.order_by(Project.name).all()
        this_services = Service.query.order_by(Service.name).all()
        this_periods = Period.query.order_by(Period.id).all()
        this_capacities = Capacity.query.order_by(Capacity.name).all()
e6499574   hitier   Rename form templ...
484
        return render_template('charge_form.html', subtitle="Affecter un agent",
09551925   hitier   New charge add pa...
485
486
                               agent=this_agent,
                               project=this_project,
12de0b1d   hitier   Now charge add ro...
487
488
489
490
491
492
493
494
495
496
497
498
499
                               projects=this_projects,
                               services=this_services,
                               periods=this_periods,
                               capacities=this_capacities,
                               agents=this_agents)
    elif request.method == 'POST':
        this_agent = Agent.query.get(request.form.get('agent_id'))
        this_charge = Charge()
        this_charge.from_request(request)
        db.session.add(this_charge)
        db.session.commit()
        flash(f"Nouvelle charge pour l'agent {this_agent.fullname}")
        return redirect(url_for('main.agent', agent_id=this_agent.id))
1b604951   hitier   Rearrange code
500
501


64515ad9   hitier   New agent charges...
502
# - - - - - - - - - - - - - - - - - - - -  REST API - - - - - - - - - - - - - - - - - - - -
1b604951   hitier   Rearrange code
503

d568e561   hitier   New generic rest ...
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
def array_to_any(array, format='csv'):
    if format == 'csv':
        exporter = array_to_csv
    elif format == 'xls':
        exporter = array_to_xls
    else:
        abort(404)
    return exporter(array)


def array_to_xls(array):
    output = io.BytesIO()
    # Create a workbook and add a worksheet.
    workbook = xlsxwriter.Workbook(output, {'in_memory': True})
    worksheet = workbook.add_worksheet()

    # Iterate over the data and write it out row by row, col by col
    row = 0
    for line in array:
        col = 0
        for cell in line:
            worksheet.write(row, col, cell)
            col += 1
        row += 1

    workbook.close()

    output.seek(0)

    try:
        file_to_return = send_file(output, as_attachment=True,
                                   mimetype='application/vnd.ms-excel',
                                   attachment_filename='charge.xlsx')
    except FileNotFoundError:
        abort(404)

    return file_to_return


9434e481   hitier   New helper funcs ...
543
def array_to_csv(array, sep=','):
f25c2405   hitier   Add corresponding...
544
    csv_table = []
9434e481   hitier   New helper funcs ...
545
546
547
    for line in array:
        line = [cell.replace(sep, "-") for cell in line]
        line_string = sep.join(line)
f25c2405   hitier   Add corresponding...
548
549
550
551
552
        csv_table.append(line_string)
    resp = make_response("\n".join(csv_table))
    resp.headers['Content-Type'] = 'text/plain;charset=utf8'
    return resp

c8b7caf5   hitier   Agent now uses co...
553

11518c93   hitier   Fix broken rest_c...
554
555
@bp.route('/charge/project/<project_id>/<sort_key>/<any_format>')
@bp.route('/charge/project/<project_id>/<sort_key>')
9434e481   hitier   New helper funcs ...
556
@role_required('project')
11518c93   hitier   Fix broken rest_c...
557
558
def rest_charge_project(project_id, sort_key, any_format='csv'):
    return array_to_any(db_mgr.charges_by_project_stacked(project_id, sort_key), any_format)
9434e481   hitier   New helper funcs ...
559
560


d568e561   hitier   New generic rest ...
561
@bp.route('/charge/agent/<agent_id>/<any_format>')
c8b7caf5   hitier   Agent now uses co...
562
@bp.route('/charge/agent/<agent_id>')
1b604951   hitier   Rearrange code
563
@role_required('service')
d568e561   hitier   New generic rest ...
564
565
def rest_charge_agent(agent_id, any_format='csv'):
    return array_to_any(db_mgr.charges_by_agent_stacked(agent_id), any_format)
95880b0d   hitier   New charges_for_p...
566
567


d568e561   hitier   New generic rest ...
568
@bp.route('/charge/labels/<category_id>/<any_format>')
c8678176   hitier   New stacked table...
569
570
@bp.route('/charge/labels/<category_id>')
@role_required('project')
d568e561   hitier   New generic rest ...
571
572
def rest_labels_stats(category_id, any_format='csv'):
    return array_to_any(db_mgr.charges_by_labels_stacked(category_id), any_format)
c8678176   hitier   New stacked table...
573
574


d568e561   hitier   New generic rest ...
575
@bp.route('/charge/projects/<any_format>')
95880b0d   hitier   New charges_for_p...
576
577
@bp.route('/charge/projects')
@role_required('project')
d568e561   hitier   New generic rest ...
578
579
def rest_projects_stats(any_format='csv'):
    return array_to_any(db_mgr.charges_for_projects_stacked(), any_format)
730521e0   hitier   New agents_stats ...
580
581
582
583
584
585
586


@bp.route('/count/agents/by_status/<period_id>/<any_format>')
@bp.route('/count/agents/by_status/<period_id>')
@role_required('project')
def rest_agents_status_count(period_id, any_format='csv'):
    return array_to_any(db_mgr.count_agents_by_status(period_id), any_format)