2010-10-21 3 views
1

étrangère J'ai un modèle avec un entier unique qui doit augmenter en ce qui concerne une clé étrangère, et le code suivant est la façon dont je gère actuellement il:Django - AutoField en ce qui concerne une clé

class MyModel(models.Model): 
    business = models.ForeignKey(Business) 
    number = models.PositiveIntegerField() 
    spam = models.CharField(max_length=255) 

    class Meta: 
     unique_together = (('number', 'business'),) 

    def save(self, *args, **kwargs): 
     if self.pk is None: # New instance's only 
      try: 
       highest_number = MyModel.objects.filter(business=self.business).order_by('-number').all()[0].number 
       self.number = highest_number + 1 
      except ObjectDoesNotExist: # First MyModel instance 
       self.number = 1 
     super(MyModel, self).save(*args, **kwargs) 

I ont les questions suivantes à ce sujet:

  1. Plusieurs personnes peuvent créer MyModel instances pour le même business, partout sur l'internet. Est-il possible pour 2 personnes créant MyModel instances en même temps, et .count() renvoie 500 en même temps pour les deux, et puis tous les deux essaient de définir essentiellement self.number = 501 en même temps (lever une IntegrityError)? La réponse semble évidente: "oui, ça pourrait arriver", mais j'ai dû demander.
  2. Existe-t-il un raccourci, ou "Meilleure façon" de le faire, que je peux utiliser (ou peut-être un SuperAutoField qui gère cela)?

Je ne peux pas frapper un while model_not_saved:try:, except IntegrityError: en, parce que d'autres dispositifs de retenue dans le modèle pourrait conduire à une boucle sans fin, et une catastrophe pire que Tchernobyl (peut-être pas tout à fait si mal).

Répondre

2

Vous souhaitez cette contrainte au niveau de la base de données. Sinon, vous finirez par rencontrer le problème de simultanéité dont vous avez parlé. La solution consiste à envelopper l'opération entière (lire, incrémenter, écrire) dans une transaction. Pourquoi ne pas utiliser un AutoField au lieu d'un PositiveIntegerField?

number = models.AutoField() 

Cependant, dans ce numéro de dossier est presque certainement va égaler yourmodel.id, alors pourquoi ne pas simplement utiliser cela?

Edit:

Oh, je vois ce que vous voulez. Vous voulez un champ numérique qui n'incrémente pas, sauf s'il y a plusieurs instances de MyModel.business.

Je vous recommande tout de même d'utiliser le champ id si vous le pouvez, car il est certain d'être unique. Si vous ne voulez absolument pas faire cela (peut-être que vous montrez ce numéro aux utilisateurs), alors vous devrez envelopper votre méthode de sauvegarde dans une transaction.

Vous pouvez en savoir plus sur les transactions dans les docs:

http://docs.djangoproject.com/en/dev/topics/db/transactions/

Si vous utilisez juste ceci pour compter combien d'instances de MyModel ont une FK Business, vous devriez le faire comme une requête plutôt que d'essayer de stocker un compte.

+0

@Paul - merci. Je ne comprends pas. Je ne savais pas qu'une transaction garantirait qu'aucune modification ne soit apportée aux lignes lues pendant la transaction, ainsi que, bien sûr, protégeant la phase de validation en cas d'échec partiel. C'est tellement? Si oui, je peux simplement envelopper enregistrer dans '@ commit_on_success', non? – orokusaki

+0

@Paul - Il semble également que les transactions ne résolvent pas le problème de 'IntegrityError' d'une autre contrainte brisée pouvant être confondue avec celle causée par' number' pendant la simultanéité. – orokusaki

+0

Le comportement précis des transactions dépend de la base de données que vous utilisez et de la manière dont vous l'avez configurée.Si vous activez le middleware de transaction et que vous définissez le niveau d'isolation sur readable dans votre base de données, votre méthode fonctionnera telle quelle. Cela ne sera probablement pas un problème de performance dans ce cas, mais cela pourrait avoir un impact sur le reste de votre code. –