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 : un modèle sur mesure : DaysField code source complet

Code source complet de DaysOfWeek

Après avoir écrit en trois parties (1, 2 et 3) comment faire un modèle sur mesure, voici le code source au complet.

from django.core.exceptions import ValidationError
from django import forms
from django.db import models
from django.utils.translation import ugettext_lazy as _
class DaysOfWeek:
    DAYS = {1: _("Monday"),
            2: _("Tuesday"),
            3: _("Wednesday"),
            4: _("Thursday"),
            5: _("Friday"),
            6: _("Saturday"),
            7: _("Sunday"), }
    DAYS_SHORT = {1: _("Mo"),
                  2: _("Tu"),
                  3: _("We"),
                  4: _("Th"),
                  5: _("Fr"),
                  6: _("Sa"),
                  7: _("Su"), }
    CHOICES = [(idx, value) for idx, value in DAYS.items()]
    @staticmethod
    def summary_from_list(tab, empty=' '):
        if tab is None:
            return '-'.join([empty for a in range(len(DaysOfWeek.DAYS))])
        return '-'.join([str(DaysOfWeek.DAYS_SHORT.get(i, empty)) for i in tab])
class DaysFormField(forms.TypedMultipleChoiceField):
    # different widget, comment to change interface:
    widget = forms.CheckboxSelectMultiple
    def __init__(self, *args, **kwargs):
        if 'max_length' in kwargs:
            kwargs.pop('max_length')
        kwargs['choices'] = DaysOfWeek.CHOICES
        super().__init__(*args, **kwargs)
class DaysField(models.CharField):
    description = _("Comma-separated integers between 1 and 7")
    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 13 # max. len = all days = "1,2,3,4,5,6,7" = 13
        super().__init__(*args, **kwargs)
    @staticmethod
    def value_to_array(value):
        if value is None:
            return None
        try:
            if isinstance(value, list):
                return [int(a) for a in value]
            elif isinstance(value, str):
                return [int(a) for a in value.split(',')]
        except (TypeError, ValueError):
            raise ValidationError(_("Unexpected value"))
        raise ValidationError(_("Unexpected value"))
    @staticmethod
    def from_db_value(value, expression, connection):
        return DaysField.value_to_array(value)
    def to_python(self, value):
        return DaysField.value_to_array(value)
    def get_prep_value(self, value):
        return ','.join([str(a) for a in value]) if value is not None else None
    def formfield(self, **kwargs):
        # ignore the admin directives: directly override with our custom form:
        return super().formfield(form_class=DaysFormField,
                                 initial=[])
    # region - Validator -
    class ListBetween1And7Validator:
        def __call__(self, value):
            try:
                if isinstance(value, list):
                    loop = [int(a) for a in value]
                elif isinstance(value, str):
                    loop = [int(a) for a in value.strip('[]').split(',')]
                else:
                    raise ValueError()
                for v in loop:
                    if not (7 >= v >= 1):
                        raise ValueError()
            except (TypeError, ValueError):
                raise ValidationError(
                    _("Enter a list if coma-separated values between 1 and 7."),
                    code='invalid')
    # endregion - Validator -
    default_validators = [ListBetween1And7Validator()]

Django : un modèle sur mesure : DaysField étape 3/3

Formulaire spécifique pour un modèle sur-mesure

Après les conversions de notre modèle dans les deux sens :

Il ne nous reste qu’à faire le validateur = s’assurer que ce qui arrive (peu importe la source : base de données ou code Python) :

  • est bien un tableau :
  • que ce tableau n’est rempli que d’entiers ;
  • que chacun de ces entiers est compris entre 1 et 7.

Il faut créer une classe, qui doit implémenter __call__. Vous ne savez pas ce que c’est ? L’explication sur SO ici est excellente.
En une phrase : __init__() est appelé lorsque la classe est instanciée, __call__ est appelé lorsque l’instance est appelée (avec des ())

