snorklerjoe/CubeServer

View on GitHub
src/CubeServer-app/cubeserver_app/blueprints/home/__init__.py

Summary

Maintainability
B
4 hrs
Test Coverage
"""Flask blueprint to manage the home page"""

from bson.objectid import ObjectId
from flask import (
    abort,
    Blueprint,
    flash,
    redirect,
    render_template,
    request,
    session,
    url_for,
)
from flask_login import login_required, login_user, logout_user, current_user
from is_safe_url import is_safe_url

from cubeserver_common.config import DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD
from cubeserver_common.models.team import Team, TeamStatus, TeamLevel
from cubeserver_common.models.user import User, UserLevel
from cubeserver_common.models.datapoint import DataPoint
from cubeserver_common.models.config.conf import Conf
from cubeserver_app import util
from cubeserver_app.tables.team import LeaderboardTeamTable
from cubeserver_app.blueprints.home.activation_form import UserActivationForm
from cubeserver_app.blueprints.home.login_form import LoginForm
from cubeserver_app.tables.datapoints import LeaderboardDataTable

bp = Blueprint("home", __name__, url_prefix="/", template_folder="templates")


@bp.route("/")
def home():
    """Renders the home page"""
    if (
        current_user
        and current_user.is_authenticated
        and current_user.level == UserLevel.ADMIN
    ):
        return redirect(url_for("admin.admin_home"))
    return render_template("home.html.jinja2")


@bp.route("/stats", defaults={"sel_div": "all"})
@bp.route("/stats/<sel_div>")
def leaderboard(sel_div: str = ""):
    """Renders the leaderboard/stats"""
    # Figure out which division is selected:
    selected_division = None
    if sel_div in [l.value for l in TeamLevel]:
        selected_division = TeamLevel(sel_div)
    # Fetch teams from database and populate a table:
    team_objects = [
        team
        for team in Team.find(
            {
                "status": {
                    "$nin": [TeamStatus.UNAPPROVED.value, TeamStatus.INTERNAL.value]
                }
            }
            if selected_division is None
            else {
                "status": {
                    "$nin": [TeamStatus.UNAPPROVED.value, TeamStatus.INTERNAL.value]
                },
                "weight_class": selected_division.value,
            }
        )
    ]
    teams_table = LeaderboardTeamTable(team_objects)
    # Render the template:
    return render_template(
        "leaderboard.html.jinja2",
        teams_table=teams_table.__html__(),
        divisions=[TeamLevel.JUNIOR_VARSITY, TeamLevel.VARSITY],
        selected_division=selected_division,
    )


@bp.route("/team/<team_name>")
def team_info(team_name: str = ""):
    """A page showing team info & score tally"""
    # Look-up the team:
    team = Team.find_by_name(team_name)
    if team is None:
        return abort(400)
    table = LeaderboardDataTable([])
    if request.args.get("ajax") == "true":
        # order[0][column]=0&order[0][dir]=desc&start=0&length=5
        count, results = util.parse_query(
            DataPoint,
            table._cols,
            request.args,
            filter={"team_reference": ObjectId(team.id)},
        )
        data = [
            [c.td_contents(item, [attr]) for attr, c in table._cols.items() if c.show]
            for item in results
        ]
        return {
            "draw": int(request.args.get("draw", 0)) + 1,
            "recordsTotal": count,
            "recordsFiltered": count,
            "data": data,
        }
    else:
        return render_template(
            "team_info.html.jinja2", team=team, table=table.__html__()
        )


@bp.route("/activate", methods=["GET", "POST"])
def activation():
    """Activates a user invitation
    When the HTTP method is GET, this assumes it is from the
    invitation URL generated by the admin panel and shared with
    a prospective user. When this link is opened by the user, it
    will produce a form such that the user may activate the account
    with their own username/email/password. This form will submit
    back to this page with POST, after which their account will be
    updated and activated."""
    # GET variables from the invitation link:
    form = UserActivationForm()
    user = None
    updating = False
    if current_user and current_user.is_authenticated and current_user.is_active:
        user = current_user
        updating = True
    else:
        inactive_username = request.args.get("user")
        activation_token = request.args.get("token")
        if not (inactive_username and activation_token):
            return abort(400)
        user = User.find_by_username(inactive_username)
        if not user.verify_pwd(activation_token):
            return abort(403)
    if user.is_active or user is not None and user.verify_pwd(activation_token):
        if form.validate_on_submit():
            new_email = form.email.data
            new_username = form.username.data
            new_password = form.password.data
            user.activate(new_username, new_email, new_password)
            user.save()
            # Log the user out so they can attempt to log in anew...
            if current_user and current_user.is_authenticated:
                logout_user()
                session.clear()
            flash("Successfully reset account. Try logging in.")
            return redirect(url_for("home.login"))
        if updating:
            form.username.data = user.name
            form.email.data = user.email
        return render_template("activation_form.html.jinja2", form=form)
    else:
        return abort(403)


# TODO: Add a forgot-password that emails a reactivation link
@bp.route("/login", methods=["GET", "POST"])
def login():
    """The login page
    Users must be activated to be able to log in.
    Logging in is only useful for admin privileges so far."""
    # Make sure there is a default admin user if the database is empty:
    if len(User.find()) == 0:  # The database is newly initialized:
        default_user = User(level=UserLevel.ADMIN)
        default_user.activate(DEFAULT_ADMIN_USERNAME, "", DEFAULT_ADMIN_PASSWORD)
        default_user.save()
    # Now on to the rest of logging in...
    form = LoginForm()
    if form.validate_on_submit():  # If the input is validated, the pwd is good
        user = User.find_by_username(form.username.data)
        assert user.verify_pwd(
            form.password.data
        ), "The LoginForm validations aren't working!"
        # Log the user in:
        login_user(user)  # , remember=form.remember_me.data)
        session["username"] = user.name
        if user.is_active and user.is_authenticated:
            flash("Logged in successfully.")
        else:
            flash("Your account has been suspended or deactivated.")
            logout_user()
            session.clear()
            return abort(403)
        next_loc = request.args.get("next")
        if next_loc is None and user.level == UserLevel.ADMIN:
            next_loc = url_for("admin.admin_home")
        if next_loc is not None and not is_safe_url(
            next_loc, {request.headers["Host"]}
        ):
            return abort(400)
        return redirect(next_loc or url_for("home.home"))
    return render_template("login.html.jinja2", form=form)


@bp.route("/logout")
@login_required
def logout():
    """Logs out the user"""
    logout_user()
    session.clear()
    return render_template("logout.html.jinja2")


@bp.route("/my_profile")
@login_required
def settings():
    """User settings/profile"""
    return render_template("settings.html.jinja2")