Manuel du développeur¶
Si vous avez exécuté les instructions d’installation devinstall
, vous avez téléchargé les sources, et elles sont maintenant connectées au dépôt github. Vous êtes donc dans la situation la plus idéale pour commencer à examiner le logiciel, comprendre son fonctionnement, contribuer au développement de ghini.desktop.
Aider le développement de Ghini¶
Si vous souhaitez contribuer à Ghini, vous pouvez le faire de différents manières :
- Utiliser le logiciel, notez les choses que vous n’aimez pas, ouvrir une *issue* pour chacun d’eux. Un développeur va réagir plus tôt que vous pouvez imaginer.
- Si vous avez une idée de ce que vous ne trouvez pas dans le logiciel, mais ne pouvez pas tout à fait il codifier dans des questions distinctes, vous pourriez envisager l’embauche d’un professionnel. Ça c’est la meilleure façon de s’assurer que une chose entre rapidement dans Ghini. Assurez-vous que le développeur ouvre les issue et publie leur contribution sur github.
- Traduisez ! Toutes aide pour les traductions seront les bienvenues, alors s’il vous plaît, traduisez ! Vous pouvez faire cela sans rien n’installer sur votre ordinateur, simplement en utilisant le service de traduction en ligne offert par http://hosted.weblate.org/
- fourchéz le dépôt, choisissez un problème, résolvez-le, ouvrez une demande pull request. Voir le « flux de travail résolution de bogue » ci-dessous.
Si vous n’avez pas encore installé Ghini et veulez avoir un regard sur son histoire de code, vous pouvez ouvrir notre page projet github et voir tout ce qui s’est passé autour de Ghini depuis sa création, vers l’an 2004.
Si vous installez le logiciel en suivant les instructions devinstall
, vous avez l’historique complet dans votre clone git local.
Source du logiciel, versions, branches¶
Si vous voulez une version en particulièr de Ghini, on libère et maintent les versions en forme de branches. Vous devriez faire le git checkout
de la branche correspondant à la version de votre choix.
ligne de production¶
Les noms de branche pour les versions stable (production) Ghini sont de la forme ghini-x.y
(par exemple : ghini-1.0
) ; les noms de branche où sont publiées les versions tests Ghini sont de la forme ghini-x.y-dev
(par exemple : ghini-1. 0-dev
).
Flux de travail de développement¶
Notre flux de travail prévoit de publiquer continuement à la branche de test, puosser souvent à github, laisser travis-ci et coveralls.io vérifier la qualité du contenu de la branche de test, enfin, de temps en temps, de fusionner la branche de test dans la version correspondante.
Lorsque je travaille à des questions plus vastes, qui semblent prendre plus de quelques jours, je pourrais ouvrir une branche liée à la question. Je ne fais cela pas trop souvent.
grandes questions¶
Face à un seul problème plus vaste, créez une étiquette de branche à l’extrémité d’une ligne de développement principal (par exemple : ghini-1.0-dev
) et suivez le workflow décrit à
https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
En bref
git up
git checkout -b issue-xxxx
git push origin issue-xxxx
Travailler sur la nouvelle branche temporaire. Lorsque vous êtes prêt, allez à github, fusionner la branche avec la ligne de développement principale d’où vous avez creé la branche, résoudre les conflits, le cas échéant, puis supprimez la branche temporaire.
Lorsque vous êtes prêt pour la publication, fusionner la ligne de développement dans la ligne correspondante.
Mise à jour de l’ensemble des chaînes traduisibles¶
De temps à autre, pendant le processus de mise à jour le logiciel, vous ajoutez ou modifiez les chaînes dans les sources python, dans la documentation, dans les sources de glade. La plupart de nos chaînes est traduisible et est offerts sur weblate pour contribuer, sous forme de plusieurs fichiers .po
.
Un po
est composé de paires de parties de texte, original et la traduction corréspondante et est spécifique à une langue. Quand un traducteur ajoute une traduction sur weblate, cela arrive à notre dépôt sur github. Quand un programmeur ajoute une chaîne au logiciel, cela arrive sur weblate « à être traduite ».
Weblate héberge le projet Ghini. Dans ce projet, nous avons des composants, dont chacun correspond à une branche d’un dépôt sur github. Chaque composant accepte des traductions en plusieurs langues.
composant | dépôt | branche |
---|---|---|
Desktop 1.0 | ghini.desktop | ghini-1.0-dev |
Desktop 3.1 | ghini.desktop | ghini-3.1-dev |
Documentation 1.0 | ghini.desktop-docs.i18n | ghini-1.0-dev |
Documentation 3.1 | ghini.desktop-docs.i18n | ghini-3.1-dev |
Web 1.2 | ghini.web | master |
ghini.pocket | master | |
Tour | ghini.tour | master |
Pour mettre à jour les fichiers po
relatif à le logiciel, vous définissez le répertoire de travail à la racine de votre copie locale de ghini.desktop et vous exécutez le script :
./scripts/i18n.sh
Pour mettre à jour les fichiers po
relatif à la documentation, vous définissez le répertoire de travail à la racine de votre copie locale de ghini.desktop-docs.i18n, et vous exécutez le script :
./doc/runme.sh
Lorsque vous exécutez une des scripts mentionnés ci-dessus, il est trés probable que vous avez besoin de commettre touts fichier po
du projet. Vous pouvez examiner les modifications avant de les engager dans le référentiel. Ça c’est plus important lorsque vous effectuez une correction marginale à une chaîne, comme enlever une faute de frappe.
Quelque chose qui se passe: se présenter à un conflit. Résoudre les conflits n’est pas difficile une fois que vous savez comment le faire. Tout d’abord, ajouter weblate comme remote:
git remote add weblate-doc10 https://hosted.weblate.org/git/ghini/documentation-10/
Ensuite, assurez-vous que nous sommes dans le dépôt correct, sur la branche correcte, mettre à jour le remote, fusionner avec elle :
git checkout ghini-1.0-dev
git remote update
git merge weblate-doc10/ghini-1.0-dev
Notre documentation sur readthedocs a une version anglaise et plusieurs traductions. Ici on suive la description de la localisation, il n’y a rien que nous avons nous-mêmes inventé ici.
Readthedocs vérifie le reglage Language du projet et appelle sphinx-intl
pour produire la documentation mise en forme dans la langue cible. Avec la configuration par défaut — dont nous n’avons pas modifié — sphinx-intl
attend un fichier po
par le document source, nommé que le document source, et qu’ils sont tous résident dans le répertoire local/$(LANG)/LC_MESSAGES/
.
En revanche, Weblate (et nous-mêmes) préfère un fichier unique po
par langue et on les garde tous dans le même répertoire /po
, tout comme nous le faisons pour le projet logiciel : /po/$(LANG).po
.
Afin de ne pas répéter cette information et de laisser les deux systèmes fonctionnent à leur manière naturelle, nous avons deux ensembles de liens symboliques (git rend hommage à eux).
Pour résumer : lorsqu’un fichier dans la documentation est mise à jour, le script runme.sh
:
- Copie les fichiers
rst
du logiciel à la documentation; - Crée un nouveau fichier de
pot
pour chacun des fichiersrst
; - fusionne tous les fichiers
pot
dans un seuldoc.pot
; - Utilize le nouveau
doc.pot
pour mettre à jour tous les fichiersdoc.po
(un par langue) ; - créer tous les liens symboliques :
- ceux utilisées par
sphinx-intl
en/local/$(LANG)/LC_MESSAGES/
- ceux utilisées par weblate en
/po/$(LANG).po
- ceux utilisées par
Nous pourrions certainement écrire ce qui précède dans un Makefile, ou mieux encore l’inclure dans /doc/Makefile
. Qui sait, peut-être que nous le ferons.
Produire les documents localement¶
The above description is about how we help external sites produce our documentation so that it is online for all to see. But what if you want to have the documentation locally, for example if you want to edit and review before pushing your commits to the cloud?
In order to run sphinx locally, you need to install it within the same virtual environment as ghini, and to install it there, you need to have a sphinx version whose dependencies don not conflict with ghini.desktop’s dependecies.
What we do to keep this in order?
We state this extra dependency in the setup.py
file, as an
extras_require
entry. Create and activate the virtual environment, then
run easy_install ghini.desktop[docs]
. This gets you the sphinx version
as declared in the setup.py
file.
If all you want is the html documentation built locally, run ./setup.py
install docs
. For more options, enter the doc
directory and run
make
.
Which way do the translated strings reach our users?¶
A new translator asked the question, adding: »Is this an automated process from Weblate –> GIT –> Ghini Desktop installed on users computers, or does this require manual steps?
The aswer is that the whole interaction is quite complex, and it depends on the component.
When you install ghini.desktop
or one of the Android apps, the
installation doesn’t assume a specific run-time language: a user can change
their language configuration any time. So what we do is to install the
software in English together with a translation table from English to
whatever else.
At run-time the GUI libraries (Android or GTK) know where to look for the translation strings. These translation tables are generated during the installation or upgrade process, based on the strings you see on Weblate.
The path followed by translations is: You edit strings on Weblate, Weblate
keeps accumulating them until you are done, or you don’t interact with
Weblate for a longer while; Weblate pushes the strings to github, directly
into the development line ghini-1.0-dev
; I see them and I might blindly
trust or prefer to review them, maybe I look them up in wikipedia or get
them translated back to Italian, Spanish or English by some automatic
translation service; sometimes I need to solve conflicts arising because of
changed context, not too often fortunately. As said, this lands in the
development line ghini-1.0-dev
, which I regularly publish to the
production line ghini-1.0
, and this is the moment when the new
translations finally make it to the distributed software.
Users will notice a new version available warning and can decide to ignore it, or to update.
For ghini.pocket
, it is similar, but the notification is handled by the
Android system. We publish on the Play Store, and depending on your
settings, your phone will update the software automatically, or only notify
you, or do nothing. It depends on how you configured automatic updates.
For ghini.web
, we haven’t yet defined how to distribute it.
For ghini’s documentation, it’s completely automatic, and all is handled by readthedocs.org.
Ajout des tests unitaires manquant¶
Si vous êtes intéressé à contribuer au développement de Ghini, un bon moyen de le faire serait de nous aider à trouver et écrire les tests unitaires manquantes.
Une fonction bien testée est un dont le comportement vous ne pouvez pas modifier sans casser au moins un test unitaire.
Nous sommes tous d’accord qu’en théorie la théorie et la pratique correspondant parfaitement et que l’un d’abord écrit des essais, puis implémente la fonction. En pratique, toutefois, pratique ne correspond pas à la théorie, et nous avons été l’écriture de tests après avoir écrit et même les fonctions d’édition.
Cette section décrit le processus d’ajout de tests unitaires pour les bauble.plugins.plants.family.remove_callback
.
POUR TESTER¶
Tout d’abord, ouvrir l’index de rapport de couverture et choisissez un fichier à faible couverture.
Pour cet exemple, exécutez en octobre 2015, nous avons atterri sur bauble.plugins.plants.family
, à 33 %.
https://coveralls.io/builds/3741152/source?filename=bauble%2Fplugins%2Fplants%2Ffamily.py
Les deux premières fonctions nécessitant des tests, edit_callback
et add_genera_callback
, incluent la création et l’activation d’un objet en s’appuyant sur une boîte de dialogue personnalisée. On doit vraiment première écriture des tests unitaires pour cette classe, puis revenir ici.
La fonction suivante, remove_callback
, active également quelques boîtes de dialogue et de message, mais sous forme d’appel d’une fonction demandant l’entrée de l’utilisateur par l’intermédiaire de boîtes oui-non-OK. Ces fonctions nous pouvons facilement remplacer une fonction se moquant du comportement.
How to¶
Ainsi, après avoir décidé ce qu’il faut décrire en test unitaire, nous regardons le code et nous voyons qu’il faut distinguer deux cas :
- le paramètre correct
- la liste des familles comporte aucun élément.
- la liste des familles a plus d’un élément.
- la liste des familles a exactement un élément.
- cascade
- la famille n’a aucun genres
- la famille a un ou plusieurs genres
- confirm
- l’utilisateur confirme la suppression
- l’utilisateur ne confirme pas la suppression
- deleting
- tout se passe bien lors de la suppression de la famille
- Il y a une erreur lors de la suppression de la famille
Je décide que je vais me concentrer uniquement sur le cascade et confirmer aspects. Deux questions binaires : 4 cas.
où mettre les tests¶
Recherchez le script de test et choisissez la classe où mettre les tests unitaires supplémentaires.
https://coveralls.io/builds/3741152/source?filename=bauble%2Fplugins%2Fplants%2Ftest.py#L273
que fair des tests “skipped”
La classe deFamilyTests
contient un test ignoré, sa mise en œuvre va être un peu de travail parce que nous devons réécrire le FamilyEditorPresenter, séparer de la FamilyEditorView et reconsidérer ce qu’il faut faire avec la classe FamilyEditor, qui je pense devrait être supprimé et remplacé par une seule fonction.
les essais d’écriture¶
Après le dernier essai dans la classe de FamilyTests, j’ai ajouter les quatre cas je veux décrire, et je m’assure qu’ils ne parviennent pas, et puisque je suis paresseux, j’écris le code plus compact je sais pour générer une erreur :
def test_remove_callback_no_genera_no_confirm(self):
1/0
def test_remove_callback_no_genera_confirm(self):
1/0
def test_remove_callback_with_genera_no_confirm(self):
1/0
def test_remove_callback_with_genera_confirm(self):
1/0
Un test, étape par étape¶
Commençons par le premier cas de test.
Lorsque vous écrivez des tests, j’ai suivent généralement le modèle :
- T₀ (condition initiale),
- action,
- T₁ (tester le résultat de l’action, compte tenu des conditions initiales)
what’s in a name — unit tests
Il y a une raison pourquoi les tests unitaires sont appelés des tests unitaires. Ne Testez jamais deux actions dans un essai.
Nous allons donc décrire T₀ pour le premier test, une base de données contenant une famille sans genres :
def test_remove_callback_no_genera_no_confirm(self):
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
Nous ne voulons pas la fonction testée pour appeler la fonction interactive utils.yes_no_dialog
, nous voulons des remove_callback
pour invoquer une fonction de remplacement non interactif. Nous accomplissons ceci simplement en faisant le point de utils.yes_no_dialog
à une expression lambda
qui, comme la fonction interactive originale, accepte un seul paramètre et retourne une valeur booléenne. Dans ce cas : False
:
def test_remove_callback_no_genera_no_confirm(self):
# T_0
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
# action
utils.yes_no_dialog = lambda x: False
from bauble.plugins.plants.family import remove_callback
remove_callback(f5)
Ensuite, nous vérifions le résultat.
Eh bien, nous ne voulons juste tester si oui ou non l’objet Arecaceae a été supprimé, nous aussi devrions tester la valeur renvoyée par remove_callback
, et si les yes_no_dialog
et message_details_dialog
ont été invoquées ou non.
Une expression lambda
n’est pas assez pour cela. Nous faisons quelque chose d’apparemment plus complexes, qui rendra la vie beaucoup plus facile.
Nous allons d’abord définir une fonction plutôt générique :
def mockfunc(msg=None, name=None, caller=None, result=None):
caller.invoked.append((name, msg))
return result
et de nous saisir partielle
du module standard functools
, partiellement appliquer la précède mockfunc
, laissant seulement msg
non précisée, et employer cette application partielle, qui est une fonction acceptant un paramètre et retournant une valeur, afin de remplacer les deux fonctions dans utils
. La fonction de test ressemble maintenant à ceci :
def test_remove_callback_no_genera_no_confirm(self):
# T_0
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
self.invoked = []
# action
utils.yes_no_dialog = partial(
mockfunc, name='yes_no_dialog', caller=self, result=False)
utils.message_details_dialog = partial(
mockfunc, name='message_details_dialog', caller=self)
from bauble.plugins.plants.family import remove_callback
result = remove_callback([f5])
self.session.flush()
La section test vérifie que message_details_dialog
ne était pas invoquées, que yes_no_dialog
a été, avec le paramètre message correct, Arecaceae est toujours là :
# effect
self.assertFalse('message_details_dialog' in
[f for (f, m) in self.invoked])
self.assertTrue(('yes_no_dialog', u'Are you sure you want to '
'remove the family <i>Arecaceae</i>?')
in self.invoked)
self.assertEquals(result, None)
q = self.session.query(Family).filter_by(family=u"Arecaceae")
matching = q.all()
self.assertEquals(matching, [f5])
et ainsi de suite…¶
« Il y a deux sortes de gens, ceux qui terminent ce qu’ils entreprennent et ainsi de suite »
Prochaine épreuve est presque le même, avec la différence que le utils.yes_no_dialog
doit retourner vrai
(cela nous atteindre en spécifiant résultat = True
dans l’application partielle du générique mockfunc
).
Avec cette action, la valeur retournée par remove_callback
doit être True
, et il ne devrait y avoir aucune famille Arecaceae dans la base de données plus :
def test_remove_callback_no_genera_confirm(self):
# T_0
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
self.invoked = []
# action
utils.yes_no_dialog = partial(
mockfunc, name='yes_no_dialog', caller=self, result=True)
utils.message_details_dialog = partial(
mockfunc, name='message_details_dialog', caller=self)
from bauble.plugins.plants.family import remove_callback
result = remove_callback([f5])
self.session.flush()
# effect
self.assertFalse('message_details_dialog' in
[f for (f, m) in self.invoked])
self.assertTrue(('yes_no_dialog', u'Are you sure you want to '
'remove the family <i>Arecaceae</i>?')
in self.invoked)
self.assertEquals(result, True)
q = self.session.query(Family).filter_by(family=u"Arecaceae")
matching = q.all()
self.assertEquals(matching, [])
Jetez un oeil à 734f5bb9feffc2f4bd22578fcee1802c8682ca83 de validation pour les deux autres fonctions de test.
Tests de journalisation¶
Nos objets de bauble.test.BaubleTestCase
utilisent des gestionnaires de la classe bauble.test.MockLoggingHandler
. Chaque fois qu’un test unitaire individuel est démarré, la méthode paramètres
créera un nouveau gestionnaire
et associez-le à l’enregistreur de la racine. La méthode du démontage
prend soin de retirer.
Vous pouvez vérifier la présence des messages de journalisation spécifique dans self.handler.messages
. messages
sont un dictionnaire, initialement vide, avec deux niveaux d’indexation. Tout d’abord le nom du logger délivrant la journalisation enregistrent, puis le nom du niveau de l’enregistrement de journalisation. Les clés sont créées lorsque nécessaire. Valeurs tiennent des listes de messages, mis en forme selon quel que soit le formateur, vous associez au gestionnaire, ou par défaut logging.Formatter("%(message)s")
.
Vous pouvez explicitement vider les messages collectés en invoquant des self.handler.clear()
.
Synergie et regroupement¶
De temps en temps vous voulez activer la classe de test que vous travaillez à :
nosetests bauble/plugins/plants/test.py:FamilyTests
Et à la fin du processus vous souhaitez mettre à jour les statistiques :
./scripts/update-coverage.sh
Structure de l’interface utilisateur¶
L’interface utilisateur est construit selon le modèle — vue — présentateur modèle architectural. Pendant une grande partie de l’interface, modèle est un objet de base de données de SQLAlchemy, mais nous avons aussi des éléments de l’interface où il n’y a aucun modèle de base de données correspondante. En général :
Le vue est décrite dans le cadre d’un fichier glade. Cela devrait inclure le signal-rappel et associations ListStore-TreeView. Simplement réutiliser la
GenericEditorView
bauble.editor
, selon la classe de base. Lorsque vous créez votre instance de cette classe générique, passez-lui le glade nom de fichier et le nom du widget racine, puis remettre cette instance à la présentateur constructeur.Dans le fichier de la clairière, dans la section
action-widgets
fermer votre description de l’objet GtkDialog, assurez-vous que tous les élémentsaction-widget
a une valeur valideréponse
. Utilisez les valeurs valides de GtkResponseType, par exemple :- GTK_RESPONSE_OK, -5
- GTK_RESPONSE_CANCEL, -6
- GTK_RESPONSE_YES, -8
- GTK_RESPONSE_NO, -9
Il n’y a aucun moyen facile d’écrire test unitaire une vue sous-classé, donc s’il vous plaît ne pas sous-classer aucune vue, il n’y a vraiment pas besoin de faire ça.
Dans le fichier glade, chaque widget d’entrée devrait définir quel gestionnaire est activé sur lesquels le signal. La classe générique de présentateur propose des rappels génériques qui couvrent les plupart des cas courants.
- GtkEntry (entrée de texte d’une ligne) gérera le signal
changed
, avecon_text_entry_changed
ouon_unique_text_entry_changed
. - GtkTextView : associez-le à un GtkTextBuffer. Pour gérer le signal
changed
sur le GtkTextBuffer, nous devons définir un gestionnaire qui invoque le génériqueon_textbuffer_changed
, le seul rôle de cette fonction est de transmettre notre gestionnaire générique le nom de l’attribut de modèle qui reçoit le changement. Il s’agit d’un workaroud pour un “ bug non résolu dans GTK <http: stackoverflow.com/questions/32106765/= » »>” _.</http:> - GtkComboBox avec textes traduits ne peut pas être facilement manipulé du fichier glade, donc nous n’essayez même pas. Utilisez la méthode de
init_translatable_combo
de la classe générique deGenericEditorView
, mais s’il vous plaît appeler à partir du présentateur.
Le modèle est juste un objet avec des attributs connus. Dans cette interaction, les modèle est juste un conteneur de données passives, il ne fait rien plus que de laisser le présentateur modifiez-le.
Le sous-classé présentateur définit et met en œuvre :
widget_to_field_map
, un dictionnaire associant les noms de widget au nom des attributs de modèle,view_accept_buttons
, la liste des noms de widget qui, si activé par l’utilisateur, cela signifie que la vue doit être fermée,- tous besoin de rappels,
- éventuellement, il joue le modèle rôle, trop.
Le présentateur réactualise la modèle selon les changements dans le vue. Si le modèle correspond à un objet de base de données, le présentateur commet tous mises à jour modèle de la base de données lorsque le vue est fermé avec succès, ou les annule si la vue est annulée. (ce comportement est influencé par le paramètre
do_commit
)Si le modèle est autre chose, puis la présentateur va faire quelque chose d’autre.
Note
Un sage présentateur utilise le vue api pour interroger les valeurs insérées par l’utilisateur ou pour régler par la force des statuts de widget. S’il vous plaît n’apprennent pas de la pratique de nos animateurs de mauvaise conduites, certains dont manipuler directement les champs du
view.widgets
. Ce faisant, ces présentateurs nous empêche d’écrire des tests unitaires.
La classe de base pour le présentateur, GenericEditorPresenter
, définie dans bauble.editor
, implémente de nombreux rappels génériques utiles. Il y a une classe de MockView
, que vous pouvez utiliser lorsque vous écrivez des tests à vos présentations.
Examples¶
Contact
et ContactPresenter
sont mis en œuvre suivant les lignes ci-dessus. L’affichage est défini dans le fichier contact.glade
.
Un bon exemple de modèle de mode/Présentateur (pas de modèle) est donné par le gestionnaire de connexions.
Nous utilisons le même schéma architectural d’interaction non-base de données, en définissant le présentateur aussi comme modèle. Nous faisons cela, par exemple, pour la boîte de dialogue exportation JSON. La commande suivante vous donnera une liste des instanciations de GenericEditorView
:
grep -nHr -e GenericEditorView\( bauble
Ghini qui s’étend avec des Plugins¶
Presque tout sur Ghini est extensible grâce aux plugins. Les plugins peuvent créer des tables, définir des recherches personnalisées, ajouter des éléments de menu, créer des commandes personnalisées et plus encore.
Pour créer un nouveau plugin nous devez étendre la classe bauble.pluginmgr.Plugin
.
Le plugin Tag
est un bon exemple de minime, même si le TagItemGUI
n’entre pas dans le modèle architectural de Model-View-Presenter.
Structure de plugins¶
Ghini est un cadre pour la gestion des collections et est distribué avec un ensemble de plugins des Ghini un gestionnaire de collection botanique. Mais Ghini reste un cadre, et vous pouvez en théorie supprimer tous les plugins, nous distribuons et écrire vos propres, ou écrire vos propres plugins que prolonger ou compléter le comportement actuel de Ghini.
Une fois que vous avez sélectionné et ouvert une connexion de base de données, vous atterrissez dans la fenêtre de recherche. Le moteur de recherche est une interaction entre deux objets : SearchPresenter (SP) et SearchView (SV).
SV est ce que vous voyez, SP détient le statut de programme et gère les requêtes que vous exprimez par le biais de SV. Traitement de ces demandes affectent le contenu de la SV et l’état du programme de SP.
Les résultats de recherche affichés dans la plus grande partie du SV sont rangées, les objets qui sont des instances de classes enregistrés dans un plugin.
Chacune de ces classes doit implémenter une somme de fonctions afin de se comporter correctement dans le cadre de Ghini. Le cadre Ghini réserve un espace aux classes enfichables.
SP connaît de toutes les classes (branchés) enregistrés, ils sont stockés dans un dictionnaire, associant une classe pour sa mise en oeuvre du plugin. SV a un emplacement (un gtk. Box) où vous pouvez ajouter des éléments. À tout moment, au plus qu’un seul élément dans la fente est visible.
Un plugin définit une ou plusieurs classes de plugin. Une classe de plugin joue le rôle d’un présentateur partiel (pP - plugin présentateur) car il implémente les rappels nécessaires par la vue partielle associée dans la fente (pV - plugin vue), et le MVP est complété par le présentateur de parent (SP), agissant à nouveau comme modèle. Pour résumer et remplir :
- SP sert de modèle,
- la vue partielle pV est définie dans un fichier glade.
- les rappels mis en œuvre par pP sont référencées par le fichier glade.
- un menu contextuel pour la ligne SP,
- une propriété children.
Lorsque vous vous inscrivez à une classe de plugin, le SP :
- Ajoute le pV dans la fente et le rend invisible.
- Ajoute une instance de pP dans les classes de plugin enregistrés.
- raconte le pP que la SP est le modèle.
- se connecte tous les rappels de pV à pP.
Lorsqu’un élément dans la pV de déclencher une action en pP, le pP peut transmettre l’action au SP et peut demander des SP qu’il met à jour le modèle et actualise l’affichage.
Lorsque l’utilisateur sélectionne une ligne dans SP, SP cache tout dans la fente pluggable montre seulement le seul pV par rapport au type de la ligne sélectionnée et demande à la pP pour actualiser le pV avec tout ce qui est relatif à la ligne sélectionnée.
En dehors de définir la visibilité du pV divers, rien doit être désactivé ni supprimé : un pV invisible ne peut pas déclencher des événements !
flux de travail résolution de bogue¶
flux de travail de développement normal¶
- Lorsque vous utilisez le logiciel, vous remarquez un problème, ou vous faire une idée de quelque chose qui pourrait être mieux, vous en pensez assez bon pour avoir une idée très claire de ce qu’il est vraiment, que vous avez remarqué. vous ouvrez un problème et décrivez le problème. quelqu’un pourrait réagir avec des notes.
- vous ouvrez le site de questions et choisissez celui que vous souhaitez aborder.
- assignez la question à vous-même, de cette façon que vous êtes informer le monde que vous avez l’intention d’y travailler. quelqu’un pourrait réagir avec des notes.
- éventuellement la fourche le dépôt dans votre compte et de préférence créer une branche, clairement liée à la question.
- écrire des tests unitaires et elles s’engagent à votre succursale (s’il vous plaît ne pas pousser à défaut des tests unitaires à github, exécutez tout d’abord localement :
nosetests
). - écrire des tests unitaires plus (idéalement, les tests constituent une description complète de la fonctionnalité que vous souhaitez ajouter ou corriger).
- Assurez-vous que la fonctionnalité vous ajoutez ou correction est vraiment complètement décrite par les tests unitaires, que vous avez écrit.
- Assurez-vous que vos tests unitaires sont atomiques, c’est-à-dire que vous testiez variations sur changements le long d’une seule variable. ne donnez pas de complexe d’entrée pour les tests unitaires ou les tests qui ne tiennent pas sur un seul écran (25 lignes de code).
- écrivez le code qui rend vos tests à réussir.
- mettre à jour les fichiers i18n (exécuter
./scripts/i18n.sh
). - la mesure du possible, traduire les nouvelles chaînes que vous mettez dans les fichiers de code ou de la clairière.
- Lorsque vous changez de chaînes, veuillez vous assurer que les anciennes traductions obtenir réutilisées.
- Validez vos modifications.
- pousser sur github.
- Ouvrez une demande de tirer.
publication à la production¶
please use the publish.sh
script, in the scritps
directory. This
one takes care of every single step, and produces recognizable commit
comments, it publishes the release on pypi, and in perspective it will
contain all steps for producing a deb
file, and a windows executable.
you can also do this by hand:
- Ouvrez la traction demande page utilisant comme base une ligne de production
ghini-x.y
, par rapport àghini-x.y-dev
. - Assurez-vous qu’un commit
bump
est inclus dans les différences. - Il devrait être possible de fusionner automatiquement les branches.
- Créez la nouvelle demande de tirer, appelez-le « publier à la chaîne de production ».
- éventuellement, vous devez attendre pour travis-ci effectuer les contrôles.
- fusionner les modifications.
don’t forget to tell the world about the new release: on facebook, the google group, in any relevant linkedin group, and on our web page.
your own fork¶
If you want to keep your own fork of the project, keep in mind this is full force work in progress, so staying up to date will require some effort from your side.
The best way to keep your own fork is to focus on some specific issue, work relatively quickly, often open pull requests for your work, make sure that you get it accepted. Just follow Ghini’s coding style, write unit tests, concise and abundant, and there should be no problem in having your work included in Ghini’s upstream.
If your fork got out of sync with Ghini’s upstream: read, understand, follow the github guides configuring a remote for a fork and syncing a fork.
étape de clôture¶
- revue de ce flux de travail. Considérez ceci comme une ligne directrice, à vous-même et à vos collègues. s’il vous plaît aider à faire mieux et correspondant à la pratique.
Distributing ghini.desktop¶
Python Package Index - PyPI¶
This is not much mentioned, but we keep ghini.desktop on the Python Package Index, so you could install it by no more than:
pip install ghini.desktop
There are a couple packages that can’t be installed with pip
, but
otherwise that’s really all you need to type, and it’s platform independent.
Publishing on PyPI is a standard setup
command:
python setup.py sdist --formats zip upload -r pypi
Windows¶
For building a Windows installer or executable you need a running Windows system. The methods described here has been used successfully on Windows 7, 8 and 10. Windows Vista should also work but has not been tested.
If you are on GNU/Linux, or on OSX, you are not interested in the remainder of this section. None of Ghini’s contributors knows how to produce a Windows installer without having a Windows system.
The goal of the present instructions is to help you produce a Windows installer, that is a single executable that you can run on any Windows workstation and that will install a specific version of ghini.desktop. This is achieved with the NSIS script-driven installer authoring tool.
As a side product of the installer production, you will have a massive but relocatable directory, which you can copy to a USB drive and which will let you use the software without needing an installation.
The files and directories relevant to this section:
scripts/build-win.bat
— the single batch script to run.setup.py
— implements the NSIS and py2exe commands.scripts/build-multiuser.nsi
— the nsis script, used by the above.nsis/
— contains redistributable NSIS files, put here for conveniency.ghini-runtime/
— built bypy2exe
, used bynsis
.dist/
— receives the executable installation file.
Most steps are automated in the build-win.bat
script. Installation of a few
tools needs to be done manually:
Download and install Git, Python 2.7 and PyGTK.
This is outlined in the
devinstall
-based installation instructions.Download and install NSIS v3.
A reboot is recommended.
Clone the ghini.desktop repository.
Use your own fork if you plan contributing patches, or the organization’s repository
https://github.com/Ghini/ghini.desktop.git
if you only wish to follow development.Clone the repository from GitHub to wherever you want to keep it, and checkout a branch. Replace
<path-to-keep-ghini>
with the path of your choice, e.g.Local\github\Ghini\
. Production branchghini-1.0
is recommended as used in the example.To do this, open a command prompt and type these commands:
cd <path-to-keep-ghini> git clone <ghini.desktop repository URL> cd ghini.desktop git checkout ghini-1.0
The result of the above is a complete development environment, on Windows, with NSIS. Use it to follow development, or to propose your pull requests, and to build Windows installers.
All subsequent steps are automated in the scripts\build_win.bat
script. Run
it, and after a couple of minutes you should have a new
dist\ghini.desktop-<version>-setup.exe
file, and a working, complete
relocatable directory named ghini-runtime
.
Read the rest if you need details about the way the script works.
The build_win.bat
script
A batch file is available that can complete the last few steps. To use it use this command:
scripts\build_win.bat
build_win.bat
accepts 2 arguments:
/e
— executable only.Produce an executable only, skipping the extra step of building an installer, and will copy
win_gtk.bat
into place.
venv_path
— A path to the location for the virtual environment to use.Defaults to
"%HOMEDRIVE%%HOMEPATH%"\.virtualenvs\%CHECKOUT%-exe
, whereCHECKOUT
corresponds to the name of the branch you checked out.If you want to produce an executable only and use a virtual environment in a folder beside where you have ghini.desktop, you could execute
scripts\build_win.bat /e ..\ghi2exe
py2exe will not work with eggs
Building a Windows executable with py2exe requires packages not be installed as eggs. There are several methods to accomplish this, including:
Install using
pip
. The easiest method is to install into a virtual environment that doesn’t currently have any modules installed as eggs usingpip install .
as described below. If you do wish to install over the top of an install with eggs (e.g. the environment created bydevinstall.bat
) you can trypip install -I .
but your mileage may vary.By adding:
[easy_install] zip_ok = Falseto setup.cfg (or similarly
zip_safe = False
tosetuptools.setup()
insetup.py
) you can usepython setup.py install
but you will need to download and install Microsoft Visual C++ Compiler for Python 2.7 to get any of the C extensions and will need a fresh virtual environment with no dependent packages installed as eggs.The included
build-win
script uses thepip
method.
installing virtualenv and working with environments
Install virtualenv, create a virtual environment and activate it.
With only Python 2.7 on your system (where
<path-to-venv>
is the path to where you wish to keep the virtual environment) use:pip install virtualenv virtualenv --system-site-packages <path-to-venv> call <path-to-venv>\Scripts\activate.batOn systems where Python 3 is also installed you may need to either call pip and virtualenv with absolute paths, e.g.
C:\Python27\Scripts\pip
or use the Python launcher e.g.py -2.7 -m pip
(runpython --version
first to check. If you get anything other than version 2.7 you’ll need to use one of these methods.)
Populate the virtual environment
Install dependencies and ghini.desktop into the virtual environment:
pip install psycopg2 Pygments py2exe_py2 pip install .
Compile for Windows
Build the executable:
python setup.py py2exeThe
ghini-runtime
folder will now contain a full working copy of the software in a frozen, self contained state.This folder is what is packaged by NSIS.
This same folder can also be transferred however you like and will work in place. (e.g. placed on a USB flash drive for demonstration purposes or copied manually to
C:\Program Files
with a shortcut created on the desktop). To start ghini.desktop double clickghini.exe
in explorer (or create a shortcut to it).
Fixing paths to GTK components.
If you run the relocatable compiled program, unpackaged, you might occasionally have trouble with the GUI not displaying correctly.
Should this happen, you need to set up paths to the GTK components correctly. You can do this by running the
win_gtk.bat
, from theghini-runtime
folder.You will only need to run this once each time the location of the folder changes. Thereafter
ghini.exe
will run as expected.
Finally, invoke NSIS
Build the installer:
python setup.py nsisThis should leave a file named
ghini.desktop-<version>-setup.exe
in thedist
folder. This is your Windows installer.
about the installer
Capable of single user or global installs.
At this point in time ghini.desktop installed this way will not check or or notify you of any updated version. You will need to check yourself.
Capable of downloading and installing optional extra components:
- Apache FOP - If you want to use xslt report templates install FOP. FOP requires Java Runtime. If you do not currently have it installed the installer will let you know and offer to open the Oracle web site for you to download and install it from.
- MS Visual C runtime - You most likely don’t need this but if you have any trouble getting ghini.desktop to run try installing the MS Visual C runtime (e.g. rerun the installer and select this component only).
Can be run silently from the commandline (e.g. for remote deployment) with the following arguments:
/S
for silent;
/AllUser
(when run as administrator) or/CurrentUser
/C=[gFC]
to specify components where:
g
= Deselect the main ghini.desktop component (useful for adding optional component after an initial install)
F
= select Apache FOP
C
= select MS Visual C runtime
Debian¶
Between 2009 and 2010 someone packaged the then already obsolete Bauble 0.9.7 for Debian, and the package was included in Ubuntu. That version is still being distributed, regardless being it impossible to install.
Only recently has Mario Frasca produced a new bauble debian package, for the
latest bauble.classic version 1.0.56, and proposed for inclusion in Debian.
View it on mentors. This
version depends on fibra
, a package that was never added to Debian and
which Mario also has packaged and proposed for inclusion in Debian. Mario has been trying to
activate some Debian Developer, to take action. There’s not much more we can
do, other than wait for a sponsor, and hoping the package will eventually get
all the way to Ubuntu.
Once we get in contact with a Debian Sponsor who will review what we
publish on mentors, then we
will be definitely expected to keep updating the debian package for
ghini.desktop
and fibra
.
I am not going to explain in a few words the content of several books on
Debian packaging. Please choose your sources. For a very compact idea of
what you’re expected to do, have a look at scripts/pubish.sh
.