#Portail d'idées d'InterSystems

0 Abonnés · 18 Publications

Les idées d'InterSystems constituent une plateforme permettant aux utilisateurs d'InterSystems de partager leurs idées sur l'ajout de nouvelles fonctionnalités et l'amélioration des fonctionnalités existantes des produits InterSystems afin de mieux répondre aux besoins de nos clients. Cette balise englobe tous les articles liés au portail, y compris les articles dédiés à la mise en œuvre des idées introduites sur le portail d'idées.

https://ideas.intersystems.com

Annonce Irène Mykhailova · Oct 16, 2025

Bonjour à la Communauté !

Nous sommes ravis d'annoncer un tout nouveau tirage au sort. Cette fois-ci, le thème est :

💡 Expérience initiale des développeurs 💡

Nous souhaitons connaître votre avis sur la manière de faciliter, de clarifier et de motiver les premiers pas avec les technologies InterSystems. Qu'il s'agisse de documentation, d'intégration, de configuration ou de tutoriels, vos idées peuvent faire toute la différence !

0
0 19
Article Guillaume Rongier · Août 25, 2025 8m read

Bonjour à toute la communauté InterSystems ! Je m'appelle Sidd, je suis stagiaire au bureau de Singapour et j'ai récemment eu l'occasion de développer un pilote afin de connecter IRIS à Metabase pour aider certains ingénieurs commerciaux ici. On m'a encouragé à partager cette information ici afin que ceux qui rencontrent un problème similaire puissent utiliser ce pilote et faire part de leurs commentaires sur les améliorations possibles. Le dépôt GitHub complet avec la procédure de démarrage rapide, un bref aperçu et le processus de création du pilote est disponible ici. L'objectif principal de cet article est d'approfondir le code principal du pilote et de discuter des idées de son amélioration.

Bref aperçu

La motivation à l'origine de ce pilote est assez simple : Metabase n'offre pas de prise en charge JDBC native pour InterSystems IRIS, nous avions donc besoin d'un pilote personnalisé pour combler l'écart entre le backend de Metabase et les bases de données IRIS. Sans ce pont, il n'y a tout simplement aucun moyen de connecter Metabase aux instances IRIS, ce qui pose évidemment un problème si vous essayez de créer des tableaux de bord et des analyses à partir de vos données IRIS.

La bonne nouvelle, c'est que comme IRIS est déjà fourni avec son propre pilote JDBC et que Metabase dispose d'une implémentation générique solide du pilote SQL JDBC, je n'ai pas eu à réinventer la roue. La plupart des tâches lourdes, telles que l'établissement des connexions à la base de données, le traitement des requêtes de base, la gestion des pools de connexions et les opérations SQL standard, ont pu être déléguées à l'infrastructure générique existante du pilote Metabase. Cela a permis d'économiser énormément de temps de développement et d'assurer la compatibilité avec les fonctionnalités principales de Metabase.

Remplacement des méthodes multiples

L'approche consistait essentiellement à démarrer un pilote minimal, à le tester par rapport à IRIS, à identifier les points faibles où des dysfonctionnements ou des comportements inattendus se produisaient, puis à remplacer de manière sélective ces méthodes par des implémentations spécifiques à IRIS.

Structure du pilote

La structure du pilote constitue la base en définissant les capacités SQL d'IRIS dans le contexte de Metabase et un système robuste de mappage des types qui traduit les types de données IRIS JDBC vers le système de types interne de Metabase.

Ceci est suivi de quelques fonctions DateTime qui permettent à Metabase de connaître la manière de générer le SQL nécessaire pour les visualisations de séries chronologiques. Cela n'a pas encore été testé de manière rigoureuse, mais plutôt implémenté en s'inspirant d'implémentations similaires dans d'autres pilotes JDBC.

Connexion à IRIS

L'une des méthodes les plus importantes à remplacer serait la méthode sql-jdbc.conn/connection-details->spec, qui permet au pilote d'établir une connexion avec IRIS JDBC.

(defn- jdbc-spec
	[{:keys [host port namespace user password additional-options]
		:or {host "localhost", port 1972, namespace "USER"}
		:as details}]

	(-> {:classname "com.intersystems.jdbc.IRISDriver"
	:subprotocol "IRIS"
	:subname (str "//" host ":" port "/" namespace)
	:user user
	:password password}
	(merge (dissoc details :host :port :namespace :user :password :additional-options))
	(sql-jdbc.common/handle-additional-options additional-options)))

(defmethod sql-jdbc.conn/connection-details->spec :iris-jdbc
	[_ details-map]
	(jdbc-spec details-map))

Cette méthode fait appel à la méthode d'assistance jdbc-spec qui lit d'abord les données saisies par l'utilisateur avant de créer une chaîne de connexion et de la transmettre à la fonction sql-jdbc appropriée qui se chargera de connecter l'application à l'instance IRIS.

Description des bases de données

Lorsque Metabase se connecte pour la première fois à une base de données, le programme exécute une série de requêtes SQL afin de déterminer, entre autres, les tables auxquelles l'utilisateur a accès ainsi que les métadonnées de ces tables. Cela se fait via un appel à la méthode describe-database, qui appelle ensuite sql-jdbc.sync/have-select-privilege? et sql-jdbc.sync/fallback-metadata-query, entre autres méthodes. Les premières tentatives pour remplacer la méthode globale describe-database n'ont pas vraiment fonctionné, car j'aurais dû implémenter toute la logique de la méthode. Je me suis donc concentré sur les appels de méthode qui échouaient et j'ai simplement décidé de les remplacer.

(defmethod sql-jdbc.sync/fallback-metadata-query :iris-jdbc
	[_ _db-name schema table]

	[(format "SELECT * FROM %s.%s WHERE 1=0 LIMIT 0"
		schema
		table)])

(defmethod sql-jdbc.sync/have-select-privilege? :iris-jdbc
	[_ _db schema table]

	[(format "SELECT 1 AS _ FROM %s.%s WHERE 1=0"
		schema
		table)])

En consultant les journaux Docker, nous avons constaté que Metabase interrogeait les métadonnées par défaut à l'aide d'une balise "zero row probe" qui interrogeait le nom et le type des colonnes d'une table. Cette opération était effectuée à l'aide d'une instruction SQL similaire à celle-ci:

SELECT TRUE FROM "schema"."table" WHERE 1 <> 1 LIMIT 0

Cet appel ne fonctionne pas sur IRIS pour deux raisons : l'utilisation du mot-clé TRUE et 1 <> 1. Cela a été fait dans la méthode describe-database avant l'appel de la méthode fallback-metadata-query et l'échec. La seule solution que j'ai trouvée pour résoudre ce problème était de remplacer la méthode fallback-metadata-query. J'ai donc modifié la requête SQL pour qu'elle puisse être exécutée par le pilote JDBC d'IRIS sur IRIS.

Un appel similaire:

SELECT TRUE AS _ FROM "schema"."table" WHERE 1 <> 1

était utilisé par Metabase pour vérifier si un utilisateur avait les privilèges de sélection pour une table donnée, ce qui échouait pour des raisons similaires

Ce qui est intéressant, c'est que le remplacement de la requête de secours sous-jacente semble être le moyen le plus simple de résoudre ce problème, du moins pour IRIS. Cependant, tous les autres pilotes disponibles dans le référentiel Metabase ont leur propre version de describe-database, ce qui me porte à croire qu'il existe un moyen plus clair et plus efficace d'obtenir le même résultat.

Maintenabilité

IRIS ne supporte pas non plus la maintenabilité au-delà de CLOSE_CURSORS_ON_COMMIT, alors que Metabase s'attend à ce qu'une base de données la supporte par défaut. Bien qu'IRIS prenne en charge la maintenabilité au-delà de HOLD_CURSORS_ON_COMMIT, j'ai décidé de m'inspirer d'autres pilotes confrontés au même problème en désactivant complètement la maintenabilité jusqu'à ce que je trouve un moyen de l'implémenter uniquement au-delà de HOLD_CURSORS_ON_COMMIT.

(defmethod sql-jdbc.execute/statement :iris-jdbc
	[driver ^Connection conn]
	(.createStatement conn))

(defmethod sql-jdbc.execute/prepared-statement :iris-jdbc
	[driver ^Connection conn ^String sql]
	(.prepareStatement conn sql))

Prévention des schémas/tables du système

À sa conception, IRIS est préchargé avec un ensemble de schémas et de tables système qui fournissent des fonctionnalités très utiles, mais qui ne sont pas nécessaires pour l'analyse commerciale. La manière la plus simple d'empêcher la synchronisation des schémas avec Metabase consiste à remplacer la méthode sql-jdbc.sync/excluded-schemas, qui consiste en renvoyant un ensemble de chaînes contenant les noms des schémas que vous souhaitez exclure.

