2015-10-21 27 views
10

Nie mogę znaleźć odniesienia do konkretnego problemu w dokumentach lub w Internecie.Jak dodać opcję do istniejącego ManyToManyField z migracjami i danymi w django

Mam istniejącą wiele do wielu relacji.

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

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

Dotyczy to migracji i danych. Teraz muszę użyć opcji through, aby dodać jedno dodatkowe pole w tabeli zawierające wiele do wielu relacji.

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) 

Po uruchomieniu migracji django tworzy nową migrację do modelu Authorship. Próbowałem ręcznie utworzyć plik migracji, dodając kolumnę ordering w tabeli Authorship i zmieniając kolumnę books w tabeli Authors, ale występują pewne problemy z migracją.

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'), 
    ), 
] 

Podczas próby migracji, daje KeyError: ('app_name', u'authorship') Założę istnieją inne rzeczy, które są dotknięte, a więc błędy.

Czego mi brakuje? Czy istnieje inne podejście do pracy z tym?

Odpowiedz

8

Wygląda na to, że nie ma możliwości użycia opcji bez konieczności przeprowadzania migracji danych. Tak więc musiałem uciekać się do podejścia do migracji danych, wziąłem kilka pomysłów z odpowiedzi @ pista329 i rozwiązałem problem, wykonując następujące kroki.

  • Tworzenie Authorship modelu

    class Authorship(models.Model): 
        book = models.ForeignKey(Books) 
        author = models.ForeignKey(Authors) 
        ordering = models.PositiveIntegerField(default=1) 
    
  • Zachowaj oryginalny ManyToManyField relację, ale dodać kolejne pole za pomocą wyżej opisanego modelu za pośrednictwem modelu

    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. 
    
  • Dodaj migracji danych, aby skopiować wszystkie dane od starego stołu do nowego stołu Authorship.

Po tym, books pole na Authors modelu mogą zostać usunięte jak mamy nowe pole o nazwie published_books.

2

Migracje czasami mogą być kłopotliwe.

Jeśli chcesz zmienić pole m2m za pomocą, proponuję zmienić nazwę zmienionego pola Authors.books na Authors.book. Zapytany przez makemigrations, czy zmieniłeś nazwisko z książki na książkę? [yN], wybierz "N" jako Nie. Django usunie books i utworzy pole book zamiast zmieniać.

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") 

Jeśli chcesz użyć books tak, zmienić book do books i powtórzyć proces migracji z y jako odpowiedź na pytanie o zmianę nazwy makemigrations.

+0

Będzie to wymagać migracji danych, w przeciwnym razie utracę istniejące dane, nie? – chhantyal

+0

Tak, ponieważ usuniesz pole. Jeśli nie chcesz tracić danych, nie usuwaj 'książek', po prostu dodaj' książkę'. Następnie zmigruj dane z 'books' do' book' po prostu przez SQL. – pista329

9

Istnieje sposób na dodanie "poprzez" bez migracji danych. Udało mi się to zrobić na podstawie this @MatthewWilkes' answer.

Tak więc, aby przełożyć je do modelu danych:

  1. Tworzenie modelu Authorship tylko book i author dziedzinach. Podaj nazwę tabeli, aby używać tej samej nazwy co automatycznie wygenerowana tabela M2M, którą już masz. Dodaj parametr "przelotowy".

    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. Wygeneruj migrację, ale nie uruchamiaj jej jeszcze.

  3. Edycja generowane migracji i owinąć czynności migracyjnych do operacji migrations. SeparateDatabaseAndState wszystkich operacji wewnątrz state_operations dziedzinie (z database_operations niewypełnione). Będziesz skończyć z czymś takim:

    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. Teraz można uruchomić migrację i dodać dodatkowy ordering pole do tabeli M2M.

Edycja: Najwyraźniej nazwy kolumn w DB są wytwarzane w nieco inny sposób do automatycznego tabelach M2M dla modeli-zdefiniowane tabelach. (Ja Django 1.9.3).

Po procedurze opisanej miałem również zmienić ręcznie kolumn w polu o nazwie 2-słowa (two_words=models.ForeignKey(...)) z twowords_id do two_words_id.

+1

Tak, to podejście działa. dla nazwy kolumny po prostu użyłem kolumny db_column, która pasuje do poprzedniej nazwy kolumny. – User707