def post(self) -> Response: email_pattern = self.request.POST["email_pattern"] or "" if not email_pattern: self.request.session.flash( Message(cls="error", text="Veuillez saisir un courriel ou modèle.")) return HTTPFound(location=self.request.resource_url(self.context)) allowed_email_pattern = ( DBSession.query(AllowedEmailPattern).filter_by( pattern=email_pattern).first()) if allowed_email_pattern: self.request.session.flash( Message(cls="warning", text="Cette adresse de courriel existe déjà.")) return HTTPFound(location=self.request.resource_url(self.context)) if User.email_is_allowed(email_pattern): self.request.session.flash( Message(cls="warning", text="Cette adresse de courriel est déjà acceptée.")) return HTTPFound(location=self.request.resource_url(self.context)) WhitelistAdd.create(email_pattern=email_pattern, comment=None, request=self.request) self.request.session.flash( Message( cls="success", text=("Adresse de courriel ou modèle créé(e) avec succès."), )) return HTTPFound(location=self.request.resource_url(self.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 post(self) -> Response: avis = self.request.POST.get("avis", "") objet = clean_html(self.request.POST.get("objet", "")) reponse = clean_html(self.request.POST.get("reponse", "")) comments = clean_html(self.request.POST.get("comments", "")) avis_changed = avis != (self.amendement.user_content.avis or "") objet_changed = objet != (self.amendement.user_content.objet or "") reponse_changed = reponse != (self.amendement.user_content.reponse or "") comments_changed = comments != (self.amendement.user_content.comments or "") if not self.is_on_my_table: message = ( "Les modifications n’ont PAS été enregistrées " "car l’amendement n’est plus sur votre table." ) if self.amendement.user_table: message += ( f" Il est actuellement sur la table de " f"{self.amendement.user_table.user}." ) self.request.session.flash(Message(cls="danger", text=message)) return HTTPFound(location=self.my_table_url) if avis_changed: AvisAmendementModifie.create(self.request, self.amendement, avis) if objet_changed: ObjetAmendementModifie.create(self.request, self.amendement, objet) if reponse_changed: ReponseAmendementModifiee.create(self.request, self.amendement, reponse) if comments_changed: CommentsAmendementModifie.create(self.request, self.amendement, comments) self.amendement.stop_editing() self.request.session.flash( Message(cls="success", text="Les modifications ont bien été enregistrées.") ) if "save-and-transfer" in self.request.POST: return HTTPFound( location=self.request.resource_url( self.context.parent.parent, "transfer_amendements", query={ "nums": self.amendement.num, "from_save": 1, "back": self.back_url, }, ) ) else: self.request.session["highlighted_amdt"] = self.amendement.slug return HTTPFound(location=self.back_url)
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 post(self) -> Response: user_pk = self.request.POST["user_pk"] if str(self.request.user.pk) == user_pk: message = "Vous ne pouvez pas vous retirer du statut d’administrateur." self.request.session.flash(Message(cls="warning", text=message)) return HTTPFound(location=self.request.resource_url(self.context)) user = DBSession.query(User).filter_by(pk=user_pk).first() AdminRevoke.create(target=user, request=self.request) self.request.session.flash( Message(cls="success", text=("Droits d’administration retirés avec succès."))) return HTTPFound(location=self.request.resource_url(self.context))
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 post(self) -> Response: changed = False new_title = self.request.POST["title"] if new_title != self.article.user_content.title: TitreArticleModifie.create( request=self.request, article=self.article, title=new_title ) changed = True new_presentation = clean_html(self.request.POST["presentation"]) if new_presentation != self.article.user_content.presentation: PresentationArticleModifiee.create( request=self.request, article=self.article, presentation=new_presentation, ) changed = True if changed: self.request.session.flash( Message(cls="success", text="Article mis à jour avec succès.") ) return HTTPFound(location=self.next_url())
def post(self) -> Response: user_pk = self.request.POST["user_pk"] if not user_pk: self.request.session.flash( Message( cls="warning", text="Veuillez saisir une personne dans le menu déroulant.", )) return HTTPFound( location=self.request.resource_url(self.context, "add")) user = DBSession.query(User).filter_by(pk=user_pk).first() AdminGrant.create(target=user, request=self.request) self.request.session.flash( Message(cls="success", text=("Droits d’administration ajoutés avec succès."))) return HTTPFound(location=self.request.resource_url(self.context))
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 post(self) -> Response: titre: str = self.request.POST.get("titre") table, created = get_one_or_create(SharedTable, titre=titre, lecture=self.lecture) if created: SharedTableCreee.create(lecture=self.lecture, titre=titre, request=self.request) self.request.session.flash( Message(cls="success", text=f"Boîte « {table.titre} » créée avec succès.")) else: self.request.session.flash( Message(cls="warning", text=f"La boîte « {table.titre} » existe déjà…")) return HTTPFound(location=self.request.resource_url( self.context.lecture_resource, "options", anchor="shared-tables"))
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 post(self) -> Response: dossier_slug = self._get_dossier_slug() if not dossier_slug: self.request.session.flash( Message(cls="error", text="Ce dossier n’existe pas.")) return HTTPFound(location=self.request.resource_url(self.context)) dossier = Dossier.get(slug=dossier_slug) if dossier is None: self.request.session.flash( Message(cls="error", text="Ce dossier n’existe pas.")) return HTTPFound(location=self.request.resource_url(self.context)) if dossier.team: self.request.session.flash( Message(cls="warning", text="Ce dossier appartient à une autre équipe…")) return HTTPFound(location=self.request.resource_url(self.context)) team = Team.create(name=dossier.slug) dossier.team = team for admin in DBSession.query(User).filter( User.admin_at.isnot(None) # type: ignore ): admin.teams.append(team) # Enqueue task to asynchronously add the lectures create_missing_lectures(dossier_pk=dossier.pk, user_pk=self.request.user.pk) DossierActive.create(dossier=dossier, request=self.request) self.request.session.flash( Message( cls="success", text=( "Dossier créé avec succès, lectures en cours de création." ), )) return HTTPFound( location=self.request.resource_url(self.context[dossier.url_key]))
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 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 post(self) -> Response: """ Transfer amendement(s) from this table to another one, or back to the index """ nums: List[int] = self.request.POST.getall("nums") if "submit-index" in self.request.POST: target = "" elif "submit-table" in self.request.POST: target = self.request.user.email else: target = self.request.POST.get("target") if not target: self.request.session.flash( Message(cls="warning", text="Veuillez sélectionner un·e destinataire.")) return HTTPFound(location=self.request.resource_url( self.context.lecture_resource, "transfer_amendements", query={"nums": nums}, )) target_table: Optional[UserTable] = None if target: if target == self.request.user.email: target_user = self.request.user else: target_user = DBSession.query(User).filter( User.email == target).one() if self.request.team and target_user not in self.request.team.users: raise HTTPForbidden("Transfert non autorisé") target_table = target_user.table_for(self.lecture) amendements = DBSession.query(Amendement).filter( Amendement.lecture == self.lecture, Amendement.num.in_(nums) # type: ignore ) for amendement in amendements: old = str( amendement.user_table.user) if amendement.user_table else "" new = str(target_table.user) if target_table else "" if amendement.user_table is target_table: continue amendement.user_table = target_table amendement.stop_editing() AmendementTransfere.create(self.request, amendement, old, new) if target != self.request.user.email and self.request.POST.get( "from_index"): next_location = self.request.resource_url( self.context.lecture_resource, "amendements") else: next_location = self.request.resource_url(self.context.parent, self.owner.email) return HTTPFound(location=next_location)
def post(self) -> Response: titre = self.shared_table.titre DBSession.delete(self.shared_table) SharedTableSupprimee.create(lecture=self.lecture, titre=titre, request=self.request) self.request.session.flash( Message(cls="success", text=f"Boîte « {titre} » supprimée avec succès.")) return HTTPFound(location=self.request.resource_url( self.context.lecture_resource, "options", anchor="shared-tables"))
def check_amendements_are_all_from_same_mission( self, amendements: List[Amendement]) -> None: first_mission_titre = amendements[0].mission_titre are_all_from_same_mission = all( amdt.mission_titre == first_mission_titre for amdt in amendements) if are_all_from_same_mission: return message = ( "Tous les amendements doivent être relatifs à la même mission " "pour pouvoir être associés.") self.request.session.flash(Message(cls="danger", text=message)) raise HTTPFound(location=self.my_table_url)
def check_amendements_are_all_on_my_table( self, amendements: List[Amendement]) -> None: are_all_on_my_table = all( amendement.location.user_table.user == self.request.user if amendement.location.user_table else False for amendement in amendements) if are_all_on_my_table: return message = ("Tous les amendements doivent être sur votre table " "pour pouvoir les associer.") self.request.session.flash(Message(cls="danger", text=message)) raise HTTPFound(location=self.my_table_url)
def post(self) -> Response: user_pk: str = self.request.POST.get("pk") user = DBSession.query(User).filter(User.pk == user_pk).one() target = str(user) self.dossier.team.users.remove(user) DossierRetrait.create(dossier=self.dossier, target=target, request=self.request) self.request.session.flash( Message( cls="success", text=(f"{target} a été retiré·e du dossier avec succès.") ) ) return HTTPFound(location=self.request.resource_url(self.context, "retrait"))
def post(self) -> Response: email_pattern_pk = self.request.POST["pk"] allowed_email_pattern = ( DBSession.query(AllowedEmailPattern).filter_by( pk=email_pattern_pk).first()) WhitelistRemove.create(allowed_email_pattern=allowed_email_pattern, request=self.request) self.request.session.flash( Message( cls="success", text=( "Adresse de courriel ou modèle supprimé(e) avec succès."), )) return HTTPFound(location=self.request.resource_url(self.context))
def forbidden_view(request: Request) -> Any: # Redirect unauthenticated users to the login page if request.user is None: return HTTPFound( location=request.route_url("user_login", _query={"source": request.url}) ) # Redirect authenticated ones to the home page with an error message request.session.flash( Message( cls="warning", text="L’accès à cette lecture est réservé aux personnes autorisées.", ) ) return HTTPFound(location=request.resource_url(request.root))
def post(self) -> Response: old_titre = self.shared_table.titre titre: str = self.request.POST.get("titre") self.shared_table.titre = titre self.shared_table.slug = slugify(titre) SharedTableRenommee.create( lecture=self.lecture, old_titre=old_titre, new_titre=titre, request=self.request, ) self.request.session.flash( Message(cls="success", text=f"Boîte « {titre} » sauvegardée avec succès.")) return HTTPFound(location=self.request.resource_url( self.context.lecture_resource, "options", anchor="shared-tables"))
def check_amendements_have_all_same_reponse_or_empty( self, amendements: List[Amendement]) -> None: reponses = (amendement.user_content.as_tuple() for amendement in amendements) non_empty_reponses = (reponse for reponse in reponses if not reponse.is_empty) if len(set( non_empty_reponses)) <= 1: # all the same (1) or all empty (0) return message = ( "Tous les amendements doivent avoir les mêmes réponses et commentaires " "avant de pouvoir être associés.") self.request.session.flash(Message(cls="danger", text=message)) raise HTTPFound(location=self.my_table_url)
def forbidden_view(exception: HTTPForbidden, request: Request) -> Any: # Redirect unauthenticated users to the login page if request.user is None: return HTTPFound( location=request.route_url("login", _query={"source": request.url}) ) # Default message = "L’accès à ce dossier est réservé aux personnes autorisées." next_resource = request.root if isinstance(exception.result, ACLDenied): acl_denied: ACLDenied = exception.result if acl_denied.permission == "delete": message = "Vous n’êtes pas autorisé à supprimer ce dossier." next_resource = request.context elif acl_denied.permission == "retrait": message = "Vous n’êtes pas autorisé à retirer une personne de ce dossier." next_resource = request.context request.session.flash(Message(cls="warning", text=message)) return HTTPFound(location=request.resource_url(next_resource))
def post(self) -> Response: dossier_ref = self._get_dossier_ref() lecture_ref = self._get_lecture_ref(dossier_ref) chambre = lecture_ref.chambre.value titre = lecture_ref.titre organe = lecture_ref.organe partie = lecture_ref.partie session = lecture_ref.get_session() texte = lecture_ref.texte assert texte.date_depot is not None texte_model = get_one_or_create( TexteModel, uid=texte.uid, type_=texte.type_, chambre=Chambre.AN if lecture_ref.chambre.value == "an" else Chambre.SENAT, legislature=int(session) if chambre == "an" else None, session=int(session.split("-")[0]) if chambre == "senat" else None, numero=texte.numero, titre_long=texte.titre_long, titre_court=texte.titre_court, date_depot=texte.date_depot, )[0] dossier_model = get_one_or_create(DossierModel, uid=dossier_ref.uid, titre=dossier_ref.titre)[0] if LectureModel.exists(chambre, session, texte_model, partie, organe): self.request.session.flash( Message(cls="warning", text="Cette lecture existe déjà…")) return HTTPFound(location=self.request.resource_url(self.context)) lecture_model: LectureModel = LectureModel.create( owned_by_team=self.request.team, chambre=chambre, session=session, texte=texte_model, partie=partie, titre=titre, organe=organe, dossier=dossier_model, ) get_articles(lecture_model) LectureCreee.create(self.request, lecture=lecture_model) ArticlesRecuperes.create(request=None, lecture=lecture_model) # Call to fetch_* tasks below being asynchronous, we need to make # sure the lecture_model already exists once and for all in the database # for future access. Otherwise, it may create many instances and # thus many objects within the database. transaction.commit() fetch_amendements(lecture_model.pk) self.request.session.flash( Message( cls="success", text= ("Lecture créée avec succès, amendements en cours de récupération." ), )) return HTTPFound(location=self.request.resource_url( self.context[lecture_model.url_key], "amendements"))
def manual_refresh(context: DossierResource, request: Request) -> Response: dossier = context.dossier update_dossier(dossier.pk, force=True) request.session.flash( Message(cls="success", text="Rafraîchissement des lectures en cours.")) return HTTPFound(location=request.resource_url(context))
def post(self) -> Response: # Only consider well formed addresses from allowed domains emails = self._extract_emails(self.request.POST.get("emails")) bad_emails, clean_emails = self._clean_emails(emails) # Create user accounts if needed new_users, existing_users = self._find_or_create_users(clean_emails) team = self.dossier.team # Users that already have a Zam account, but are not yet members new_members, existing_members = self._identify_members(existing_users, team) users_to_invite = new_users + new_members team.add_members(users_to_invite) invitations_sent = 0 if new_users: invitations_sent += self._send_new_users_invitations(new_users) if new_members: invitations_sent += self._send_existing_users_invitations(new_members) for user in users_to_invite: InvitationEnvoyee.create( dossier=self.dossier, email=user.email, request=self.request ) if invitations_sent: if invitations_sent > 1: message = "Invitations envoyées avec succès." else: message = "Invitation envoyée avec succès." cls = "success" else: message = "Aucune invitation n’a été envoyée." cls = "warning" if existing_members: existing_emails = [user.email for user in existing_members] message += "<br><br>" if len(existing_emails) > 1: message += ( f"Les adresses courriel {enumeration(existing_emails)} " "avaient déjà été invitées au dossier précédemment." ) else: message += ( f"L’adresse courriel {existing_emails[0]} " "avait déjà été invitée au dossier précédemment." ) if bad_emails: message += "<br><br>" if len(bad_emails) > 1: message += ( f"Les adresses courriel {enumeration(bad_emails)} " "sont mal formées ou non autorisées et n’ont pas été invitées." ) else: message += ( f"L’adresse courriel {bad_emails[0]} " "est mal formée ou non autorisée et n’a pas été invitée." ) self.request.session.flash(Message(cls=cls, text=message)) return HTTPFound(location=self.request.resource_url(self.context))
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 post(self) -> Response: """ Transfer amendement(s) from this table to another one, or back to the index """ nums: List[str] = self.get_nums() if "submit-index" in self.request.POST: target = "" elif "submit-table" in self.request.POST: target = self.request.user.email else: target = self.request.POST.get("target") if not target: self.request.session.flash( Message(cls="warning", text="Veuillez sélectionner un·e destinataire.")) return HTTPFound(location=self.request.resource_url( self.context.lecture_resource, "transfer_amendements", query={"n": nums}, )) target_user_table = self.get_target_user_table(target) target_shared_table = self.get_target_shared_table(target) amendements = DBSession.query(Amendement).filter( Amendement.lecture == self.lecture, Amendement.num.in_(nums) # type: ignore ) for amendement in Batch.expanded_batches(amendements): old = amendement.table_name_with_email if target_shared_table: if target and amendement.location.shared_table is target_shared_table: continue new = target_shared_table.titre amendement.location.shared_table = target_shared_table amendement.location.user_table = None else: if target and amendement.location.user_table is target_user_table: continue new = str(target_user_table.user) if target_user_table else "" amendement.location.user_table = target_user_table amendement.location.shared_table = None amendement.stop_editing() AmendementTransfere.create( amendement=amendement, old_value=old, new_value=new, request=self.request, ) if target != self.request.user.email and self.request.POST.get( "from_index"): amendements_collection = self.context.lecture_resource[ "amendements"] next_location = self.request.resource_url(amendements_collection) else: table = self.context.model() table_resource = self.context.parent[table.user.email] next_location = self.request.resource_url(table_resource) return HTTPFound(location=next_location)