(defmethod sql-jdbc.sync/excluded-schemas :iris-jdbc [_]
   #{"Ens"})

C'est utile, mais IRIS a tout simplement trop de schémas système pour que cela fonctionne dans la pratique. J'ai donc choisi à la place de remplacer la méthode sql-jdbc.sync/filtered-syncable-schemas.

(def ^:private sql-jdbc-default
	(get-method sync.interface/filtered-syncable-schemas :sql-jdbc))

(defmethod sync.interface/filtered-syncable-schemas :iris-jdbc
	[driver ^java.sql.Connection conn ^java.sql.DatabaseMetaData meta
	^String incl-pat ^String excl-pat]

	(let [filtered-schemas
		(into []
			(remove (fn [schema]
				(or
				(str/starts-with? schema "%")
				(str/starts-with? schema "EnsLib_")
				(str/starts-with? schema "Ens_")
				(str/starts-with? schema "EnsPortal")
				(= schema "INFORMATION_SCHEMA")
				(= schema "Ens"))))
			(sql-jdbc-default driver conn meta incl-pat excl-pat))]


	(doseq [schema filtered-schemas]
		(log/infof "[IRIS-DRIVER] Remaining schema → %s" schema))
	
	filtered-schemas))

L'implémentation par défaut de cette méthode consiste à récupérer tous les schémas disponibles et à supprimer ceux qui sont inclus dans l'ensemble renvoyé par la méthode sql-jdbc.sync/excluded-schemas. Le simple remplacement de la méthode m'obligerait à réécrire le code permettant de récupérer tous les schémas disponibles. Pour éviter cela, j'ai défini une méthode privée sql-jdbc-default qui enregistre l'implémentation par défaut de la méthode avant de la remplacer. De cette façon, je peux appeler l'implémentation par défaut dans ma méthode remplacée, ce qui me permet de filtrer les schémas de manière dynamique.

Projets futurs

Mon but final est d'intégrer officiellement ce pilote dans le référentiel principal de Metabase, ce qui le rendrait accessible à l'ensemble de la communauté sans nécessiter de l'installer manuellement. Pour cela, je vais devoir développer une suite de tests complète qui couvre tous les cas limite et garantit que le pilote fonctionne de manière fiable avec différentes versions et configurations d'IRIS. Cela implique d'écrire des tests unitaires pour les mappages de types, des tests d'intégration pour diverses opérations SQL et probablement quelques benchmarks de performances pour s'assurer que nous n'introduisons aucune régression.

Au-delà d'une simple fusion, il existe certainement certains domaines dans lesquels l'implémentation actuelle pourrait être améliorée. Le système de mappage des types, bien que fonctionnel, pourrait être plus nuancé, en particulier en ce qui concerne la manière dont nous traitons les types de données plus spécialisés d'IRIS. Pour ce faire, je devrais examiner l'implémentation JDBC d'IRIS afin de déterminer les mappages exacts entre les types IRIS et Java.

Enfin, je suis convaincu que les implémentations des fonctions arithmétiques DateTime et de date peuvent être améliorées, en commençant par quelques tests visant à déterminer si l'implémentation actuelle fonctionne correctement.

Conclusion

Je vais certainement travailler surtoutes ces améliorations dès que j'aurai un peu de temps libre. La communauté semble très intéressée par une meilleure prise en charge d'IRIS dans Metabase, cela semble donc être un investissement utile. N'hésitez pas à me faire part de vos idées ou à soumettre des pull requests :)

Un grand remerciement à @Martyn Lee et @Bryan Hoon du bureau de Singapour pour m'avoir donné l'opportunité de travailler sur ce projet et m'avoir aidé à résoudre certains problèmes qui se sont présentés au cours du processus.

0
0 27
Annonce Irène Mykhailova · Juil 21, 2025

Bonjour, la Communauté !

Notre 💡 Concours d'idées 💡est terminé. 26 nouvelles idées, conformes à la structure requise, ont été retenues !

Elles visent toutes à améliorer InterSystems IRIS et les produits associés, en mettant en avant les avantages concrets pour les développeurs une fois les idées mises en œuvre.

Et maintenant, annonçons les gagnants…

0
0 35
Annonce Irène Mykhailova · Juin 13, 2025

Bonjour la communauté,

Nous sommes très heureux d'inviter tous les membres de notre Communauté de Développeurs à participer à notre prochain concours !

💡 Le 4e concours d'idées InterSystems 💡

Nous recherchons vos idées innovantes pour améliorer InterSystems IRIS et les produits et services associés. Nous encourageons les suggestions basées sur des cas d'utilisation réels, mettant en évidence les avantages tangibles que votre idée apportera aux autres utilisateurs et comment elle améliorera l'expérience des développeurs avec la technologie InterSystems.

📅 Durée : 9 juin - 20 juillet 2024

🏆 Des prix pour les meilleures idées et un tirage au sort !

🎁 Des cadeaux pour tous : Un cadeau spécial sera offert à chaque auteur dont l'idée sera acceptée au concours.

>> SOUMETTRE UNE IDÉE <<

0
0 38
Annonce Irène Mykhailova · Fév 18, 2025

Bonjour la communauté !

Comme notre précédent tirage au sort a été un véritable succès, nous avons décidé de le répéter 😉 Et cette fois, le sujet de notre tirage au sort est

🔎 Recherche DC 🔍

Nous pensons que vous pourriez avoir quelques suggestions sur la façon dont nous pouvons améliorer notre moteur de recherche, et nous serions ravis de vous entendre !

0
0 35
Article Lorenzo Scalese · Jan 6, 2025 6m read

Salut la Communauté,

image
Dans cet article, je présenterai mon application iris-HL7v2Gen.

IRIS-HL7v2Gen est une application CSP qui facilite la génération dynamique de messages de test HL7. Ce processus est essentiel pour tester, déboguer et intégrer les systèmes de données de soins de santé. L'application permet aux utilisateurs de générer une grande variété de types de messages HL7, de valider leur structure par rapport aux spécifications HL7, d'explorer la hiérarchie des messages et de transmettre les messages par TCP/IP aux systèmes de production. Ces fonctionnalités sont particulièrement utiles dans les contextes où la conformité aux normes HL7 est obligatoire pour assurer l'interopérabilité entre différents organismes ou systèmes de soins de santé.

Fonctionnalités de l'application

  • Génération Dynamique de Messages HL7: Création instantanée de messages HL7 pour une gamme de types de messages, facilitant ainsi les tests complets.
  • Exploration de la structure des messages: Visualisation de la structure des messages générés sur la base des spécifications HL7.
  • Visualisation des jeux de valeurs: Visualisation des jeux de valeurs codées prédéfinis pour des champs spécifiques.
  • Validation des messages: Validation des messages par rapport aux normes HL7 pour garantir la conformité.
  • Communication TCP/IP: Transmission facile de messages à la production à l'aide de paramètres TCP/IP.
  • Prise en charge d'un grand nombre de types de messages: Prise en charge de 184 types de messages HL7, garantissant la polyvalence pour les différents besoins d'intégration des soins de santé.
  • ClassMethod: Génération d'un message de test par l'invocation d'une méthode de classe
  • Version prise en charge: Actuellement, la version 2.5 de HL7 est prise en charge
0
0 55
Annonce Irène Mykhailova · Juin 6, 2024

Bonjour la communauté,

Nous sommes très heureux d'inviter tous les membres de notre communauté de développeurs (employés d'InterSystems et pas) à participer à notre prochain concours !

💡 Le 3ème Concours d'Idées InterSystems 💡

Nous recherchons vos idées innovantes pour améliorer InterSystems IRIS et les produits et services associés. Nous encourageons les suggestions basées sur des cas d'utilisation réels, mettant en évidence les avantages tangibles que votre idée apportera aux autres utilisateurs et comment elle améliorera l'expérience des développeurs avec la technologie InterSystems.

📅 Durée : 10 juin - 7 juillet 2024

🏆 Des prix pour les meilleures idées !

🎁 Des cadeaux pour tous : Un cadeau spécial sera offert à chaque auteur dont l'idée sera acceptée au concours.

 

>> SOUMETTEZ VOTRE IDÉE ICI À PARTIR DU 10 JUIN <<

0
0 100
Article Iryna Mykhailova · Avr 1, 2024 5m read

Préparez une application d'échantillon (Sample)


1. Pour ce tutoriel, nous utiliserons le modèle iris-rest-api-template: https://openexchange.intersystems.com/package/iris-rest-api-template, en ajoutant du code pour installer un support de JIRISReport. Procédez comme suit :
2. Clonez le projet dans un dossier local :

$ git clone https://github.com/intersystems-community/iris-rest-api-template.git

3. Modifiez le fichier Dockerfile avec le contenu suivant :

ARG IMAGE=intersystemsdc/iris-community:2020.3.0.221.0-zpm
ARG IMAGE=intersystemsdc/iris-community
FROM $IMAGE

USER root

RUNecho ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections RUN apt-get update && \ apt-get install ttf-mscorefonts-installer -y &&
apt-get install fontconfig -y RUNfc-cache -v -f /usr/share/fonts USER ${ISC_PACKAGE_MGRUSER}

WORKDIR /home/irisowner/dev RUN --mount=type=bind,src=.,dst=.
iris start IRIS &&
iris session IRIS < iris.script &&
iris stop IRIS quietly

4. Exécutez ces commandes pour construire et exécuter l'échantillon :

docker compose build up -d

5. Ouvrez le code source sur VSCode (installez ObjectScript et les extensions InterSystems si vous ne les avez pas encore) et Choizissez en bas docker:iris52773[USER] pour ouvrir les options :

6. Choizissez l'option Ouvrir un terminal dans Docker :

7. Le terminal s'affiche :

8. Tapez zpm pour lancer l'interpréteur de commandes zpm :

