def lecture_options(context: LectureResource, request: Request) -> Response: lecture = context.model() return { "lecture": lecture, "lecture_resource": context, "current_tab": "options" }
def download_amendements(context: LectureResource, request: Request) -> Response: fmt: str = request.params.get("format", "") if fmt not in DOWNLOAD_FORMATS.keys(): raise HTTPBadRequest(f'Invalid value "{fmt}" for "format" param') if fmt == "pdf": options = PDF_OPTIONS else: options = EXPORT_OPTIONS lecture = context.model(*options) with NamedTemporaryFile() as file_: tmp_file_path = os.path.abspath(file_.name) write_func, content_type = DOWNLOAD_FORMATS[fmt] write_func(lecture, tmp_file_path, request) # type: ignore response = FileResponse(tmp_file_path) attach_name = ( f"lecture-{lecture.chambre}-{lecture.texte.numero}-" f"{lecture.organe}.{fmt}" ) response.content_type = content_type response.headers["Content-Disposition"] = f"attachment; filename={attach_name}" return response
def fetch_amendements(context: LectureResource, request: Request) -> Response: lecture = context.model() amendements, errored = get_amendements( chambre=lecture.chambre, session=lecture.session, texte=lecture.num_texte, organe=lecture.organe, ) if errored: request.session.flash(( "warning", f"Les amendements {', '.join(errored)} n’ont pu être récupérés.", )) if amendements: added, updated, unchanged = _add_or_update_amendements(amendements) assert added + updated + unchanged == len(amendements) _set_flash_messages(request, added, updated, unchanged) else: request.session.flash( ("danger", "Aucun amendement n’a pu être trouvé.")) return HTTPFound(location=request.resource_url(context))
def upload_csv(context: LectureResource, request: Request) -> Response: lecture = context.model( subqueryload("amendements").options( load_only("num"), joinedload("user_content").load_only("avis", "objet", "reponse", "comments"), joinedload("location").options( subqueryload("shared_table"), subqueryload("user_table"), ), )) next_url = request.resource_url(context["amendements"]) # We cannot just do `if not POST["reponses"]`, as FieldStorage does not want # to be cast to a boolean. if request.POST["reponses"] == b"": request.session.flash( Message(cls="warning", text="Veuillez d’abord sélectionner un fichier")) return HTTPFound(location=request.resource_url(context, "options")) try: counter = import_csv( request=request, reponses_file=request.POST["reponses"].file, lecture=lecture, amendements={ amendement.num: amendement for amendement in lecture.amendements }, team=context.dossier_resource.dossier.team, ) except CSVImportError as exc: request.session.flash(Message(cls="danger", text=str(exc))) return HTTPFound(location=next_url) if counter["reponses"]: request.session.flash( Message( cls="success", text=f"{counter['reponses']} réponse(s) chargée(s) avec succès", )) ReponsesImportees.create(lecture=lecture, request=request) if counter["reponses_errors"]: request.session.flash( Message( cls="warning", text= (f"{counter['reponses_errors']} réponse(s) " "n’ont pas pu être chargée(s). " "Pour rappel, il faut que le fichier CSV contienne au moins " "les noms de colonnes suivants « Num amdt », " "« Avis du Gouvernement », « Objet amdt » et « Réponse »."), )) return HTTPFound(location=next_url)
def _do_upload_liasse_xml(context: LectureResource, request: Request) -> Response: try: liasse_field = request.POST["liasse"] except KeyError: request.session.flash( ("warning", "Veuillez d’abord sélectionner un fichier")) return if liasse_field == b"": request.session.flash( ("warning", "Veuillez d’abord sélectionner un fichier")) return try: amendements = import_liasse_xml(liasse_field.file) except ValueError: request.session.flash( ("danger", "Le format du fichier n’est pas valide.")) return if len(amendements) == 0: request.session.flash( ("warning", "Aucun amendement n’a été trouvé dans ce fichier.")) return lecture = context.model() filtered_amendements = [ amendement for amendement in amendements if amendement.chambre == lecture.chambre and amendement.session == lecture.session and amendement.num_texte == lecture.num_texte and amendement.organe == lecture.organe ] ignored = len(amendements) - len(filtered_amendements) if len(filtered_amendements) == 0: amendement = amendements[0] other_lecture = Lecture( chambre=amendement.chambre, session=amendement.session, num_texte=amendement.num_texte, organe=amendement.organe, ) request.session.flash( ("danger", f"La liasse correspond à une autre lecture ({other_lecture}).")) return if ignored > 0: request.session.flash( ("warning", f"{ignored} amendements ignorés car non liés à cette lecture.")) added, updated, unchanged = _add_or_update_amendements( filtered_amendements) assert added + updated + unchanged == len(filtered_amendements) _set_flash_messages(request, added, updated, unchanged)
def __init__(self, context: LectureResource, request: Request) -> None: self.context = context self.request = request self.lecture = context.model() self.amendements_query = DBSession.query(Amendement).filter( Amendement.chambre == self.lecture.chambre, Amendement.session == self.lecture.session, Amendement.num_texte == self.lecture.num_texte, Amendement.organe == self.lecture.organe, )
def manual_refresh(context: LectureResource, request: Request) -> Response: lecture = context.model() fetch_amendements(lecture.pk) fetch_articles(lecture.pk) request.session.flash( Message( cls="success", text="Rafraichissement des amendements et des articles en cours.", )) return HTTPFound(location=request.resource_url(context, "amendements"))
def upload_json(context: LectureResource, request: Request) -> Response: lecture = context.model(joinedload("articles")) next_url = request.resource_url(context["amendements"]) # We cannot just do `if not POST["backup"]`, as FieldStorage does not want # to be cast to a boolean. if request.POST["backup"] == b"": request.session.flash( Message(cls="warning", text="Veuillez d’abord sélectionner un fichier")) return HTTPFound(location=request.resource_url(context, "options")) try: counter = import_json( request=request, backup_file=request.POST["backup"].file, lecture=lecture, amendements={ amendement.num: amendement for amendement in lecture.amendements }, articles={ article.sort_key_as_str: article for article in lecture.articles }, team=context.dossier_resource.dossier.team, ) except ValueError as exc: request.session.flash(Message(cls="danger", text=str(exc))) return HTTPFound(location=next_url) if counter["reponses"] or counter["articles"]: if counter["reponses"]: message = f"{counter['reponses']} réponse(s) chargée(s) avec succès" if counter["articles"]: message += f", {counter['articles']} article(s) chargé(s) avec succès" elif counter["articles"]: message = f"{counter['articles']} article(s) chargé(s) avec succès" request.session.flash(Message(cls="success", text=message)) ReponsesImporteesJSON.create(lecture=lecture, request=request) if counter["reponses_errors"] or counter["articles_errors"]: message = "Le fichier de sauvegarde n’a pas pu être chargé" if counter["reponses_errors"]: message += f" pour {counter['reponses_errors']} amendement(s)" if counter["articles_errors"]: message += f" et {counter['articles_errors']} article(s)" elif counter["articles_errors"]: message += f" pour {counter['articles_errors']} article(s)" request.session.flash(Message(cls="warning", text=message)) return HTTPFound(location=next_url)
def lecture_options(context: LectureResource, request: Request) -> Response: lecture = context.model(noload("amendements")) shared_tables = (DBSession.query(SharedTable).filter( SharedTable.lecture_pk == lecture.pk).options( load_only("lecture_pk", "nb_amendements", "slug", "titre"))).all() return { "lecture": lecture, "dossier_resource": context.dossier_resource, "lecture_resource": context, "current_tab": "options", "shared_tables": shared_tables, }
def export_pdf(context: LectureResource, request: Request) -> Response: lecture = context.model( noload("amendements"), DOSSIER_OPTIONS, subqueryload("articles").options(joinedload("user_content")), ) nums, article_param = parse_params(request, lecture=lecture) if article_param == "all": article_amendements = ( DBSession.query(Amendement) .join(Article) .filter(Amendement.lecture == lecture,) .options(USER_CONTENT_OPTIONS, LOCATION_OPTIONS) ) else: article_type, article_num, article_mult, article_pos = article_param.split(".") article_amendements = ( DBSession.query(Amendement) .filter( Article.pk == Amendement.article_pk, Amendement.lecture == lecture, Article.type == article_type, Article.num == article_num, Article.mult == article_mult, Article.pos == article_pos, ) .options(USER_CONTENT_OPTIONS, LOCATION_OPTIONS,) ) amendements = [ amendement for amendement in article_amendements if amendement.num in nums ] expanded_amendements = list(Batch.expanded_batches(amendements)) with NamedTemporaryFile() as file_: tmp_file_path = os.path.abspath(file_.name) write_pdf_multiple( lecture=lecture, amendements=amendements, article_amendements=AmendementList(article_amendements), filename=tmp_file_path, request=request, ) return write_response( tmp_file_path=tmp_file_path, fmt="pdf", lecture=lecture, article_param=article_param, amendements=expanded_amendements, )
def lecture_journal(context: LectureResource, request: Request) -> Response: lecture = context.model(noload("amendements")) settings = request.registry.settings refreshable = lecture.refreshable_for("articles", settings) or lecture.refreshable_for( "amendements", settings) can_refresh = request.has_permission("refresh", context) refreshing = lecture.get_fetch_progress() allowed_to_refresh = refreshable and can_refresh and not refreshing return { "lecture": lecture, "dossier_resource": context.dossier_resource, "lecture_resource": context, "current_tab": "journal", "today": date.today(), "allowed_to_refresh": allowed_to_refresh, }
def manual_refresh(context: LectureResource, request: Request) -> Response: lecture = context.model() amendements_collection = context["amendements"] if lecture.get_fetch_progress(): request.session.flash( Message(cls="warning", text="Rafraîchissement des amendements déjà en cours.")) return HTTPFound(location=request.resource_url(amendements_collection)) fetch_amendements(lecture.pk) # The progress is initialized even if the task is async for early feedback # to users and ability to disable the refresh button. # The total is doubled because we need to handle the dry_run. total = len(lecture.amendements) * 2 if lecture.amendements else 100 lecture.set_fetch_progress(1, total) request.session.flash( Message(cls="success", text="Rafraîchissement des amendements en cours.")) return HTTPFound(location=request.resource_url(amendements_collection))
def export_xlsx(context: LectureResource, request: Request) -> Response: lecture = context.model(noload("amendements")) nums, article_param = parse_params(request, lecture=lecture) if article_param == "all": amendements = ( DBSession.query(Amendement) .join(Article) .filter( Amendement.lecture == lecture, Amendement.num.in_(nums), # type: ignore ) .options(USER_CONTENT_OPTIONS, LOCATION_OPTIONS) ) else: article_type, article_num, article_mult, article_pos = article_param.split(".") amendements = ( DBSession.query(Amendement) .filter( Article.pk == Amendement.article_pk, Amendement.lecture == lecture, Article.type == article_type, Article.num == article_num, Article.mult == article_mult, Article.pos == article_pos, Amendement.num.in_(nums), # type: ignore ) .options(USER_CONTENT_OPTIONS, LOCATION_OPTIONS) ) expanded_amendements = list(Batch.expanded_batches(amendements)) with NamedTemporaryFile() as file_: tmp_file_path = os.path.abspath(file_.name) write_xlsx(lecture, tmp_file_path, request, amendements=expanded_amendements) return write_response( tmp_file_path=tmp_file_path, fmt="xlsx", lecture=lecture, article_param=article_param, amendements=expanded_amendements, )
def download_amendements(context: LectureResource, request: Request) -> Response: lecture = context.model() amendements = ( DBSession.query(Amendement) .filter( Amendement.chambre == lecture.chambre, Amendement.session == lecture.session, Amendement.num_texte == lecture.num_texte, Amendement.organe == lecture.organe, ) .order_by( case([(Amendement.position.is_(None), 1)], else_=0), # type: ignore Amendement.position, Amendement.num, ) .all() ) fmt: str = request.params.get("format", "") if fmt not in ("csv", "xlsx", "pdf"): raise HTTPBadRequest(f'Invalid value "{fmt}" for "format" param') with NamedTemporaryFile() as file_: tmp_file_path = os.path.abspath(file_.name) write_func, content_type = DOWNLOAD_FORMATS[fmt] write_func(lecture, amendements, tmp_file_path, request) response = FileResponse(tmp_file_path) attach_name = ( f"amendements-{lecture.chambre}-{lecture.session}-{lecture.num_texte}-" f"{lecture.organe}.{fmt}" ) response.content_type = content_type response.headers["Content-Disposition"] = f"attachment; filename={attach_name}" return response
def export_pdf(context: LectureResource, request: Request) -> Response: lecture = context.model( joinedload("articles").joinedload("amendements").joinedload( "children")) try: nums: List[int] = [int(num) for num in request.params.getall("nums")] except ValueError: raise HTTPBadRequest() amendements = [ amendement for amendement in (lecture.find_amendement(num) for num in nums) if amendement is not None ] with NamedTemporaryFile() as file_: tmp_file_path = os.path.abspath(file_.name) write_pdf_multiple( lecture=lecture, amendements=amendements, filename=tmp_file_path, request=request, ) response = FileResponse(tmp_file_path) attach_name = ( f"amendement{'s' if len(nums) > 1 else ''}-" f"{','.join(str(num) for num in nums)}-" f"{lecture.chambre}-{lecture.session}-{lecture.texte.numero}-" f"{lecture.organe}.pdf") response.content_type = "application/pdf" response.headers[ "Content-Disposition"] = f"attachment; filename={attach_name}" return response
def search_amendement(context: LectureResource, request: Request) -> dict: lecture = context.model(noload("amendements")) try: num_param: str = request.params.get("num", "") num: int = int(num_param) except ValueError: raise HTTPBadRequest() amendement = (DBSession.query(Amendement).filter( Amendement.lecture == lecture, Amendement.num == num).first()) if amendement is None: raise HTTPBadRequest() total_count_amendements = lecture.nb_amendements max_amendements_for_full_index = int( request.registry.settings.get( "zam.limits.max_amendements_for_full_index", 1000)) too_many_amendements = total_count_amendements > max_amendements_for_full_index result = { "index": request.resource_url( context["amendements"], query={ "article": amendement.article.url_key if too_many_amendements else "all" }, anchor=amendement.slug, ), } if amendement.is_displayable: result["visionneuse"] = request.resource_url( context["articles"][amendement.article.url_key], "reponses", anchor=amendement.slug, ) return result
def list_reponses(context: LectureResource, request: Request) -> Response: lecture = context.model() amendements = ( DBSession.query(Amendement).filter( Amendement.chambre == lecture.chambre, Amendement.session == lecture.session, Amendement.num_texte == lecture.num_texte, Amendement.organe == lecture.organe, ).order_by( case([(Amendement.position.is_(None), 1)], else_=0), # type: ignore Amendement.position, Amendement.num, ).all()) articles = build_tree(amendements) check_url = request.resource_path(context, "check") return { "dossier_legislatif": lecture.dossier_legislatif, "lecture": str(lecture), "articles": articles, "timestamp": lecture.modified_at_timestamp, "check_url": check_url, }
def lecture_check(context: LectureResource, request: Request) -> dict: lecture = context.model() return {"modified_at": lecture.modified_at_timestamp}
def fetch_articles(context: LectureResource, request: Request) -> Response: lecture = context.model() get_articles(lecture) request.session.flash(("success", f"Articles récupérés")) return HTTPFound(location=request.resource_url(context))
def lecture_journal(context: LectureResource, request: Request) -> Response: lecture = context.model() return {"lecture": lecture, "today": date.today()}
def upload_liasse_xml(context: LectureResource, request: Request) -> Response: try: liasse_field = request.POST["liasse"] except KeyError: request.session.flash( Message(cls="warning", text="Veuillez d’abord sélectionner un fichier")) return HTTPFound(location=request.resource_url(context, "options")) if liasse_field == b"": request.session.flash( Message(cls="warning", text="Veuillez d’abord sélectionner un fichier")) return HTTPFound(location=request.resource_url(context, "options")) # Backup uploaded file to make troubleshooting easier backup_path = get_backup_path(request) if backup_path is not None: save_uploaded_file(liasse_field, backup_path) lecture = context.model() try: amendements, errors = import_liasse_xml(liasse_field.file, lecture) except ValueError: logger.exception("Erreur d'import de la liasse XML") request.session.flash( Message(cls="danger", text="Le format du fichier n’est pas valide.")) return HTTPFound(location=request.resource_url(context, "options")) except LectureDoesNotMatch as exc: request.session.flash( Message( cls="danger", text= f"La liasse correspond à une autre lecture ({exc.lecture_fmt}).", )) return HTTPFound(location=request.resource_url(context, "options")) if errors: if len(errors) == 1: what = "l'amendement" else: what = "les amendements" uids = ", ".join(uid for uid, cause in errors) request.session.flash( Message(cls="warning", text=f"Impossible d'importer {what} {uids}.")) if len(amendements) == 0: request.session.flash( Message( cls="warning", text="Aucun amendement valide n’a été trouvé dans ce fichier.", )) return HTTPFound(location=request.resource_url(context, "options")) if len(amendements) == 1: message = "1 nouvel amendement récupéré (import liasse XML)." else: message = ( f"{len(amendements)} nouveaux amendements récupérés (import liasse XML)." ) request.session.flash(Message(cls="success", text=message)) AmendementsRecuperesLiasse.create(request, lecture, count=len(amendements)) DBSession.add(lecture) return HTTPFound(location=request.resource_url(context, "amendements"))
def progress_status(context: LectureResource, request: Request) -> dict: lecture = context.model(noload("amendements")) return lecture.get_fetch_progress() or {}
def __init__(self, context: LectureResource, request: Request) -> None: self.context = context self.request = request self.lecture = context.model()