Catégorie : développement

Tout ce qui concerne le développement en général, que ce soit des choses générales, ou des choses bien précises. Cela va de la gestion de projet à la recherche du fonctionnement de pointeurs en C.

Django : Not naive datetime (blah) (tzinfo is already set)

Django a une manière de gérer la date et l’heure de façon très pratique… encore faut-il la comprendre !

Il y a la date et l’heure qui prend en compte le fuseau horaire.
Imaginez qu’un type en Chine envoie une date et une heure avec le fuseau horaire de Chine sur votre serveur, via un formulaire Web.
Le plus pratique est donc d’enregistrer au format UTC = 0 décalage horaire en se souvenant que l’heure est basée sur le fuseau de Chine.
Comme ça, si un Français demande cette heure, hop, il voit que c’est pour un fuseau +1 (ou +2 selon la saison) et fait le calcul tout seul.
Génial non ?

Seule chose à laquelle il faut faire attention : Python a son package datetime, et il y a une fonction now() qui n’est pas la même que la fonction now() de Django !

Donc si vous voulez enregistrer une date « style Django », il faut faire cet import :

from django.utils.timezone import now

Ensuite, vous pourrez, par exemple, faire des différences de secondes qui fonctionneront :
def is_expired(self, nb_seconds):
    return (now()-self.date_last_update).total_seconds() > nb_seconds

(Attention au code précédent : deux choses : le now() qui doit être importé comme je l’ai dit, et total_seconds() auquel il faut faire attention, car il y a une propriété seconds qui ne fait pas du tout la même chose….)

et surtout ne pas faire from datetime import datetime

Si vous faites comme moi et faites le mauvais import, l’heure ne sera pas « recentrée » à fuseau + 0 afin que tout le monde puisse la lire !

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.

PostgreSQL : mémo

Comment créer une base de données :
CREATE DATABASE interro WITH OWNER 'monutilisateur' ENCODING 'UTF8' TEMPLATE=template0;

Changer le mot de passe de l’utilisateur monutilisateur :
Se connecter en root puis :
root@inyab:~# su postgres
postgres@inyab:/root$ psql
could not change directory to "/root"
psql (9.1.18)
Type "help" for help.
postgres=# ALTER USER monutilisateur WITH PASSWORD 'nommotdepasse';

Comment pouvoir se connecter en python :

try:
    conn = psycopg2.connect('dbname=interro '\
                            'host=localhost '\
                            'user=monutilisateur '\
                            'password=monmotdepasse')
except Exception as e:
    print(e)
sys.exit(u'Script terminé')

Attention j’ai l’impression qu’on ne peut se connecter que sous le nom d’utilisateur sous lequel c’est exécuté, mais à valider.

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 !

Varnish 4.x : comment faire un redirect

Après avoir passé plusieurs heures sans aucune réponse satisfaisante, j’ai enfin trouvé la solution du redirect.
Varnish n’a pas la possibilité de faire un redirect « simplement », il faut normalement laisser cela au « backend », c’est à dire au serveur derrière (Apache ou autre).
Mais moi je ne voulais pas. C’est mon droit non ?
Voici comment faire un redirect qui fonctionne :

sub vcl_recv {
    # Rediriger tous les ".fr" vers ".com"
    if (
        (req.http.host ~ "(.*)monsite\.fr$")
    ) {
        # ! error = envoyer vers la sous-routine "vcl_error"
        set req.http.x-redir = "http://www.bb.com" + req.url;
        return(synth(850, "Moved permanently"));
    }
}

L’astuce principale était : utiliser vcl_synth

D’après le code, je vous résume ce que j’ai compris : si jamais on a un status 850 qui semble être un ordre particulier, alors automatiquement changer les headers en y appliquant la redirection, et les renvoyer directement le résultat.
Donc, après la routine vcl_recv il vous suffit d’ajouter vcl_synth comme ceci :

sub vcl_synth {
    if (resp.status == 850) {
        set resp.http.Location = req.http.x-redir;
        set resp.status = 302;
        return (deliver);
    }
}

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>