9. Tapez, exécutez et attendez l'installation (cela prend un certain temps) :

zpm:USER>install jirisreport -verbose

10. Découvrez les résultats de l'installation :




11. Dans VSCode, accédez à Person.cls et exécutez la méthode AddTestData (cliquez sur Debug) avec 20 enregistrements :


12. Nous disposons à présent d'une base de données contenant des exemples de données pour vos rapports.

Téléchargez le pilote JDBC d'IRIS

1. Téléchargez à partir de https://github.com/intersystems-community/iris-driver-distribution/raw/main/JDBC/JDK18/intersystems-jdbc-3.7.1.jar et sauvegardez dans n'importe quel dossier (dans mon cas, D:\projetos\isc\jirisreport-api-sample).


Conception de rapports avec JasperReports Studio

1. Accédez à https://community.jaspersoft.com/community-download et téléchargez Jaspersoft Studio CE :


2. Choisissez la bonne version pour votre OS :


3. Maintenant que Jasper Studio est installé, exécutez-le :



4. L'explorateur de projet (Project Explorer) s'affiche :

5. Cliquez sur l'explorateur de référentiel (Repository Explorer) > Faites un clic droit sur les adaptateurs de données > Créer un adaptateur de données (Data Adapter) :

6. Choizissez Database JDBC Connection et cliquez sur Next :

7. Définissez les valeurs suivantes pour l'onglet d'emplacement de la base de données (Database Location) :

  • Nom: IRISAdapter
  • Pilote JDBC : com.intersystems.jdbc.IRISDriver
  • JDBC Url: jdbc:IRIS://127.0.0.1:51773/User
  • Nom d'utilidateur : _SYSTEM
  • Mot de passe : SYS

8. Choizissez ensuite l'onglet Driver Classpath :