class ListBetween1And7Validator:
    def __call__(self, value):
        try:
            if isinstance(value, list):
                loop = [int(a) for a in value]
            elif isinstance(value, str):
                loop = [int(a) for a in value.strip('[]').split(',')]
            else:
                raise ValueError()
            for v in loop:
                if not (7 >= v >= 1):
                    raise ValueError()
        except (TypeError, ValueError):
            raise ValidationError(
                _("Enter a list if coma-separated values between 1 and 7."),
                  code='invalid')

Puis il faut le déclarer dans la liste des validateurs de notre classe :

    default_validators = [ListBetween1And7Validator()]

Dans le prochain et dernier article, je mets le code source au complet.

Django : un modèle sur mesure : DaysField étape 2/3

Formulaire spécifique pour un modèle sur-mesure

Après les conversions (base / et / ou / Python) vers données Python (dans les deux sens), il nous faut écrire notre formulaire spécifique pour notre modèle DaysField sur-mesure.

Ici, l’utilisateur doit pouvoir cocher / ou pas / des valeurs qui correspondent aux jours de la semaine.

Au jour de l’écriture de ce document, la documentation n’est pas complète.
Officiellement, il « suffirait » de surcharger def formfield(self, **kwargs) en y ajoutant sa propre classe de formulaire via l’index « form_class » de kwargs.

L’exemple proposé ne fonctionne pas !

En effet, lorsqu’on affiche le modèle dans l’interface d’administration, kwargs est déjà pré-rempli avec l’indice « widget » qui est le composant spécifique pour l’administration. Pour que leur exemple fonctionne, il faudrait supprimer « widget » avant d’y ajouter « form_class ».

J’ai opté pour la méthode directe qui ignore « kwargs » :
    def formfield(self, **kwargs):
        # ignore the admin directives: directly override with our custom form:
        return super().formfield(form_class=DaysFormField,
                                 initial=[])

Enfin, la classe DaysFormField elle-même :
class DaysFormField(forms.TypedMultipleChoiceField):
    def __init__(self, *args, **kwargs):
        if 'max_length' in kwargs:
            kwargs.pop('max_length')
        kwargs['choices'] = DaysOfWeek.CHOICES
        super().__init__(*args, **kwargs)

Cela affiche maintenant un choix ainsi :
Custom field DaysField choice 1.jpg

On peut facilement le modifier en y précisant un autre widget, par exemple :
class DaysFormField(forms.TypedMultipleChoiceField):
    widget = forms.CheckboxSelectMultiple
    # (le reste du code avec __init_())

Cela affiche maintenant un choix ainsi :
Custom field DaysField choice 2.jpg

Django : un modèle sur mesure : DaysField étape 1/3

Le principe

Je voulais un modèle de données qui stocke les jours d’une semaine que l’utilisateur désire, et qui les stocke sous forme de chaîne simple, avec des numéros qui correspondent aux jours de la semaine.
J’ai appelé ce modèle DaysField.
Les valeurs sont stockées par rapport aux jours choisis. Par exemple, 1,3 signifie que l’utilisateur a choisi lundi et mercredi parmi les jours. 6,7 correspondent à samedi et dimanche. Pour finir, tous les jours de la semaine cochés serait donc : 1,2,3,4,5,6,7

Toujours descendre d’un modèle classique

J’ai retenu ces principes et cela semble suffire pour fonctionner : on descend du modèle qu’on veut écrire en base de données. Ici, je voulais écrire une chaîne de caractères dans la base ⇒ on descend de CharField(), en modifiant la description au passage :
class DaysField(models.CharField):
    description = _("Comma-separated integers between 1 and 7")

Conversion de « sources » différentes en données Python

Pour résumer : les modèles ont deux « arrivées » de données :

  • quand on les données viennent de la base = from_db_value() ;
  • quand on les données viennent d’un formulaire (ou autre, mais en gros quand ça vient pas de la base de données mais du code Python lui-même) = to_python().

