Skip to content
Snippets Groups Projects
Select Git revision
  • b4da9f4dc49abfaa8fff486f7ca10502caf78bdb
  • devel default
  • master
  • fo
  • jirka/typing
  • fo-base
  • mj/submit-images
  • jk/issue-96
  • jk/issue-196
  • honza/add-contestant
  • honza/mr7
  • honza/mrf
  • honza/mrd
  • honza/mra
  • honza/mr6
  • honza/submit-images
  • honza/kolo-vs-soutez
  • jh-stress-test-wip
  • shorten-schools
19 results

org_round.py

Blame
  • org_place.py 13.75 KiB
    from flask import render_template, g, redirect, url_for, flash, request
    from flask_wtf import FlaskForm
    import locale
    from markupsafe import Markup
    from sqlalchemy.orm import joinedload
    from typing import List, Optional
    import werkzeug.exceptions
    import wtforms
    
    import mo
    import mo.db as db
    import mo.imports
    import mo.rights
    import mo.util
    from mo.web import app
    import mo.web.fields as mo_fields
    import wtforms.validators as validators
    
    
    @app.route('/org/place/<int:id>/')
    def org_place(id: int):
        sess = db.get_session()
    
        place = sess.query(db.Place).get(id)
        if not place:
            raise werkzeug.exceptions.NotFound()
    
        if place.type == db.PlaceType.school:
            school = sess.query(db.School).get(place.place_id)
        else:
            school = None
    
        children = sorted(place.children, key=lambda p: locale.strxfrm(p.name))
        rr = g.gatekeeper.rights_for(place)
    
        contests = (sess.query(db.Contest)
                        .options(joinedload(db.Contest.round))
                        .filter_by(place=place)
                        .all())
    
        contests.sort(key=lambda c: (-c.round.year, c.round.category, c.round.seq, c.round.part))
    
        return render_template(
            'org_place.html', place=place, school=school,
            can_edit=rr.can_edit_place(place),
            can_add_child=rr.can_add_place_child(place),
            children=children,
            contests=contests
        )
    
    
    class PlaceEditForm(FlaskForm):
        name = wtforms.StringField(
            'Název', render_kw={'autofocus': True},
            validators=[validators.DataRequired()]
        )
        code = wtforms.StringField(
            'Kód', filters=[lambda x: x or None],  # may be NULL in db
            description="Na místo se lze odkazovat kódem z písmen a číslic."
        )
        type = wtforms.SelectField(
            'Typ', choices=db.PlaceType.choices(), coerce=db.PlaceType.coerce
        )
        nuts = wtforms.StringField(
            'NUTS', filters=[lambda x: x or None],  # may be NULL in db
            description="Pro okresy a výše"
        )
        note = wtforms.StringField('Poznámka')
        submit = wtforms.SubmitField('Uložit')
    
    
    class PlaceSchoolEditForm(PlaceEditForm):
        red_izo = wtforms.StringField('RED_IZO')
        ico = wtforms.StringField('IČO')
        official_name = wtforms.StringField('Oficiální název')
        address = wtforms.StringField('Adresa')
        is_zs = wtforms.BooleanField('')
        is_ss = wtforms.BooleanField('')
        submit = wtforms.SubmitField('Uložit')
    
    
    def place_breadcrumbs(place: db.Place, action: Optional[str] = None) -> Markup:
        elements = []
        parents: List[db.Place] = reversed(g.gatekeeper.get_parents(place))
        for parent in parents:
            elements.append((url_for('org_place', id=parent.place_id), parent.name))
        if action:
            elements.append(('', action))
    
        if len(elements) == 0:
            return ""
    
        return Markup(
            "\n".join([f"<li><a href='{url}'>{name}</a>" for url, name in elements[:-1]])
            + "<li>" + elements[-1][1]
        )
    
    
    @app.route('/org/place/<int:id>/edit', methods=('GET', 'POST'))
    def org_place_edit(id: int):
        sess = db.get_session()
    
        place = sess.query(db.Place).get(id)
        if not place:
            raise werkzeug.exceptions.NotFound()
    
        rr = g.gatekeeper.rights_for(place)
        if not rr.can_edit_place(place):
            raise werkzeug.exceptions.Forbidden()
    
        if place.type == db.PlaceType.school:
            school = sess.query(db.School).get(place.place_id)
            # Pass school data as additional dict (data is used after obj)
            form = PlaceSchoolEditForm(obj=place, data=db.row2dict(school))
            form.name.description = ('Název školy tak, jak se má objevovat ve výsledkové listině. Viz '
                + Markup('<a href="' + url_for('doc_garant') + '#kodskoly">pojmenovací konvence</a>.'))
        else:
            form = PlaceEditForm(obj=place)
            school = None
    
        form.code.description += f' Kromě zadaného kódu funguje též #{id}.'
        form.type.choices = db.PlaceType.choices(level=place.level)
    
        if form.validate_on_submit():
            form.populate_obj(place)
            if school:
                form.populate_obj(school)
    
            msg = 'Změny místa uloženy'
            redirectURL = url_for('org_place', id=id)
    
            if sess.is_modified(place) or school and sess.is_modified(school):
                placeChanges = db.get_object_changes(place)
                schoolChanges = {}
                if school:
                    if request.form.get('type') != 'school':
                        # School record removed
                        mo.util.log(
                            type=db.LogType.place,
                            what=school.place_id,
                            details={'action': 'school-delete', 'school': db.row2dict(school)},
                        )
                        app.logger.info(f"Deleting school record for place {place.place_id}")
                        db.get_session().delete(school)
                        msg = 'Změny místa uloženy, záznam o škole smazán'
                    else:
                        schoolChanges = db.get_object_changes(school)
                elif request.form.get('type') == 'school':
                    # School record created
                    new_school = db.School()
                    new_school.place_id = place.place_id
                    mo.util.log(
                        type=db.LogType.place,
                        what=new_school.place_id,
                        details={'action': 'school-add'},
                    )
                    app.logger.info(f"Creating new school for place {place.place_id}")
                    db.get_session().add(new_school)
                    # Take org directly to the school edit to fill the data
                    msg = 'Záznam o škole vytvořen, vyplňte prosím všechna data'
                    redirectURL = url_for('org_place_edit', id=id)
    
                changes = {**placeChanges, **schoolChanges}
                app.logger.info(f"Place {id} modified, changes: {changes}")
                mo.util.log(
                    type=db.LogType.place,
                    what=id,
                    details={'action': 'edit', 'changes': changes},
                )
                db.get_session().commit()
                flash(msg, 'success')
            else:
                flash(u'Žádné změny k uložení', 'info')
    
            return redirect(redirectURL)
    
        return render_template(
            'org_place_edit.html', place=place, school=school,
            form=form,
        )
    
    
    class PlaceMoveForm(FlaskForm):
        new_parent = mo_fields.Place(validators=[validators.DataRequired()], render_kw={'autofocus': True})
        submit = wtforms.SubmitField('Najít místo')
        reset = wtforms.HiddenField()
        move = wtforms.HiddenField()
    
    
    class PlaceMoveConfirmForm(FlaskForm):
        new_parent = mo_fields.Place(widget = wtforms.widgets.HiddenInput())
        reset = wtforms.SubmitField('Zrušit')
        move = wtforms.SubmitField('Přesunout')
    
    
    @app.route('/org/place/<int:id>/move', methods=('GET', 'POST'))
    def org_place_move(id: int):
        sess = db.get_session()
    
        # Tests: can move only existing places that we can edit
        place = sess.query(db.Place).get(id)
        if not place:
            raise werkzeug.exceptions.NotFound()
        rr = g.gatekeeper.rights_for(place)
        if not rr.can_edit_place(place):
            raise werkzeug.exceptions.Forbidden()
    
        new_parents = None
        search_error = None
    
        form = PlaceMoveForm()
        form_confirm = None
        if not form.validate_on_submit():
            if form.new_parent.place_error:
                search_error = form.new_parent.place_error
        else:
            if form.reset.data:
                return redirect(url_for('org_place_move', id=id))
    
            new_parent = form.new_parent.place
            new_parents = reversed(g.gatekeeper.get_parents(new_parent))
            (_, levels) = db.place_type_names_and_levels[place.type]
    
            rr = g.gatekeeper.rights_for(new_parent)
            if not rr.can_edit_place(new_parent):
                search_error = 'Nemáte právo k editaci vybraného nadřazeného místa, přesun nelze uskutečnit'
            elif (new_parent.level + 1) not in levels:
                search_error = f'Toto místo ({place.type_name()}) nelze přemístit pod vybrané místo ({new_parent.type_name()}), dostalo by se na nepovolený level'
            elif new_parent.place_id == place.parent:
                search_error = 'Žádná změna, místo je zde již umístěno'
            elif form.move.data:
                # Everything is OK, if submitted with 'move' do the move
                place.parent = new_parent.place_id
                place.level = new_parent.level + 1
                changes = db.get_object_changes(place)
                mo.util.log(
                    type=db.LogType.place,
                    what=id,
                    details={'action': 'move', 'changes': changes},
                )
                app.logger.info(f"Place {id} moved, changes: {changes}")
                db.get_session().commit()
                flash('Místo úspěšně přesunuto', 'success')
                return redirect(url_for('org_place', id=id))
            else:
                # OK but not confirmed yet, display the confirm form
                form_confirm = PlaceMoveConfirmForm()
                form_confirm.new_parent.data = form.new_parent.data
                # tady se používá hnusný trik, že políčko new_parents z PlaceMoveConfirmForm se
                # parsuje jako new_parents z PlaceMoveForm
    
        return render_template(
            'org_place_move.html',
            place=place, form=form, form_confirm=form_confirm, search_error=search_error,
            new_parents=new_parents
        )
    
    
    @app.route('/org/place/<int:id>/delete', methods=('POST',))
    def org_place_delete(id: int):
        sess = db.get_session()
    
        # Tests: can delete only existing places that we can edit
        place = sess.query(db.Place).get(id)
        if not place:
            raise werkzeug.exceptions.NotFound()
        rr = g.gatekeeper.rights_for(place)
        if not rr.can_edit_place(place):
            raise werkzeug.exceptions.Forbidden()
    
        # Cannot delete place with children
        if place.children:
            flash("Nelze smazat místo s podřízenými místy", "danger")
            return redirect(url_for('org_place', id=id))
    
        # Cannot delete place with contests
        if db.get_count(sess.query(db.Contest).filter_by(place_id=id)) > 0:
            flash("Nelze smazat místo ke kterému se váže nějaká soutěž ", "danger")
            return redirect(url_for('org_place', id=id))
    
        if place.type == db.PlaceType.school:
            school = sess.query(db.School).get(place.place_id)
            mo.util.log(
                type=db.LogType.place,
                what=school.place_id,
                details={'action': 'school-delete', 'school': db.row2dict(school)},
            )
            app.logger.info(f"Deleting school record for place {id}")
            db.get_session().delete(school)
    
        mo.util.log(
            type=db.LogType.place,
            what=id,
            details={'action': 'delete', 'place': db.row2dict(place)},
        )
        app.logger.info(f"Deleting place {id}")
    
        parent = place.parent
        db.get_session().delete(place)
        db.get_session().commit()
    
        flash("Místo smazáno", "success")
        return redirect(url_for('org_place', id=parent))
    
    
    @app.route('/org/place/<int:id>/new-child', methods=('GET', 'POST'))
    def org_place_new_child(id: int):
        sess = db.get_session()
    
        # Tests: can add new child only under existing places that we can edit
        parent_place = sess.query(db.Place).get(id)
        if not parent_place:
            raise werkzeug.exceptions.NotFound()
        rr = g.gatekeeper.rights_for(parent_place)
        if not rr.can_edit_place(parent_place):
            raise werkzeug.exceptions.Forbidden()
        if not parent_place.can_have_child():
            raise werkzeug.exceptions.Forbidden()
    
        form = PlaceEditForm()
        form.type.choices = db.PlaceType.choices(level=parent_place.level + 1)
    
        if form.validate_on_submit():
            new_place = db.Place()
            form.populate_obj(new_place)
            new_place.parent = parent_place.place_id
            new_place.level = parent_place.level + 1
            sess.add(new_place)
            sess.flush()
    
            app.logger.info(f"New place created: {db.row2dict(new_place)}")
            mo.util.log(
                type=db.LogType.place,
                what=new_place.place_id,
                details={'action': 'new', 'place': db.row2dict(new_place)},
            )
    
            redirect_url = url_for('org_place', id=new_place.place_id)
            msg = 'Nové místo uloženo'
    
            if new_place.type == db.PlaceType.school:
                new_school = db.School()
                new_school.place_id = new_place.place_id
                mo.util.log(
                    type=db.LogType.place,
                    what=new_school.place_id,
                    details={'action': 'school-add'},
                )
                app.logger.info(f"Creating new school for place {new_place.place_id}")
                sess.add(new_school)
                # Take org directly to the school edit to fill the data
                msg = 'Záznam o škole vytvořen, vyplňte prosím všechna data'
                redirect_url = url_for('org_place_edit', id=new_place.place_id)
    
            sess.commit()
            flash(msg, 'success')
            return redirect(redirect_url)
    
        return render_template('org_place_new.html', parent_place=parent_place, form=form)
    
    
    @app.route('/org/place/')
    def org_place_root():
        root = db.get_root_place()
        return redirect(url_for('org_place', id=root.place_id))
    
    
    @app.route('/org/place/<int:id>/rights')
    def org_place_rights(id: int):
        sess = db.get_session()
    
        place = sess.query(db.Place).get(id)
        if not place:
            raise werkzeug.exceptions.NotFound()
    
        parent_ids = [p.place_id for p in g.gatekeeper.get_parents(place)]
        roles = (sess.query(db.UserRole)
                 .filter(db.UserRole.place_id.in_(parent_ids))
                 .options(joinedload(db.UserRole.user))
                 .all())
        roles.sort(key=lambda r: (mo.rights.role_order_by_type[r.role], r.user.sort_key()))
    
        rr = g.gatekeeper.rights_for(place)
        rights = sorted(rr.rights, key=lambda r: r.name)
    
        return render_template(
            'org_place_rights.html', place=place, rights=rights,
            roles=roles, roles_by_type=mo.rights.roles_by_type
        )