9. Cliquez sur le bouton d'ajout (Add) et Choizissez l'emplacement du fichier du pilote JDBC (dans le dossier choisi dans la section Télécharger le pilote JDBC d'IRIS) :

10. Cliquez ensuite sur Test :

11. Cliquez sur Finish. Nous disposons maintenant d'une connexion à la base de données pour IRIS :

 

Créez un nouveau rapport

1. Accédez à: Fichier > Nouveau > Rapport Jasper :

2. Choisissez Blank A4 et cliquez sur Next :

3. Définissez le nom du rapport AllPersons.jrxml et cliquez sur Next :

4. Choisissez IRISAdapter et écrivez :

select * from"dc_Sample"."Person"

5. Cliquez sur Next.

6. Choisissez tous les champs, à l'exception du champ ID, et cliquez sur Next :

7. Ce rapport n'a pas de bandes, cliquez sur Next :

8. Découvrez félicitations (Congratulations) et cliquer sur Finish :

9. Nous avons maintenant un rapport vide, mais connecté aux données IRIS :

10. JasperReports travaille avec un concept de bande et nous travaillerons avec des bandes suivantes :

  • Titre : section permettant d'imprimer le titre du rapport sur la première page uniquement.
  • En-tête de colonne : utilisé pour les rapports comportant des colonnes, pour imprimer le titre de la colonne sur une nouvelle page.
  • Détail 1 : section pour les données de la base de données.

11. Dans la palette, sélectionnez Static Text et effectuez un glisser-déposer pour la section de titre (Title) :

12. Modifiez la boîte de texte contenant la valeur de la liste des personnes Person List, mettez-la en forme et augmentez la taille des caractères (Arial, 20 Bold, Center).

13. Sous l'onglet bordures, cliquez sur les bordures du haut et du bas :

14. Ajuster la section titre (Title) pour qu'elle occupe moins d'espace :

15. Faites un clic droit sur l'en-tête de la page et supprimez-le :

16. Faites de même pour les bandes "Pied de colonne", "Pied de page" et "Résumé" (Column Footer, Page Footer et Summary, respectivement) et nous n'avons plus que 3 bandes :


17. Développez les champs sur Outline (aperçu), sélectionnez le nom et faites un glisser-déposer pour la section Detail 1 :


18. Ajustez la position du nom du texte statique (Name) dans l'en-tête de colonne et du champ de nom (Name Field) dans la bande de détail (Detail band) :


19. Réduisez maintenant la hauteur de l'en-tête de colonne (Column Header) et du détail (Detail) :

20. Glissez-déposez les autres champs de la bande de détail et ajustez-les :

21. Maintenant, mettez en forme les étiquettes de titre (Arial, 14, gras) :


22. Mettez en forme les champs (Arial, 14) :


23. Cliquez sur le bouton de sauvegarde Save (ou Fichier > Sauvegarder) en haut de la page et cliquez sur l'onglet d'aperçu (Preview) pour visualiser les résultats :


24. Votre rapport est prêt !

Dans le prochain article (Partie II), nous apprendrons comment exécuter ce rapport à partir d'un rapport IRIS de l'API REST.

0
0 70
Article Iryna Mykhailova · Mars 4, 2024 24m read

Index

Partie 1

  • Présentation de Flask : une revue rapide des documents de Flask (Flask Docs), où vous trouverez toutes les informations dont vous avez besoin pour ce tutoriel;
  • Connexion à InterSystems IRIS : un guide détaillé étape par étape sur l'utilisation de SQLAlchemy pour se connecter à une instance d'IRIS;

Partie 2

  • Discussion sur cette forme de mise en œuvre : pourquoi nous devrions l'utiliser et les situations où elle est applicable.
  • L'application OpenExchange : si vous n'êtes pas intéressé par le développement de cet exemple, vous pouvez passer directement à cette section et apprendre à l'utiliser comme modèle pour votre application flask-iris.
  • Bonus : quelques erreurs commises lors du développement et comment je les ai résolues.

Présentation de Flask

Flask est un outil web écrit en Python et conçu pour vous faciliter la vie lors de la création d'une application web. Il utilise Werkzeug pour gérer l'interface Web Server Gateway Interface (WSGI) - la spécification standard de Python pour la communication avec les serveurs web. Il utilise également Jinja pour gérer les modèles HTML, et le Click CLI toolkit pour créer et gérer le code d'interface de ligne de commande.

Installation

La première étape est facultative. Cependant, c'est une bonne idée de configurer un environnement virtuel avant de commencer des projets. Vous pouvez le faire sur le terminal en modifiant le répertoire vers le dossier que vous voulez utiliser comme racine et en tapant la commande mentionnée ci-dessous.

> python -m venv .venv

Si vous optez pour un environnement virtuel, activez-le avec la commande suivante (sous Windows) avant d'installer les prérequis :

> .venv\Scripts\activate

Enfin, installez les paquets avec pip (paquetage d'installation Python).

> pip install Flask
> pip install Flask-SQLAlchemy
> pip install sqlalchemy-iris

Quickstart

Nous suivrons la structure présentée dans ce Tutoriel, en adaptant les connexions à l'IRIS. Cela minimisera vos problèmes lors de l'amélioration de votre application puisque vous pourrez consulter la Documentation de Flask sans conflits. Cependant, comme Flask est très flexible, il ne vous oblige pas à suivre des modèles, et vous pouvez considérer les étapes suivantes comme de simples suggestions.

L'approche présentée ici consiste à utiliser les applications comme des paquets et à définir une fabrique d'applications pour couvrir les grands projets. Cependant, vous pouvez toujours construire l'application Flask avec cinq lignes de code seulement, comme on le voit ci-dessous.

from flask import Flask

app = Flask(__name__)


@app.route('/')defhello():return'Hello, world!'

 

Une application modèle

Cette section présente les étapes à suivre pour créer l'application flask-iris. Ce tutoriel peut être suivi si vous n'avez jamais utilisé de framework web auparavant. Cependant, si vous voulez vraiment comprendre la théorie de ce type de flux de travail, lisez mon article précédent sur Exemples de travail avec IRIS à partir de Django, en prenant particulièrement en compte l'image au début de l'article. Vous constaterez avec satisfaction que les différents frameworks web ont une logique très similaire. Cependant, dans Flask, nous pouvons définir les URL et les vues ensemble en tant que groupes, appelés plans originaux (blueprints).

TLes conditions requises concernent Flask, Flask-SQLAlchemy et sqlalchemy-iris. Intuitivement, le pont entre l'application web et votre instance IRIS est construit !
 

Le paquet

Tout d'abord, créez ou sélectionnez le dossier que vous souhaitez utiliser comme racine. Appelons-le flaskr-iris. Nous avons besoin que Python comprenne ce dossier comme un paquet, nous devons donc créer un fichier __init_.py. C'est là que nous placerons notre fabrique d'applications.

Étape 1 - Création de la fabrique d'applications

La fabrique d'applications n'est rien de plus qu'une fonction. A l'intérieur de celle-ci, nous devons définir une instance Flask() (l'application), définir ses configurations, la connecter à la base de données, et enregistrer les blueprints si nous choisissons de les utiliser. Nous allons explorer plus en détail les blueprints plus tard, donc pour l'instant vous n'avez pas besoin de vous en préoccuper.

Cette fonction doit être appelée create_app(). Commencez par importer Flask et créez une instance avec l'argument \N_nom\N_ (consultez la documentation pour plus de détails). Ensuite, mappez la propriété config avec une clé secrète et l'URI de la base de données. Utilisez le format "dialect://username:password@host:port/NAMESPACE", en suivant les recommandations de SQLAlchemy. Dans ce cas, le dialecte devrait être 'iris', créé par CaretDev. Le nom d'utilisateur et le mot de passe sont ceux que vous utilisez pour vous connecter à l'instance InterSystems IRIS indiquée par l'hôte et le port, et l'espace de noms parle de lui-même.

# __init__.pyfrom flask import Flask

defcreate_app():# créer et configurer l'application
    app - Flask(__name__, instance_relative_config=True)

    app.config.from_mapping(
        SECRET_KEY = "dev", # remplacer celle-ci par une clé de génération aléatoire lors du déploiement
        SQLALCHEMY_DATABSE_URI = "iris://_SYSTEM:sys@loclhost:1972/SAMPLE"
    )

Les prochaines lignes de notre fabrique auront besoin de la base de données, alors réservons ce fichier pour un instant pendant que nous parlons de données et de modèles.
 

Étape 2 - Gestion des données

Nous utiliserons SQLAlchemy pour importer et gérer les données puisque nous pouvons utiliser Flask-SQLAlchemy et SQLAlchemy-IRIS pour résoudre les problèmes de connexion. Tout d'abord, il faut créer le fichier database.py dans lequel nous importerons la version Flask de SQLAlchemy et l'instancierons. Cette étape ne nécessite pas de fichier séparé. Cependant, elle peut être très utile plus tard pour développer d'autres méthodes pour gérer la base de données.

# database.pyfrom flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

Ensuite, il faut définir les modèles qui indiqueront à Python comment interpréter chaque enregistrement dans un tableau. Créez le fichier models.py, importez l'instance de SQLAlchemy et les fonctionnalités dont vous avez besoin pour traiter les données. En suivant l'exemple donné dans la documentation officielle de Flask, nous pouvons créer un blog nécessitant un modèle pour les utilisateurs et un autre pour les messages. Cette implémentation couvre un nombre substantiel de cas et vous donne une bonne longueur d'avance.

Nous allons utiliser le Mapping Objet-Relationnel (ORM) comme suggéré dans SQLAlchemy 2.0, en définissant des colonnes mappées et une relation de type Un-à-plusieurs (One-to-many). Vous pouvez vérifier comment modéliser d'autres types de relations dans le Guide de SQLAlchemy.

# models.pyfrom .database import db
from sqlalchemy.orm import Mapped, mapped_column
from typing import List, Optional
import datetime

classUser(db.Model):
    id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
    username: Mapped[str] = mapped_column(db.String(1000), unique=True, nullable=False)
    password: Mapped[str] = mapped_column(db.String(1000), nullable=False)
    posts: Mapped[List["Post"]] =  db.relationship(back_populates="user")

classPost(db.Model):
    id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
    author_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user: Mapped["User"] = db.relationship(back_populates="posts")
    created: Mapped[datetime.datetime] = mapped_column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP'))
    title: Mapped[str] = mapped_column(db.String(1000), nullable=False)
    body: Mapped[Optional[str]] = mapped_column(db.String(1000000))

Le format général à utiliser pour définir les colonnes dans la dernière version de SQLAlchemy est le suivant :

columnName: Mapped[type] = mapped_column(db.Type, arguments)

En ce qui concerne les relations, du côté des "nombreux", vous devrez également définir la clé étrangère.

Obs.: les noms des classes seront convertis de "CamelCase" en "snake_case" lors de la création des tableaux SQL.

Enfin, nous pouvons retourner à la fabrique sur _init_.py_ et faire les connexions. Importez la base de données et les modèles, assignez cette application à l'instance db, et créez toutes les tableaux à l'intérieur de la méthode create_app().

# __init__.pyfrom flask import Flask
from sqlalchemy.exc import DatabaseError

defcreate_app():# créer et configurer l'application
    app = Flask(__name__)

    app.config.from_mapping(
        SECRET_KEY="dev", # remplacer par un système aléatoire lors du déploiement
        SQLALCHEMY_DATABASE_URI = "iris://_SYSTEM:sys@localhost:1972/SAMPLE"
    )

    # avec cette application, flask initialise Alchemyfrom .database import db
    from .models import User, Post

    db.init_app(app)

    try:
        with app.app_context():
            db.create_all()
    except DatabaseError as err:
        if'already exists'in err._sql_message():
            print("Databases already exist.")
        else:
            print(err)

Ensuite, nous passerons à la création des vues.

Étape 3 - Les vues

Étape 3.1 - Les blueprints : l'authentification

Un objet Blueprint a été conçu pour organiser vos vues en groupes et les enregistrer dans l'application. Nous allons créer un Blueprint pour les vues qui traitent de l'authentification et un autre pour l'affichage, l'édition et la suppression, de sorte que cette partie du tutoriel couvrira la gestion CRUD et la gestion des sessions.
En commençant par le Blueprint d'authentification, créez le fichier auth.py et importez les utilitaires de Flask et Werkzeug, la base de données et le modèle Utilisateur. Ensuite, instanciez l'objet Blueprint, en spécifiant son nom et le préfixe de l'URL.

# auth.pyfrom flask import (
    Blueprint, request, g, redirect, url_for, flash, render_template, session
)
from werkzeug.security import generate_password_hash, check_password_hash
from .database import db
from .models import User
import functools

bp = Blueprint('auth', __name__, url_prefix='/auth')

Ensuite, nous pouvons utiliser des décorateurs pour définir des chemins pour l'enregistrement, la connexion et la déconnexion avec toutes les méthodes nécessaires. Dans notre exemple, nous utiliserons GET et POST et commencerons par l'enregistrement.

Le décorateur est accessible comme une méthode de notre Blueprint et possède un argument pour l'URL et un autre pour les méthodes qu'il accepte. À la ligne suivante, créez la fonction indiquant à l'application ce qu'elle doit faire lorsqu'elle reçoit une requête avec les paramètres correspondants. Dans l'exemple suivant, nous aurons accès à la fonction en GETtant ou POSTant l'URL http://host:port/auth/register.
 

# auth.py@bp.route('/register', methods=('GET', 'POST'))defregister():if request.method=='POST':
        pass

Si l'application reçoit un POST renvoyant à '/register', cela signifie qu'un utilisateur souhaite créer un compte. L'accès au nom d'utilisateur et au mot de passe choisis avec l'objet de contenu de la demande est géré par Flask. Ensuite, créez une instance du modèle Utilisateur, en stockant ses propriétés en tant que valeurs reçues. Vous pouvez également utiliser les méthodes werkzeug.security pour protéger le contenu sensible. Ensuite, utilisez la propriété de session de notre base de données, fournie par SQLAlchemy, pour ajouter un nouvel utilisateur au tableau et le valider. À ce stade, les informations ont déjà été envoyées au tableau correspondant dans l'instance IRIS mentionnée à la dernière étape. Enfin, redirigez l'utilisateur vers la page de connexion, que nous créerons à l'étape suivante. N'oubliez pas de traiter les erreurs telles que la réception d'entrées vides, l'intégrité de l'index et la connexion à la base de données. Vous pouvez utiliser flash() pour afficher les détails du problème à l'utilisateur. Nous pouvons également utiliser la même fonction register() pour rendre le modèle de la page d'enregistrement si GET est utilisé dans la requête.

# auth.py@bp.route('/register', methods=('GET', 'POST'))defregister():if request.method=='POST':
        username = request.form['username']
        password = request.form['password']
        error = Noneifnot username:
            error = "Username is required."elifnot password:
            error = "Password is required."if error isNone:
            try:
                user = User(
                    username=username,
                    password=generate_password_hash(password)
                )

                db.session.add(user)
                db.session.commit()
            except Exception as err: # TODO: remplacer par l'équivalent db.IntegrityError de sqlite3
                error = str(err)
            else:
                return redirect(url_for("auth.login"))

        flash(error)

    return render_template('auth/register.html')

Ensuite, nous répétons la même logique pour la page de connexion. Lorsque nous traitons un POST, nous recevons d'abord un nom d'utilisateur et un mot de passe, puis nous vérifions s'ils existent dans notre tableau IRIS. Cette fois, nous n'utiliserons pas la propriété session (bien que nous aurions pu le faire). Nous utiliserons plutôt la méthode one_or_404(). Nous devons le faire parce que le nom d'utilisateur doit être unique, comme nous l'avons défini dans notre modèle. Ainsi, si la requête ne renvoie pas précisément une ligne suite à notre demande, nous pouvons la considérer comme non trouvée. À l'intérieur de cette fonction, enchaîner des commandes SQL pour trouver le résultat requis, en utilisant les modèles comme des tableaux et leurs propriétés comme des colonnes. Enfin, on efface la session de Flask, on ajoute l'utilisateur si la connexion a été effective et on le redirige vers la page d'accueil. S'il s'agit d'un GET, envoyer l'utilisateur à la page de connexion.

#auth.py@bp.route('/login', methods=('GET', 'POST'))deflogin():if request.method=="POST":
        username = request.form['username']
        password = request.form['password']
        error = Nonetry:
            user = db.one_or_404(db.select(User).where(User.username==username))

            ifnot check_password_hash(user.password, password):
                error = "Incorrect password"except Exception as err: # TODO vérifier également cette erreur
            error = f"User {username} not found."if error isNone:
            session.clear()
            session['user_id'] = user.id
            return redirect(url_for('index'))

        flash(error)


    return render_template('auth/login.html')
    

Pour se déconnecter, il suffit d'effacer la session et de rediriger vers la page d'accueil. 

# auth.py@bp.route('/logout')deflogout():
    session.clear()
    return redirect(url_for('index'))

Pour les fonctions suivantes, nous allons traiter l'utilisateur qui se connecte sur différentes requêtes, qui ne sont pas nécessairement liées à l'authentification. L'objet 'g' de Flask est unique pour chaque demande, ce qui signifie que vous pouvez l'utiliser pour définir l'utilisateur actuel à partir de la base de données lorsque vous traitez des accès éventuellement interdits.

Heureusement, le Blueprint possède la propriété before_app_request. Elle peut être utilisée comme décorateur suivi d'une fonction pour déterminer ce qu'il convient de faire lors de la réception d'une requête avant de la traiter. Pour charger l'utilisateur connecté, nous devons à nouveau nous connecter, mais cette fois, nous obtiendrons des informations à partir de la session autres que la requête.

# auth.py@bp.before_app_requestdefload_logged_in_user():
    user_id = session.get('user_id')

    if user_id isNone:
        g.user = Noneelse:
        g.user = db.one_or_404(
            db.select(User).where(User.id==user_id)
        )

Enfin, pour notre dernière fonction d'authentification, il est possible de créer quelque chose qui traitera les vues qui doivent être interdites si l'utilisateur ne s'est pas connecté. Si vous ne voulez pas afficher un simple code 403, redirigez l'utilisateur vers la page de connexion.

# auth.pydeflogin_required(view):    @functools.wraps(view)defwrapped_view(**kwargs):if session.get('user_id') isNone:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

Étape 3.2 - Les blueprints: blog (CRUD)

Là encore, commencez par créer le fichier (appelons-le blog.py) et y importer tout ce qui est nécessaire. Cela peut sembler beaucoup de travail, mais chacune de ces importations prendra tout son sens au fur et à mesure que nous avancerons. Créez également une autre instance de Blueprint.

# blog.pyfrom flask import (
    Blueprint, flash, g, redirect, render_template, request, url_for, session
)
from werkzeug.exceptions import abort
from .auth import login_required
from .database import db
from .models import Post, User
from sqlalchemy import desc

bp = Blueprint('blog', __name__)

Tout d'abord, il faut commencer par créer l'URL par défaut, qui pointe vers la page d'accueil. Ici, nous afficherons les messages stockés dans IRIS, de sorte que la propriété de session de la base de données (qui est une instance SQLAlchemy() de Flask-SQLAlchemy) puisse être utilisée. Il y a plusieurs façons d'exécuter une requête avec le module SQLAlchemy. J'ai choisi d'utiliser la fonction scalars() avec la requête SQL comme argument, enchaînée avec la fonction all(). Vous avez peut-être vu un comportement similaire dans d'autres langages et plugins comme fetchall(). La prochaine étape sera de rendre le modèle de la page d'accueil, en passant la variable où nous avons stocké tous les messages comme argument afin de pouvoir y accéder dans les fichiers HTML. Regardez notre READ (cRud) ci-dessous :

# blog.py@bp.route('/')defindex():
    posts = db.session.scalars(
        db.select(Post).order_by(desc(Post.created))
    ).all()

    return render_template('blog/index.html', posts=posts)

Ensuite, la fonction de création (Crud) d'un nouveau message sera très similaire à l'enregistrement d'un utilisateur. Nous obtenons l'entrée de l'utilisateur à partir de la requête, définissons ses valeurs en fonction des propriétés du modèle de message Post, utilisons l'objet session de la base de données pour faire un ajout (équivalent à l'instruction SQL d'insertion) et validons. Ici, nous allons accéder à l'utilisateur actuel à partir de la requête avec l'objet g. Nous utiliserons également un second décorateur, référençant la fonction login_required(), pour rediriger l'utilisateur vers la page de connexion s'il ne s'est pas encore connecté.

# blog.py@bp.route('/create', methods=('GET', 'POST'))@login_requireddefcreate():if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = Noneifnot title:
            error = 'Title is required.'if error isnotNone:
            flash(error)
        else:
            post = Post(
                title=title,
                body=body,
                author_id=g.user.id
            )
            db.session.add(post)
            db.session.commit()

            return redirect(url_for('blog.index'))

    return render_template('blog/create.html')

Pour effectuer une mise à jour (crUd), nous utiliserons une approche très similaire. Cependant, au lieu de créer une nouvelle instance de Post, nous la renverrons à partir d'une requête, puisqu'elle existe déjà dans la base de données. Nous pouvons utiliser l'URL pour recevoir un identifiant pour le message (Post). Une fois de plus, le login est nécessaire ici.

# blog.py@bp.route('//update', methods=('GET', 'POST'))@login_requireddefupdate(id):
    post = get_post(id)

    if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = Noneifnot title:
            error = 'Title is required.'if error isnotNone:
            flash(error)
        else:
            post.title = title
            post.body = body

            db.session.commit()

            return redirect(url_for('blog.index'))

    return render_template('blog/update.html', post=post)

Puisque pour la suppression il nous faudra également accéder au message (Post) par l'ID, nous pouvons créer une fonction get_post(id) et l'utiliser sur les deux vues. De la même manière que pour les autres requêtes que nous avons faites jusqu'à présent, nous pouvons enchaîner les commandes SQL pour l'écrire. Puisque notre modèle pour l'utilisateur a une relation, nous pouvons sélectionner le message Post et le joindre à Utilisateur (User). De cette manière, nous pourrons accéder aux informations de l'utilisateur par le biais de l'objet résultat. Nous pouvons également utiliser abort() pour envoyer des réponses 404 (non trouvé) et 403 (interdit).

# blog.pydefget_post(id, check_author=True):
    post = db.one_or_404(
        db.select(Post).join(User).where(Post.id == id)
    )

    if post isNone:
        abort(404, f"Post id {id} doesn't exist.")

    if check_author and post.author_id != session['user_id']:
        abort(403)

    return post

Enfin, vous pouvez effectuer la suppression en utilisant simplement une fonction de suppression de la session de la base de données.

# blog.py@bp.route('//delete', methods=('POST',))@login_requireddefdelete(id):
    post = get_post(id)

    db.session.delete(post)
    db.session.commit()

    return redirect(url_for('blog.index'))

 

Étape 3.3 - Enregistrer vos blueprints

Enfin, nous pouvons achever notre fabrique d'applications en enregistrant les blueprints et en renvoyant l'application, comme indiqué ci-dessous.

# __init__.pyfrom flask import Flask
from sqlalchemy.exc import DatabaseError

defcreate_app():# créer et configurer l'application
    app = Flask(__name__)

    app.config.from_mapping(
        SECRET_KEY="dev", # remplacer par un système aléatoire lors du déploiement
        SQLALCHEMY_DATABASE_URI = "iris://_SYSTEM:sys@localhost:1972/SAMPLE"
    )

    # avec cette application, flask initialise Alchemyfrom .database import db
    from .models import User, Post
    db.init_app(app)
    try:
        with app.app_context():
            db.create_all()
    except DatabaseError as err:
        if'already exists'in err._sql_message():
            print("Databases already exist.")
        else:
            print(err)

    # enregistrer les blueprintsfrom . import auth
    app.register_blueprint(auth.bp)

    from . import blog
    app.register_blueprint(blog.bp)
    app.add_url_rule('/', endpoint='index')

    return app

 

Étape 4 - Les modèles

Vous devez avoir remarqué que chaque vue se termine par un retour redirect(), qui envoie l'utilisateur vers une autre vue ou renvoie render_template(), ce qui est explicite. Ces fonctions reçoivent des modèles HTML comme arguments, ainsi que tout autre objet que vous pourriez vouloir utiliser à l'intérieur. Cela signifie qu'à ce stade du tutoriel, vous apprendrez à accéder à votre base de données IRIS à partir d'un fichier HTML, ce qui vous permettra de gérer les données avec CSS et JavaScript, et d'évoluer vers d'autres outils de développement web.

Le chemin par défaut de votre application Flask intégrée dans un paquetage pour rechercher des modèles se trouve dans le dossier app/templates. À l'intérieur de ce dossier, il est d'usage d'ajouter un fichier base.html. Le modèle de ces fichiers est le Jinja's. Cela signifie que vous pouvez utiliser {% ... %} pour ajouter des instructions telles que des blocs, des "ifs" (si) et des "fors" (pour), en les terminant toujours (avec {% endblock %}, par exemple). Vous pouvez également utiliser {{ ... }} pour des expressions telles que l'accès aux objets que vous avez passés dans la fonction de rendu du modèle. Enfin, vous pouvez utiliser {# ... #} pour les commentaires. 

Outre les arguments passés dans les vues, vous pouvez accéder à des contextes tels que l'objet g, qui est unique pour chaque requête, et get_flashed_messages() pour afficher les arguments passés dans flash(). Étant donné que nous avons déjà vu dans les étapes précédentes comment effectuer des requêtes et passer le résultat dans le contexte de la requête, l'exemple suivant pour un fichier de base vous montrera comment accéder aux données d'IRIS en utilisant l'objet g. En plus de cela, l'exemple montrera également comment accéder aux données d'IRIS en utilisant l'objet g. En outre, il montre également comment afficher les messages flashés.
 

<!-- templates/base.html --><!DOCTYPE html><title>{% block title %}{% endblock %} - Flaskr</title><linkrel="stylesheet"href="{{ url_for('static', filename='style.css') }}"><nav><h1>Flaskr</h1><ul>
        {% if g.user %}
          <li><span>{{ g.user['username'] }}</span><li><ahref="{{ url_for('auth.logout') }}">Log Out</a>
        {% else %}
          <li><ahref="{{ url_for('auth.register') }}">Register</a><li><ahref="{{ url_for('auth.login') }}">Log In</a>
        {% endif %}
    </ul></nav><sectionclass="content"><header>
        {% block header %}{% endblock %}
    </header>
    {% for message in get_flashed_messages() %}
      <divclass="flash">{{ message }}</div>
    {% endfor %}
    {% block content %}{% endblock %}
</section>

La base étant maintenant définie, vous pouvez enfin créer d'autres pages en agrandissant ce fichier et en personnalisant les blocs. Explorez l'exemple suivant pour la page Create Posts.

<!-- templates/blog/create.html -->
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}

