formulaire - template django




Django: Comment puis-je me protéger contre la modification simultanée des entrées de la base de données? (7)

S'il existe un moyen de protéger contre les modifications simultanées de la même entrée de base de données par deux utilisateurs ou plus?

Il serait acceptable d'afficher un message d'erreur à l'utilisateur effectuant la deuxième opération de validation / sauvegarde, mais les données ne devraient pas être écrasées silencieusement.

Je pense que le verrouillage de l'entrée n'est pas une option, car un utilisateur peut utiliser le bouton "Retour" ou simplement fermer son navigateur, laissant le verrou pour toujours.


Cette question est un peu ancienne et ma réponse est un peu tardive, mais après ce que j'ai compris, cela a été corrigé dans Django 1.4 en utilisant:

select_for_update(nowait=True)

voir les docs

Renvoie un jeu de requêtes qui verrouille les lignes jusqu'à la fin de la transaction, en générant une instruction SQL SELECT ... FOR UPDATE sur les bases de données prises en charge.

Habituellement, si une autre transaction a déjà acquis un verrou sur l'une des lignes sélectionnées, la requête se bloque jusqu'à ce que le verrou soit libéré. Si ce n'est pas le comportement que vous voulez, appelez select_for_update (nowait = True). Cela rendra l'appel non bloquant. Si un verrou conflictuel est déjà acquis par une autre transaction, DatabaseError sera levé lors de l'évaluation du jeu de requêtes.

Bien sûr, cela ne fonctionnera que si le back-end supporte la fonctionnalité "select for update", ce qui n'est pas le cas de sqlite par exemple. Malheureusement: nowait=True n'est pas supporté par MySql, il faut utiliser: nowait=False , qui ne bloquera que jusqu'à la libération du verrou.


D'ici:
Comment éviter d'écraser un objet que quelqu'un d'autre a modifié

Je suppose que l'horodatage sera tenu comme un champ caché sous la forme que vous essayez d'enregistrer les détails de.

def save(self):
    if(self.id):
        foo = Foo.objects.get(pk=self.id)
        if(foo.timestamp > self.timestamp):
            raise Exception, "trying to save outdated Foo" 
    super(Foo, self).save()

En fait, les transactions ne vous aident pas beaucoup ici ... sauf si vous voulez que les transactions s'exécutent sur plusieurs requêtes HTTP (ce que vous ne voulez probablement pas).

Ce que nous utilisons habituellement dans ces cas est "Optimistic Locking". L'ORM de Django ne supporte pas cela autant que je sache. Mais il y a eu quelques discussions sur l'ajout de cette fonctionnalité.

Donc vous êtes seul. Fondamentalement, ce que vous devriez faire est d'ajouter un champ "version" à votre modèle et le passer à l'utilisateur comme un champ caché. Le cycle normal d'une mise à jour est:

  1. lire les données et le montrer à l'utilisateur
  2. utilisateur modifier les données
  3. utilisateur publie les données
  4. l'application l'enregistre dans la base de données.

Pour implémenter le verrouillage optimiste, lorsque vous enregistrez les données, vous vérifiez si la version que vous avez récupérée auprès de l'utilisateur est la même que celle de la base de données, puis mettez à jour la base de données et incrémentez la version. Si ce n'est pas le cas, cela signifie qu'il y a eu un changement depuis le chargement des données.

Vous pouvez le faire avec un seul appel SQL avec quelque chose comme:

UPDATE ... WHERE version = 'version_from_user';

Cet appel ne mettra à jour la base de données que si la version est toujours la même.


L'idée ci-dessus

updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\
      .update(updated_field=new_value, version=e.version+1)
if not updated:
      raise ConcurrentModificationException()

semble très bien et devrait fonctionner correctement, même sans transactions sérialisables.

Le problème est de savoir comment augmenter le comportement d '.save () pour ne pas avoir à faire de plomberie manuelle pour appeler la méthode .update ().

J'ai regardé l'idée de Custom Manager.

Mon plan consiste à remplacer la méthode Manager _update appelée par Model.save_base () pour effectuer la mise à jour.

Ceci est le code actuel dans Django 1.3

def _update(self, values, **kwargs):
   return self.get_query_set()._update(values, **kwargs)

Ce qui doit être fait à mon humble avis est quelque chose comme:

def _update(self, values, **kwargs):
   #TODO Get version field value
   v = self.get_version_field_value(values[0])
   return self.get_query_set().filter(Q(version=v))._update(values, **kwargs)

Une chose similaire doit se produire lors de la suppression. Cependant, supprimer est un peu plus difficile car Django implémente un peu de vaudou dans cette zone via django.db.models.deletion.Collector.

Il est étrange que l'outil modren comme Django manque de conseils pour Optimictic Concurency Control.

Je vais mettre à jour ce post quand je résous l'énigme. J'espère que la solution sera d'une belle manière pythonique qui n'implique pas des tonnes de codage, des vues bizarres, en sautant des morceaux essentiels de Django etc.


Pour référence ultérieure, consultez https://github.com/RobCombs/django-locking . Il verrouille d'une manière qui ne laisse pas de verrous éternels, par un mélange de déverrouillage javascript lorsque l'utilisateur quitte la page, et verrouille les délais d'attente (par exemple, dans le cas où le navigateur de l'utilisateur se bloque). La documentation est assez complète.


Une autre chose à rechercher est le mot "atomique". Une opération atomique signifie que votre changement de base de données se produira avec succès, ou échouera évidemment. Une recherche rapide montre cette question sur les opérations atomiques dans Django.


Vous devriez probablement utiliser au moins le middleware de transaction django, même si ce n'est pas le cas.

Quant à votre problème actuel d'avoir plusieurs utilisateurs éditant les mêmes données ... oui, utilisez le verrouillage. OU:

Vérifiez la version contre laquelle un utilisateur met à jour (faites-le en toute sécurité, de sorte que les utilisateurs ne puissent pas simplement pirater le système pour dire qu'ils mettent à jour la dernière copie!), Et mettez à jour seulement si cette version est actuelle. Sinon, renvoyez à l'utilisateur une nouvelle page avec la version originale qu'il modifiait, sa version soumise et la ou les nouvelle (s) version (s) écrite (s) par d'autres. Demandez-leur de fusionner les changements en une version complètement mise à jour. Vous pouvez essayer de les fusionner automatiquement à l'aide d'un jeu d'outils tel que diff + patch, mais vous aurez besoin de la méthode de fusion manuelle pour les cas de défaillance, alors commencez par cela. En outre, vous devez conserver l'historique des versions et autoriser les administrateurs à annuler les modifications, au cas où quelqu'un aurait involontairement ou intentionnellement bousillé la fusion. Mais vous devriez probablement avoir ça de toute façon.

Il y a très probablement une application / bibliothèque django qui fait le plus pour vous.





atomic