[ English | 中文 (简体, 中国) | русский | português (Brasil) | नेपाली | 한국어 (대한민국) | Indonesia | français | español | esperanto | English (United Kingdom) | Deutsch ]

Tutorial: Membuat Plugin Horizon

Mengapa saya harus mengemas kode saya sebagai plugin?

Kami sangat menyarankan Anda menulis dan memelihara kode Anda menggunakan arsitektur plugin kami. Plugin menurut definisi berarti kemampuan untuk terhubung. Secara praktis, plugin adalah cara untuk memperluas dan menambah fungsionalitas yang sudah ada. Anda dapat mengontrol kontennya dan berkembang pada tingkat yang tidak tergantung pada Horizon. Jika Anda menulis dan mengemas kode Anda sebagai plugin, itu akan terus berfungsi di rilis mendatang.

Menulis kode Anda sebagai plugin juga memodulasi kode Anda sehingga lebih mudah untuk menerjemahkan dan menguji. Ini juga membuatnya lebih mudah bagi para penyebar untuk menggunakan kode Anda yang memungkinkan pemberdayaan fitur secara selektif. Kami saat ini menggunakan pola ini secara internal untuk dasbor kami.

Membuat Plugin

Tutorial ini mengasumsikan Anda memiliki pemahaman dasar tentang Python, HTML, JavaScript. Pengetahuan tentang AngularJS adalah opsional tetapi disarankan jika Anda mencoba membuat plugin Angular.

Nama repositori Anda

Tak perlu dikatakan, penting untuk memilih nama repositori yang bermakna.

Selain itu, jika Anda berencana untuk mendukung terjemahan pada plugin dashboard Anda, disarankan untuk memilih nama seperti xxxx-dashboard (atau xxxx-ui. xxxx-horizon). Skrip infra CI OpenStack menganggap repositori dengan sufiks ini sebagai proyek Django.

Jenis-jenis Plugin yang menambahkan konten

Struktur file untuk tipe plugin Anda akan berbeda tergantung pada kebutuhan Anda. Plugin Anda dapat dikategorikan ke dalam dua jenis:

  • Pengaya (plugin) yang membuat panel atau dasbor baru

  • Plugin yang mengubah alur kerja, tindakan, dll ... (hanya Angular)

Kami akan membahas dasar-dasar bekerja dengan panel untuk Python dan Angular. Jika Anda tertarik untuk membuat panel baru, ikuti langkah-langkah di bawah ini.

Catatan

Tutorial ini menunjukkan kepada Anda cara membuat panel baru. Jika Anda tertarik untuk membuat plugin dasbor baru, gunakan struktur file dari :ref: tutorials-dashboard.

Struktur File

Di bawah ini adalah kerangka tampilan plugin Anda.

myplugin
│
├── myplugin
│   ├── __init__.py
│   │
│   ├── enabled
│   │   └──_31000_myplugin.py
│   │
│   ├── api
│   │   ├──__init__.py
│   │   ├── my_rest_api.py
│   │   └── myservice.py
│   │
│   ├── content
│   │   ├──__init__.py
│   │   └── mypanel
│   │       ├── __init__.py
│   │       ├── panel.py
│   │       ├── tests.py
│   │       ├── urls.py
│   │       ├── views.py
│   │       └── templates
│   │           └── mypanel
│   │               └── index.html
│   │
│   └── static
│   |   └── dashboard
│   |       └── identity
│   |           └── myplugin
│   |               └── mypanel
│   |                   ├── mypanel.html
│   |                   ├── mypanel.js
│   |                   └── mypanel.scss
│   │
│   └── locale
│       └── <lang>
│            └── LC_MESSAGES
│                ├── django.po
│                └── djangojs.po
│
├── setup.py
├── setup.cfg
├── LICENSE
├── MANIFEST.in
├── README.rst
├── babel-django.cfg
└── babel-djangojs.cfg

Jika Anda membuat plugin Python, Anda dapat mengabaikan folder static. Sebagian besar kelas yang Anda butuhkan disediakan dengan Python. Jika Anda ingin menambahkan logika front-end kustom, Anda harus memasukkan JavaScript tambahan di sini.

Plugin AngularJS adalah kumpulan file JavaScript atau sumber daya statis. Karena sepenuhnya berjalan di browser Anda, kami perlu menempatkan semua sumber daya statis kami di dalam folder static. Ini memastikan bahwa kolektor statis Django mengambilnya dan mendistribusikannya ke browser dengan benar.

File Diaktifkan