{% block content %}
    <formmethod="post"><labelfor="title">Title</label><inputname="title"id="title"value="{{ request.form['title'] }}"required><labelfor="body">Body</label><textareaname="body"id="body">{{ request.form['body'] }}</textarea><inputtype="submit"value="Save"></form>
{% endblock %}

Le paramètre "name" (nom) de l'entrée sera la clé du dictionnaire request.form, accessible dans les vues. Les pages d'authentification (login et register) ont une syntaxe assez similaire puisqu'elles ne nécessitent qu'un seul formulaire. Cependant, vous pouvez les consulter sur mon dépôt GitHub si vous le souhaitez.

Maintenant, regardons la page d'accueil, appelée index.html.

<!-- templates/blog/index.html -->
{% extends 'base.html' %}

{% block header %}
 <h1>{% block title %}Posts{% endblock %}</h1>
 {% if g.user %}
    <aclass="action"href="{{ url_for('blog.create') }}">New</a>
 {% endif %}
{% endblock %}

{% block content %}
    {% for post in posts %}
        <articleclass="post"><header><div><h1>{{ post['title'] }}</h1><divclass="about">by {{ post.user.username }} on {{ post['created'].strftime('%Y-%m-%d') }}</div></div>
                {% if g.user['id'] == post['author_id'] %}
                    <aclass="action"href="{{ url_for('blog.update', id=post['id'] )}}">Edit</a>
                {% endif%}
            </header><pclass="body">{{ post['body'] }}</p></article>
        {% if not loop.last %}
            <hr>
        {% endif %}
    {% endfor %}
{% endblock %}

