Exemplo n.º 1
0
    def post(self) -> Any:
        email = self.request.params.get("email")
        if not email:
            self.request.session["missing_email"] = True
            return HTTPFound(location=self.request.route_url("user_login"))

        email = User.normalize_email(email)
        if not User.validate_email(email):
            self.request.session["incorrect_email"] = True
            return HTTPFound(location=self.request.route_url("user_login"))

        user, created = get_one_or_create(User, email=email)

        # Automatically add user without a team to the authenticated team
        if not user.teams and self.request.team is not None:
            user.teams.append(self.request.team)

        if created:
            DBSession.flush()  # so that the DB assigns a value to user.pk

        # Prevent from impersonating an existing member of another team
        if self.request.team and self.request.team not in user.teams:
            self.request.session["already_in_use"] = True
            return HTTPFound(location=self.request.route_url("user_login"))

        user.last_login_at = datetime.utcnow()

        next_url = self.next_url
        if not user.name:
            next_url = self.request.route_url("welcome", _query={"source": next_url})

        headers = remember(self.request, user.pk)

        return HTTPFound(location=next_url, headers=headers)
Exemplo n.º 2
0
def create_or_update_lecture(dossier: Dossier, lecture_ref: LectureRef,
                             user: Optional[User]) -> bool:
    changed = False

    lecture_created = False
    lecture_updated = False

    texte = Texte.get_or_create_from_ref(lecture_ref.texte,
                                         lecture_ref.chambre)

    lecture = Lecture.get_from_ref(lecture_ref, dossier)

    if lecture is not None and lecture.texte is not texte:
        # We probably created the Lecture before a new Texte was adopted
        # by the commission. Time to update with the final one!
        TexteMisAJour.create(lecture=lecture, texte=texte)
        lecture_updated = True

    if lecture is None:
        lecture = Lecture.create_from_ref(lecture_ref, dossier, texte)
        LectureCreee.create(lecture=lecture, user=user)
        lecture_created = True

    if lecture_created or lecture_updated:
        changed = True

        # Make sure the lecture gets its primary key.
        DBSession.flush()

        # Enqueue tasks to fetch articles and amendements.
        huey.enqueue_on_transaction_commit(fetch_articles.s(lecture.pk))
        huey.enqueue_on_transaction_commit(fetch_amendements.s(lecture.pk))

    return changed
Exemplo n.º 3
0
def _add_or_update_amendements(
        amendements: Iterable[Amendement]) -> Tuple[int, int, int]:
    added, updated, unchanged = 0, 0, 0
    for amendement in amendements:
        existing = (DBSession.query(Amendement).filter(
            Amendement.chambre == amendement.chambre,
            Amendement.session == amendement.session,
            Amendement.num_texte == amendement.num_texte,
            Amendement.organe == amendement.organe,
            Amendement.num == amendement.num,
        ).first())
        if existing is None:
            DBSession.add(amendement)
            added += 1
        else:
            changes = amendement.changes(existing,
                                         ignored_fields=REPONSE_FIELDS)
            if changes:
                for field_name, (new_value, old_value) in changes.items():
                    setattr(existing, field_name, new_value)
                updated += 1
            else:
                unchanged += 1
    if added or updated:
        DBSession.flush()
    return added, updated, unchanged
Exemplo n.º 4
0
def get_team(request: Request) -> Optional[Team]:
    team_name = request.environ.get("HTTP_X_REMOTE_USER")
    if team_name is None:
        return None
    team: Optional[Team] = DBSession.query(Team).filter_by(
        name=team_name).first()
    if team is None:
        team = Team.create(name=team_name)
        DBSession.flush()
    return team
Exemplo n.º 5
0
 def post(self) -> Response:
     DBSession.delete(self.dossier.team)
     for lecture in self.dossier.lectures:
         DBSession.delete(lecture)
     DBSession.flush()
     DossierDesactive.create(dossier=self.dossier, request=self.request)
     self.request.session.flash(
         Message(cls="success", text="Dossier supprimé avec succès."))
     return HTTPFound(
         location=self.request.resource_url(self.context.parent))