Folder yang diaktifkan berisi file konfigurasi yang mendaftarkan plugin Anda dengan Horizon. File diawali dengan string alpha-numeric yang menentukan urutan pemuatan plugin Anda. Untuk informasi lebih lanjut tentang apa yang bisa Anda sertakan dalam file ini, lihat pengaturan pluggable di Referensi Pengaturan.

_31000_myplugin.py:

# The name of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'mypanel'

# The name of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'identity'

# Python panel class of the PANEL to be added.
ADD_PANEL = 'myplugin.content.mypanel.panel.MyPanel'

# A list of applications to be prepended to INSTALLED_APPS
ADD_INSTALLED_APPS = ['myplugin']

# A list of AngularJS modules to be loaded when Angular bootstraps.
ADD_ANGULAR_MODULES = ['horizon.dashboard.identity.myplugin.mypanel']

# Automatically discover static resources in installed apps
AUTO_DISCOVER_STATIC_FILES = True

# A list of js files to be included in the compressed set of files
ADD_JS_FILES = []

# A list of scss files to be included in the compressed set of files
ADD_SCSS_FILES = ['dashboard/identity/myplugin/mypanel/mypanel.scss']

# A list of template-based views to be added to the header
ADD_HEADER_SECTIONS = ['myplugin.content.mypanel.views.HeaderView',]

Catatan

Saat ini, AUTO_DISCOVER_STATIC_FILES = True hanya akan menemukan file JavaScript, bukan file SCSS.

my_rest_api.py

File ini kemungkinan akan diperlukan jika membuat plugin menggunakan Angular. Plugin Anda perlu berkomunikasi dengan layanan baru atau memerlukan interaksi baru dengan layanan yang sudah didukung oleh Horizon. Dalam contoh khusus ini, plugin akan menambah dukungan untuk layanan Identity yang sudah didukung, Keystone. File ini berfungsi untuk mendefinisikan antarmuka REST baru untuk sisi klien plugin untuk berkomunikasi dengan Horizon. Biasanya, antarmuka REST di sini membuat panggilan ke myservice.py.

File ini tidak diperlukan dalam plugin murni berbasis Django, atau jika plugin berbasis Angular Anda mengandalkan dukungan CORS dalam layanan yang diinginkan. Untuk informasi lebih lanjut tentang CORS, lihat https://docs.openstack.org/oslo.middleware/latest/admin/cross-project-cors.html

myservice.py

File ini kemungkinan akan diperlukan jika membuat plugin yang didorong oleh Django atau Angular. File ini dimaksudkan untuk bertindak sebagai lokasi yang nyaman untuk berinteraksi dengan layanan baru yang didukung plugin ini. Sementara interaksi dengan layanan dapat ditangani di views.py, mengisolasi logika adalah pola yang sudah ada di Horizon.

panel.py

Kami menetapkan panel tempat konten plugin kami berada. Saat ini merupakan keharusan bahkan untuk plugin Angular. Slug adalah pengidentifikasi unik panel dan sering digunakan sebagai bagian dari URL. Pastikan itu cocok dengan apa yang Anda miliki di file yang diaktifkan.

from django.utils.translation import gettext_lazy as _
import horizon


class MyPanel(horizon.Panel):
    name = _("My Panel")
    slug = "mypanel"

tests.py

Tulis beberapa tes untuk bagian Django dari plugin Anda dan letakkan di sini.

urls.py

Sekarang kami memiliki panel, kami perlu menyediakan URL agar pengguna dapat mengunjungi panel baru kami! URL ini umumnya akan mengarah ke view.

from django.urls import re_path

from myplugin.content.mypanel import views

urlpatterns = [
    re_path(r'^$', views.IndexView.as_view(), name='index'),
]

views.py

Karena rendering dilakukan di sisi klien, semua kebutuhan tampilan kami adalah untuk merujuk beberapa halaman HTML. Jika Anda menulis plugin Python, view ini bisa jauh lebih kompleks. Lihat panduan topik untuk lebih jelasnya.

from django.views import generic


class IndexView(generic.TemplateView):
    template_name = 'identity/mypanel/index.html'

index.html

Indeks HTML adalah tempat rendering terjadi. Dalam contoh ini, kami hanya menggunakan Django. Jika Anda tertarik menggunakan arahan Angular, baca bagian AngularJS di bawah ini

{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "My plugin" %}{% endblock %}

{% block page_header %}
  {% include "horizon/common/_domain_page_header.html" with title=_("My Panel") %}
{% endblock page_header %}

{% block main %}
  Hello world!
{% endblock %}

Pada titik ini, Anda memiliki plugin yang sangat mendasar. Perhatikan bahwa templat baru diperlukan untuk memperpanjang base.html. Termasuk base.html penting karena sejumlah alasan. Ini adalah templat yang berisi semua sumber daya statis Anda beserta fungsi apa pun di luar panel Anda (hal-hal seperti navigasi, pemilihan konteks, dll ...). Pada saat ini, ini juga berlaku untuk plugin Angular.

