Journal DjangoFloor

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
5
25
avr.
2015

Sommaire

Même si je ne suis pas développeur web, j'ai eu à faire (plus ou moins volontairement) un certain nombre de sites.
Il y a quelques années, j'ai eu la chance de partir sur le couple Python + Django pour faire un premier site web (http://www.aviationsmilitaires.net/ pour les curieux), et c'est un choix que je n'ai jamais regretté par la suite.
En revanche, j'ai pu constater quelques manques, principalement deux :
* la gestion des paramètres et de l'installation
* les websockets
Accessoirement, il y avait toute une palanquée de petits réglages à faire systématiquement et de paquets à ajouter.

Projets inclus

Je considère que l'espace disque ne coûte pas grand-chose, donc c'est sans remord que j'ai mis ces paquets en dépendance. Je pars également du principe qu'on va utiliser du Redis pour pas mal d'usages.

  • uwsgi
  • bootstrap3 (django-bootstrap3 et django-admin-bootstrapped)
  • font-awesome
  • CSS et JS (django-pipeline, jsmin et rcssmin)
  • authentificaiton (django-allauth)
  • gestion de tâches (celery via Redis)
  • cache, websockets et sessions (django-websocket-redis, django-redis-sessions-fork, django-redis-cache)
  • django-debug-toolbar
  • gunicorn
  • celery

Paramètres

Je vois souvent que la façon d'installer un site django est de copier les sources dans un répertoire, puis de modifier le fichier de conf'.
Personnellement, je préfère faire quelque chose du genre :