Exemplo n.º 6
0
def test_generate_pdf_amendement_with_similaire(app, lecture_senat,
                                                article1_senat,
                                                amendements_senat):
    from zam_repondeur.models import AmendementList, DBSession
    from zam_repondeur.services.import_export.pdf import generate_html_for_pdf

    amendement_6666, amendement_9999 = amdt_list = AmendementList(
        amendements_senat)

    amendement_6666.auteur = "M. JEAN"
    amendement_6666.groupe = "Les Indépendants"
    amendement_6666.user_content.avis = "Favorable"
    amendement_6666.user_content.objet = "L’objet"
    amendement_6666.user_content.reponse = "La réponse"

    amendement_9999.auteur = "M. CLAUDE"
    amendement_9999.groupe = "Les Mécontents"
    amendement_9999.user_content.avis = "Favorable"
    amendement_9999.user_content.objet = "L’objet"
    amendement_9999.user_content.reponse = "La réponse"

    DBSession.add_all(amendements_senat)
    DBSession.flush()

    assert amdt_list.similaires(amendement_6666) == [amendement_9999]

    parser = HTMLParser(
        generate_html_for_pdf(
            DummyRequest(),
            "print/multiple.html",
            {
                "amendements": AmendementList([amendement_6666]),
                "article_amendements": amdt_list,
            },
        ))

    assert _html_page_titles(parser) == ["Réponse", "Amendement nº 6666"]
    response_node = parser.css_first(".reponse")
    assert _cartouche_to_list(response_node) == [
        "Article",
        "Art. 1",
        "Amendements",
        "6666 et 9999",
        "Auteurs",
        "M. CLAUDE et M. JEAN",
        "Groupes",
        "Les Indépendants et Les Mécontents",
        "Avis",
        "Favorable",
    ]
    assert response_node.css_first("div h5").text() == "Objet"
    assert "L’objet" in response_node.css_first("div p").text()
    assert response_node.css("div h5")[-1].text() == "Réponse"
    assert "La réponse" in response_node.css("div p")[-1].text()
Exemplo n.º 7
0
    def _create_or_update_amendement(
        self,
        lecture: Lecture,
        article: Optional[Article],
        parent: Optional[Amendement],
        amend: OrderedDict,
        position: Optional[int],
        id_discussion_commune: Optional[int],
        id_identique: Optional[int],
    ) -> Tuple[Amendement, bool]:
        amendement, created = get_one_or_create(
            Amendement,
            create_kwargs={
                "article": article,
                "parent": parent
            },
            lecture=lecture,
            num=int(amend["numero"]),
        )

        raw_auteur = amend.get("auteur")
        if not raw_auteur:
            logger.warning("Unknown auteur for amendement %s", amend["numero"])
            matricule, groupe, auteur = "", "", ""
        else:
            matricule = raw_auteur["tribunId"]
            groupe = get_groupe(raw_auteur, amendement.num)
            auteur = get_auteur(raw_auteur)

        modified = False
        modified |= self.update_rectif(amendement, get_rectif(amend))
        modified |= self.update_corps(
            amendement, unjustify(get_str_or_none(amend, "dispositif") or ""))
        modified |= self.update_expose(
            amendement,
            unjustify(get_str_or_none(amend, "exposeSommaire") or ""))
        modified |= self.update_sort(amendement, get_sort(amend))
        modified |= self.update_attributes(
            amendement,
            article=article,
            parent=parent,
            position=position,
            id_discussion_commune=id_discussion_commune,
            id_identique=id_identique,
            matricule=matricule,
            groupe=groupe,
            auteur=auteur,
        )

        DBSession.flush()  # make sure foreign keys are updated

        return amendement, created
Exemplo n.º 8
0
def test_amendement_parent_relationship(amendements_an):
    from zam_repondeur.models import Amendement, DBSession

    a, b = DBSession.query(Amendement).all()

    assert a.parent is None
    assert b.parent is None

    b.parent = a

    # Note: when updating a relationship, the foreign key is only updated on a flush
    DBSession.flush()
    assert b.parent_pk == a.pk
Exemplo n.º 9
0
def test_amendement_unicity(amendements_an, article1av_an):
    from zam_repondeur.models import Amendement, DBSession

    existing = amendements_an[0]
    with transaction.manager, pytest.raises(IntegrityError) as error_info:
        Amendement.create(
            lecture=existing.lecture,
            num=existing.num,
            rectif=existing.rectif + 1,
            article=article1av_an,
            parent=None,
            objet="don't worry, this is an expected error",
        )
        DBSession.flush()
    assert "constraint" in error_info.value._message()
Exemplo n.º 10
0
 def post(self) -> Response:
     if self.request.user.can_delete_lecture:
         DBSession.delete(self.lecture)
         DBSession.flush()
         self.request.session.flash(
             Message(cls="success", text="Lecture supprimée avec succès."))
     else:
         self.request.session.flash(
             Message(
                 cls="warning",
                 text=
                 "Vous n’avez pas les droits pour supprimer une lecture.",
             ))
     return HTTPFound(
         location=self.request.resource_url(self.context.parent))
