Exercice sur CoreData

Philippe49Philippe49 Membre
mai 2009 modifié dans API AppKit #1
Pour se familiariser avec CoreData, voici une suite à  l'exemple de Hillegass (chapitre 30) sur les Departments et Employees, en y ajoutant le troisième étage de la Company. L'objectif serait de pouvoir faire la gestion complète de cette population y compris les mouvements d'employés, éventuellement de départements complets, d'absorption d'une companie par une autre ...
Le second objectif serait l'option de stocker sous la forme d'une plist, ou d'un fichier formaté à  ma façon.

1) Double Relationship
Le model initial est représenté ci-dessous. Pensez-vous nécessaire de faire la Relationship entre l'entity Employee et l'entity Company ?

Réponses

  • psychoh13psychoh13 Mothership Developer Membre
    02:34 modifié #2
    Non seulement c'est inutile mais en plus c'est pas logique... Et ça coûtera plus que ça n'aidera, quand bien même il peut y avoir des employés qui ne sont dans aucun département il vaut mieux créer un département "pas de département" plutôt que de relier directement les employés à  la compagnie.

    Si un employé est forcément dans un département et qu'un département est forcément dans une compagnie, alors, par transitivité, un employé est forcément dans une compagnie, si tu veux connaà®tre tous les employés d'un compagnie il suffit de récupérer tous les employés de tous les départements.
  • Philippe49Philippe49 Membre
    02:34 modifié #3
    Oui plus coûteux en stockage, et on peut utiliser les KeyPath ...
  • AliGatorAliGator Membre, Modérateur
    02:34 modifié #4
    +1

    Et risque de doublons non synchronisés en plus (par exemple tu risques de pouvoir indiquer qu'un employé est dans une companie par transitivité (car il est dans un département qui est lui-même dans une companie), et oublier de mettre à  jour le lien direct employé/companie... qui en plus de faire doublon et est inutile et risque donc de fausser les choses plus que de les améliorer.

    Conclusion, non seulement ce n'est pas forcément une bonne idée, mais en plus c'en est plutôt une mauvaise  ;D
  • Philippe49Philippe49 Membre
    mai 2009 modifié #5
    dans 1242303802:

    Et risque de doublons non synchronisés en plus

    Oui c'est un peu pourquoi je me posais la question, je veux utiliser les bindings, et je me demandais si c'est plus difficile à  synchroniser en triplette qu'en doublette ..


    Bon , les Companies tout d'abord

    Interface ci-dessous , réglages comme Hillegass. J'ai mis comme d'hab un NSArrayController avec le ManagedObjectContext bindé/synchronisé au managedObjectContext du document. J'ai bien réglé à  Entity et Company le panel Attribute.

    Surprise : si on ne définit pas le fichier pour l'entity Company, on a le droit à  "Failed to create new object" lorsqu'on cherche à  ajouter une compagnie. On peut penser que le type de collection utilisé pour  Departments est imprécis, et que donner l'interface permet de le préciser.


     
    <br />#import &lt;CoreData/CoreData.h&gt;<br /><br />@class Department;<br /><br />@interface Company :&nbsp; NSManagedObject&nbsp; <br />{<br />}<br /><br />@property (nonatomic, retain) NSString * companyName;<br />@property (nonatomic, retain) NSMutableSet* departments;<br /><br />@end<br /><br /><br />@interface Company (CoreDataGeneratedAccessors)<br />- (void)addDepartmentsObject:(Department *)value;<br />- (void)removeDepartmentsObject:(Department *)value;<br />//- (void)addDepartments:(NSSet *)value;<br />//- (void)removeDepartments:(NSSet *)value;<br /><br />@end
    

  • Philippe49Philippe49 Membre
    mai 2009 modifié #6

    Les Departments

    On rajoute  un NSArrayController  Departments dans les fichiers du xib, que l'on configure ainsi :
    Bindings
        MOC : File's Owner  managedObjectContext
        content set : Company  selection departments
    Attributes :
        Entity Department ,
        Prepares Content coché

    On rajoute le fichier Departments.h selon la procédure décrite dans Hille gass (new file > choisir Managed Object Class)


  • Philippe49Philippe49 Membre
    mai 2009 modifié #7
    Même chose pour le troisième étage. On peut enregistrer,
  • Philippe49Philippe49 Membre
    mai 2009 modifié #8

    Interface pour l'ajout d'un employé


    1) Ouverture d'un document fixe au lancement de l'application
    C'est pénible de refaire open à  chaque fois, et d'avoir un document vide d'ouvert. On définit un delegate de l'application, dans lequel on rédéfinit la méthode applicationShouldOpenUntitledFile: pour répondre NO !
    On utilise [NSDocumentController sharedDocumentController] pour ouvrir un document que l'on a déjà  enregistré et faire nos tests ainsi.

    2) Interface de création d'un nouvel employé
    Elle est présentée ci-dessous. Pour les bindings entre les deux menus, on met en place deux Array Controller, un pour chaque pop up.
    Indication, parce que ce n'est pas évident  B) , pour maintenir la sélection entre le popup Company et l'array controller associé, on peut utiliser le binding "selectedIndex". Faut le savoir !  B) >:D

    Rq: Je n'ai pas synchronisé les table view et les pop-up ( je m'aperçois que par hasard c'est le cas sur l'image ci-jointe)
  • Philippe49Philippe49 Membre
    02:34 modifié #9

    Codage de l'ajout d'un employé


    On connecte le bouton, et on code

    NSManagedObjectContext * context=[self managedObjectContext];<br />	Employee * newEmployee=[NSEntityDescription insertNewObjectForEntityForName:@&quot;Employee&quot; inManagedObjectContext:context];<br />
    


    avec quelques broutilles de complément que l'on trouve naturellement
  • Philippe49Philippe49 Membre
    mai 2009 modifié #10

    Refactoring du xib


    Avant de compléter l'interface, il est temps de clarifier le xib, en introduisant des xib annexes et leurs view controllers. Cette version ne fera que cela, pas de fonctionnalité nouvelle, simplement une réorganisation.

    3 xib : MyDocument.xib , Store.xib  et AddEmployee.xib

    MyDocument.h se décharge d'un certain nombre de tâches sur les deux contrôleurs :
    <br />#import &lt;Cocoa/Cocoa.h&gt;<br />@class SpecialViewController,AddEmployeeViewController;<br />@interface MyDocument : NSPersistentDocument {<br />	SpecialViewController * storeViewController;<br />	AddEmployeeViewController * addEmployeeViewController;<br />	IBOutlet NSView * storeView;<br />	IBOutlet NSView * detailView;<br />}<br />@end
    


    Dans windowControllerDidLoadNib: , le document crée par le code les deux contrôleurs et ajoute leurs vues respectivement dans storeView et dans detailView.

    Les classes SpecialViewController et AddEmployeeViewController
    <br />@interface SpecialViewController : NSViewController {<br />	NSManagedObjectContext * managedObjectContext; <br />}<br />@property (nonatomic,retain)&nbsp; NSManagedObjectContext *&nbsp; managedObjectContext;<br />@end<br /><br />//-------------------------------------------------------------------------------<br /><br />#import &lt;Cocoa/Cocoa.h&gt;<br />#import &quot;SpecialViewController.h&quot;<br /><br />@interface AddEmployeeViewController : SpecialViewController {<br />	IBOutlet NSTextField * nameTextField;<br />	IBOutlet NSArrayController * departmentArrayController;<br />}<br />-(IBAction) newEmployee:(id) sender;<br />@end<br />
    






  • Philippe49Philippe49 Membre
    juin 2009 modifié #11

    Mise en place d'un attribute transformable : une NSColor


    1) Transformer la zone DetailView en une detailTabView
    On recode la méthode windowControllerDidLoadNib: en installant la vue contrôlée par un addEmployeeViewController dans le premier onglet et celle d'une DepartmentColorViewController dans le second onglet. Par souci d'uniformisation on définit une classe StoreViewController héritant de SpecialViewController qui pourtant l'instant ne définit rien.
    Dans le DepartmentColor.xib on aura besoin des mêmes array controller que dans le addEmployee.xib, on peut faire un dupliquer dans le système de fichier, suivi d'un add dans les resources du projet.


    - (void)windowControllerDidLoadNib:(NSWindowController *)windowController <br />{<br />	[super windowControllerDidLoadNib:windowController];<br />	<br />	// display the store<br />	storeViewController=[[StoreViewController alloc] initWithNibName:@&quot;Store&quot; bundle:nil];<br />	storeViewController.managedObjectContext=[self managedObjectContext];<br />	[storeView addSubview:storeViewController.view];<br />	<br />	// initializing  the tab view	<br />	addEmployeeViewController=[[AddEmployeeViewController alloc] initWithNibName:@&quot;AddEmployee&quot; bundle:nil];<br />	addEmployeeViewController.managedObjectContext=[self managedObjectContext];<br />	[[detailTabView tabViewItemAtIndex:0] setView:addEmployeeViewController.view];<br />	<br />	departmentColorViewController=[[SpecialViewController alloc] initWithNibName:@&quot;DepartmentColor&quot; bundle:nil];<br />	departmentColorViewController.managedObjectContext=[self managedObjectContext];<br />	[[detailTabView tabViewItemAtIndex:1] setView:departmentColorViewController.view];<br />}
    


    2) La couleur dans le model
    Ajouter un attribut textColor dans l'entity Department, en choisissant transformable ou Binary Data dans le menu déroulant. Changer le .h et le .m en conséquence.

    3) Les bindings pour la couleur
    Synchroniser la value du color Well.
    On peut essayer ici, on a bien la synchronisation du color well et du popup department. On a perdu momentanément l'ouverture du fichier de test car il n'est plus lisible après le changement du model, cela reviendra au prochain enregistrement.

    4) Les bindings pour la couleur dans la table view
    Synchroniser le textColor dans la table column , et cocher rich text dans le text field cell de cette dernière.

    Coder un NSValueTransformer pour que ce soit une NSAttributedString qui soit présenté par la table view, et non une simple NSString. On en profite pour choisir une police lisible avec la couleur.
    Ajouter ce ValueTransformer dans le binding value de la table column.


    #import &lt;Cocoa/Cocoa.h&gt;<br />@interface String2AttributedValueTransformer : NSValueTransformer {<br />}<br />@end<br /><br />#import &quot;String2AttributedValueTransformer.h&quot;<br />@implementation String2AttributedValueTransformer<br />+ (Class)transformedValueClass { <br />	return [NSAttributedString class]; <br />}<br />+ (BOOL)allowsReverseTransformation { <br />	return YES; <br />}<br />- (id) transformedValue:(id)value {	<br />	NSFont * font=[NSFont  boldSystemFontOfSize:14.0];<br />	NSDictionary * att=[NSDictionary dictionaryWithObjectsAndKeys:font,NSFontAttributeName,nil];<br />	return [[[NSAttributedString alloc] initWithString:value  attributes:att] autorelease];<br />}<br />- (id)reverseTransformedValue:(id)value{<br />	return [NSString stringWithString:[value string]];	<br />}<br />@end
    



    5) Le titre de la NSBox
    L'occasion d'utiliser un Display Pattern : Il faut en utiliser 2 et la syntaxe  %{title1}

    On remarquera la possibilité de se déplacer entre les table view et à  l'intérieur des table view à  l'aide de la tbulation et des touches fléchées.
  • Philippe49Philippe49 Membre
    mai 2009 modifié #12
    Question : Comment synchroniser le backgroundColor de la table view des employés à  la couleur du department ? J'ai essayé par le code
    <br />- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundleOrNil {<br />	NSLog(@&quot;init StoreVC&quot;);<br />	self=[super initWithNibName:nibName bundle:nibBundleOrNil];<br />	NSDictionary * optionsDictionary=[NSDictionary dictionaryWithObject:NSUnarchiveFromDataTransformerName forKey:NSValueTransformerNameBindingOption];<br />	[employeeTableView bind:@&quot;backgroundColor&quot;  toObject:departmentArrayController withKeyPath:@&quot;selection.textColor&quot; options:optionsDictionary];<br />	return self;<br />}
    


    marche pas  :'(

    [EDIT] Sur un essai simple le backgroundColor d'une table view se binde correctement ...

    Bon je laisse tomber, si l'un d'entre vous a une idée ...
  • Philippe49Philippe49 Membre
    mai 2009 modifié #13

    Un autre attribut persistent d'un type non standard


    Pour confirmer le principe, créer un attribut logo pour les companies, et le récupérer par drag and drop dans une image view. (c'est tout simple, ...)
  • uocramuocram Membre
    02:34 modifié #14
    Une solution au chalenge :

    Que du clic (surtout dans IB) sauf 2 lignes de code :

    - (void)awakeFromNib<br />{<br />	NSDictionary	*optionsDictionary=[NSDictionary dictionaryWithObject:NSUnarchiveFromDataTransformerName forKey:NSValueTransformerNameBindingOption];<br />	[employeesTableView bind:@&quot;backgroundColor&quot; toObject:departmentsSet withKeyPath:@&quot;selection.textColor&quot; options:optionsDictionary];<br />}<br />
    


    J'ai essayé d'appliquer un maximum d'attributs juste en bindant dans IB.
    Le résultat est un peu laid, mais ça marche (en utilisant ton code pour le binding, enfin il me semble).
    Bon, on va aller se boire un coupe de "vieille vigne" pour se refaire la santé.  :p
  • uocramuocram Membre
    02:34 modifié #15
    En buvant, une question m'est venue :
    le Model Key Path @backgroundColor doit être exposé par Apple en "underGround" pour que cela fonctionne, non? ou aurais-je trop bu?  :(renaud):
  • Philippe49Philippe49 Membre
    02:34 modifié #16
    dans 1242762852:

    En buvant, une question m'est venue :

    Ce sont les moments les plus productifs ...

    dans 1242762852:

    le Model Key Path @backgroundColor doit être exposé par Apple en "underGround" pour que cela fonctionne, non? ou aurais-je trop bu?  :(renaud):

    En fait, je n'ai pas encore rencontré un exemple où le manque d'un exposeBinding: empêchait le binding de fonctionner...
  • Philippe49Philippe49 Membre
    02:34 modifié #17
    dans 1242762439:

    Une solution au chalenge :

    - (void)awakeFromNib

    Ah oui, j'ai fait une erreur classique de le mettre dans initWithNibName:
    Merci du coup de pouce 

    dans 1242762439:

    Bon, on va aller se boire un coupe de "vieille vigne" pour se refaire la santé.  :p

    Ben voyons  :p
  • CéroceCéroce Membre, Modérateur
    02:34 modifié #18
    dans 1242765811:

    dans 1242762852:

    le Model Key Path @backgroundColor doit être exposé par Apple en "underGround" pour que cela fonctionne, non? ou aurais-je trop bu?  :(renaud):

    En fait, je n'ai pas encore rencontré un exemple où le manque d'un exposeBinding: empêchait le binding de fonctionner...


    Implémenter exposeBinding: est nécessaire pour que le binding apparaisse sous IB quand on fait ses propres IBPlugIns, mais on peut tout à  fait binder manuellement.

    Philippe, as-tu essayé plus simplement:
    <br />[employeeTableView bind:@&quot;backgroundColor&quot;&nbsp; toObject:departmentArrayController withKeyPath:@&quot;selection.textColor&quot; options:nil];<br />
    


    Je ne vois pas bien quel NSData tu aurais à  désarchiver ici (mais il y a peut-être un aspect qui m'échappe).
  • Philippe49Philippe49 Membre
    mai 2009 modifié #19
    dans 1242800073:

    dans 1242765811:

    dans 1242762852:

    le Model Key Path @backgroundColor doit être exposé par Apple en "underGround" pour que cela fonctionne, non? ou aurais-je trop bu?  :(renaud):

    En fait, je n'ai pas encore rencontré un exemple où le manque d'un exposeBinding: empêchait le binding de fonctionner...


    Implémenter exposeBinding: est nécessaire pour que le binding apparaisse sous IB quand on fait ses propres IBPlugIns, mais on peut tout à  fait binder manuellement.

    Ok
    Il reste curieux que la doc demande d'exposer les bindings dans les autres cas. Peut-être pour préserver une évolution future ? pour l'analyse en vue des warnings ...

    dans 1242800073:

    Philippe, as-tu essayé plus simplement:
    <br />[employeeTableView bind:@&quot;backgroundColor&quot;  toObject:departmentArrayController withKeyPath:@&quot;selection.textColor&quot; options:nil];<br />
    


    C'est ce que j'ai fait. Je l'avais simplement mal positionné dans le code. (Il faut désarchiver le data pour obtenir la NSColor)
  • Philippe49Philippe49 Membre
    juin 2009 modifié #20

    Challenge : Introduire une information sur l'un des attributs


    Il s'agit de mettre sur l'onglet logo la taille de l'image/logo attribuée à  une entreprise.
    Bon on peut rajouter encore un attribut, mais c'est plutôt lourd. Comment faire sans changer quoi que ce soit au ManagedObject Company ?

  • Philippe49Philippe49 Membre
    mai 2009 modifié #21

    Valider un changement d'attribut


    Lors de l'attribution d'un logo à  une compagnie, on prévoit de refuser si la taille du logo est trop importante. Pour cela, on code dans Company.h la méthode :

    -(BOOL) validateLogo:(id *)ioValue error:(NSError **)outError ;

    et on coche l'option Validates Immediatly dans IB.
    Si cette option n'est pas cochée, la validation n'aura lieu que lors de l'enregistrement.




    La doc
  • uocramuocram Membre
    02:34 modifié #22
    En utilisant un attribut "Transient" logoSize (NSString) déclaré dans le model (non déclaré dans le model : fonctionnement idem et undo-redo efficient...) :
    Dans Company.h :
    @property (nonatomic, readonly) NSString	*logoSize;
    

    Dans Company.m :
    + (NSSet *)keyPathsForValuesAffectingLogoSize {<br />&nbsp; &nbsp; return [NSSet setWithObjects:@&quot;logo&quot;, nil];<br />}<br /><br />- (NSString *)logoSize<br />{<br />	NSData	*data = [NSData dataWithData:self.logo];<br />	NSImage	*logoImage = [[NSImage alloc] initWithData:data];<br />	NSSize	imageSize = [logoImage size];<br />	[logoImage release];<br />	return [NSString stringWithFormat:@&quot;%.0f x %.0f&quot;, imageSize.width, imageSize.height];<br />}
    


    Ce qui donne :

  • Philippe49Philippe49 Membre
    mai 2009 modifié #23
    Pour ma part, j'ai mis logoSize dans une catégorie de NSData et un binding "logo.logoSize" . A l'expérience un changement de logo a l'air de marcher , mais n'ayant qu'une seule possibilité d'entrer le logo (via l'image view), je n'ai pas confirmation expérimentale de la robustesse du procédé.


  • Philippe49Philippe49 Membre
    02:34 modifié #24

    Utiliser des setter/getter personnalisés , FetchRequest


    Lorsque l'on rentre le nom d'un nouvel employé, une sheet indique si existe déjà  dans le store un/plusieurs employés dont le nom débute avec le même préfixe, en en faisant la liste. 

    1) Vérification du bon fonctionnement des setter/getter
    On met en place les accesseurs à  l'attribut dans l'entité Employee.
    La la doc doc[/url] indique précisément comment faire ses setter/getter
    personnalisés. Il faut déclarer les méthodes primitives dans le .h et les méthodes setName: et  name dans le .m
    Vérifier avec un NSLog, ou déjà  un NSRunAlertpanel.

    2) Dans le setter
    La solution donnée utilise une requête sur le modèle de la doc
    Cette requête est mise ici dans le setter.
  • Philippe49Philippe49 Membre
    mai 2009 modifié #25

    Core Data avec un IKImageBrowserView


    1) Les difficultés
    Ce n'est pas facile, c'est même acrobatique.
    • Le protocole informel qui gère l'affichage dans le browser est consultable dans la doc sous le nom de IKImageBrowserItem : attention IKImageBrowserItem n'est pas une classe mais un protocole  qui est peut-être implémenté par n'importe quel classe, sans en faire la déclaration puisque le protocole est informel. Cela amènera à  renommer l'attribut logo en attribut primitiveImageRepresentation (1)
    • Il faut sous-classer l'array controller des entités Company afin de se placer en observateur, et faire des reloadData pour rafraà®chir le browser. Le modèle à  suivre se trouve dans l'exemple fourni par Apple IKImageBrowserViewWithCoreData (2)
    • Mais cela ne suffit pas encore, pour que le reloadData soit effectif, il faut absolument incrémenter une property imageVersion .. fat le savoir



    2) La réalisation
    • Modifier MyDocument.xcdatamodel, en supprimant logo, en ajoutant imageRepresentation de type Binary data , Optional. L'interface de Company devient :


    @interface Company :&nbsp; NSManagedObject&nbsp; <br />{<br />&nbsp; &nbsp; NSUInteger imageVersion;<br />}<br />@property&nbsp; &nbsp;  NSUInteger imageVersion;<br />@property (nonatomic, retain) NSString * companyName;<br />@property (nonatomic, retain) NSData * primitiveImageRepresentation;<br />@property (nonatomic, retain) NSMutableSet* departments;<br />@end
    


    • Inclure Quartz.framework.
    • Ajouter un contrôleur TakeoverController héritant de SpecialViewController, avec cette interface


    <br />@interface TakeoverViewController : SpecialViewController {<br />	CGFloat&nbsp; zoomValue;<br />	CGFloat fontSize;<br />	IBOutlet IKImageBrowserView * browser;<br />}<br />@property CGFloat zoomValue;<br />@property&nbsp; CGFloat fontSize;<br />@end<br />
    

    • Créer un xib Takeover.xib conforme à  la vue ci-dessus, et contenant un ImageBrowserArrayController , d'entités de type Company, de managedObject synchronisé avec celui du File's Owner.
    • Synchroniser le Content avec les arrangedObject de l'array controller, car ce sont les instances  de l'entité Company qui vont répondre aux méthodes du protocole IKImageBrowserItem.
      Synchroniser le Zoom et la taille de la fonte avec les properties du TakeoverController=File's Owner
    • Il ne reste plus qu'à  recoder Company.m


    <br />@implementation Company <br />@synthesize&nbsp; imageVersion;<br />@dynamic companyName;<br />@dynamic departments;<br />@dynamic primitiveImageRepresentation;<br />- (void)setImageRepresentation:(NSData *)imageRep {<br />	[self willChangeValueForKey:@&quot;imageRepresentation&quot;];<br />	[self setPrimitiveImageRepresentation:imageRep];<br />	[self didChangeValueForKey:@&quot;imageRepresentation&quot;];<br />	imageVersion++;<br />}<br />- (NSString *) imageRepresentationType { 	<br />	return IKImageBrowserNSDataRepresentationType; <br />} <br />- (NSString *) imageUID { <br />	return self.companyName;<br />} <br />- (NSString *) imageTitle {<br />	return self.companyName;<br />} <br />....<br />
    













    (1) J'ai cherché en vain à  conserver les versions précédentes en faisant un pont entre logo et imageRepresentation. Pas réussi, il faut dire que j'ai trouvé le truc du imageVersion en dernière minute ... à  voir 

    (2) Le code de cet ArrayController
    <br />#import &lt;Cocoa/Cocoa.h&gt;<br />#import &lt;Quartz/Quartz.h&gt;<br /><br />@interface ImageBrowserArrayController : NSArrayController {<br />	IBOutlet IKImageBrowserView * browser;<br />}<br /><br />@end<br /><br />=======================================<br />@implementation ImageBrowserArrayController<br />- (void)awakeFromNib {<br />	[super awakeFromNib];<br />	// The image browser observes its content as a whole, but not changes to the individual imageBrowserItems.<br />	//&nbsp; We observe the imageBrowserItems&nbsp; through the array controller and then instruct the browser to reload as needed.<br />	[self addObserver:self forKeyPath:@&quot;arrangedObjects.imageRepresentation&quot;&nbsp; options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];<br />	[self addObserver:self forKeyPath:@&quot;arrangedObjects.companyName&quot;&nbsp; options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];<br />}<br /><br />- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {<br />	if ([keyPath isEqual:@&quot;arrangedObjects.imageRepresentation&quot;] || [keyPath isEqual:@&quot;arrangedObjects.companyName&quot;] ) {<br />		fprintf(stderr,&quot;-- observing : %s&#092;n&quot;,[keyPath UTF8String]);<br />		[browser reloadData];<br />	}<br />	// Essential to call super class implementation - NSArrayController relies heavily on KVO<br />	[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];<br />}<br /><br />@end<br />
    

Connectez-vous ou Inscrivez-vous pour répondre.