Christian Haintz
Gründer und CTO von Carrot & Company. Schreibt über Technologie und Business Themen.
37 Minuten

Migriere deine Wagtail Website von wagtailtrans auf das neue wagtail-localize

37 Minuten
Article posted on January 10, 2021
So migrierst du eine mehrsprachige Wagtail-Website von wagtailtrans zum neuen nativen wagtail translation system, vorgestellt in Version 2.11, und zu wagtail-localize, dem neuen Translation-Frontend.

Dieser Artikel beschreibt eine Migrations-Strategie, mit der du existierende Wagtail -Websites (Version <2.11) mit wagtailtrans auf die neue Version 2.11 updaten kannst und gleichzeitig Übersetzungen von wagtailtrans auf das neue interne Translation System von Wagtail migrierst. Weil das interne Translation System ohne Admin-Frontend kommt, installieren wir auch wagtail-localize . Dieses moderne Übersetzungs-Frontend kann neben der manuellen Übersetzung auch deinen existierenden Content mit Übersetzern wie zum Beispiel DeepL übersetzen.

In diesem Artikel konzentrieren wir uns auf die Migration bestehender mehrsprachiger Inhalte in das neue Wagteil -System, das Hinzufügen von wagtail-localize und - nachdem wir es dann nicht mehr brauchen - auf das Entfernen von wagtailtrans aus deinem Wagtail-System.

Disclamer : Stell sicher, dass du ein Backup deiner Datenbank und deines Sourcecodes hast. Und wir raten dazu, die kompletten Migrationsschritte vorher in einer Development-Umgebung zu testen (nicht direkt am Produktivsystem). Einige Teile können variieren, je nachdem wie du wagtailtrans in dein System integriert hast. Dieser Ansatz hat für unser Setup funktioniert, muss aber vielleicht für deines angepasst werden.

Der Zugang

Die Idee basiert auf Karl Hobleys Vorschlag von der Github Issue #297 . Wir haben sie aber ein wenig angepasst.

  1. Mach ein Backup von deiner Datenbank und deinem Code.
  2. Installiere die Migration Fork von Github
  3. Upgrade wagtail auf 2.11+ und installiere wagtail-localize
  4. Migriere die Datenbank-Schemas und Data
  5. Entferne wagtailtrans

Bedingungen

Das bestehende Setup, das in diesem Beispiel aktualisiert wird, ist wagtail Version 2.10.2 und wagtailtrans Version 2.2.1 mit django 3.1.2. Wir gehen davon aus, dass wir eine laufende Website mit wagtailtrans konfiguriert haben und dass sie funktioniert.

Mach ein Backup von deiner DB und deinem Code

Wahrscheinlich hast du bereits eine anständige Backup-Strategie und deinen Code in einem Versionssystem, also sind wir mit diesem Schritt fertig. Wenn du keine hast, können wir dir Django DB Backup und git für die Quellcode-Versionierung empfehlen.

Installiere den Migration Fork von wagtailtrans

Um ein Upgrade auf wagtail 2.11 durchführen zu können, müssen wir eine forked version von wagtailtrans installieren (basierend auf 2.2.1). Wir tun das, weil einige der Funktionen von wagtailtrans nun nativ in wagtail ausgeführt werden. Daher müssen wir etwas Code anpassen, damit sie parallel arbeiten und sich möglichst nicht in die Quere kommen, damit wir die Migration durchführen können.

pip git+https://github.com/carrotandcompany/wagtailtrans@master#egg=wagtailtrans

Installiere das neue Wagtail und wagtail-localize

Nach der Installation der Forked Version können wir Wagtail und Wagtailtrans upgraden. Ändere dafür die requirements.txt

wagtail==2.11.3
# wagtailtrans==2.2.1  <-- remove this line as we manually installed the forked version for migration
wagtail-localize==0.9.4

Installiere es

pip install -r requirements.txt

Setze die Internationalization mit wagtail auf

Dieser Schritt basiert auf den Wagtail docs und wagtail-localize Readme .

Änderungen in django settings.py

Wir müssen einige Django Einstellungen setzen, damit Wagtail das interne Übersetzungssystem aktivieren und verwenden kann.

LANGUAGE_CODE = 'en'

Füge wagtail_localize hinzu.

INSTALLED_APPS = [
    ....
    'wagtailtrans' # leave it in for now, as we need it for migration
    'wagtail_localize',
    'wagtail_localize.locales'
]

