2015-10-21 18 views
10

Non riesco a trovare riferimenti a particolari problemi in documenti o online.Come aggiungere tramite l'opzione a ManyToManyField esistente con migrazioni e dati in django

Ho una relazione molti a molti esistente.

class Books(models.Model): 
    name = models.CharField(max_length=100) 

class Authors(models.Model): 
    name = models.CharField(max_length=100) 
    books = models.ManyToManyField(Books) 

Questo ha migrazioni e dati. Ora ho bisogno di usare attraverso l'opzione per aggiungere un campo in più nella tabella che contiene molte più relazioni.

class Authorship(models.Model): 
    book = models.ForeignKey(Books) 
    author = models.ForeignKey(Authors) 
    ordering = models.PositiveIntegerField(default=1) 

class Authors(models.Model): 
    name = models.CharField(max_length=100) 
    books = models.ManyToManyField(Books, through=Authorship) 

Quando eseguo le migrazioni, django crea una nuova migrazione per il modello Authorship. Ho provato a creare manualmente il file di migrazione aggiungendo la colonna ordering nella tabella Authorship e modificando la colonna books nella tabella Authors ma ottengo alcuni problemi di migrazione.

operations = [ 
    migrations.AddField(
     model_name='authorship', 
     name='ordering', 
     field=models.PositiveIntegerField(default=1), 
    ), 
    migrations.AlterField(
     model_name='authors', 
     name='books', 
     field=models.ManyToManyField(to='app_name.Books', through='app_name.Authorship'), 
    ), 
] 

Quando si tenta di migrare, dà KeyError: ('app_name', u'authorship') Scommetto che ci sono altre cose che sono interessati e quindi gli errori.

Quali cose mi mancano? C'è qualche altro approccio per lavorare con questo?

risposta

8

Sembra che non ci sia modo di utilizzare l'opzione senza dover eseguire migrazioni di dati. Quindi ho dovuto ricorrere all'approccio alla migrazione dei dati, ho preso alcune idee dalla risposta di @ pista329 e ho risolto il problema usando i seguenti passaggi.

  • Crea Authorship modello

    class Authorship(models.Model): 
        book = models.ForeignKey(Books) 
        author = models.ForeignKey(Authors) 
        ordering = models.PositiveIntegerField(default=1) 
    
  • Tenere relazione ManyToManyField originale, ma aggiungere un altro campo usando sopra modello definito come attraverso il modello:

    class Authors(models.Model): 
        name = models.CharField(max_length=100) 
        books = models.ManyToManyField(Books) 
        published_books = models.ManyToManyField(Books, through=Authorship, related_name='authors_lst') # different related name is needed. 
    
  • Aggiungi la migrazione dei dati per copiare tutti i dati dal vecchio tavolo alla nuova tabella Authorship.

Dopo questo, books campo Authors modello può essere rimosso come abbiamo nuovo campo denominato published_books.

2

A volte le migrazioni possono essere complicate.

Se si desidera modificare il campo m2m con passaggio, suggerire di rinominare il campo modificato Authors.books a Authors.book. Quando è stato chiesto da makemigrations, se hai cambiato nome da libri a libro? [yN], scegliere "N" come N. Django eliminerà books e creerà il campo book invece di alterare.

class Authorship(models.Model): 
    book = models.ForeignKey("Books") 
    author = models.ForeignKey("Authors") 
    ordering = models.PositiveIntegerField(default=1) 

class Authors(models.Model): 
    name = models.CharField(max_length=100) 
    book = models.ManyToManyField("Books", through="Authorship") 

Se si desidera utilizzare books in ogni caso, cambiare book a books e ripetere processo di migrazione con y come risposta alla domanda makemigrations sulla ridenominazione.

+0

Ciò richiederà la migrazione dei dati altrimenti perderò i dati esistenti, no? – chhantyal

+0

Sì, poiché rimuoverai il campo. Se non vuoi perdere dati, non rimuovere 'books', basta aggiungere' book'. Quindi migrare i dati da 'libri' a' libro' semplicemente tramite SQL. – pista329

9

C'è un modo per aggiungere "attraverso" senza migrazioni di dati. Sono riuscito a farlo in base a this @MatthewWilkes' answer.

Così, da tradurre al vostro modello di dati:

  1. creare il modello Authorship soltanto con book e author campi. Specifica il nome della tabella per utilizzare lo stesso nome della tabella M2M generata automaticamente che hai già. Aggiungi il parametro 'through'.

    class Authorship(models.Model): 
        book = models.ForeignKey(Books) 
        author = models.ForeignKey(Authors) 
    
        class Meta: 
         db_table = 'app_name_authors_books' 
    
    class Authors(models.Model): 
        name = models.CharField(max_length=100) 
        books = models.ManyToManyField(Books, through=Authorship) 
    
  2. Generare una migrazione, ma non eseguirla ancora.

  3. Modificare la migrazione generata e avvolgere le operazioni di migrazione in un'operazione migrations. SeparateDatabaseAndState con tutte le operazioni all'interno del campo state_operations (con database_operations lasciato vuoto). Vi ritroverete con qualcosa di simile:

    operations = [ 
        migrations.SeparateDatabaseAndState(state_operations=[ 
         migrations.CreateModel(
          name='Authorship', 
          fields=[ 
           ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 
           ('book', models.ForeignKey(to='app_name.Books')), 
          ], 
          options={ 
           'db_table': 'app_name_authors_books', 
          }, 
         ), 
         migrations.AlterField(
          model_name='authors', 
          name='books', 
          field=models.ManyToManyField(through='app_name.Authorship', to='app_name.Books'), 
         ), 
         migrations.AddField(
          model_name='authorship', 
          name='author', 
          field=models.ForeignKey(to='app_name.Author'), 
         ), 
        ]) 
    ] 
    
  4. È ora possibile eseguire la migrazione e aggiungere il ordering campo aggiuntivo al vostro tavolo M2M.

Edit: A quanto pare, i nomi delle colonne nel DB sono generati in modo leggermente diverso per le tabelle M2M automatico per le tabelle di modelli definiti. (Sto usando Django 1.9.3.)

Dopo la procedura descritta, ho dovuto anche cambiare manualmente i nomi delle colonne di un campo con un nome 2-word (two_words=models.ForeignKey(...)) da twowords_id a two_words_id.

+1

Sì, questo approccio funziona. per il nome della colonna, ho appena usato db_column che corrisponde al nome della colonna precedente. – User707