Il faut donc gérer ces deux cas, qui, dans les deux cas, doivent renvoyer quelque chose au format Python qui nous intéresse.
Ici, c’est un tableau d’entiers compris entre 1 et 7 qui correspond aux jours de la semaine.

J’ai centralisé la gestion des deux « arrivées » (base et Python) dans une seule fonction : value_to_array() :

    @staticmethod
    def value_to_array(value):
        if value is None:
            return None
        try:
            if isinstance(value, list):
                return [int(a) for a in value]
            elif isinstance(value, str):
                return [int(a) for a in value.split(',')]
        except (TypeError, ValueError):
            raise ValidationError(_("Unexpected value"))
        raise ValidationError(_("Unexpected value"))
    @staticmethod
    def from_db_value(value, expression, connection):
        return DaysField.value_to_array(value)
    def to_python(self, value):
        return DaysField.value_to_array(value)

Peut-être existe-t-il des cas où la provenance du code Python doit être gérée différemment de la provenance de la base de données… ici ce n’est pas le cas – et si vous avez des exemples de gestion différentes pour des entrées de base et de code, laissez-moi un commentaire, car je n’en vois pas…

Conversion de données Python ⇒ données pour la base

C’est la fonction get_prep_value().

Dans le cas DaysField, c’est très simple :
    def get_prep_value(self, value):
        return ','.join([str(a) for a in value]) \
            if value is not None else None

On prend toutes les valeurs du tableau, on les concatène avec la virgule , comme séparateur. C’est plus long de l’expliquer que de le coder !

Gitlab Continuous Integration

gitlab CI

Gitlab CI (Continuous Integration)

Layer A set of read-only files or commands that describe how to set up the underlying system beneath the container. Layers are built on top of each other, and each one represents a change to the filesystem.
Image An immutable layer that forms the base of the container.
Container An instance of the image that can be executed as an independent application. The container has a mutable layer that lies on top of the image and that is separate from the underlying layers.
Registry A storage and content delivery system used for distributing Docker images.
Repository A collection of related Docker images, often different versions of the same application.

Docker memo

Docker memo

docker images List all images present on host.
docker rmi To remove an image you no longer plan to use.
docker run [image] Start a container of the image [image]. If the image is not present on the host, it will go out to docker hub and pull the image down, it's only done the first time. For the subsequent executions, the same image will be re-used.
docker run [image] -d Start a container of the image docker run [image] in detached mode = in the background.
docker run -p [src]:[dst] [image] -d Like rStart a container of the image docker run [image] in detached mode = in the background.
docker pull [image] Like docker run but only pull the [image], not run it.
docker exec [container name] [command] Runs the command [command] on a running container.
docker ps List all running containers with informations such as container id, the image used for it.
docker ps -a To see all containers running or not.
docker ps A collection of related Docker images, often different versions of the same application.
docker stop [name] To stop a running container.
docker rm [name] rm = remove container. To remove a stopped or exited container permanently = stop using space.
docker rmi rmi = "remove images". To remove an image you no longer plan to use.
docker rmi To remove an image you no longer plan to use.

Django rest framework : Got AttributeError when attempting to get a value for field `my_field` on serializer `UserSerializer`.

Si vous voulez faire un mapping avec un modèle c’est ultra simple. Si vous voulez ajouter un champ custom, là ça devient compliqué…

Aucune réponse viable sur stackoverflow, vous risquez de chercher longtemps.

Si vous avez fait un code comme cela, pour rajouter un champ « custom » :

class UserSerializer(serializers.ModelSerializer):
    my_field = serializers.CharField(allow_blank=True)
    class Meta:
        model = User
        fields = ['url', 'email', 'password', 'my_field']

Eh bien cela ne marchera jamais, avec une erreur qui ne vous met absolument pas sur la voie :

Got AttributeError when attempting to get a value for field `my_field` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'my_field'.

Voici la solution : autoriser le null :