Ersetze die wagtailtrans middleware mit der von wagtail.

MIDDLEWARE = [
    ...,
    # 'wagtailtrans.middleware.TranslationMiddleware',  # <-- remove this line 
    'django.middleware.locale.LocaleMiddleware', #  <-- add this line instead 
    ...,
]

Activiere i18n mit folgenden Einstellungen (Abweichungen je nach deinem Setup, Ländereinstellungen möglich /wahrscheinlich).

USE_I18N = True
USE_L10N = True
WAGTAIL_I18N_ENABLED = True
WAGTAIL_CONTENT_LANGUAGES = LANGUAGES = [
    ('en', "English"),
    ('de', "German"),
]

Änderungen in urls.py

Da wagtail jetzt Djangos eigenes i18 System verwendet, müssen wir die wagtails URLs in einen i18_patterns Block einbetten. Dieser Abschnitt basiert auf der wagtail-localize Readme .

from django.conf.urls.i18n import i18n_patterns
...

urlpatterns += i18n_patterns(
    ...,
    url(r"", include(wagtail_urls)),
)

Migriere die Datenbank

Nach der Installation und dem Setup müssen wir die Migration der installierten Packages ausführen. In der Forked Version von wagtailtrans gibt es eine zusätzlichen Data Migrations, die:

  • wagtailtrans' Language model zum wagtail Local model migriert
  • wagtails neue translation_key and locale page property basierend auf den wagtailtrans Daten setzt.

Wenn du wissen willst, was die Migration im Detail macht, schau dir 0010_migrate_info_to_wagtail.py an.

Um die Migrationen nach der Installation des wagtailtrans-Migrationsforks tatsächlich auszuführen, führe folgendes aus

python manage.py migrate

Verschiebe deine Translation Sites

Wagtailtrans verwendet den Ansatz einer Translation-Root-Seite für jede gewünschte mehrsprachige Site. Der Ansatz in wagtail ist anders. Jede Sprachvariantenseite befindet sich direkt unterhalb der Wagtail Root Page. Das bedeutet, dass wir alle Sprachunterseiten von der Translation-Root-Page auf die Wagtail Root Page verschieben müssen.

Ändere die Site Root Site

Das geht im Wagtail Admin unter dem Menüpunkt 'Settings' --> 'Sites'. Passe deine Sites an und ändere die das Feld "Root Site" entsprechend der vorhin verschobenen Sprachvariantenseiten (wähle die Default Sprach-Version aus).

Entferne die wagtailtrans Translation Root Page.

Im Wagtail-Admin solltest du nun deine verschobenen Sprachvariantenseiten und die Translation Root Sites von wagtailtrans sehen. Letztere sollten keine Unterseiten mehr haben, da wir sie zuvor alle in die Root verschoben haben. Nun können wir alle leeren Translation Root Sites löschen. Du kannst sie am Typ erkennen: Translatable site root page .

Zu diesem Zeitpunkt sollte deine Seite schon wieder funktionieren. Wir müssen nur noch wagtailtrans sauber von deinem System entfernen.

Wagtailtrans sauber entfernen

Wagtailtrans ist normalerweise sehr tief in unsere Website integriert, da jede übersetzte Custom Page von der 'TranslatablePage' erbt. Um diese Beziehung sauber zu entfernen, müssen wir die folgenden Schritte durchführen:

  1. Page als zusätzliche Eltern-Klassen-Modell zu allen benutzerdefinierten Seitenmodellklassen hinzufügen, die von TranslatablePage erben
  2. die Migrationsdatei für diese Änderung erstellen
  3. die Migration anpassen
  4. die Migration ausführen
  5. TranslatablePage aus allen benutzerdefinierten Seitenmodellen entfernen
  6. Migrationen generieren und erneut migrieren
  7. wagtailtrans aus alten Migrationen entfernen
  8. Wagtailtrans aus dem Quellcode entfernen
  9. die wagtailtrans-Tabellen löschen

Das Page model als Parent-Klasse hinzufügen

Zu allen unseren Seitenmodellklassen, die von der TranslatablePage erben, müssen wir zusätzlich auch von Page ableiten.

YourPage(TranslatablePage, Page):
    ...

Migrationsdatei generieren

Wir generieren jetzt die db-Migrationen, und da es keinen Standardwert für page_ptr gibt, müssen wir einen setzen . Wir setzen ihn auf -1 (keine Sorge, wir ändern ihn im nächsten Schritt)

python manage.py makemigrations

