Mots-clé : python

Linux : compilation de Python en local

A l’IUT d’Aix en Provence, tous les ordinateurs sont gérés à distance, et sous Linux, aucune possibilité d’installer un logiciel.
Par contre, il est possible de compiler en local !

Compilation Python

Récupérez les dernières sources sur le site officiel.
Puis allez dans le dossier source et tapez :

./configure --prefix=$HOME/python
make && make install

A partir de là, sous PyCharm, précisez le chemin Python qui correspondra à votre $HOME/python et précisez que vous voulez installer les packages dans le répertoire de l’utilisateur courant (une case à cocher).

Et voilà : vous aurez un serveur Web Python et pourrez développer et installer tous les packages et librairies que vous voulez !

From Django/Python to NodeJS. Une année après : from NodeJS to Django/Python

Tiens, un blog d’un geek qui explique pourquoi il est passé de python à NodeJS.

Why I’m switching from Python to NodeJS

Ah j’oubliais le plus important : un an après avoir utilisé nodejs en production :

After a year of nodejs in production: back to Python

Au moins ça vient d’un développeur talentueux qui a bossé jour et nuit sur du NodeJS pendant toute une année et il sait bien de quoi il parle.

Spoiler :

  • Easy to learn, impossible to master
  • Good luck handling errors: You’ll need to double your client invoices to makeup for debug time
  • Would I recommend it for large-scale products? Absolutely not.

Traduction de ce qui importe le plus : votre client devra payer le double pour le même projet : développement + autant de temps pour déboguer.

Je vous laisse faire votre propre expérience, moi j’ai la mienne qui se rapproche assez de ces billets de blog.

Django : créer un checkbox multiple choice avec bootstrap

Je me sers de cet habillage ici.

C’est, en gros, une « surcharge » de bootstrap avec quelques composants en plus.

Seulement j’aurais aimé avoir une sélection de plusieurs choix = cases à cocher qui soit « compatible » avec cet habillage.
Je n’aurais jamais cru que c’était aussi simple.
En fait Django a généralisé plein d’affichages HTML à tel point qu’il suffit de faire du pas à pas et de regarder ce qu’il fait pour comprendre.
Même le rendu par « item » de chaque « Renderer » est surchargeable. Donc voici le code qui prend… 10 lignes, et qui réhabille totalement le rendu des cases à cocher :

from django.forms import CheckboxSelectMultiple
from django.forms.widgets import ChoiceFieldRenderer, \
    CheckboxChoiceInput

class BootstrapChoiceFieldRenderer(ChoiceFieldRenderer):
    choice_input_class = CheckboxChoiceInput
    outer_html = '<div{id_attr}>{content}</div>'
    inner_html = '<div class="checkbox">'
                 '{choice_value}{sub_widgets}'
                 '</div>'

class CheckboxSelectMultipleBootstrap(CheckboxSelectMultiple):
    renderer = BootstrapChoiceFieldRenderer

Django : comment changer un label de fieldset dynamiquement

Voici ma classe :

class GameAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': (('name', 'number_of_players'), )
        }),
        (_(u'Validity'), {
            'classes': ('collapse',),
            'fields': ('date_v_debut', 'date_v_fin')
        }),
    )
    inlines = (GamePersonsInline,)

Mon problème est que je voulais changer dynamiquement 'Validity' afin de montrer soit, si le jeu n’était plus valide, soit s’il était toujours en cours.
L’idée est que s’il a une date de fin de validité, c’est que le jeu n’est plus en cours.

Voici la solution complète, extrêmement simple, et qui me permet sans avoir à déplier le groupe « dates de validité », de voir si la partie n’est plus en cours :

class GameAdmin(admin.ModelAdmin):

    def get_fieldsets(self, request, obj=None):
        fieldsets = super(GameAdmin, self).get_fieldsets(
            request, obj)
        retour = [list(x) for x in fieldsets]
        if obj.date_v_fin:
            retour[1][0] = _(u'Expired: {}').format(
                obj.date_v_fin.strftime('%Y-%m-%d %H:%M:%S'))
        else:
            retour[1][0] = _(u'Running!')
        return retour

    fieldsets = (
        (None, {
            'fields': (('name', 'number_of_players'), )
        }),
        (_(u'Validity'), {
            'classes': ('collapse',),
            'fields': ('date_v_debut', 'date_v_fin')
        }),
    )

Après c’est vrai, il y a une chose de codée en dur, le [1][0], mais je ne voyais pas d’autre solution.

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 : 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 : 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 cheatsheet

Django aide-mémoire

Templating

Héritage du parent {% block menu %}
{{ block.super }}
{% endblock %}

Astuces

Afficher des messages uniques à l’utilisateur
Ex: « Merci de vous être inscrit » etc.
Infrastructure des messages

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

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

Multilangue / JavaScript

Declaration dans urls.py

Attention, ici « app » = dossier d’une application qu’on veut traduire, moi je les appelle souvent « app ». :
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 2.7

virtualenv venvpython2.7/
source venvpython2.7/bin/activate
pip install --upgrade pip
pip install 'django==1.8'
mkdir htdocs
cd htdocs

Python 3.6

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

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>

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)…

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