Voici quelques éléments qui valent la peine d'être examinés de plus près. Le lien pour créer un nouveau message n'apparaît que lorsque g.user confirme le vrai, ce qui signifie qu'un utilisateur s'est connecté. Il y a également une instruction "for" (pour), itérant à travers l'objet posts renvoyé par une requête sur la vue pour l'URL vide "" et transmis à ce fichier en tant qu'argument de render_template(). Pour chaque message, nous pouvons accéder au nom d'utilisateur lié à chaque utilisateur avec une syntaxe simple (post.user.username), grâce à la db.relationship ajoutée dans le modèle utilisateur User. Une autre façon d'accéder aux données est d'utiliser les messages comme des dictionnaires et d'afficher leurs propriétés avec une syntaxe d'index (comme post['title']). Portez une attention particulière à la façon dont le lien de mise à jour d'un article prend son ID comme argument et n'apparaît que si son utilisateur correspond à celui qui est actuellement connecté.

Enfin, nous avons la page de mise à jour, qui renvoie simultanément à la vue de suppression. Elle est analogue à la page Créer des messages, mais elle possède une logique particulière pour définir les valeurs par défaut à partir de la base de données. Je laisserai une illustration ci-dessous au cas où vous décideriez de l'explorer.

<!-- templates/blog/udpate.html -->
{% extends 'base.html' %}

{% block header %}
    <h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}

{% block content %}
    <formmethod="post"><labelfor="title">Title</label><inputname="title"id="title"value="{{ request.form['title'] or post['title'] }}"required><labelfor="body">Body</label><textareaname="body"id="body">{{ request.form['body'] or post['body'] }}</textarea><inputtype="submit"value="Save"></form><hr><formaction="{{ url_for('blog.delete', id=post['id']) }}"method="post"><inputtype="submit"class="danger"value="Delete"onclick="return confirm('Are you sure?');"></form>
{% endblock %}

 

Partie 5 - Ajout de fichiers statiques

Nous disposons à présent d'une interface interactive qui reçoit des informations depuis l'utilisateur jusqu'à la base de données et vice-versa, ainsi que d'objets IRIS accessibles en Python à l'aide de SQLAlchemy et de modèles créés avec la syntaxe des classes. Dans la dernière étape, nous avons également réussi à transformer ces objets en contenu d'éléments HTML, ce qui signifie que vous pouvez facilement les personnaliser et interagir avec eux à l'aide de CSS et de JavaScript.

Si l'on revient à la base HTML, on remarque la ligne contenant un lien renvoyant à l'élément stylesheet, juste au début :

Le premier argument de la fonction url_for() à l'intérieur de l'expression href est le dossier à l'intérieur de la racine où vous pouvez trouver les statiques. Le second, bien sûr, est le nom du fichier. A partir de maintenant, le concept est à vous ! Créez le fichier style.css et modifiez-le à votre guise.

C'est fait !

Avec tous les fichiers que nous avons créés, le dossier racine devrait ressembler à l'image ci-dessous :

Maintenant, rendez-vous dans le répertoire racine du terminal et tapez

flask --app flaskr-iris run --debug

Suivez ensuite le lien http://127.0.0.1:5000 pour voir comment tout fonctionne.  

Conclusion

Dans cet article, nous avons vu comment Flask rend l'apport d'informations depuis une instance IRIS vers une application web possible et facile, en la transformant en un CRUD entièrement personnalisable, avec la possibilité de se connecter à d'autres bases de données et à d'autres systèmes. Désormais, vous pouvez facilement créer, afficher, mettre à jour et supprimer des informations d'une base de données IRIS, via une interface qui vous permet non seulement de personnaliser son apparence, ainsi que de traiter les données avec n'importe quel outil Python sans difficulté.

Vous pouvez me contacter pour me faire part de vos doutes ou des idées que vous avez rencontrées après la lecture ! Dans le prochain article, j'aborderai plus en détail la théorie de cette implémentation et présenterai l'application OpenExchange développée ici. En prime, je révèlerai quelques erreurs que j'ai rencontrées et les solutions que j'ai réussi à trouver dans la dernière section.

0
0 408
Article Sylvain Guilbaud · Fév 15, 2024 4m read

Django, un framework Web de haut niveau écrit en Python, est devenu un incontournable pour les développeurs à la recherche d'une solution robuste, efficace et facile à apprendre pour créer des applications Web. Sa popularité vient de sa polyvalence, offrant aux développeurs une boîte à outils efficace pour créer des applications Web. L'intégration de Django avec InterSystems IRIS introduit une synergie dynamique, offrant aux développeurs une solution complète de développement Web et de gestion de bases de données. C'est pourquoi sur le Portail des Idées, @Evgeny Shvarov a suggéré qu'avoir des Exemples pour travailler avec IRIS et Django serait bénéfique. Dans cet article, nous explorerons deux projets créés pour répondre à l'idée publiée —Django-iris par @Dmitry Maslennikov et Iris-size-django par @Heloisa.Paiva.

   

0
0 53
Annonce Sylvain Guilbaud · Jan 4, 2024

Bonjour La Communauté!!

Nous vous apportons les dernières nouvelles de InterSystems Ideas, le portail de suggestions InterSystems. Il s'agit de :

​​​​✓ Nouvelle page dans le portail des idées -> Gagnants de l'Ideathon (InterSystems Ideas Marathon)

✓ Idées mises en œuvre qui seront expliquées dans les annonces futures

✓ Nouvelles idées publiées récemment

0
0 54
Article Lorenzo Scalese · Sept 5, 2023 9m read

Hibernate est le framework le plus populaire pour réaliser des projets ORM (Mapping Objet-Relationnel). Avec Hibernate, un logiciel peut utiliser les principaux SGBD du marché, et même changer de fournisseur de base de données à tout moment, sans impact sur le code source. Cela est possible car Hibernate prend en charge les dialectes. Chaque produit de base de données a un dialecte différent qui peut être assigné dans un fichier de configuration. Ainsi, si un logiciel utilise Oracle et souhaite évoluer vers InterSystems IRIS, il suffit de modifier le fichier de configuration avec les informations relatives à la connexion et au dialecte. Si votre logiciel nécessite une préparation à l'utilisation de la base de données indiquée par votre client, Hibernate est la solution qu'il vous faut.

Est-ce qu'il existe un dialecte d'InterSystems IRIS pour le nouveau Hibernate 6 ?