You are trying to add a non-nullable field 'page_ptr' to flexpage without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> -1
Migrations for 'website':
  webapp/website/migrations/0002_yourpage_page_ptr.py
    - Add field page_ptr to yourpage

Die Migration anpassen

Wir müssen die zuvor erstellte Migrationsdatei 0002_yourpage_page_ptr.py ändern (der tatsächliche Name wird bei dir anders lauten), weil der Wert -1 einfach willkürlich ist. Was wir dort wirklich brauchen, ist die ID des Eltern-Seiten-Models (wegen der Django Table Inheritance). Die ID dieser Seite ist bereits gespeichert, aber in einer anderen Spalte translatable_page_ptr . Also müssen wir die Migrationsdatei bearbeiten, um diese Information in das Feld "page_ptr" zu übertragen.

Das funktioniert durch Änderungen im Migration File:

  1. Entferne den Standardwert des Eintrags AddField (Du weißt schon, die -1 vom letzten Schritt.)
  2. Dupliziere das "AddField" und benenne den unteren Eintrag in "AlterField" um.
  3. Füge die Argumente blank=True und null=True zum AddField-Eintrag hinzu. Damit wir die neue Spalte anlegen können ohne Werte für page_ptr zu Beginn zu haben.
  4. Wir müssen also sicherstellen, dass wir die IDs von der alten TranslatablePage zur neuen Page zwischen den Einträgen AddField und AlterField übertragen haben. Also fügen wir einen RunPython -Eintrag hinzu in dem wir diese Übertragung machen.

Hier stellen wir das finale modifizierte Migration Script für eine Custom Page namens 'yourpage' zur Verfügung. Du brauchst eine ähnliche Migration für jede deiner Custom Pages.

def transfer_ids(apps, schema_editor):
    YourPage = apps.get_model('website', 'YourPage')
    for page in YourPage.objects.all():
        # copy the page pointer from wagtailtrans to the new wagtail page
        page.page_ptr = page.translatable_page_ptr
        page.save()


class Migration(migrations.Migration):

    dependencies = [
        ('wagtailcore', '0059_apply_collection_ordering'),
        ('website', '0001_gen'),
    ]

    operations = [
        migrations.AddField(
            model_name='yourpage',
            name='page_ptr',
            field=models.OneToOneField(auto_created=True, blank=True, null=True,
                                       on_delete=django.db.models.deletion.CASCADE,
                                       parent_link=False, to='wagtailcore.page'),
            preserve_default=False,
        ),
        migrations.RunPython(transfer_ids),
        migrations.AlterField(
            model_name='yourpage',
            name='page_ptr',
            field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='wagtailcore.page'),
            preserve_default=False,
        ),
    ]

Die Migration ausführen

Führe die modifizierte Migration aus:

python manage.py migrate

TranslatablePage entfernen

Jetzt, da die Datenbank migriert ist, können wir endlich die TranslatablePage aus den Custom Page Classes entfernen.

Ändere dazu von

YourPage(TranslatablePage, Page):
    ...

auf die neue Modelklassen Signatur:

YourPage(Page):
    ...

Nicht vergessen: Wir müssen auch die Importe von Wagtailtrains entfernen, wenn wir sie nicht mehr brauchen.

Erneut Migrations generieren

Jetzt müssen wir die Migrations erstellen, in welcher die translatable_page_ptr von der DB Tabelle und unseren eignen Page Modelle entfernt wird.

python manage.py makemigrations

Danach migrieren wir wieder die Datenbank:

python manage.py migrate

wagtailtrans von den alten Migrations entfernen

Wagtailtrans ist Teil der bestehenden Migrationsdateien unserer custom Page Models. Wir müssen es entfernen, bevor wir die wagtailtrans-App deaktivieren und deinstallieren können. Es gibt verschiedene Ansätze, um es aus den Migrationsdateien zu entfernen. Wir wollen hier eine mögliche Lösung erläutern, die weder alte Datanbank States vor wagtailtrans noch Datenbank-Versionen mit bereits angewendeten wagtailtrans-Migrationen negativ beeinflusst. Für die erste Situation mocken wir wagtailtrans und für die zweite Situation gilt unsere Migrationslogik.

Um dieses Verhalten zu erreichen, werfen wir einen Blick auf eine typische bestehende Migrationsdatei, die eine benutzerdefinierte Seite erstellt. (Unberührt) Sollte das sehr ähnlich wie das hier aussehen:

class Migration(migrations.Migration):

    initial = True

    dependencies = [
       ('wagtailtrans', '0009_create_initial_language'),
    ]

    operations = [
        migrations.CreateModel(
            name='YourPage',
            fields=[
                ('translatablepage_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailtrans.translatablepage')),
                ('body', wagtail.core.fields.StreamField([...])),
            ],
            options={
                'abstract': False,
            },
            bases=('wagtailtrans.translatablepage',),
        ),
    ]

Unser Ansatz ist, alle Vorkommen von wagtailtrans in solchen Migrationen mit den folgenden Schritten zu entfernen.

  1. wagtailtrans Dependencies mit wagtailcore ersetzen
  2. translatablepage_ptr Felder mit einem IntegerField mocken
  3. Bases, die abhängig von translatablepage sind, entfernen

Von den Dependencies entfernen

Im Abschnitt "Dependencies" können wir wagtailtrans entfernen und die Migration von einer anderen vorherigen Migration abhängig machen. Zum Beispiel können wir die Wagtail-Migration ('wagtailcore', '0059_apply_collection_ordering'), verwenden.

dependencies = [
   ('wagtailtrans', '0009_create_initial_language'),
]

translatablepage_ptr Felder mit einem IntegerField mocken

Der nächste Teil, der wagtailtrans verwendet, ist die translatablepage_ptr in der CreateModel Operation. Wir simulieren es einfach mit einem IntegerField. Warum das funktioniert? Wenn wir diese Migrationsdatei bereits in einer DB angewendet haben, wird sie nicht mehr ausgeführt, sodass keine bestehenden translatablepage_ptrs überschrieben werden. Wenn wir diese Migration noch nicht angewendet haben, können wir sicher sein, dass wir keine übersetzten Inhalte für diese Seiten haben, also können wir einfach ein IntegerField erstellen, das leer ist, da es in den Migrationsdateien, die wir vorher erstellt haben, entfernt wird, sobald unsere vollständige Migrationslogik ausgeführt wird.

...
operations = [
    migrations.RemoveField( # <-- this is were we are removing the translatablepage_ptr field again
        model_name='yourpage',
        name='translatablepage_ptr',
    ),
    migrations.AlterField(
        model_name='yourpage',
        name='page_ptr',
        field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page'),
    ),
]
...

Aus diesem Grund ist dieser Ansatz ein sicherer.

Basisklassen, die abhängig von translatablepage sind, entfernen

Der letzte Teil, in dem wir einen Verweis auf wagtailtrans haben, ist die Basisklasse unserer Seite. Wir können diese Ableitung einfach entfernen, da wir sie nicht benötigen, da wir wagtailtrans ohnedies entfernen werden und diese Ableitung bei der Migration ohnehin nicht verwendet wird.

migrations.CreateModel(
            name='YourPage',
            fields=[
                ('translatablepage_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailtrans.translatablepage')),
                ('body', wagtail.core.fields.StreamField([...])),
            ],
            options={
                'abstract': False,
            },
            bases=('wagtailtrans.translatablepage',), # <-- simply remove this whole line 
        ),

Jetzt sollte es keine Referenzen auf Wagtailtrans mehr in unserem Source Code der Migration geben.

wagtailtrans von der source entfernen

Jetzt ist ein guter Zeitpunkt, um die übrigen Quellcode zu durchsuchen und alle Verweise auf wagtailtrans zu entfernen/ersetzen. In den Einstellungen können wir wagtailtrans sicher aus den INSTALLED_APPS entfernen.

INSTALLED_APPS = [
    ....
    'wagtailtrans'   # <-- remove this line
    'wagtail_localize',
    'wagtail_localize.locales'
]

wagtailtrans deinstallieren

Nachdem wir nun alles migriert haben, können wir unsere Forked Version von wagtailtrans sicher deinstallieren.

pip uninstall wagtailtrans

Die wagtailtrans Datenbanktabellen löschen

Schließlich können wir die DB-Tabellen von wagtailtrans entfernen. Lösche die folgenden Tabellen in deinem bevorzugten Datenbank-Frontend.

drop table wagtailtrans_translatablepage;
drop table wagtailtrans_translatablesiterootpage;
drop table wagtailtrans_sitelanguages_other_languages;
drop table wagtailtrans_sitelanguages;
drop table wagtailtrans_language;

Das war's! Wir verwenden wagtail auch als Teil unseres SaaS Development Kit Carrot Seed .

Wir verwenden Cookies 🍪