MANIFEST.in

File ini bertanggung jawab untuk mendaftarkan jalur yang ingin Anda sertakan dalam tar Anda.

include setup.py

recursive-include myplugin *.js *.html *.scss

setup.py

import setuptools

setuptools.setup(
    setup_requires=['pbr>=2.0.0'],
    pbr=True)

setup.cfg

[metadata]
name = myplugin
summary = A panel plugin for OpenStack Dashboard
description_file =
    README.rst
author = myname
author_email = myemail
home_page = __REPLACE_YOUR_PLGUIN_HOMEPAGE_URL__
classifier =
    Environment :: OpenStack
    Framework :: Django
    Intended Audience :: Developers
    Intended Audience :: System Administrators
    License :: OSI Approved :: Apache Software License
    Operating System :: POSIX :: Linux
    Programming Language :: Python
    Programming Language :: Python :: 3 :: Only
    Programming Language :: Python :: 3
    Programming Language :: Python :: 3.6
    Programming Language :: Python :: 3.7
    Programming Language :: Python :: 3.8
    Programming Language :: Python :: 3.9

[files]
packages =
    myplugin

AngularJS Plugin

Jika Anda tidak memiliki rencana untuk menambahkan AngularJS ke plugin Anda, Anda dapat melewati bagian ini. Dalam tutorial di bawah ini, kami akan menunjukkan kepada Anda bagaimana menyesuaikan panel Anda menggunakan Angular.

index.html

Indeks HTML adalah tempat rendering terjadi dan berfungsi sebagai titik masuk untuk Angular. Di sinilah kita mulai menyimpang dari plugin Python tradisional. Dalam contoh ini, kami menggunakan templat Django sebagai perekat pada templat Angular kami. Mengapa kita melalui templat Django untuk plugin Angular? Singkat cerita, base.html berisi bagian navigasi yang masih kita butuhkan untuk setiap panel.

{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "My panel" %}{% endblock %}

{% block page_header %}
  <hz-page-header
    header="{$ 'My panel' | translate $}"
    description="{$ 'My custom panel!' | translate $}">
  </hz-page-header>
{% endblock page_header %}

{% block main %}
  <ng-include src="'{{ STATIC_URL }}dashboard/identity/myplugin/mypanel/mypanel.html'">
  </ng-include>
{% endblock %}

Template ini mengandung kode Django dan AngularJS. Angular dilambangkan dengan {$..$} sementara Django dilambangkan dengan {{..}} dan {%..%}. Template ini diproses dua kali, sekali oleh Django di sisi server dan sekali lagi oleh Angular di sisi klien. Ini berarti bahwa ekspresi dalam {{..}} dan {%..%} diganti dengan nilai pada saat mencapai templat Angular Anda.

Apa yang Anda pilih untuk dimasukkan dalam block main sepenuhnya terserah Anda. Karena Anda membuat plugin Angular, kami sarankan Anda menyimpan semuanya di bagian Angular. Jangan campur kode Python di sini! Jika Anda menemukan diri Anda mengirimkan data Python, lakukan itu melalui layanan REST kami.

Ingatlah untuk selalu menggunakan STATIC_URL saat merujuk sumber daya statis Anda. Ini memastikan bahwa perubahan pada jalur statis dalam pengaturan akan terus melayani sumber daya statis Anda dengan benar.

Catatan

Arahan Angular diawali dengan ng. Demikian pula, arahan Horizon diawali dengan hz. Anda dapat menganggapnya sebagai ruang nama (namespace).

mypanel.js

Pengontrol Anda adalah perekat antara model dan view. Dalam contoh ini, kita akan memberikan beberapa data palsu untuk ditampilkan (render). Untuk memuat data yang lebih kompleks, pertimbangkan untuk menggunakan layanan $http.

(function() {
  'use strict';

  angular
    .module('horizon.dashboard.identity.myplugin.mypanel', [])
    .controller('horizon.dashboard.identity.myPluginController',
      myPluginController);

  myPluginController.$inject = [ '$http' ];

  function myPluginController($http) {
    var ctrl = this;
    ctrl.items = [
      { name: 'abc', id: 123 },
      { name: 'efg', id: 345 },
      { name: 'hij', id: 678 }
    ];
  }
})();

Ini adalah contoh dasar di mana kami mengejek data. Untuk latihan, muat data Anda menggunakan layanan $http.

mypanel.html