Exemplo n.º 11
0
    def test_parent_has_changed(self, lecture_an, app, source):
        from zam_repondeur.fetch.an.amendements import build_url
        from zam_repondeur.models import DBSession

        responses.add(
            responses.GET,
            build_url(lecture_an, 155),
            body=read_sample_data("an/269/155.xml"),
            status=200,
        )

        responses.add(
            responses.GET,
            build_url(lecture_an, 941),
            body=read_sample_data("an/269/941.xml"),
            status=200,
        )

        parent1, created = source.fetch_amendement(lecture=lecture_an,
                                                   numero_prefixe="155",
                                                   position=1)
        assert created

        child1, created = source.fetch_amendement(lecture=lecture_an,
                                                  numero_prefixe="941",
                                                  position=2)
        assert created

        assert child1.parent is parent1
        assert child1.parent_pk == parent1.pk

        child1.parent = None  # let's change the parent amendement
        DBSession.flush()

        parent2, created = source.fetch_amendement(lecture=lecture_an,
                                                   numero_prefixe="155",
                                                   position=1)
        assert not created
        assert parent2 is parent1

        child2, created = source.fetch_amendement(lecture=lecture_an,
                                                  numero_prefixe="941",
                                                  position=2)
        assert not created
        assert child2 is child1

        assert child2.parent_pk == parent2.pk
        assert child2.parent is parent2
Exemplo n.º 12
0
Arquivo: auth.py Projeto: betagouv/zam
    def get(self) -> Any:

        if self.ip_limiter.exceeded(self.request.remote_addr):
            return HTTPTooManyRequests()

        if self.request.unauthenticated_userid:
            return HTTPFound(location=self.next_url)

        token = self.request.params.get("token")
        auth = repository.get_auth_token_data(token)
        if auth is None:
            self.log_failed_login_attempt(token)
            self.request.session.flash(
                Message(
                    cls="error",
                    text="Le lien est invalide ou a expiré. Merci de renouveler votre demande.",  # noqa
                )
            )
            raise HTTPFound(location=self.request.route_url("login"))

        # Delete token from repository after it's been used successfully
        repository.delete_auth_token(token)

        email = auth["email"]
        user, created = get_one_or_create(User, email=email)

        if created:
            DBSession.flush()  # so that the DB assigns a value to user.pk

        self.log_successful_login_attempt(email)

        user.last_login_at = datetime.utcnow()

        next_url = self.next_url
        if not user.name:
            next_url = self.request.route_url("welcome", _query={"source": next_url})

        # Compute response headers for the session cookie
        headers = remember(self.request, user.pk)

        app_name = self.request.registry.settings["zam.app_name"]
        self.request.session.flash(
            Message(cls="success", text=f"Bienvenue dans {app_name} !")
        )

        return HTTPFound(location=next_url, headers=headers)
Exemplo n.º 13
0
    def apply_changes(self, lecture: Lecture,
                      changes: CollectedChanges) -> FetchResult:
        result = FetchResult.create(
            fetched=changes.unchanged,
            errored=changes.errored,
            next_start_index=changes.next_start_index,
        )

        # Build amendement -> position map
        moved_amendements = {
            amendement: changes.position_changes[amendement.num]
            for amendement in lecture.amendements
            if amendement.num in changes.position_changes
        }

        # Reset positions first, so that we never have two with the same position
        # (which would trigger an integrity error due to the unique constraint)
        for amendement in moved_amendements:
            amendement.position = None
        DBSession.flush()

        # Create amendements in numerical order, because a "sous-amendement"
        # must always be created after its parent
        for create_action in sorted(changes.creates, key=attrgetter("num")):
            result += create_action.apply(lecture)

        # Update amendements
        for update_action in changes.updates:
            result += update_action.apply(lecture)

        # Apply new amendement positions
        for amendement, position in moved_amendements.items():
            if amendement.position != position:
                amendement.position = position

        DBSession.flush()

        # Was it the last batch?
        if changes.next_start_index is None:
            lecture.reset_fetch_progress()

        return result
Exemplo n.º 14
0
    def fetch_amendement(
            self, lecture: Lecture, numero_prefixe: str,
            position: Optional[int]) -> Tuple[Optional[Amendement], bool]:

        logger.info("Récupération de l'amendement %r", numero_prefixe)
        amend_data = _retrieve_amendement(lecture, numero_prefixe)
        amendement, action = self.inspect_amendement(
            lecture=lecture,
            amend_data=amend_data,
            position=position,
            id_discussion_commune=None,
            id_identique=None,
        )

        created = isinstance(action, CreateAmendement)
        if action is not None:
            result = action.apply(lecture)
            num = first(result.fetched)
            DBSession.flush()
            amendement = Amendement.get(lecture, num)
        return amendement, created
