Mots-clé : django

Django : internationalisation des fichiers JavaScript

La documentation est ici, elle est très claire :

docs.djangoproject.com/translation

Enfin très claire.. jusqu’à un certain point….
Disons qu’ils oublient de préciser une chose :

Lorsqu’il faut créer la liste des messages à traduire, ils ont codé en dur deux listes à générer :

  • django
  • djangojs

Donc si vous voulez que manage.py crée correctement les fichiers JavaScript, il faut obligatoirement passer le paramètre djangojs. L’ordre à donner est donc :

manage.py makemessages -d djangojs --locale=fr_FR --locale=en_US

Django : créer un many-to-many self-referencing double way !

Je vous explique, et c’est bien plus facile en français en fait 😉
Je voulais mettre en place une relation style facebook mais en plus évolué : une personne peut avoir une ou plusieurs personnes ami(e)(s). Seulement, on ne gère qu’un seul type de relation par personne. Par exemple, Olivier est ami proche avec Elsa. Déjà, cela implique qu’Elsa est amie proche avec Olivier. Donc, il faut imaginer une table qui aura une relation qui va s’auto-référencer, via une table intermédiaire dans laquelle on précisera le type de relation.

Ce n’est pas très dur à imaginer :

Personne <-> PersonneRelation.

Déclaration de Personne :

class Personne(BaseModel):
    user = models.ForeignKey(User)
    relations = models.ManyToManyField('self',
                                       through='PersonneRelation',
                                       symmetrical=False)

Maintenant, là où le problème se pose c’est qu’au moment de l’ajout d’une relation dans un sens, par exemple mari femme, il faut que la relation soit aussi ajoutée dans l’autre sens, à la fois pour des questions de performance, mais aussi pour des questions d’affichage (« Simon est le mari de Arlette » mais dans l’autre sens, « Arlette est la femme de Simon », on constate que les phrases sont totalement différentes… et quand c’est mari femme, on peut imaginer ne pas se compliquer l’existence en regardant dans si la personne est un homme ou une femme et en déduire le sens, mais si jamais c’est une relation maître – élève ? HEIN ? COMMENT ON FAIT ? Si c’est un formateur qui forme des adultes ? HEIN ? ON DIT QUOI LA ? On fait moins le malin d’un coup HEIN ! Oui bon ok il faut que je décompresse un peu…). Donc l’idée est (1) de définir les relations possibles en dur (vous pourrez très facilement faire évoluer cela en une relation supplémentaire vers une table qui définit le type de relation et vous n’aurez plus de limites en termes de types de relations possibles, mais ce qui suit est déjà assez long à expliquer, je ne vais pas en plus l’alourdir avec du code supplémentaire) et de (2) gérer au moment où on insère un nouvel enregistrement : si jamais la relation opposée n’est pas encore présente, on l’ajoute. Ah. J’oubliais le (3) modifications = appliquer la même de l’autre côté et suppression : supprimer l’autre côté aussi.

Stop bullshit, du code :

@python_2_unicode_compatible
class PersonneRelation(BaseModel):

    TYPE_AMI = u'0'
    TYPE_CONNAISSANCE = u'1'
    TYPE_PARENT_ENFANT = u'2'
    TYPE_MARI_FEMME = u'3'
    TYPE_PROFESSEUR_ELEVE = u'4'
    TAB_TYPES = {
        TYPE_AMI: _(u'friend'),
        TYPE_CONNAISSANCE: _(u'relationship'),
        TYPE_PARENT_ENFANT: _(u'parent > child'),
        TYPE_MARI_FEMME: _(u'husband <> wife'),
        TYPE_PROFESSEUR_ELEVE: _(u'teacher > student'),
    }
    TAB_TYPES_REVERSE = {
        TYPE_AMI: _(u'friend'),
        TYPE_CONNAISSANCE: _(u'relationship'),
        TYPE_PARENT_ENFANT: _(u'child > parent'),
        TYPE_MARI_FEMME: _(u'wife <> husband'),
        TYPE_PROFESSEUR_ELEVE: _(u'student > teacher'),
    }
    type_relation = models.CharField(max_length=1,
                                     choices=[(a, b) for a, b in
                                              list(TAB_TYPES.items())],
                                     default=TYPE_AMI)
    src = models.ForeignKey('Personne', related_name='src')
    dst = models.ForeignKey('Personne', related_name='dst')
    opposite = models.ForeignKey('PersonneRelation',
                                 null=True, blank=True, default=None)
    is_reverse = models.BooleanField(default=False)

    def __str__(self):
        return _(u'n.{} {} --> {}').format(
                str(self.pk),
                self.TAB_TYPES[self.type_relation] if not self.is_reverse
                else self.TAB_TYPES_REVERSE[self.type_relation],
                str(self.dst))

    class Meta:
        verbose_name = _(u'Relation')
        verbose_name_plural = _(u'Relations')