class UserSerializer(serializers.ModelSerializer):
    my_field = serializers.CharField(allow_blank=True, allow_null=True)
    class Meta:
        model = User
        fields = ['url', 'email', 'password', 'my_field']

git : exemple de fichier

Voici un exemple de fichier de configuration qui ignore la plupart des fichiers qui posent problème :

# --------------------------------------
# git cleanup if too many files / or / problems:
# git reflog expire --expire=now --all && git repack -ad && git prune
# --------------------------------------
# PyCharm
.idea/
# --------------------------------------
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# --------------------------------------
# C extensions
*.so
# Distribution / packaging
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# --------------------------------------
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# --------------------------------------
# Unit test / coverage reports
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# --------------------------------------
# Translations
*.mo
# --------------------------------------
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# --------------------------------------
# Rope
.ropeproject
# --------------------------------------
# Django stuff:
*.log
*.pot
# --------------------------------------
# Sphinx documentation
docs/_build/
# --------------------------------------
# git
objects/
# --------------------------------------
# swap files
*.swp
*.swo

Bootstrap : classes utilitaires

Vous allez peut-être trouver du code qui ressemble à cela :
<p class="mb-0">Texte dans un paragraphe</p>

Bootstrap >= 4 a mis en place plein de possibilités pour faire du padding et des marges « responsives ».
Elles fonctionnent pour tous les breakpoints :

  • xs (<=576px)
  • sm (>=576px)
  • md (>=768px)
  • lg (>=992px)
  • xl (>=1200px)

<p class="{propriété}{côtés}-{taille}">texte</p>

propriété :

  • m – les marges
  • p – le padding

côtés :

  • tmargin-top or padding-top
  • bmargin-bottom or padding-bottom
  • lmargin-left or padding-left
  • rmargin-right or padding-right
  • x*-left and *-right
  • y*-top and *-bottom
  • blank – les 4 côtés à la fois

taille :

  • 0 – supprimer les marges ou mettre le padding à 0
  • 1 – $spacer *0.25
  • 2 – $spacer *0.5
  • 3 – $spacer
  • 4 – $spacer *1.5
  • 5 – $spacer *3
  • auto – margin à auto

Python : hints & cheats

Son propre fichier basique de log

import datetime
msg = "Test de log"
dt = datetime.datetime.now()
with open("monlogfile.log", 'a+') as f:
    f.write('{:02}:{:02} - {}\n'.format(
        dt.hour, dt.minute, msg))

Teaching Python

Here

Découpages Python : formation

Checkio

Demystifying Two Factor Auth

Two-factor auth

Python Open Source Projects of the Year

Here!

Thanks to Dan Bader

Learning Python in minutes
https://learnxinyminutes.com/docs/python3/

How to Send an Email With Python
https://dbader.org/blog/python-send-email

The Python range() Function
https://realpython.com/courses/python-range-function/

Python sleep(): How to Add Time Delays to Your Code
https://realpython.com/python-sleep/

Cool New Features in Python 3.8
https://realpython.com/python38-new-features/

Python Decorators From the Ground Up
https://pabloariasal.github.io/python-decorators-from-the-ground-up/

How — and why — you should use Python Generators
https://medium.freecodecamp.org/how-and-why-you-should-use-python-generators-f6fb56650888

Download information on all your gmail emails and the body text to either csv or json. I developed this to download my 100K + emails stored over several years on gmail.
https://teklern.blogspot.fr/2017/11/download-all-your-email-information.html

Memoization in Python: How to Cache Function Results
https://dbader.org/blog/python-memoization

Implementing a Neural Network from Scratch in Python – An Introduction
https://www.datasciencecentral.com/profiles/blogs/implementing-a-neural-network-from-scratch-in-python-an

—–

Mailtrap – Sending Emails

—–

Introduction to NumPy and Pandas – A Simple Tutorial

https://cloudxlab.com/blog/numpy-pandas-introduction

Fastest way to uniquify a list in Python >=3.6