Exemplo n.º 15
0
def reset_amendements_positions(lecture: Lecture,
                                discussion_items: List[OrderedDict]) -> None:

    current_order = {
        amdt.num: amdt.position
        for amdt in lecture.amendements if amdt.position is not None
    }

    new_order = {
        parse_num_in_liste(item["@numero"])[1]: index
        for index, item in enumerate(discussion_items, start=1)
    }

    # Reset position for all amendements that moved,
    # so that we don't break the UNIQUE INDEX constraint later
    for amdt in lecture.amendements:
        if amdt.num not in current_order:
            continue
        if new_order.get(amdt.num) != current_order[amdt.num]:
            amdt.position = None
        if amdt.num not in new_order:  # removed
            logger.info("Amendement %s retiré de la discussion", amdt.num)
    DBSession.flush()
Exemplo n.º 16
0
def test_generate_pdf_amendement_with_similaire_different_articles(
        app, lecture_senat, article1_senat, article7bis_senat,
        amendements_senat):
    from zam_repondeur.models import Amendement, AmendementList, DBSession
    from zam_repondeur.services.import_export.pdf import generate_html_for_pdf

    amendement_6666, amendement_9999 = amendements_senat

    amendement_6666.auteur = "M. JEAN"
    amendement_6666.groupe = "Les Indépendants"
    amendement_6666.user_content.avis = "Favorable"
    amendement_6666.user_content.objet = "L’objet"
    amendement_6666.user_content.reponse = "La réponse"

    amendement_9999.auteur = "M. CLAUDE"
    amendement_9999.groupe = "Les Mécontents"
    amendement_9999.user_content.avis = "Favorable"
    amendement_9999.user_content.objet = "L’objet"
    amendement_9999.user_content.reponse = "La réponse"

    amendement = Amendement.create(
        lecture=lecture_senat,
        article=article7bis_senat,
        alinea="",
        num=42,
        rectif=1,
        auteur="M. DUPONT",
        groupe="RDSE",
        matricule="000000",
        corps="<p>L'article 1 est supprimé.</p>",
        expose="<p>Cet article va à l'encontre du principe d'égalité.</p>",
        resume="Suppression de l'article",
        position=3,
        avis="Favorable",
        objet="L’objet",
        reponse="La réponse",
    )

    DBSession.add_all(amendements_senat)
    DBSession.flush()

    amdt_list = AmendementList(amendements_senat + [amendement])

    assert amdt_list.similaires(amendement_6666) == [amendement_9999]
    assert amdt_list.similaires(amendement) == []

    parser = HTMLParser(
        generate_html_for_pdf(
            DummyRequest(),
            "print/multiple.html",
            {
                "amendements": AmendementList([amendement_6666, amendement]),
                "article_amendements": amdt_list,
            },
        ))

    assert _html_page_titles(parser) == [
        "Réponse",
        "Amendement nº 6666",
        "Réponse",
        "Amendement nº 42 rect.",
    ]
    response_node_6666, response_node_42_rect = parser.css(".reponse")

    assert _cartouche_to_list(response_node_6666) == [
        "Article",
        "Art. 1",
        "Amendements",
        "6666 et 9999",
        "Auteurs",
        "M. CLAUDE et M. JEAN",
        "Groupes",
        "Les Indépendants et Les Mécontents",
        "Avis",
        "Favorable",
    ]
    assert response_node_6666.css_first("div h5").text() == "Objet"
    assert "L’objet" in response_node_6666.css_first("div p").text()
    assert response_node_6666.css("div h5")[-1].text() == "Réponse"
    assert "La réponse" in response_node_6666.css("div p")[-1].text()

    assert _cartouche_to_list(response_node_42_rect) == [
        "Article",
        "Art. 7 bis",
        "Amendement",
        "42 rect.",
        "Auteur",
        "M. DUPONT",
        "Groupe",
        "RDSE",
        "Avis",
        "Favorable",
    ]
    assert response_node_42_rect.css_first("div h5").text() == "Objet"
    assert "L’objet" in response_node_42_rect.css_first("div p").text()
    assert response_node_42_rect.css("div h5")[-1].text() == "Réponse"
    assert "La réponse" in response_node_42_rect.css("div p")[-1].text()