@receiver(post_save, sender=PersonneRelation)
def signal_receiver(sender, **kwargs):
    created = kwargs['created']
    obj = kwargs['instance']
    if created and not obj.opposite:
        opposite = PersonneRelation(
            src=obj.dst, dst=obj.src, opposite=obj,
            type_relation=obj.type_relation, is_reverse=True)
        opposite.save()
        obj.opposite = opposite
        obj.save()
    elif not created and obj.type_relation != obj.opposite.type_relation:
        obj.opposite.type_relation = obj.type_relation
        obj.opposite.save()

Vous remarquerez que j’ai vraiment réindenté le code pour qu’il reste lisible ici !

J’espère qu’il vous servira !

Python : batteries included.
Django : La Plateforme de développement Web pour les perfectionnistes sous pression.

Django : formulaire qui édite aussi des champs d’un modèle clé étrangère

Déclarer les modèles

Voici mes modèles en résumé, parce que vous y arriverez nécessairement un jour ou l’autre :

class Personne(models.Model):
    user = models.OneToOneField(User)
    date_naissance = models.DateField(default=None,
                                      null=True, blank=True,
                                      verbose_name=_(u'Birth date'))
    class Meta(BaseModel.Meta):
        ordering = ['date_v_debut']

Si on veut faire une forme qui édite nom, prenom et date_naissance : facile… pour date_naissance. Faire la forme avec les champs du modèle :

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Personne
        fields = ('date_naissance',)

Ajoutons la date de naissance :

    a = _(u'Birthdate:')
    date_naissance = forms.DateField(
        label=a,
        widget=forms.DateInput(attrs={'title': a}))

Déclarer les champs de la clé étrangère

Etape précédente facile ! Mais pour les champs d’un modèle clé étrangère, ici User ?
D’abord l’exclure de la forme (ne vous inquiétez pas, je ne vous mets qu’une partie du code, mais vous aurez le code en entier à la fin !) : exclude = ('user',)
Ensuite mettre les champs dans __init__, ici exemple avec last_name :

    def __init__(self, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)
        a = _(u'Last name:')
        self.fields['user_last_name'] = forms.CharField(
            label=a, max_length=100,
            widget=forms.TextInput(attrs={
                'title': a, 'size': 100, 'type': 'text',
                'placeholder': _(u'my last name'),
                'class': 'form-control'}),
            error_messages=self.e)

Donc là vous aurez tous vos champs affichés, mais : (1) ils ne seront pas remplis avec la valeur de votre modèle si vous êtes en mode édition et (2) ils ne sont pas ordonnés, car ajoutés à la fin = ils apparaîtront forcément à la fin.

Pré-remplir le champ

Voici comment pré-remplir le champ : ce n’est pas dans la déclaration de la forme, mais dans la déclaration de la vue via get_initial() :

class EditView(LoginRequiredMixin, generic.UpdateView):
    model = Personne
    template_name = 'my_home/profile/edit.html'
    form_class = ProfileForm
    success_url = reverse_lazy('my_home_index')

    def get_initial(self):
        initial = super(EditView, self).get_initial()
        a = self.object.user
        initial['user_last_name'] = a.last_name if a.last_name else u''
        return initial

Les étapes en résumé

Ce titre = principal = h1.
There’s no need to say more… 🙂

Modèles

User

Déjà fait, merci Django ! Ahahaha. Bon.

Personne

class Personne(models.Model):
    user = models.OneToOneField(User)
    date_naissance = models.DateField(default=None,
                                      null=True, blank=True,
                                      verbose_name=_(u'Birth date'))
    class Meta(BaseModel.Meta):
        ordering = ['date_v_debut']