github : mémo pour les pressés

Voici un mémo de mon expérience très rapide de github, qui n’est destiné à l’origine qu’à moi, mais que je partage pour ceux qui voudraient aller vite :

  • Forker un repo : c’est à dire, faire « sa » propre branche d’un source pour pouvoir travailler dessus.
    Sur github : en haut à droite du « repo » principal, vous avec le menu « fork ». Cliquez dessus et c’est fini !
    Details »» ici ««
  • Copier en local
    De l’étape précédente, vous aurez une adresse genre
    https://github.com/VOTRE-USERNAME/repo-qui-vous-plait
    Faites :
    git clone https://github.com/VOTRE-USERNAME/repo-qui-vous-plait
    et git fera un copier coller en local des sources github
  • Ajouter le repo d’origine pour les futures synchronisations :
    Copier coller l’URL du repo d’origine qui est sur github :
    git remote add upstream https://github.com/repo-qui-vous-plait
  • Assurez-vous que tout est ok :
    Avec ce code :
    git remote -v
    Vous devriez avoir :
    origin https://github.com/VOTRE-USERNAME/repo-qui-vous-plait.git (fetch)
    origin https://github.com/VOTRE-USERNAME/repo-qui-vous-plait.git (push)
    upstream https://github.com/POSSESSEUR-DU-REPO/repo-qui-vous-plait.git (fetch)
    upstream https://github.com/POSSESSEUR-DU-REPO/repo-qui-vous-plait.git (push)
  • Ensuite ces étapes en boucle :
    • Repo origine distant vers local :
      Ce qui a été fait par les autres sur le repo général (s’il y en a) vers local.
      git fetch upstream
    • Repo perso distant vers local :
      Ce qui a été fait par les autres sur votre repo (s’il y en a) vers local.
      git checkout master
      puis
      git merge upstream/master
      Details »» ici ««
    • Noter vos modifications en local :
      Préciser à git de chercher (1) toutes vos modifications, option « a » (2) le commentaire, option « m ».
      git commit -am "Mon commentaire"
    • Appliquer vos modifications à distance :
      git push
      Details pas de github, mais en français et très bien faits :
      »» ici ««

La tendance python : vivement que la France rattrape l’international !

J’ai eu une discussion avec des fans de Symfony il y a quelques temps. Ils me soutenaient (avec une prétention qui m’a surpris) que Php était de très loin le langage le plus recherché actuellement. Ils se trompaient lourdement. Dire que Php est le langage le plus utilisé, oui. Dire qu’il est le plus demandé, et qu’il le restera, non.

Python est l’avenir

Aucune discussion possible.

Python c’est tout. End of story

Voici le mail que j’ai envoyé récemment à plusieurs de mes clients, et, qui explique la tendance python :

En tapant « most popular development languages » sur google, on voit que :

Enfin je l’ai appris ce matin de la part d’un professeur : deux universités de France retirent Php pour le remplacer par Python cette année (si vous êtes intéressé je vous dirais lesquelles, je n’ai plus en tête les villes de ces universités).

J’imagine les gens de mauvaise foi qui vont aller chercher sur le Web et sortir les deux ou trois sites qui mettent Php devant Python… oui vous allez en trouver, mais en proportion, la plupart des sites expliquent que la tendance d’aujourd’hui c’est Python et JavaScript. Quant à ce dernier, moi qui ai fait quelques sites en NodeJS, je confirme que c’est l’âge de pierre aussi bien côté serveur Web que côté langage JavaScript lui-même… peut être qu’enfin à sa version 6, il fera du scoping normal (lisez ici) déjà rien que ça c’est moisi, sans parler des principes des closures qui empêchent carrément de faire de grosses applications qu’on peut facilement maintenir… là aussi, je ne pourrai jamais convaincre des gens qui ont principalement fait du JavaScript sans avoir essayé aussi intensivement d’autres langages (on ne peut pas comparer dans ce cadre, et toute discussion devient alors impossible)…