Máquinas de estado y django

Cuando se trabaja en un proyecto de django, hay una serie de bibliotecas de terceros imprescindibles si no desea reinventar la rueda sin cesar. Una herramienta para depurar consultas sql (debug-toolbar, silk, --print-sql de django-extensions), algo para almacenar estructuras de árbol, tareas periódicas / diferidas (por cierto, uswgi tiene una interfaz similar a cron . EAV todavía es necesario, aunque a menudo puede ser reemplazado por jsonfield.Y una de estas cosas extremadamente útiles, pero por alguna razón menos discutida en la red es FSM.





Casi todos los registros de la base de datos tienen algún estado. Por ejemplo, para un comentario, un moderador puede publicarlo / eliminarlo / eliminarlo. Para ordenar en una tienda: emitido / pagado / entregado / devuelto, etc. Además, la transición de un estado a otro a menudo se difumina en el código y hay una lógica empresarial en él, que debe cubrirse abundantemente con pruebas (todavía tiene que hacerlo, pero puede evitar probar cosas elementales, por ejemplo, que un pedido puede pasar al estado de "reembolso" sólo después de estar en condición de "pago".





Sería bastante lógico describir tales transiciones de manera más declarativa y en un solo lugar. Junto con la lógica necesaria y la verificación de acceso.





Aquí hay un código de muestra de las pruebas de la biblioteca django-fsm





class BlogPost(models.Model):
    """
    Test workflow
    """
    state = FSMField(default='new', protected=True)

    def can_restore(self, user):
        return user.is_superuser or user.is_staff

    @transition(field=state, source='new', target='published',
                on_error='failed', permission='testapp.can_publish_post')
    def publish(self):
        pass

    @transition(field=state, source='published')
    def notify_all(self):
        pass

    @transition(field=state, source='published', target='hidden', on_error='failed',)
    def hide(self):
        pass

    @transition(
        field=state,
        source='new',
        target='removed',
        on_error='failed',
        permission=lambda self, u: u.has_perm('testapp.can_remove_post'))
    def remove(self):
        raise Exception('No rights to delete %s' % self)

    @transition(field=state, source='new', target='restored',
                on_error='failed', permission=can_restore)
    def restore(self):
        pass

    @transition(field=state, source=['published', 'hidden'], target='stolen')
    def steal(self):
        pass

    @transition(field=state, source='*', target='moderated')
    def moderate(self):
        pass

    class Meta:
        permissions = [
            ('can_publish_post', 'Can publish post'),
            ('can_remove_post', 'Can remove post'),
        ]
      
      



Esto es ideal para rest api, entre otras cosas. Podemos crear puntos finales para las transiciones entre estados de forma automática. Por ejemplo, request / orders / id / cancel parece una acción perfectamente lógica para un conjunto de vistas. ¡Y ya tenemos la información necesaria para verificar el acceso! Y también para los botones en el panel de administración, y la capacidad de dibujar hermosos gráficos con flujo de trabajo :) Incluso hay editores de flujo de trabajo visual, es decir, los no programadores pueden describir los procesos de negocio





Cuanto más código genérico y declarativo escribimos, más fiable es. Menos código, menos duplicación, menos errores. Las pruebas se transfieren parcialmente al autor de la biblioteca y puede centrarse en la lógica empresarial exclusiva del proyecto.








All Articles