pip install monsuperprojet
[modifier un fichier de conf sans toucher aux fichiers que je viens d'installer]
monsuperprojet run

Pour les paramètres, je distingue trois types de paramètres :

  • les paramètres qui peuvent communs à plusieurs sites (utilisation de i18n)
  • les paramètres spécifiques à un site particulier (les fichiers JS/CSS, les composantes, …)
  • les paramètres spécifiques à une installation d'un site (paramètres de la base de données)

De base avec Django, vous avez un fichier settings.py avec tous ces paramètres.
Quand vous créer un nouveau site Django, il faut commencer par recopier un settings.py existant, puis le personnaliser un peu pour le nouveau site (qui doit être versionné).
Et quand vous déployez votre site, vous devez le recopier avec la conf' de prod. Vous devez donc recopier le fichier versionné en modifiant certaines valeurs, et recommencer à chaque nouvelle mise à jour.

Je propose donc un fichier settings.py générique qui va fusionner trois fichiers de conf' :

  • un fichier de conf' générique dans djangofloor (suffisant pour avoir quelque chose de fonctionnel, par exemple avec une base SQLite)
  • un fichier de conf' fourni par votre projet
  • un fichier de conf' local (pour la vraie base de données), qui sera stocké dans $PREFIX/etc/monsuperproject/settings.py

Mais je ne me contente pas d'écraser bêtement le contenu de djangofloor.defaults par monsuperprojet.defaults puis par local.settings : on peut réutiliser certaines variables dans les autres.

settings de DjangoFloor :

LOCAL_PATH = '/tmp/'
MEDIA_ROOT = '{DATA_PATH}/media'
STATIC_ROOT = '{DATA_PATH}/static'

settings de monsuperprojet :

MEDIA_ROOT = '{DATA_PATH}/data'

paramètres locaux :

LOCAL_PATH = '/var/www/data'
STATIC_ROOT = '/var/www/static'

Voilà ce que ça donne au final :

… from django.conf import settings
… print(settings.LOCAL_PATH)
/var/www/data
… print(settings.MEDIA_ROOT)
/var/www/data/data
… print(settings.STATIC_ROOT)
/var/www/static

Accessoirement, je propose les scripts pour lancer votre projet :

djangofloor-manage --dfproject monsuperprojet config
djangofloor-manage --dfproject monsuperprojet migrate
djangofloor-manage --dfproject monsuperprojet runserver
djangofloor-celery --dfproject monsuperprojet worker
djangofloor-gunicorn --dfproject monsuperprojet

Vous pouvez également en faire de simples :

monsuperprojet-manage config
monsuperprojet-manage migrate
monsuperprojet-manage runserver
monsuperprojet-celery worker
monsuperprojet-gunicorn

Pour déployer notre projet, on peut donc faire :

[mkvirtualenv `monprojet`]
pip install monprojet
[fichier de conf dans ~/.virtualenvs/monprojet/etc/monprojet/settings.py]
monsuperprojet-manage migrate
monsuperprojet-manage collectstatic
monsuperprojet-gunicorn -D

Websockets

Malheureusement, cette partie ne fonctionne qu'avec Python 2.7.
Je distingue quatre types de code :

  • la partie JS (qui peut parler au Python via du HTTP ou du websocket)
  • la partie Python dans les vues (reçoit une HttpRequest et renvoie une HttpResponse, peut appeler du Celery)
  • la partie Python appelé par Celery (peut simplement appeler du Celery)
  • la partie Python dans les websockets (peut appeler du Celery)

Pas évidement de faire parler tous ces composants.
Avec DjangoFloor, j'utilise des signaux (un simple nom, auquel on connecte du code JS ou Python).
Exemple Python :

from djangofloor.decorators import connect
@connect(path='demo.my_signal')
def my_signal(request, arg):
    [ some interesting code ]
    print('blablabla', arg)

Quand on a des calculs un peu lourds, on les fait via Celery :

from djangofloor.decorators import connect
@connect(path='demo.my_signal', delayed=True)
def my_signal(request, arg):
    [ some interesting code ]
    print('blablabla', arg)

Et en JS :

df.connect('demo.my_signal', function (options) { alert(options.arg); });

Maintenant, pour appeler ces signaux :

from djangofloor.tasks import call, SESSION
call('demo.my_signal', request, SESSION, arg='argument')

Voilà ce que ça donne en JS :

df.call('demo.my_signal', {arg: 'argument'});

N'importe quel code Python ou JS peut appeler n'importe quel signal (qu'il soit JS ou Python).
Plusieurs fonctions peuvent s'abonner à un même signal, on peut avoir simultanément du JS et du Python.
Quand c'est du Python qui appelle le signal, il peut spécifier la portée côté JS :

  • aucun client JS ne reçoit le signal,
  • seule le client JS qui a appelé initialement un signal Python le reçoit,
  • toutes les sessions JS de l'utilisateur,
  • toutes les sessions JS.

Quand on fait du Python 3, on ne peut pas faire de websockets. Un mode dégradé existe donc : les signaux seront appelés du JS vers le Python par des requêtes HTTP, et cette requête va renvoyer une liste de dictionnaires (autant de signaux à appeler côté JS).

Conclusion

J'ai juste présenté les deux plus gros trucs de Djangofloor, mais il y a quelques autres subtilités.
Le projet est encore loin d'être achevé (notamment la doc :D), mais il m'est déjà bien pratique pour commencer rapidement un nouveau projet web.
Le but n'est pas d'avoir LE truc ultime pour des sites hyper personnalisés et à très haute fréquentation, mais d'avoir quelque chose de suffisant pour des sites utilitaires (genre en intranet).

Pour finir : le code est dispo sur Pypi et sur Github : https://github.com/d9pouces/django-floor

  • # Des liens

    Posté par  (site web personnel) . Évalué à 4.

    websockets
    gunicorn
    celery

    Génial du céleri, du maïs guni, et des chaussettes en toile d'araignée :-D
    Non sans rire quand on ne connais pas c'est lourd d'aller chercher à chaque fois la page du paquet pour savoir ce qu'il fait, un petit lien simplifierais tellement la vie.

    kentoc'h mervel eget bezan saotred

    • [^] # Re: Des liens

      Posté par  (site web personnel) . Évalué à 5.

      uwsgi -> un serveur applicatif, un peu l'équivalent de Tomcat pour Python (et quelques autres langages). En l'occurrence, je ne l'ai pas mis en dépendance explicite
      bootstrap3 (django-bootstrap3 et django-admin-bootstrapped) -> une feuille CSS généraliste pour avoir un site web qui ressemble à quelque chose par défaut
      font-awesome -> une feuille CSS avec des icônes sous forme de caractères d'une police faite sur mesure
      CSS et JS (django-pipeline, jsmin et rcssmin) -> fusionne et minimise les feuilles CSS et fichiers JS
      gestion de tâches (celery via Redis) ->
      cache, websockets et sessions (django-websocket-redis, django-redis-sessions-fork, django-redis-cache) -> les websockets sont un moyen de faire des communications bi-directionnelles sur une page web, en gardant une connexion active
      django-debug-toolbar
      gunicorn -> un serveur applicatif 100% Python

  • # La solution WebSockets n'est pas un peu over-compliquée ?

    Posté par  . Évalué à 2. Dernière modification le 26 avril 2015 à 08:46.

    Salut,

    Ton projet est une bonne idée, dommage que je ne fasse plus de Django ;-)

    Plus sérieusement, je reconnais que ta solution pour faire du WebSockets me paraît vraiment compliqué. Je comprends bien pourquoi tu as fait ça, ça permet de faire du WebSockets tout en restant dans Django.
    Quand tu commences à avoir des bugs, l'empilage de toutes ces couches ne rend pas le debug trop compliqué ?

    Mais dès que tu vas vouloir faire du flux bidirectionnel dans la même requête, tu vas être vite coincé, ou plutôt, tu vas décomposer ça en plusieurs endpoints côté Django, et en gros, tu te retrouves avec une logique de callbacks à la Twisted.

    Pour ma part, quand je faisais du Django, pour la partie WebSockets, j'avais un démon Twisted à côté afin d'avoir un pattern async qui matche bien avec la business logic des WebSockets.

    Maintenant, je fais de l'AsyncIO avec aiohttp.web, ce qui me permet d'avoir des endpoints qui peuvent à la fois répondre en HTTP pur et en WebSockets sur la même URL.

    Mais je comprends bien que quand tu as un website existant qui est déjà tout codé en Django, tu ne vas pas tout refaire ;-)

    Bon dimanche.

  • # Tu sers avec quoi ?

    Posté par  (site web personnel) . Évalué à 2.

    uwsgi et gunicorn dans les dépendances c'est pas redondant ?

    • [^] # Re: Tu sers avec quoi ?

      Posté par  (site web personnel) . Évalué à 2.

      Il y a uniquement gunicorn en dépendance, pour des raisons de simplicité (pas de module C à compiler…). En revanche, le script pour utiliser uwsgi est fourni (qui se contente d'importer le bon module, au final).

  • # Gabarits de démarrage/déploiement de projet

    Posté par  (site web personnel) . Évalué à 1.

    Alors pour rester dans la modernité, maintenant on a des outils de démarrage/déploiement de projets tel que :

    Cela te permet d'avoir un dépôt avec un gabarit de projet prêt à démarrer/déployer avec un minimum de choses à modifier à chaque fois. Et si tu veux automatiser encore plus le déploiement pour en faire le moins possible tu peux intégrer Buildout.

    • [^] # Re: Gabarits de démarrage/déploiement de projet

      Posté par  (site web personnel) . Évalué à 3.

      Mais MrBob rajoute un outil supplémentaire. Là, on utilise uniquement easy_install/pip (pour rester en mode Python) ou apt/yum (si on a pris la peine de les packager avec les setuptools), deux types d'outils bien intégrés avec les outils classiques de déploiement (Ansible, Puppet, Salt, etc.). On déploie un paquet (dont on ne touche pas les fichiers) et on écrit un fichier de conf, et c'est tout.

      Pour moi, cookiecutter permettait de créer un début de code pour un nouveau projet, pas d'en déployer un existant.
      L'inconvénient d'un template super complet, c'est que s'il est modernisé, alors tu dois modifier les projets existants à la main.

      • [^] # Re: Gabarits de démarrage/déploiement de projet

        Posté par  (site web personnel) . Évalué à 1.

        Ben Mr.Bob ou cookiecutter c'est un peu pareil, il faut au préalable installer l'outil qui permet d'utiliser les templates.

        Mais effectivement, tu es obligé de suivre les éventuelles mises à jour et les appliquer toi même, mais dans le cadre de sites vitrines qui ont un cycle de vie de quelques années, en général tu te positionnes sur un Django 'LTS'.

        Par contre j'ai regardé un peu ton dépôt, je n'ai rien vu qui suggérait la possibilité qu'il permette d'être mis auto-magiquement à jour, j'imagine que tu te reposes sur l'utilisation de Git pour ça ? Mais dans la réalité c'est peut être un peu plus compliqué de gérer quelques lignes de conflits de merge à résoudre.

        Pour le déploiement je me suis fourvoyé effectivement parce que j'intègre toujours un buildout dans mes templates de projets.

        Bref en tout cas dans le cadre d'une webagency qui produit près d'une dizaine de projet toute les deux semaines, un outil comme Mr.bob ou cookiecutter est vraiment efficace en pratique, par exemple https://github.com/emencia/cookiecutter-djangocms3-buildout qui te produit rapidement un projet django-cms + foundation avec des possiblités d'options.

        • [^] # Re: Gabarits de démarrage/déploiement de projet

          Posté par  (site web personnel) . Évalué à 3.

          DjangoFloor est un module Python (et une app Django), donc tu peux le mettre à jour facilement.
          Pas de merge à faire, vu que ses fichiers restent séparés de ceux du projet (et également séparés du fichier de conf local).

          Normalement, les settings Django font partie du code « maison ». Avec DjangoFloor, les settings n'est plus dans le code maison, il se contente d'aller piocher dans le code maison les paramètres qui changent (puis dans la conf locale les paramètres spécifiques).

          • [^] # Re: Gabarits de démarrage/déploiement de projet

            Posté par  (site web personnel) . Évalué à 0.

            Et pourquoi tu n'a pas essayé d'intégrer django-configurations ?

            • [^] # Re: Gabarits de démarrage/déploiement de projet

              Posté par  (site web personnel) . Évalué à 2. Dernière modification le 27 avril 2015 à 20:00.

              D'une part parce que je ne connaissais pas ( :D ), d'autre part c'est plus lourd à utiliser que DjangoFloor :

              • pas de classe à définir
              • possibilité de réutiliser les variables au sein de variable (par exemple je définis LOCAL_PATH, puis je redéfinis les autres valeurs avec : STATIC_ROOT = '{LOCAL_PATH}/static' )
              • tu peux te passer des --configuration ou de export DJANGO_CONFIGURATION en utilisant un fichier nommé monprojet-manage (à la place de manage.py)
              • il va chercher un fichier de conf' local (qui ne sera pas donc versionné, important quand il y a des mots de passe).

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.