Forme

Champs normaux + champs de la clé étrangère = du modèle « étranger » :
class ProfileForm(forms.ModelForm):

    class Meta:
        model = Personne
        fields = ('sexe', 'statut', 'est_fumeur',
                  'est_physique', 'date_naissance')
        exclude = ('user', 'est_physique')

    a = _(u'Birthdate:')
    date_naissance = forms.DateField(
        label=a,
        widget=forms.DateInput(attrs={
            'title': a,
            'class': 'form-control datetimepicker'}))

    def __init__(self, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)
        a = _(u'Last name:')
        self.fields['user_last_name'] = forms.CharField(
            label=a, max_length=100,
            widget=forms.TextInput(attrs={
                'title': a, 'size': 100, 'type': 'text',
                'placeholder': _(u'my last name'),
                'class': 'form-control'}),
            error_messages=self.e)

Vue

Pré-remplir les champs :
class EditView(LoginRequiredMixin, generic.UpdateView):
    model = Personne
    template_name = 'my_home/profile/edit.html'
    form_class = ProfileForm
    success_url = reverse_lazy('my_home_index')

    def get_initial(self):
        initial = super(EditView, self).get_initial()
        a = self.object.user
        initial['user_last_name'] = a.last_name if a.last_name else u''
        return initial

Voilà résumé : Vue + Forme + Modèle. Inévitable sur des bons frameworks, et classique non ?

J’ai oublié une dernière chose : ces champs sont dans un OrderedDict() c’est à dire que c’est l’ordre dans lequel ont été mis les éléments qui importe, donc dans notre cas, ils seront affichés en dernier. Si jamais vous voulez les afficher en premier, vous êtes obligés de reconstruire un OrderedDict() dans lequel vous ajoutez vos champs dans l’ordre que vous désirez. Ici, je ne vais que remettre mon champ en premier, et remettre les autres.

Voici le code de __init__ final :

    def __init__(self, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)
        field_user_first_name = forms.CharField(
            label=a, max_length=100,
            widget=forms.TextInput(attrs={
                'title': a, 'size': 100, 'type': 'text',
                'placeholder': _(u'my first name'),
                'class': 'form-control'}),
            error_messages=self.e)
        new_fields = OrderedDict([
            ('user_first_name', field_user_first_name),
        ])
        for k, v in self.fields.items():
            new_fields[k] = v
        self.fields = new_fields

Django : django.db.utils.IntegrityError: table__new.colonne may not be NULL

C’est le genre de problème qui arrive très souvent lorsqu’on touche à une base de données, en ajoutant un champ.
Pourquoi ? Parce que par défaut, les champs crées sans paramètres ne doivent pas être vides.

Exemple concret : je veux rajouter pour un modèle, un champ « exemple » :

class Groupe(models.Model):
    description = models.CharField(max_length=150)
    exemple = models.CharField()

Je fais « makemigration / migrate » et là, horreur : « django.db.utils.IntegrityError: main_groupe__new.exemple may not be NULL »

La solution

  • Supprimer le dossier « migrations » de l’application concernée
  • Lancer un migrate --fake-initial qui va « simuler » un migrate, sans essayer de créer les tables
  • Lancer makemigration nomappli (!! c’est ici que si on oublie le nom de l’appli rien ne se passe !!)
  • Seconde astuce : supprimer le champ fautif
  • Lancer migrate : là il va supprimer de la base le champ fautif
  • Remettre le champ avec blank=True, default=None
  • Refaire makemigration puis migrate sans préciser le nom de l’appli, comme d’habitude.

Et votre nouveau code sera pris en compte :
class Groupe(models.Model):
    description = models.CharField(max_length=150)
    exemple = models.CharField(max_length=150,
                               blank=True, default=None)

Oui je sais ça a l’air long, mais en pratique ça prend deux-trois minutes seulement !

Django

Django aide-mémoire

Templating

Héritage du parent {% block menu %}
{{ block.super }}
{% endblock %}
Créer une variable s2_lang
puis la sortir dans le template
{% with 'x/y/js/i18n/'|add:LANGUAGE_CODE|add:'.js' as s2_lang %}
    <script src="{% static s2_lang %}"></script>
{% endwith %}

Astuces

