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)
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
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
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
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))
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()
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
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
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()
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))
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
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)
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
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
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()
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()