Ini adalah pandangan kami. Dalam contoh ini, kami mengulang daftar item yang disediakan oleh controller dan menampilkan nama dan id. Yang penting untuk dicatat adalah referensi ke controller kami menggunakan direktif ng-controller.

<div ng-controller="horizon.dashboard.identity.myPluginController as ctrl">
  <div>Loading data from your controller:</div>
  <ul>
    <li ng-repeat="item in ctrl.items">
      <span class="c1">{$ item.name $}</span>
      <span class="c2">{$ item.id $}</span>
    </li>
  </ul>
</div>

mypanel.scss

Anda dapat memilih untuk menyesuaikan panel Anda dengan memberikan scss Anda sendiri. Pastikan Anda memasukkannya ke dalam file yang diaktifkan melalui pengaturan ADD_SCSS_FILES.

Dukungan Terjemahan

Instruksi umum tentang cara mengaktifkan dukungan terjemahan dijelaskan dalam Infrastructure User Manual 1.

Bagian ini menjelaskan topik khusus untuk plugin Horizon.

ADD_INSTALLED_APPS

Pastikan untuk memasukkan <modulename> `` (``myplugin dalam contoh ini) dalam ADD_INSTALLED_APPS dalam file enabled terkait.

  • Jika Anda menyiapkan plugin baru, Anda akan menggunakan <modulename> `` sebagai ``INSTALLED_APPS dalam banyak kasus seperti yang disarankan dalam tutorial ini. Ini bagus dan tidak ada lagi yang bisa dilakukan.

  • Jika karena alasan tertentu plugin Anda perlu mendaftarkan modul python lain ke ADD_INSTALLED_APPS, pastikan Anda menyertakan ``<modulename> `` tambahannya.

Ini berasal dari kombinasi dua alasan berikut.

  • Django mencari katalog pesan terjemahan dari setiap jalur yang ditentukan dalam INSTALLED_APPS [#] _.

  • Skrip infra OpenStack mengasumsikan katalog pesan terjemahan ditempatkan di bawah <modulename>/locale (misalnya myplugin/locale).

1

https://docs.openstack.org/infra/manual/creators.html#enabling-translation-infrastructure

2

https://docs.djangoproject.com/es/1.9/topics/i18n/translation/#how-django-discovers-translations

myplugin/locale

File katalog pesan yang diterjemahkan (file PO) ditempatkan di bawah direktori ini.

babel-django.cfg, babel-djangojs.cfg

File ini digunakan untuk mengekstraksi pesan dengan pybabel: babel-django.cfg untuk kode python dan file templat, dan babel-djangojs.cfg untuk file JavaScript.

Mereka diminta untuk mengaktifkan dukungan terjemahan oleh OpenStack CI infra. Jika tidak ada, pekerjaan terjemahan akan melewati pemrosesan untuk proyek Anda.

Menginstal Plugin Anda

Sekarang Anda memiliki plugin yang lengkap, sekarang saatnya untuk menginstal dan mengujinya. Petunjuk di bawah ini menganggap bahwa Anda memiliki plugin yang berfungsi.

  • plugin adalah lokasi plugin Anda

  • ``horizon``adalah lokasi horizon

  • `` paket`` adalah nama lengkap dari plugin yang Anda paket

  1. Jalankan "cd` plugin` & python setup.py sdist"

  2. Jalankan "cp -rv enabled horizon/openstack_dashboard/local/"

  3. Jalankan "horizon/tools/with_venv.sh pip install dist/package.tar.gz"

  4. Restart Apache atau server uji Django Anda

Catatan

Langkah 3 instal paket Anda ke lingkungan virtual Horizon. Anda dapat menginstal plugin Anda tanpa menggunakan with_venv.sh dan pip. Paket hanya akan diinstal di PYTHON_PATH sistem sebagai gantinya.

Jika Anda dapat menekan pola URL di urls.py di browser Anda, Anda telah berhasil menggunakan plugin Anda! Untuk plugin yang tidak memiliki URL, periksa apakah sumber daya statis Anda dimuat menggunakan inspektur browser.

Dengan asumsi Anda menerapkan my_rest_api.py, Anda dapat menggunakan klien REST untuk menekan url secara langsung dan mengujinya. Seharusnya ada banyak klien REST yang tersedia di browser web Anda.

Perhatikan bahwa Anda mungkin perlu membangun kembali lingkungan virtual Anda jika plugin Anda tidak muncul dengan benar. Jika plugin Anda tidak muncul dengan benar, periksa folder .tox Anda untuk memastikan konten plugin sesuai dengan yang Anda harapkan.

Catatan

Untuk menghapus instalan, gunakan pip uninstall. Anda juga perlu menghapus file yang diaktifkan dari folder local/enabled.