Afficher des messages uniques à l’utilisateur
Ex: « Merci de vous être inscrit » etc.
Infrastructure des messages
Faire un import des settings, mais relatif
= pas dépendant de l’application où vous avez le fichier.
Coder « proprement » pour Django
Calculer un template « manuellement »

Aide ici
from django.template import loader
def render_sample(request):
    s = loader.get_template(
        'alerts/subject.txt'
    ).render({})
    m = loader.get_template(
        'alerts/message.txt'
    ).render({})

SQL

Requête directe

Lancer la console python (Tools » Python console),
puis taper :
from django.db import connection
cursor = connection.cursor()
cursor.execute("PRAGMA table_info(langue)")
for c in cursor.fetchall():
    print(c)


Autre exemple :
from django.db import connection
cursor = connection.cursor()
cursor.execute("delete from app_persongame where person_id=1")
cursor.fetchall()
cursor.execute("update app_persongame set state=1 where person_id=2")
cursor.fetchall()

Sauvegarde / restauration

Lancer via manage.py ([CTRL] + [ALT] + r sous PyCharm) :
Sauvegarde : dumpdata -o data.json
Restauration : loaddata data.json

Changer un mot de passe

from django.contrib.auth.models import User
u = User.objects.get(username='nomutilisateur')
u.set_password('motdepasse')
u.save()

Multilangue

Créer tous les fichiers en ignorant mes librairies tierces :
makemessages --locale fr --locale en --ignore third_party
Multilangue JavaScript : préciser le domaine
makemessages -d djangojs -i third_party --locale fr --locale en --ignore static/vendors
Ne pas oublier : compilemessages … et de redémarrer le serveur !

Multilangue : chaînes custom

Dupliquer le dossier locale hors de vos applications (= je le fais dans le dossier principal), exemple :
locale »»»» locale_unity
Modifier LOCALE_PATHS du fichier settings.py comme suit :
LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
    os.path.join(BASE_DIR, 'locale_unity'),
)
Modifiez les fichiers .po concernés.
Ne pas oublier : compilemessages … et de redémarrer le serveur !

Multilangue / JavaScript

Declaration dans urls.py

Attention, ici « app » = dossier d’une application qu’on veut traduire, moi je les appelle souvent « app ». :

from django.views.i18n import javascript_catalog, JavaScriptCatalog

js_info_dict = {
    'packages': ('app',)
}

urlpatterns = [
    url(r'^i18n/', include('django.conf.urls.i18n')),
    # blabla
]
urlpatterns += i18n_patterns(
    url(r'^jsi18n/$', javascript_catalog, js_info_dict,
        name='javascript_catalog'),
    # blabla
)

Inclure les fichiers js dans la page HTML

<script src="{% url 'javascript_catalog' %}"></script>

Ajouter aussi le fichier personnel où je mets ma fonction « raccourci » pour la traduction :
<script src="{% static 'js/globals.js' %}"></script>

Ma fonction « raccourci » pour la traduction :
function _(a) {
    return gettext(a);
}

Extraction des chaines et traduction

Très important : récupérer les chaines en précisant le domaine djangojs :
makemessages -d djangojs -i third_party --locale fr --locale en

À partir de là, deux fichiers de traduction : le classique django.po et le nouveau djangojs.po.

Exemple de code JavaScript qui fera partie des chaînes à traduire

$('#menu').empty().append(
    $('<h5 />').html(_('Waiting...'))
);

Installation

Python 3.9

virtualenv -p /usr/local/bin/python3.9 venvpython3.9
source venvpython3.9/bin/activate
pip install --upgrade pip
pip install 'django==3.0'

Installez django_markdown_app au lieu de django_markdown (car c’est le successeur, django_markdown n’est plus maintenu !)

pip install django_markdown_app
pip install django-compressor
pip install python3-openid
pip install pytz
mkdir htdocs
cd htdocs

PostgreSQL

Création d’un utilisateur + mot de passe

Être root. De là, taper :
sudo -i -u postgres
psql [nom base de donnees]
CREATE USER interro WITH PASSWORD 'i';
Très important : lui donner tous les droits sur la base :
GRANT ALL PRIVILEGES ON DATABASE "interro" to interro;
Et si l’utilisateur existe déjà :
ALTER USER interro WITH PASSWORD 'i';