Actuellement, il n'existe pas de dialecte officiel pour utiliser IRIS avec le nouveau Hibernate 6. Pour résoudre ce problème, Dmitry Maslennikov a proposé une idée dans l'excellent portail d'idées (https://ideas.intersystems.com/ideas/DPI-I-372) et je l'ai mise en œuvre.
Si vous suivez ce tutoriel, vous verrez l'idée et ce nouveau dialecte d'IRIS en action et en fonctionnement.

Ce qu'il vous faut pour réaliser ce tutoriel

Pour réaliser ce tutoriel, vous avez besoin de :

  1. Une instance IRIS en cours d'exécution (si vous n'en avez pas, vous pouvez l'obtenir sur https://openexchange.intersystems.com/package/ObjectScript).
  2. Les outils Spring Tools installés (téléchargez-les sur https://spring.io/tools). Choisissez la version Eclipse pour ce tutoriel.
  3. Java JDK version 17 (téléchargez-le sur https://jdk.java.net/archive/). Choisissez Java 17 pour ce tutoriel.
  4. Tout le code source de ce tutoriel : https://github.com/yurimarx/iris-java-tools/tree/main/springboot-sample.

Les étapes du tutoriel

1.    Ouvrez Spring Tool Suite (STS) et choisissez un chemin d'accès valide à l'espace de travail (n'importe quel dossier) et cliquez sur Launch ("lancement") :

2.    Cliquez sur le lien Créer un nouveau Projet Spring Starter :

3.    Cet assistant créera un nouveau projet Spring. Remplissez les champs avec les valeurs suivantes :
•    Service URL: https://start.spring.io
•    Type : Maven (c'est un gestionnaire de paquets comme NPM, ZPM ou IPM)
•    Emballage : Jar (type d'exécutable pour le projet compilé)
•    Version de Java : 17 ou 20 (pour ce tutoriel, j'ai choisi la version 17)
•    Langage : Java
•    Groupe : com.tutorial (domaine du projet pour Maven)
•    Artifact : iris-tutorial (nom du projet pour Maven)
•    Version : 0.0.1-SNAPSHOT (version du projet pour Maven)
•    Description : Tutoriel IRIS
•    Paquet : com.tutorial.iris (paquet roott pour le projet)

4.    Cliquez sur Next (Suivant). 5.    Choisissez les dépendances suivantes pour votre projet :

6.    Cliquez sur Finish (Terminer) pour créer votre projet.
7.    Ouvrez votre fichier pom.xml et incluez 2 nouvelles dépendances (pour le dialecte IRIS et pour le pilote IRIS JDBC), ainsi qu'un dépôt (ce qui est nécessaire car le pilote IRIS JDBC d'InterSystems n'est pas publié dans un dépôt maven public).

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.1</version><relativePath /><!-- lookup parent from repository --></parent><groupId>com.tutorial</groupId><artifactId>tutorial-dialect</artifactId><version>0.0.1-SNAPSHOT</version><name>tutorial-dialect</name><description>Tutorial for IRIS Hibernate 6 Dialect</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-rest</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-hateoas</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-rest-hal-explorer</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>io.github.yurimarx</groupId><artifactId>hibernateirisdialect</artifactId><version>1.0.0</version></dependency><dependency><groupId>com.intersystems</groupId><artifactId>intersystems-jdbc</artifactId><version>3.7.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>InterSystems IRIS DC Git Repository</id><url>
				https://github.com/intersystems-community/iris-driver-distribution/blob/main/JDBC/JDK18</url><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></project>

8.    Allez dans le fichier application.properties (src > main > java > dossier resources) et définissez les propriétés connection et dialect avec ces valeurs :

spring.datasource.username=_SYSTEMspring.datasource.url=jdbc:IRIS://localhost:1972/USERspring.datasource.password=SYSspring.jpa.properties.hibernate.default_schema=Examplespring.jpa.hibernate.ddl-auto=updatespring.datasource.driver-class-name=com.intersystems.jdbc.IRISDriverspring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=falsespring.jpa.database-platform=io.github.yurimarx.hibernateirisdialect.InterSystemsIRISDialectspring.jpa.show-sql=truespring.jpa.properties.hibernate.format_sql=true

9.    Créez une nouvelle classe persistante (cliquez sur le bouton droit de la souris sur le projet > Nouveau > Classe) :

10.    Remplissez les champs suivants pour créer la classe :
•    Paquet: com.tutorial.iris.model
•   Nom: Product (Produit)

11.    Cliquez sur Finish (Terminer) pour créer la classe. 12.    Développez la classe persistante Produit (classe dont les valeurs sont conservées dans un tableau SQL) à l'aide de ce code source :

package com.tutorial.dialect.model;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;

@Entity@Table(name = "Product")
publicclassProduct{

	
	@Id@GeneratedValue (strategy = GenerationType.IDENTITY)
    private Long id;
	
	private String name;
	
	private String description;
	
	private Double height;
	
	private Double width;
	
	private Double weight;
	
	@Column(name="releasedate")
	@Temporal(TemporalType.DATE)
	@JsonFormat(pattern = "yyyy-MM-dd")
	private Date releaseDate;

	public Long getId(){
		return id;
	}

	publicvoidsetId(Long id){
		this.id = id;
	}

	public String getName(){
		return name;
	}

	publicvoidsetName(String name){
		this.name = name;
	}

	public String getDescription(){
		return description;
	}

	publicvoidsetDescription(String description){
		this.description = description;
	}

	public Double getHeight(){
		return height;
	}

	publicvoidsetHeight(Double height){
		this.height = height;
	}

	public Double getWidth(){
		return width;
	}

	publicvoidsetWidth(Double width){
		this.width = width;
	}

	public Double getWeight(){
		return weight;
	}

	publicvoidsetWeight(Double weight){
		this.weight = weight;
	}

	public Date getReleaseDate(){
		return releaseDate;
	}

	publicvoidsetReleaseDate(Date releaseDate){
		this.releaseDate = releaseDate;
	}

}

13.    Créez un référentiel d'interface pour les opérations CRUD sur la classe Produit (cliquez sur le bouton droit de la souris dans le projet > Nouveau > Interface) :

14.    Saisissez les valeurs de l'interface et cliquez sur Finish (Terminer) :
•   Paquet: com.tutorial.iris.repository
•   Nom: ProductRepository

15.    Cliquez sur Finish (Terminer) pour créer l'interface.
16.    Développez l'interface ProductRepository (un référentiel CRUD implémentant les fonctions de sauvegarde, de suppression, de recherche, de recherche unique et de mise à jour) (src > main > java > com > tutoriel > dialecte > dossier référentiel) à l'aide de ce code source :

package com.tutorial.dialect.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.tutorial.dialect.model.Product;

@RepositorypublicinterfaceProductRepositoryextendsCrudRepository<Product, Long> {

}

17.    Maintenant, en utilisant le navigateur HAL de Springboot, vous pouvez tester les fonctions CRUD dans un écran web.
18.    Assurez-vous d'exécuter une instance IRIS sur localhost, port 1972 avec l'utilisateur _SYSTEM et le mot de passe SYS (ou modifiez l'application.properties pour d'autres valeurs de connexion).
19.    Exécutez l'application (cliquez avec le bouton droit de la souris sur le projet > Exécuter en tant que > Spring boot app).
20.    Sur la console, vous verrez le journal indiquant le démarrage de l'application :

21.    Allez dans votre navigateur et tapez http://localhost:8080. Découvrez le navigateur HAL :

22.    Cliquez sur le bouton "plus" de produits endpoint pour créer un nouveau produit :

23.    Remplissez les valeurs suivantes pour créer un nouveau produit et cliquez sur le bouton "Go" (aller) pour confirmer :

24.    Un nouveau produit a été lancé :

Vérifier la nouvelle ligne sur IRIS (tableau du produit sur l'espace de noms USER)

Testez d'autres opérations, vérifiez la base de données et amusez-vous !

0
0 62
Article Guillaume Rongier · Mars 1, 2023 4m read

Bonjour à tous, 

Nous voici de nouveau réunis. Nouvelle année, nouveau concours, nouveau projet, vieilles raisons.

Triple barre oblique "Triple Slash" est dans la place" !

1999, c'était l'année où j'ai appris à coder, mon premier "IF", mon premier "Bonjour le monde"

Je me souviens encore de mon professeur expliquant à tout le monde dans cette classe le simple "while" et comment nous pouvons déterminer si une condition spécifique a été atteinte @Renato Banzai vous en souvenez vous ? Le professeur Barbosa, quel homme unique.   

Depuis lors, j'aime l'idée de coder, de transformer des idées en projets, en quelque chose d'utile. Mais nous savons tous que pour créer quelque chose, nous devons nous assurer que cela fonctionne ; nous ne nous contentons pas de créer mais nous testons si cela fonctionne et si cela continue à fonctionner si nous ajoutons quelque chose de nouveau.

Et pour être honnête avec chacun d'entre vous, créer des tests, c'est ennuyeux. Du moins pour moi, mais si vous aimez le faire, je n'ai rien contre vous.

En utilisant une mauvaise analogie, je peux dire que la création de méthodes d'essai est comme le nettoyage de votre maison ou le repassage de vos vêtements. C'est ennuyeux, mais c'est nécessaire si vous voulez quelque chose de mieux.  

En gardant cela à l'esprit, pourquoi ne pas créer un moyen meilleur et plus facile pour les tests ? 

Ainsi, inspirés par le style elixir style and in et par cette idée d' InterSystems Ideas (Merci @Evgeny Shvarov)! Nous avons essayé d'améliorer le processus de test et de le transformer à nouveau en une tâche agréable.

Nous simplifions le %UnitTest et pour vous montrer comment utiliser TripleSlash pour créer vos tests unitaires, utilisons un exemple simple.

Disons que vous avez la classe et la méthode suivantes, pour lesquelles vous souhaitez écrire un test unitaire :

Class dc.sample.ObjectScript
{
ClassMethod TheAnswerForEverything() As%Integer
{

   Set a = 42Write"Bonjour le monde !",!

   Write"C'est InterSystems IRIS avec la version ",$zv,!

   Write"L'heure actuelle est : "_$zdt($h,2)

   Return a

}
}

Comme vous pouvez le constater, la méthode TheAnswerForEverything() ne fait que retranscrire le nombre 42. Indiquons donc dans la documentation de la méthode comment TripleSlash doit créer un test unitaire pour cette méthode :

/// Une méthode simple à des fins de tests./// /// /// Ecrivez ##class(dc.sample.ObjectScript).Test()/// 42/// ClassMethod TheAnswerForEverything() As%Integer
{
    ...
}

Les tests unitaires doivent être entourés de la balise <exemple></exemple>. Vous pouvez ajouter tout type de documentation, mais tous les tests doivent être contenus dans une telle balise.

Maintenant, démarrez une session de terminal IRIS, allez dans l'espace de noms IRISAPP créez une instance de la classe Core en passant le nom de la classe (ou son nom de paquet pour toutes ses classes) et exécutez ensuite la méthode Execute() :

USER>ZN"IRISAPP"
IRISAPP>Do##class(iris.tripleSlash.Core).%New("dc.sample.ObjectScript").Execute()

TripleSlash interprétera ceci comme "Étant donné le résultat de la méthode Test(), affirme qu'il est égal à 42". Ainsi, une nouvelle classe sera créée dans le test unitaire :

Class iris.tripleSlash.tst.ObjectScript Extends%UnitTest.TestCase
{

Method TestTheAnswerForEverything()
{
    Do$$$AssertEquals(##class(dc.sample.ObjectScript).TheAnswerForEverything(), 42)
}

}

Ajoutons maintenant une nouvelle méthode pour tester les autres moyens permettant d'indiquer à TripleSlash comment écrire vos tests unitaires.

Class dc.sample.ObjectScript
{

ClassMethod GuessTheNumber(pNumber As%Integer) As%Status
{
    Set st = $$$OKSet theAnswerForEveryThing = 42Try {
        Throw:(pNumber '= theAnswerForEveryThing) ##class(%Exception.StatusException).%New("Sorry, wrong number...")
    } Catch(e) {
        Set st = e.AsStatus()
    }

    Return st

}

}

Comme vous pouvez le constater, la méthode GuessTheNumber() attend un nombre, renvoie $$$OK uniquement si le nombre 42 est transmis ou une erreur pour toute autre valeur. Il faut donc indiquer dans la documentation de la méthode comment TripleSlash doit créer un test unitaire pour cette méthode :

/// Une autre méthode simple à des fins de test./// /// /// Exécutez ##class(dc.sample.ObjectScript).GuessTheNumber(42)/// $$$OK/// Do ##class(dc.sample.ObjectScript).GuessTheNumber(23)/// $$$NotOK/// ClassMethod GuessTheNumber(pNumber As%Integer) As%Status
{
    ...
}

Exécutez à nouveau la méthode Execute() et vous verrez une nouvelle méthode de test dans la classe de test unitaire iris.tripleSlash.tst.ObjectScript:

Class iris.tripleSlash.tst.ObjectScript Extends%UnitTest.TestCase
{

La méthode TestGuessTheNumber()
{

    Do$$$AssertStatusOK(##class(dc.sample.ObjectScript).GuessTheNumber(42))
    Do$$$AssertStatusNotOK(##class(dc.sample.ObjectScript).GuessTheNumber(23))
}

}

Actuellement, les assertions suivantes sont disponibles : $$$AssertStatusOK, $$$AssertStatusNotOK et $$$AssertEquals

TripleSlash nous permet de générer des tests à partir d'exemples de code trouvés dans les descriptions de méthodes. Il vous permet de faire d'une pierre deux coups, en améliorant la documentation de vos classes et en créant l'automatisation des tests. 

Remerciements

Une fois encore, nous vous remercions pour tout le soutien de la communauté dans chacune des applications que nous créons.

Si vous avez trouvé notre application intéressante et avez apporté votre contribution, veuillez voter pour iris-tripleslash et nous aider dans cette aventure !laugh

0
0 71
Annonce Irène Mykhailova · Sept 26, 2022

Bonjour à la communauté !

Nous sommes ravis d'annoncer un nouveau type de concours : le concours de l'idée la plus brillante ! Bienvenue : 

💡 InterSystems Idea-A-Thon 💡

Proposez une idée liée aux produits et services d'InterSystems entre le 26 septembre et le 16 octobre et recevez un prix garanti.

En outre, les employés d'InterSystems aussi bien que les Membres de la Communauté peuvent participer !

>> Envoyez vos idées ici <<

InterSystems Ide-A-Thon est organisé via le portail de feedback sur les idées d'InterSystems où vous pouvez soumettre des demandes d'amélioration de produits et des idées liées à nos services (Documentation, Communauté Dev, Global Masters, etc.) et voter pour celles qui vous plaisent.

Dans le cadre de ce concours, nous invitons chacun à partager ses idées sur ce portail et à voter pour les autres. 

Pour participer au concours, il suffit de soumettre une idée sur le portail des Idées d'InterSystems.

**Les idées acceptées devraient : **

  • être créées pendant la durée du Ide-A-Thon par un utilisateur inscrit sur le portail des Idées d'InterSystems  (vous pouvez vous connecter via InterSystems SSO);
  • ne pas faire partie d'autres idées déjà existantes - seules les nouvelles idées sont autorisées ;
  • ne pas décrire la fonctionnalité existante des produits ou services d'InterSystems ;
  • comporter, outre un titre, une explication détaillée et claire de l'essence des idées ;
  • être affiché en anglais ;
  • être acceptées comme significatives par les experts.
  • Toutes les idées éligibles auront un statut spécial "Idea-A-Thon" sur le portail des idées d'InterSystems et pourront être facilement trouvées à l'adresse Idea-A-Thon Ideas.

    Qui peut participer ?

    Nous invitons TOUT LE MONDE à participer à notre Idea-A-Thon. Tant les employés d'InterSystems que les membres de la communauté sont invités à participer et à soumettre leurs idées.. 

    Les prix

    1. Prix pour les participants – des prix pour tous ceux qui postent une idée éligible :

    🎁 T-Shirt à la marque InterSystems

    2. Prix de l'expert – le gagnant sera sélectionné par l'équipe d'experts d'InterSystems, et recevra :

    🎁 Ensemble d'enceintes Bluetooth® LEGO Star Wars™ R2-D2™ / BOSE Sleepbuds™ II / BOSE SoundLink Flex

    3. Prix de la communauté – l'idée ayant reçu le plus de votes sera récompensée :

    🎁 Ensemble d'enceintes Bluetooth® LEGO Star Wars™ R2-D2™ / BOSE Sleepbuds™ II / BOSE SoundLink Flex

    _Remarque importante : les employés d'InterSystems ne peuvent obtenir que le prix de participation. Les prix d'expert et de la Communauté ne peuvent être remportés que par des membres de la Communauté n'appartenant pas à InterSystems. _

    Période du concours

    📝  Du 26 septembre au 16 octobre : Publication des idées et période de vote.

    Publier une ou plusieurs idées pendant cette période. Les membres inscrits sur le portail des idées peuvent voter pour les idées publiées - ces votes sont comptabilisés pour le prix de la communauté.

    Remarque : Plus tôt vous publiez votre idée où vos idées, plus vous aurez de temps pour recueillir des votes.

    Alors !

    Publiez votre/vos idée(s) sur le portail des idée d'InterSystems InterSystems Ideas, gagnez des prix et restez à l'affût des mises à jour du statut de votre idée !

    Bonne chance !👍


    Remarque importante : Tous les prix sont soumis à la disponibilité et aux options d'expédition. Il se peut que certains articles ne puissent pas être expédiés dans certains pays. Les prix ne peuvent être livrés aux résidents de Crimée, de Russie, de Biélorussie, d'Iran, de Corée du Nord, de Syrie ou d'autres pays frappés d'embargo par les États-Unis. Nous vous informerons si un prix n'est pas disponible et vous proposerons un remplacement éventuel.

    0
    0 68
    Annonce Irène Mykhailova · Août 29, 2022

    Salut la communauté !

    Nous avons toujours eu cette idée concernant l'amélioration du processus de collecte, d'analyse et de réponse aux demandes d'amélioration de produit de nos membres. Nous savions que nous avions besoin d'une bonne expérience utilisateur et de processus internes encore meilleurs pour nous assurer que les meilleures idées étaient recueillies, entendues et prises en compte. Et enfin, cette pensée s'est concrétisée !

    Donc, au cas où vous l'auriez manqué, laissez-moi vous présenter le Portail de commentaires officiel d'InterSystems :

    💡 >> InterSystems Ideas << 💡

    0
    0 60