https://www.peterbe.com/plog/fastest-way-to-uniquify-a-list-in-python-3.6

8 Python Modules For Files Handling
http://devarea.com/8-python-modules-for-files-handling/

How do async for loops work in Python? Using asynchronous for loops in Python
https://quentin.pradet.me/blog/using-asynchronous-for-loops-in-python.html

How to use Python and Flask to build a web app — an in-depth tutorial
https://medium.freecodecamp.org/how-to-use-python-and-flask-to-build-a-web-app-an-in-depth-tutorial-437dbfe9f1c6

Framework ultra simple pour faire des micro-services en Json
Falcon is a bare-metal Python web API framework for building very fast app backends and microservices.
http://falconframework.org

How to break a CAPTCHA system in 15 minutes with Machine Learning
https://medium.com/@ageitgey/how-to-break-a-captcha-system-in-15-minutes-with-machine-learning-dbebb035a710

Python Exceptions: An Introduction
https://realpython.com/python-exceptions/

Python Metaclasses
https://realpython.com/python-metaclasses/

Building a Simple Web App with Bottle, SQLAlchemy, and the Twitter API
https://realpython.com/blog/python/building-a-simple-web-app-with-bottle-sqlalchemy-twitter-api/

Python – Regular Expressions Practical Guide
http://devarea.com/python-regular-expressions-practical-guide/#.Wki2nN_iZhE

A fast high-level screen scraping and web crawling framework.
https://scrapy.org

A fast high-level screen scraping and web crawling framework.
https://pyfiddle.io/

Python Web scraping
http://scrapingauthority.com/python-scrapy-mysql-and-matplotlib-to-gain-web-data-insights/

Tips for writing extremely short Python programs
Extremely short Python programs (aka « golfing »)

Instagramming with Python for Data Analysis
The guide


Julien Danjou blog

Easy Python logging with daiquiri

The three things you need to know about packaging are:

  • – Use pip to install your packages from PyPI
  • – Use pbr to package your modules
  • – Use PyPI to publish your package

Read more here.

A safe GitHub workflow with Pastamaker
The definitive guide to Python exceptions
How do you write your Python tests?
The unittest module in Python is the natural entry point to start writing test, and it’s really easy to use. It’s not really harder than using assert, and it will provide much nicer output when run. Once you get on that road, there’s a lot of other nice modules you can start using, such pytest, coverage, nose or mock. And if your project is hosted on places such as GitHub, it’s really easy to use services such as Travis to automate tests runs.

A simple filtering syntax tree in Python

Stop merging your pull requests manually

How I stopped merging broken code

How to Log Properly in Python

More GitHub workflow automation

Code Style Checks in Python


Nginx

Nginx hints / aide

Mon objectif était :

  • Si on tape l’URL sans le / à la fin, il redirige en ajoutant le / à la fin
  • Si on tape l’URL avec le / à la fin, tout doit fonctionner
  • Tout ne doit être que statique et les fichiers doivent obligatoirement exister, sauf index.html et index.htm
  • J’en suis donc arrivé à ces règles, plus « proches » de la configuration possible dans Nginx :

    1. Filtre « custom » avec / à la fin. Si oui, n’accepter que index.html ou index.htm
    2. Filtre « custom » en ignorant le / à la fin. Si oui, le nom, qui doit être forcément un fichier, sinon, rediriger en ajoutant un / pour qu’il reboucle au début
      location ~* ^/unity/(?<p>.+)/$ {
        root /web/htdocs/unity;
        try_files /$p/index.html /$p/index.htm /$p =403;
        access_log off;
        expires 1h;
      }
      location ~* ^/unity/(?<p>.+) {
        root /web/htdocs/unity;
        try_files /$p $p @redirect_with_slash_at_the_end;
        access_log off;
        expires 1h;
      }
      location @redirect_with_slash_at_the_end {
        return 301 $scheme://www.mywebsite.com$request_uri/;
      }