Changement d’owner des tables

Se connecter à la base. Allez je mets la commande Windows :
"\Program Files\PostgreSQL\9.5\bin\psql.exe" -U postgres interro
. De là, taper (j’ai bien mis en gras le nom de l’utilisateur à qui on donne les tables) :
SELECT 'ALTER TABLE '|| schemaname || '.' || tablename ||'
OWNER TO interro;' FROM pg_tables
WHERE NOT schemaname IN ('pg_catalog', 'information_schema')
ORDER BY schemaname, tablename;

Et PostgreSQL va sortir une floppée de « ALTER TABLE...« , il suffit juste de les copier/coller dans le prompt pour les appliquer.

Vues Login / logout

– Créer l’URL de login :
url(r'^login/$', LoginView.as_view(), name='login'),

– Créer la vue LoginView
from django.contrib.auth import views

class LoginView(views.LoginView):
    template_name = 'login.html'

    def __init__(self):
        pass

– Créer l’URL de logout :
url(r'^logout/$', LogoutView.as_view(), name='logout'),

– Créer la vue LogoutView
from django.contrib.auth import views

class LogoutView(views.LogoutView):

    def __init__(self):
        self.next_page = '/'

<form action="{% url 'login' %}" method="post" accept-charset="utf-8">
    {% csrf_token %}
    {% for field in form %}
        <label>{{ field.label }}</label>
        {% if field.errors %}
            {{ field.errors }}
        {% endif %}
        {{ field }}
    {% endfor %}
    <input type="hidden" name="next" value="{{ next }}" />
    <input class="button small" type="submit" value="Submit"/>
</form>

PyCharm et Django : comment faire une requête directe

Si, comme moi, vous voulez faire des requêtes directement et voir toutes les tables, pour les modifier manuellement, rien de plus simple, il faut juste chercher sur le Web pendant pas mal de temps. Voici la solution pour économiser de précieuses minutes :

  • Lancer la console python (Tools » Python console)
  • Taper :

    from django.db import connection
    cursor = connection.cursor()
    cursor.execute("PRAGMA table_info(langue)")
    for c in cursor.fetchall():
        print(c)
  • Et vous obtiendrez un résultat qui pourrait ressembler à :
    (0, 'id', 'integer', 1, None, 1)
    (1, 'date_creation', 'datetime', 1, None, 0)
    (2, 'date_last_modif', 'datetime', 1, None, 0)
    (3, 'date_v_debut', 'datetime', 1, None, 0)
    (4, 'date_v_fin', 'datetime', 0, None, 0)
    (5, 'nom', 'varchar(50)', 1, None, 0)
    (6, 'locale', 'varchar(2)', 1, None, 0)
    (7, 'bidirectionnel', 'bool', 1, None, 0)
    (8, 'nom_local', 'varchar(50)', 1, None, 0)
    (9, 'active', 'bool', 1, None, 0)
  • Si vous voulez plus de détail, et que la console a des problèmes avec les accents, voici un code qui remplace les accents par un point d’interrogation :
  • Taper :

    from django.db import connection
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM langue")
    for c in cursor.fetchall():
        for d in c:
            if type(d) is str:
                print(d.encode(
                    sys.stdout.encoding,
                    errors='replace'))
            else:
                print(d)
  • Et vous obtiendrez un résultat qui pourrait ressembler à :
    None
    b'Russian'
    b'ru'
    False
    b'???????'
    False

Django et authomatic : comment s’enregistrer en un click

Les étapes à faire

Installer :

  • Python 2.7 maximum car google ne fournit des sources Python que pour la 2.7
  • Django (version en cours = 1.8)
  • Et puis ce qui n’est pas précisé avec authomatic :
    • Installer defusedxml
    • Installer google app engine (faut le chercher sur le site de google)
    • Installer Python openid
  • Installer enfin authomatic

Personnellement j’ai crée un dossier que j’ai appelé third_party.
Ce qui fait que mon arborescence est comme cela :

.
├── locale
│   ├── en
│   ├── fr
│   └── sv
├── produits
│   ├── migrations
│   └── templatetags
├── pyweb
├── static
│   ├── css
│   ├── fonts
│   ├── images
│   ├── js
│   └── produits
├── templates
│   └── produits
└── third_party
   ├── authomatic_0_1_0
   ├── defusedxml-0.4.1
   ├── google_appengine_1_9_25
   └── python-openid_2_2_5

