-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] Add attendance display #577
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,6 +109,61 @@ def valid_transitions(self, requires_payment): | |
""" | ||
return self.__class__.transition_table(requires_payment)[self.value] | ||
|
||
@classmethod | ||
def colors(cls): | ||
""" | ||
:return: a dict defining display colors for all enum values | ||
:rtype: dict | ||
""" | ||
return { | ||
cls.Active: "#00a5cc", | ||
cls.Rejected: "#005aa0", | ||
cls.PaymentPending: "#eee8a9", | ||
cls.SelfUnregistered: "#f49431", | ||
cls.JustifiedAbsentee: "#005aa0", | ||
cls.UnJustifiedAbsentee: "#ffad8f", | ||
cls.ToBeDeleted: "", | ||
cls.Waiting: "#e6f4f1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Si possible ce serait pas mal de définir les couleurs au niveau des templates Jinja2 |
||
} | ||
|
||
def color(self): | ||
"""Select the value color. | ||
|
||
:returns: a CSS color | ||
:rtype: string""" | ||
cls = self.__class__ | ||
return cls.colors()[self.value] | ||
|
||
@classmethod | ||
def absent_status(cls): | ||
""" | ||
:return: a list of values defined as absent status | ||
:rtype: list | ||
""" | ||
return [ | ||
cls.PaymentPending, | ||
cls.SelfUnregistered, | ||
cls.JustifiedAbsentee, | ||
cls.UnJustifiedAbsentee, | ||
] | ||
|
||
def is_absent(self): | ||
"""Check if this status is considered as absent. | ||
|
||
:rtype: bool""" | ||
return self in self.__class__.absent_status() | ||
|
||
@classmethod | ||
def infamous_status(cls): | ||
""" | ||
:return: a list of values defined as infamous status | ||
:rtype: list | ||
""" | ||
return [ | ||
cls.SelfUnregistered, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pour moi SelfUnregistered ne devrait pas être infamous par défaut, je pense qu'il y a beaucoup d'encadrants qui ne mettent pas à jour le statut pour une absence justifiée. Si elle est vraiment non justifié ils peuvent changer le statut en UnjustifiedAbsentee a posteriori. Mieux vaut avoir des faux négatifs que des faux positifs à mon avis. |
||
cls.UnJustifiedAbsentee, | ||
] | ||
|
||
|
||
class Registration(db.Model): | ||
"""Object linking a user (participant) and an event. | ||
|
@@ -139,7 +194,7 @@ class Registration(db.Model): | |
:type: :py:class:`collectives.models.registration.RegistrationStatus`""" | ||
|
||
level = db.Column( | ||
db.Enum(RegistrationLevels), nullable=False | ||
db.Enum(RegistrationLevels), nullable=False, default=RegistrationLevels.Normal | ||
) # Co-encadrant, Normal | ||
""" Level of the participant for this event (normal, co-leader...) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,15 @@ | ||
""" Module for misc User methods which does not fit in another submodule""" | ||
import os | ||
import datetime | ||
|
||
import phonenumbers | ||
from flask_uploads import UploadSet, IMAGES | ||
from sqlalchemy import func | ||
|
||
from collectives.models.globals import db | ||
from collectives.models.event import Event, EventType | ||
from collectives.models.configuration import Configuration | ||
from collectives.models.registration import Registration | ||
from collectives.models.registration import Registration, RegistrationStatus | ||
from collectives.models.reservation import ReservationStatus | ||
from collectives.models.user.enum import Gender | ||
from collectives.utils.time import current_time | ||
|
@@ -211,3 +213,60 @@ def form_of_address(self): | |
if self.gender == Gender.Man: | ||
return "M." | ||
return "" | ||
|
||
def attendance_report(self, time=datetime.timedelta(days=99 * 365)): | ||
"""Compile an attendance report of the user by status. | ||
|
||
Only Event types Collectives and Training are looked. | ||
|
||
:returns: attendance report | ||
:rtype: dict | ||
:param datetime.timedelta time: How far in the past the registrations should be | ||
searched. | ||
""" | ||
start_date = datetime.datetime.now() - time | ||
query = Registration.query.with_entities( | ||
Registration.status, func.count(Registration.status) | ||
) | ||
query = query.filter_by(user=self) | ||
query = query.filter(Registration.event.has(Event.start > start_date)) | ||
query = query.filter( | ||
Registration.event.has(Event.event_type.has(EventType.attendance == True)) | ||
) | ||
return dict(query.group_by(Registration.status).all()) | ||
|
||
def attendance_grade(self, time=datetime.timedelta(days=99 * 365)): | ||
"""Compile an attendance grade of the user by status, from A to E. | ||
|
||
Basically, it counts infamous registration status. Algorithms select the most | ||
favorable. For the time period: | ||
|
||
* A: No infamous status | ||
* B: Less than 5% infamous status. | ||
* C: One infamous status | ||
* D: Two infamous status | ||
* E: others | ||
|
||
:param datetime.timedelta time: How far in the past the registrations should be | ||
searched. | ||
:returns: A for good attendance, F for awful | ||
:rtype: String | ||
""" | ||
report = self.attendance_report(time) | ||
infamous_strikes = sum( | ||
report.get(status, 0) for status in RegistrationStatus.infamous_status() | ||
) | ||
|
||
if infamous_strikes == 0: | ||
return "A" | ||
|
||
total = sum(report.values()) | ||
percent = infamous_strikes / total | ||
|
||
if percent < 0.05: | ||
return "B" | ||
if infamous_strikes == 1: | ||
return "C" | ||
if infamous_strikes == 2: | ||
return "D" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quelle est la motivation pour définir parfois en pourcentage et parfois en nombre absolu ? Tu as un exemple en tête ? |
||
return "E" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
.bar { | ||
box-shadow: $box-shadow-default; | ||
background: $color-white; | ||
padding: 0px; | ||
display:flex; | ||
max-width: 800px; | ||
margin: auto; | ||
text-align: center; | ||
|
||
& .item{ | ||
padding: 0.5em 0px; | ||
width: 1px; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
color: $color-gray-dark; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* Tooltip container */ | ||
.tooltip { | ||
position: relative; | ||
display: inline-block; | ||
|
||
& .tooltiptext { | ||
visibility: hidden; | ||
background-color: $color-gray-bg-light; | ||
padding: 5px; | ||
border-radius: 6px; | ||
box-shadow: $box-shadow-default; | ||
min-width: 200px; | ||
color: $color-gray-dark; | ||
|
||
/* Position the tooltip text - see examples below! */ | ||
position: absolute; | ||
z-index: 1; | ||
|
||
&.tooltip-right{ | ||
top: -5px; | ||
right: 105%; | ||
} | ||
&.tooltip-left{ | ||
top: -5px; | ||
left: 105%; | ||
} | ||
&.tooltip-bottom{ | ||
top: 120%; | ||
left: 50%; | ||
} | ||
&.tooltip-top{ | ||
bottom: 120%; | ||
left: 50%; | ||
} | ||
} | ||
|
||
/* Show the tooltip text when you mouse over the tooltip container */ | ||
&:hover .tooltiptext { | ||
visibility: visible; | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"track_attendance" peut être ? Histoire d'avoir un nom un peu plus explicite
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
En effet, je vais modifier