Enfin, les associations dans authomatic :

Dans Facebook il faudra aller dans le coin des développeurs, et créer une application jusqu’à arriver à un écran comme celui-ci :
Informations facebook

Même chose pour gmail :
Informations gmail

Et enfin la relation dans le code :
Code dans authomatic

Pour terminer : Facebook ne renvoyait pas les emails, lorsqu’on s’authentifiait.
C’est un bogue connu depuis que Facebook a modifié son API très récemment.
La solution est ici : editez votre fichier \authomatic\providers\oauth2.py.

Allez à la classe Facebook.
Copiez-collez ce code, qui ne change presque rien (je vous laisse chercher) sauf l’URL user_info_url qui a été modifiée pour la v2.4 : et voilà, il ne vous reste plus qu’à suivre le tutoriel de authomatic avec Django, qui est assez bien fait, et tout devrait fonctionner !

class Facebook(OAuth2):
    """
    Facebook |oauth2| provider.
    
    * Dashboard: https://developers.facebook.com/apps
    * Docs: http://developers.facebook.com/docs/howtos/login/server-side-login/
    * API reference: http://developers.facebook.com/docs/reference/api/
    * API explorer: http://developers.facebook.com/tools/explorer

    Supported :class:`.User` properties:

    * city
    * country
    * email
    * first_name
    * gender
    * id
    * last_name
    * link
    * locale
    * location
    * name
    * picture
    * timezone
    * username

    Unsupported :class:`.User` properties:

    * birth_date
    * nickname
    * phone
    * postal_code

    """
    user_authorization_url = 'https://www.facebook.com/dialog/oauth'
    access_token_url = 'https://graph.facebook.com/oauth/access_token'
    # Correction merci à miohtama :
    # https://github.com/peterhudec/authomatic/issues/112
    user_info_url = 'https://graph.facebook.com/me?fields=' \
                    'id,email,name,first_name,last_name,address,gender,' \
                    'hometown,link,timezone,verified,website,locale,languages'
    user_info_scope = ['email', 'user_about_me', 'user_birthday',
                       'user_location']

À propos

Qui suis-je ?

Dans le désordre

  • Développeur expert en Django ;
  • Développeur avancé en Python ;
  • Développeur avancé en JavaScript ;
  • Relecteur technique de plus de 17 livres sur des techniques avancées Packtpub ;
  • Développeur expert en Delphi (Pascal orienté objet) ;
  • Anglais courant ;
  • Développeur avancé en Php (aucune prestation Php, je me spécialise Python) ;
  • Développeur intermédiaire en C ;
  • Développeur en Purebasic (pour sa rapidité impressionnante) ;
  • Chef de projet Web ;

Ce qui suit n’est pas professionnel : si seul le côté pro vous intéresse, n’allez pas plus loin !
J’insiste : ne perdez pas votre temps si ma vie personnelle ne vous intéresse pas !


Une citation que j’ai parfois en tête :

Un ami est l’être dont la traîtrise provoque la plus vive surprise. Napoléon Ier

Et :

Non licet omnibus adire Corinthum

Locution Latine, littéralement « Tous ne peuvent se rendre à Corinthe », i.e., de nos jours « tous n’ont pas l’électricité jusqu’au dernier étage »… en monde anglo-saxon « not the sharpest tool in the shed ».


Qui suis-je, pas que dans le monde professionnel ?

Dans le désordre

  • Amateur de jeux de sociétés / jeux en général (+1000 sur steam) ;
  • Passionné de labyrinthes ;
  • Fan Terry Pratchett.

Mes musiques

Avec Dire Straits (qui n’a que très peu de rapport), les albums d’Extreme sont aussi les seuls albums que j’écoute encore 15 ans après et qui me donnent encore des sensations, qui suggèrent toujours des émotions et me font rêver.

Extrême (prononcez « aix-twiiime »): vraiment très peu de personnes connaissent leurs albums, à part une vieille chanson (vieille = une quinzaine d’années) qui a été un temps à la mode, qui est presque à mon sens une des plus mauvaise de leur album : « More than words ». Limite ils ont fait ça pour vendre l’album.

Ce groupe n’est pas bien, il n’est pas cool, il est pour moi vraiment la perfection.

Album #1 : Pornograffiti : le rythme Rock’n roll avec des excellents guitaristes (je n’ose pas dire « Hard Rock » parce que ça pourrait être mal interprété) et des chansons qui donnent envie de bouger est tout simplement géniallissime.

Album #2 : Yours, Mine & The Truth : Traduction (approximative) : « Vos idées, les miennes, et la vérité ». Déjà vous avez le ton de l’album. Même s’il bouge toujours très bien, il est déjà plus lent, il y a plus de mélodies douces, plus de violons, et les paroles… rien que d’y penser… bref :
– dans la section « vos idées » : l’une s’appelle « pourquoi les faiseurs de paix meurent ? » avec un speech original de Martin Luther King qui s’intègre de manière fluide dans la chanson, une autre s’appelle « le zoo politique », rien que la première chanson est incroyable : on entend le père qui parle à son fils : « tu vas faire ci et ça ok ? tu m’ENTENDS », et si on écoute bien on entend le fils qui répond « oui papa ». Si on écoute plus attentivement, on l’entend sa voix qui se modifie, il répète encore 3 – 4 fois « oui papa » et sa voix change, en s’éloignant, à la fin on entend très lointaine une voix d’homme qui dit « oui, papa »… c’est digne d’un Pink Floyd des temps modernes.
– ensuite section « mine », toutes les chansons sont intimes, et superbes, il y a des choses sympathiques comme « je suis un tragico comique » dans laquelle il raconte ses péripéties de personne super maladroite qui tente de draguer (beaucoup d’entre nous – moi y compris – ont connu à un moment ou à un autre ce genre de situation), et d’autre chansons bouleversantes : je découvrais l’album en même temps qu’une amie de 17 ans s’est tuée en mobylette, et une des chansons qui est l’incarnation de la pureté et du questionnement sain demande : « please tell me God isn’t dead… » (« dis moi que Dieu n’est pas mort »), « Look at all the lonely people loosing faith in a world full of despair please tell me God isn’t dead… » (« Regarde tous ces gens qui perdent la foi dans un monde plein de désespoir s’il te plait dis moi que Dieu n’est pas mort »), qu’on soit croyant ou pas, la voix, la question, la force de la musique, le ton qui monte, à la fin on sent que le chanteur, en souffrance, pose vraiment la question « s’il te plait dis moi que Dieu n’est pas mort ! » et à la fin il n’a pas de réponse, il se termine en disant doucement « je veux savoir… » presque comme s’il parlait à soi même… c’est juste beau.
– et pour finir la section « the truth », la vérité. A vous de la découvrir, je ne veux pas vous influencer en quoi que ce soit. Ici aussi, tout est parfait.

Attendez ce n’est pas tout ! Je n’ai pas parlé de la qualité d’écoute ! Quant à la technique du son pur… Après avoir entendu le Titanic sur des baffles d’une qualité incroyable et avoir eu vraiment l’impression que Céline Dion était à deux pas de moi, les albums d’Extrême sont les seuls autres albums qui ont fait cette impression sur ces baffles. Ça m’a donné une gifle. Ces albums sont d’une clarté sonore extraordinaire.

Si vous aimez :
– le rock’n roll,
– la guitare électrique,
– les émotions fortes,
– des albums qui ont tous un sens plus poussé que « Pour t’oublier, j’ai du t’imaginer en train de chier »,

alors courrez les acheter. Et ne cherchez pas à les télécharger illégalement ils ne sont même pas en partage !

Ignorez ce qui suit si ma vie ne vous intéresse pas !
Ne perdez pas votre temps si ma vie ne vous intéresse pas !

Note personnelles sur la MSDN

On est réellement obligé de lire toute la MSDN, parce qu’ils s’attardent sur certains détails APRÈS la doc, genre : « ah oui au fait, on a oublié : si ça fonctionne pas, c’est normal… peut-être qu’il faudrait, hein, pourquoi pas, parce que oui on dit que c’est comme ça mais pas tout le temps, hein, HEIN ».

Lis ça :
http://msdn2.microsoft.com/en-us/library/ms738547.aspx

A+