Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Python REST API Dengan Flask, Connexion, dan SQLAlchemy – Bagian 3

Di Bagian 2 seri ini, Anda menambahkan kemampuan untuk menyimpan perubahan yang dibuat melalui REST API ke database menggunakan SQLAlchemy dan mempelajari cara membuat serial data tersebut untuk REST API menggunakan Marshmallow. Menghubungkan REST API ke database sehingga aplikasi dapat membuat perubahan pada data yang ada dan membuat data baru sangat bagus dan membuat aplikasi jauh lebih berguna dan tangguh.

Namun, itu hanya sebagian dari kekuatan yang ditawarkan database. Fitur yang lebih hebat lagi adalah R bagian dari RDBMS sistem:hubungan . Dalam database, hubungan adalah kemampuan untuk menghubungkan dua atau lebih tabel bersama-sama dengan cara yang berarti. Dalam artikel ini, Anda akan mempelajari cara menerapkan hubungan dan mengubah Person . Anda database ke dalam aplikasi web mini-blogging.

Dalam artikel ini, Anda akan mempelajari:

  • Mengapa lebih dari satu tabel dalam database berguna dan penting
  • Bagaimana tabel terkait satu sama lain
  • Bagaimana SQLAlchemy dapat membantu Anda mengelola hubungan
  • Bagaimana hubungan membantu Anda membangun aplikasi mini-blogging

Untuk Siapa Artikel Ini

Bagian 1 dari seri ini memandu Anda dalam membangun REST API, dan Bagian 2 menunjukkan cara menghubungkan REST API ke database.

Artikel ini memperluas sabuk alat pemrograman Anda lebih jauh. Anda akan belajar cara membuat struktur data hierarkis yang direpresentasikan sebagai hubungan satu-ke-banyak oleh SQLAlchemy. Selain itu, Anda akan memperluas REST API yang telah Anda buat untuk memberikan dukungan CRUD (Buat, Baca, Perbarui, dan Hapus) untuk elemen dalam struktur hierarki ini.

Aplikasi web yang disajikan di Bagian 2 akan memiliki file HTML dan JavaScript yang dimodifikasi dengan cara utama untuk membuat aplikasi mini-blogging yang lebih berfungsi penuh. Anda dapat meninjau versi final kode dari Bagian 2 di repositori GitHub untuk artikel tersebut.

Tunggu saat Anda mulai membuat hubungan dan aplikasi mini-blogging Anda!



Ketergantungan Tambahan

Tidak ada dependensi Python baru di luar apa yang diperlukan untuk artikel Bagian 2. Namun, Anda akan menggunakan dua modul JavaScript baru di aplikasi web untuk membuat segalanya lebih mudah dan lebih konsisten. Kedua modul tersebut adalah sebagai berikut:

  1. Stang.js adalah mesin templating untuk JavaScript, seperti Jinja2 untuk Flask.
  2. Moment.js adalah modul penguraian dan pemformatan datetime yang membuat tampilan stempel waktu UTC lebih mudah.

Anda tidak perlu mengunduh salah satu dari ini, karena aplikasi web akan mendapatkannya langsung dari Cloudflare CDN (Content Delivery Network), seperti yang sudah Anda lakukan untuk modul jQuery.



Data Orang Diperluas untuk Blogging

Di Bagian 2, Person data ada sebagai kamus di build_database.py kode python. Inilah yang Anda gunakan untuk mengisi database dengan beberapa data awal. Anda akan mengubah Person struktur data untuk memberi setiap orang daftar catatan yang terkait dengan mereka. Person new yang baru struktur data akan terlihat seperti ini:

# Data to initialize database with
PEOPLE = [
    {
        "fname": "Doug",
        "lname": "Farrell",
        "notes": [
            ("Cool, a mini-blogging application!", "2019-01-06 22:17:54"),
            ("This could be useful", "2019-01-08 22:17:54"),
            ("Well, sort of useful", "2019-03-06 22:17:54"),
        ],
    },
    {
        "fname": "Kent",
        "lname": "Brockman",
        "notes": [
            (
                "I'm going to make really profound observations",
                "2019-01-07 22:17:54",
            ),
            (
                "Maybe they'll be more obvious than I thought",
                "2019-02-06 22:17:54",
            ),
        ],
    },
    {
        "fname": "Bunny",
        "lname": "Easter",
        "notes": [
            ("Has anyone seen my Easter eggs?", "2019-01-07 22:47:54"),
            ("I'm really late delivering these!", "2019-04-06 22:17:54"),
        ],
    },
]

Setiap orang dalam Person kamus sekarang menyertakan kunci yang disebut notes , yang dikaitkan dengan daftar yang berisi tupel data. Setiap tuple dalam notes list mewakili satu catatan berisi konten dan cap waktu. Stempel waktu diinisialisasi (bukan dibuat secara dinamis) untuk mendemonstrasikan pengurutan nanti di REST API.

Setiap satu orang dikaitkan dengan beberapa catatan, dan setiap satu catatan dikaitkan dengan hanya satu orang. Hirarki data ini dikenal sebagai hubungan satu ke banyak, di mana objek induk tunggal terkait dengan banyak objek anak. Anda akan melihat bagaimana hubungan satu-ke-banyak ini dikelola dalam database dengan SQLAlchemy.



Pendekatan Brute Force

Basis data yang Anda buat menyimpan data dalam tabel, dan tabel adalah larik baris dan kolom dua dimensi. Bisakah Person kamus di atas diwakili dalam satu tabel baris dan kolom? Bisa jadi, dengan cara berikut, di person your Anda tabel basis data. Sayangnya, memasukkan semua data aktual dalam contoh akan membuat bilah gulir untuk tabel, seperti yang akan Anda lihat di bawah:

person_id lname fname timestamp content note_timestamp
1 Farrell Doug 08-08-2018 21:16:01 Keren, aplikasi mini-blogging! 06-01-2019 22:17:54
2 Farrell Doug 08-08-2018 21:16:01 Ini bisa berguna 08-01-2019 22:17:54
3 Farrell Doug 08-08-2018 21:16:01 Yah, agak berguna 06-03-2019 22:17:54
4 Brockman Kent 08-08-2018 21:16:01 Saya akan melakukan pengamatan yang sangat mendalam 07-01-2019 22:17:54
5 Brockman Kent 08-08-2018 21:16:01 Mungkin mereka akan lebih jelas dari yang saya kira 06-02-2019 22:17:54
6 Paskah Kelinci 08-08-2018 21:16:01 Apakah ada yang melihat telur Paskah saya? 07-01-2019 22:47:54
7 Paskah Kelinci 08-08-2018 21:16:01 Saya sangat terlambat mengirimkan ini! 06-04-2019 22:17:54

Tabel di atas akan benar-benar berfungsi. Semua data diwakili, dan satu orang dikaitkan dengan kumpulan catatan yang berbeda.


Keuntungan

Secara konseptual, struktur tabel di atas memiliki kelebihan karena relatif mudah dipahami. Anda bahkan dapat membuat kasus bahwa data dapat disimpan ke file datar alih-alih database.

Karena struktur tabel dua dimensi, Anda dapat menyimpan dan menggunakan data ini dalam spreadsheet. Spreadsheet telah ditekan ke dalam layanan sebagai penyimpanan data cukup sedikit.



Kekurangan

Sementara struktur tabel di atas akan berfungsi, ia memiliki beberapa kelemahan nyata.

Untuk mewakili kumpulan catatan, semua data untuk setiap orang diulang untuk setiap catatan unik, oleh karena itu data orang tersebut berlebihan. Ini bukan masalah besar untuk data orang Anda karena tidak banyak kolom. Tapi bayangkan jika seseorang memiliki lebih banyak kolom. Bahkan dengan disk drive besar, ini bisa menjadi masalah penyimpanan jika Anda berurusan dengan jutaan baris data.

Memiliki data yang berlebihan seperti ini dapat menyebabkan masalah pemeliharaan seiring berjalannya waktu. Misalnya, bagaimana jika Kelinci Paskah memutuskan perubahan nama adalah ide yang bagus. Untuk melakukan ini, setiap catatan yang berisi nama Kelinci Paskah harus diperbarui agar data tetap konsisten. Pekerjaan terhadap database semacam ini dapat menyebabkan inkonsistensi data, terutama jika pekerjaan tersebut dilakukan oleh orang yang menjalankan kueri SQL dengan tangan.

Penamaan kolom menjadi canggung. Pada tabel di atas, ada timestamp kolom yang digunakan untuk melacak waktu pembuatan dan pembaruan seseorang dalam tabel. Anda juga ingin memiliki fungsi serupa untuk waktu pembuatan dan pembaruan catatan, tetapi karena timestamp sudah digunakan, nama yang dibuat-buat dari note_timestamp digunakan.

Bagaimana jika Anda ingin menambahkan hubungan satu-ke-banyak tambahan ke person meja? Misalnya, untuk memasukkan anak atau nomor telepon seseorang. Setiap orang dapat memiliki banyak anak dan beberapa nomor telepon. Ini dapat dilakukan dengan relatif mudah ke People Python kamus di atas dengan menambahkan children dan phone_numbers kunci dengan daftar baru yang berisi data.

Namun, mewakili hubungan satu-ke-banyak yang baru dalam person . Anda tabel database di atas menjadi jauh lebih sulit. Setiap hubungan satu-ke-banyak baru meningkatkan jumlah baris yang diperlukan untuk mewakilinya untuk setiap entri dalam data anak secara dramatis. Selain itu, masalah yang terkait dengan redundansi data menjadi lebih besar dan lebih sulit untuk ditangani.

Terakhir, data yang Anda dapatkan kembali dari struktur tabel di atas tidak akan terlalu Pythonic:itu hanya daftar besar daftar. SQLAlchemy tidak akan dapat banyak membantu Anda karena hubungannya tidak ada.




Pendekatan Basis Data Relasional

Berdasarkan apa yang telah Anda lihat di atas, menjadi jelas bahwa mencoba merepresentasikan bahkan kumpulan data yang cukup kompleks dalam satu tabel menjadi tidak dapat dikelola dengan cukup cepat. Mengingat itu, alternatif apa yang ditawarkan database? Di sinilah R bagian dari RDBMS database ikut bermain. Mewakili hubungan menghilangkan kerugian yang diuraikan di atas.

Alih-alih mencoba mewakili data hierarkis dalam satu tabel, data dipecah menjadi beberapa tabel, dengan mekanisme untuk menghubungkannya satu sama lain. Tabel dipecah di sepanjang garis koleksi, jadi untuk Person . Anda kamus di atas, ini berarti akan ada tabel yang mewakili orang dan satu lagi yang mewakili catatan. Ini mengembalikan person asli Anda tabel, yang terlihat seperti ini:

person_id lname fname timestamp
1 Farrell Doug 08-08-2018 21:16:01.888444
2 Brockman Kent 08-08-2018 21:16:01.889060
3 Paskah Kelinci 08-08-2018 21:16:01.886834

Untuk mewakili informasi catatan baru, Anda akan membuat tabel baru bernama note . (Ingat konvensi penamaan tabel tunggal kita.) Tabelnya terlihat seperti ini:

note_id person_id content timestamp
1 1 Keren, aplikasi mini-blogging! 06-01-2019 22:17:54
2 1 Ini bisa berguna 08-01-2019 22:17:54
3 1 Yah, agak berguna 06-03-2019 22:17:54
4 2 Saya akan melakukan pengamatan yang sangat mendalam 07-01-2019 22:17:54
5 2 Mungkin mereka akan lebih jelas dari yang saya kira 06-02-2019 22:17:54
6 3 Apakah ada yang melihat telur Paskah saya? 07-01-2019 22:47:54
7 3 Saya sangat terlambat mengirimkan ini! 06-04-2019 22:17:54

Perhatikan bahwa, seperti person tabel, note tabel memiliki pengidentifikasi unik yang disebut note_id , yang merupakan kunci utama untuk note meja. Satu hal yang tidak jelas adalah pencantuman person_id nilai dalam tabel. Untuk apa itu digunakan? Inilah yang menciptakan hubungan dengan person meja. Sedangkan note_id adalah kunci utama untuk tabel, person_id adalah apa yang dikenal sebagai kunci asing.

Kunci asing memberikan setiap entri dalam note tabel kunci utama person merekam itu terkait dengan. Dengan menggunakan ini, SQLAlchemy dapat mengumpulkan semua catatan yang terkait dengan setiap orang dengan menghubungkan person.person_id kunci utama ke note.person_id kunci asing, menciptakan hubungan.


Keuntungan

Dengan memecah kumpulan data menjadi dua tabel, dan memperkenalkan konsep kunci asing, Anda telah membuat data sedikit lebih rumit untuk dipikirkan, Anda telah menyelesaikan kekurangan dari representasi tabel tunggal. SQLAlchemy akan membantu Anda mengkodekan peningkatan kompleksitas dengan cukup mudah.

Data tidak lagi berlebihan dalam database. Hanya ada satu entri orang untuk setiap orang yang ingin Anda simpan dalam database. Ini menyelesaikan masalah penyimpanan dengan segera dan secara dramatis menyederhanakan masalah pemeliharaan.

Jika Kelinci Paskah masih ingin mengganti nama, maka Anda hanya perlu mengubah satu baris di person tabel, dan hal lain yang terkait dengan baris tersebut (seperti note table) akan segera memanfaatkan perubahan tersebut.

Penamaan kolom lebih konsisten dan bermakna. Karena data orang dan catatan ada di tabel terpisah, stempel waktu pembuatan dan pembaruan dapat dinamai secara konsisten di kedua tabel, karena tidak ada konflik nama di seluruh tabel.

Selain itu, Anda tidak perlu lagi membuat permutasi setiap baris untuk hubungan satu-ke-banyak baru yang mungkin ingin Anda wakili. Ambil children kami dan phone_numbers contoh dari sebelumnya. Menerapkan ini akan membutuhkan children dan phone_numbers tabel. Setiap tabel akan berisi kunci asing person_id menghubungkannya kembali dengan person tabel.

Menggunakan SQLAlchemy, data yang Anda dapatkan kembali dari tabel di atas akan lebih berguna, karena apa yang Anda dapatkan adalah objek untuk setiap baris orang. Objek itu memiliki atribut bernama yang setara dengan kolom dalam tabel. Salah satu atribut tersebut adalah daftar Python yang berisi objek catatan terkait.



Kekurangan

Di mana pendekatan brute force lebih sederhana untuk dipahami, konsep kunci asing dan hubungan membuat pemikiran tentang data agak lebih abstrak. Abstraksi ini perlu dipikirkan untuk setiap hubungan yang Anda buat di antara tabel.

Memanfaatkan hubungan berarti berkomitmen untuk menggunakan sistem database. Ini adalah alat lain untuk menginstal, mempelajari, dan memelihara di atas dan di luar aplikasi yang benar-benar menggunakan data.




Model SQLAlchemy

Untuk menggunakan dua tabel di atas, dan hubungan di antara keduanya, Anda harus membuat model SQLAlchemy yang mengetahui kedua tabel dan hubungan di antara keduanya. Inilah Person SQLAlchemy model dari Bagian 2, diperbarui untuk menyertakan hubungan dengan kumpulan note :

 1class Person(db.Model):
 2    __tablename__ = 'person'
 3    person_id = db.Column(db.Integer, primary_key=True)
 4    lname = db.Column(db.String(32))
 5    fname = db.Column(db.String(32))
 6    timestamp = db.Column(
 7        db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
 8    )
 9    notes = db.relationship(
10        'Note',
11        backref='person',
12        cascade='all, delete, delete-orphan',
13        single_parent=True,
14        order_by='desc(Note.timestamp)'
15    )

Baris 1 hingga 8 dari kelas Python di atas terlihat persis seperti yang Anda buat sebelumnya di Bagian 2. Baris 9 hingga 16 membuat atribut baru di Person kelas yang disebut note . note baru ini atribut didefinisikan dalam baris kode berikut:

  • Baris 9: Seperti atribut kelas lainnya, baris ini membuat atribut baru yang disebut notes dan menyetelnya sama dengan instance objek yang disebut db.relationship . Objek ini membuat hubungan yang Anda tambahkan ke Person class dan dibuat dengan semua parameter yang ditentukan dalam baris berikutnya.

  • Baris 10: Parameter string 'Note' mendefinisikan kelas SQLAlchemy yang Person kelas akan berhubungan dengan. Note class belum didefinisikan, itulah sebabnya string di sini. Ini adalah referensi ke depan dan membantu menangani masalah yang dapat disebabkan oleh urutan definisi ketika sesuatu diperlukan yang tidak ditentukan hingga nanti dalam kode. 'Note' string memungkinkan Person kelas untuk menemukan Note kelas saat runtime, yang setelah keduanya Person dan Note telah ditentukan.

  • Baris 11 backref='person' parameternya lebih rumit. Ini menciptakan apa yang dikenal sebagai referensi mundur di Note objek. Setiap contoh Note objek akan berisi atribut yang disebut person . person atribut mereferensikan objek induk yang Note tertentu contoh dikaitkan dengan. Memiliki referensi ke objek induk (person dalam hal ini) pada anak bisa sangat berguna jika kode Anda berulang di atas catatan dan harus menyertakan informasi tentang induknya. Ini sering terjadi secara mengejutkan dalam kode rendering tampilan.

  • Baris 12: cascade='all, delete, delete-orphan' parameter menentukan bagaimana memperlakukan instance objek catatan ketika perubahan dilakukan pada Person parent induknya contoh. Misalnya, ketika Person objek dihapus, SQLAlchemy akan membuat SQL yang diperlukan untuk menghapus Person dari database. Selain itu, parameter ini memberitahunya untuk juga menghapus semua Note kasus yang terkait dengannya. Anda dapat membaca lebih lanjut tentang opsi ini di dokumentasi SQLAlchemy.

  • Baris 13: single_parent=True parameter diperlukan jika delete-orphan adalah bagian dari cascade previous sebelumnya parameter. Ini memberi tahu SQLAlchemy untuk tidak mengizinkan Note instance (Note tanpa Person orang tua objek) ada karena setiap Note memiliki orang tua tunggal.

  • Baris 14: order_by='desc(Note.timestamp)' parameter memberitahu SQLAlchemy cara mengurutkan Note instance yang terkait dengan Person . Saat Person objek diambil, secara default note daftar atribut akan berisi Note objek dalam urutan yang tidak diketahui. SQLAlchemy desc(...) fungsi akan mengurutkan catatan dalam urutan dari terbaru ke terlama. Jika baris ini sebagai gantinya order_by='Note.timestamp' , SQLAlchemy akan secara default menggunakan asc(...) fungsi, dan mengurutkan catatan dalam urutan menaik, terlama ke terbaru.

Sekarang setelah Person . Anda model memiliki note . baru atribut, dan ini mewakili hubungan satu-ke-banyak ke Note objek, Anda harus mendefinisikan model SQLAlchemy untuk Note :

 1class Note(db.Model):
 2    __tablename__ = 'note'
 3    note_id = db.Column(db.Integer, primary_key=True)
 4    person_id = db.Column(db.Integer, db.ForeignKey('person.person_id'))
 5    content = db.Column(db.String, nullable=False)
 6    timestamp = db.Column(
 7        db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
 8    )

Note class mendefinisikan atribut yang membuat catatan seperti yang terlihat pada contoh note tabel database dari atas. Atribut didefinisikan di sini:

  • Baris 1 membuat Note kelas, mewarisi dari db.Model , persis seperti yang Anda lakukan sebelumnya saat membuat Person kelas.

  • Baris 2 memberitahu kelas tabel database apa yang digunakan untuk menyimpan Note objek.

  • Baris 3 membuat note_id atribut, mendefinisikannya sebagai nilai integer, dan sebagai kunci utama untuk Note objek.

  • Baris 4 membuat person_id atribut, dan mendefinisikannya sebagai kunci asing, menghubungkan Note kelas ke Person kelas menggunakan person.person_id kunci utama. Ini, dan Person.notes atribut, adalah bagaimana SQLAlchemy mengetahui apa yang harus dilakukan saat berinteraksi dengan Person dan Note objek.

  • Baris 5 membuat content atribut, yang berisi teks sebenarnya dari catatan. nullable=False parameter menunjukkan bahwa tidak apa-apa untuk membuat catatan baru yang tidak memiliki konten.

  • Baris 6 membuat timestamp atribut, dan persis seperti Person class, ini berisi waktu pembuatan atau pembaruan untuk Note tertentu contoh.



Inisialisasi Database

Sekarang Anda telah memperbarui Person dan membuat Note model, Anda akan menggunakannya untuk membangun kembali database pengujian people.db . Anda akan melakukannya dengan memperbarui build_database.py kode dari Bagian 2. Berikut tampilan kodenya:

 1import os
 2from datetime import datetime
 3from config import db
 4from models import Person, Note
 5
 6# Data to initialize database with
 7PEOPLE = [
 8    {
 9        "fname": "Doug",
10        "lname": "Farrell",
11        "notes": [
12            ("Cool, a mini-blogging application!", "2019-01-06 22:17:54"),
13            ("This could be useful", "2019-01-08 22:17:54"),
14            ("Well, sort of useful", "2019-03-06 22:17:54"),
15        ],
16    },
17    {
18        "fname": "Kent",
19        "lname": "Brockman",
20        "notes": [
21            (
22                "I'm going to make really profound observations",
23                "2019-01-07 22:17:54",
24            ),
25            (
26                "Maybe they'll be more obvious than I thought",
27                "2019-02-06 22:17:54",
28            ),
29        ],
30    },
31    {
32        "fname": "Bunny",
33        "lname": "Easter",
34        "notes": [
35            ("Has anyone seen my Easter eggs?", "2019-01-07 22:47:54"),
36            ("I'm really late delivering these!", "2019-04-06 22:17:54"),
37        ],
38    },
39]
40
41# Delete database file if it exists currently
42if os.path.exists("people.db"):
43    os.remove("people.db")
44
45# Create the database
46db.create_all()
47
48# Iterate over the PEOPLE structure and populate the database
49for person in PEOPLE:
50    p = Person(lname=person.get("lname"), fname=person.get("fname"))
51
52    # Add the notes for the person
53    for note in person.get("notes"):
54        content, timestamp = note
55        p.notes.append(
56            Note(
57                content=content,
58                timestamp=datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S"),
59            )
60        )
61    db.session.add(p)
62
63db.session.commit()

Kode di atas berasal dari Bagian 2, dengan beberapa perubahan untuk membuat hubungan satu-ke-banyak antara Person dan Note . Berikut adalah baris yang diperbarui atau baris baru yang ditambahkan ke kode:

  • Baris 4 telah diperbarui untuk mengimpor Note kelas yang ditentukan sebelumnya.

  • Baris 7 hingga 39 berisi PEOPLE . yang diperbarui kamus yang berisi data orang kita, beserta daftar catatan yang berhubungan dengan setiap orang. Data ini akan dimasukkan ke dalam database.

  • Baris 49 hingga 61 ulangi PEOPLE kamus, dapatkan setiap person pada gilirannya dan menggunakannya untuk membuat Person obyek.

  • Baris 53 mengulangi person.notes list, dapatkan setiap note secara bergantian.

  • Baris 54 membongkar content dan timestamp dari setiap note tupel.

  • Baris 55 hingga 60 membuat Note objek dan menambahkannya ke koleksi catatan orang menggunakan p.notes.append() .

  • Baris 61 menambahkan Person objek p ke sesi database.

  • Baris 63 melakukan semua aktivitas dalam sesi ke database. Pada titik inilah semua data ditulis ke person dan note tabel di people.db file basis data.

Anda dapat melihatnya bekerja dengan note koleksi di Person contoh objek p sama seperti bekerja dengan daftar lain dengan Python. SQLAlchemy menangani informasi hubungan satu-ke-banyak yang mendasari ketika db.session.commit() panggilan dibuat.

Misalnya, seperti Person instance memiliki bidang kunci utama person_id diinisialisasi oleh SQLAlchemy ketika di-commit ke database, instance dari Note akan memiliki bidang kunci utama mereka diinisialisasi. Selain itu, Note kunci asing person_id juga akan diinisialisasi dengan nilai kunci utama Person contoh yang terkait dengannya.

Berikut adalah contoh instance dari Person objek sebelum db.session.commit() dalam semacam pseudocode:

Person (
    person_id = None
    lname = 'Farrell'
    fname = 'Doug'
    timestamp = None
    notes = [
        Note (
            note_id = None
            person_id = None
            content = 'Cool, a mini-blogging application!'
            timestamp = '2019-01-06 22:17:54'
        ),
        Note (
            note_id = None
            person_id = None
            content = 'This could be useful'
            timestamp = '2019-01-08 22:17:54'
        ),
        Note (
            note_id = None
            person_id = None
            content = 'Well, sort of useful'
            timestamp = '2019-03-06 22:17:54'
        )
    ]
)

Berikut contoh Person objek setelah db.session.commit() :

Person (
    person_id = 1
    lname = 'Farrell'
    fname = 'Doug'
    timestamp = '2019-02-02 21:27:10.336'
    notes = [
        Note (
            note_id = 1
            person_id = 1
            content = 'Cool, a mini-blogging application!'
            timestamp = '2019-01-06 22:17:54'
        ),
        Note (
            note_id = 2
            person_id = 1
            content = 'This could be useful'
            timestamp = '2019-01-08 22:17:54'
        ),
        Note (
            note_id = 3
            person_id = 1
            content = 'Well, sort of useful'
            timestamp = '2019-03-06 22:17:54'
        )
    ]
)

Perbedaan penting antara keduanya adalah bahwa kunci utama dari Person dan Note objek telah diinisialisasi. Mesin database menangani hal ini saat objek dibuat karena fitur peningkatan otomatis dari kunci utama yang dibahas di Bagian 2.

Selain itu, person_id kunci asing di semua Note instance telah diinisialisasi untuk mereferensikan induknya. Hal ini terjadi karena urutan Person dan Note objek dibuat dalam database.

SQLAlchemy menyadari hubungan antara Person dan Note objek. Saat Person objek dikomit ke person tabel database, SQLAlchemy mendapatkan person_id nilai kunci utama. Nilai tersebut digunakan untuk menginisialisasi nilai kunci asing person_id dalam Note objek sebelum di-commit ke database.

SQLAlchemy menangani pekerjaan pembersihan database ini karena informasi yang Anda berikan saat Person.notes atribut diinisialisasi dengan db.relationship(...) objek.

Selain itu, Person.timestamp atribut telah diinisialisasi dengan stempel waktu saat ini.

Menjalankan build_database.py program dari baris perintah (dalam lingkungan virtual akan membuat ulang database dengan tambahan baru, menyiapkannya untuk digunakan dengan aplikasi web. Baris perintah ini akan membangun kembali database:

$ python build_database.py

build_database.py program utilitas tidak menampilkan pesan apa pun jika berhasil dijalankan. Jika melempar pengecualian, maka kesalahan akan dicetak di layar.



Perbarui REST API

Anda telah memperbarui model SQLAlchemy dan menggunakannya untuk memperbarui people.db basis data. Sekarang saatnya memperbarui REST API untuk memberikan akses ke informasi catatan baru. Inilah REST API yang Anda buat di Bagian 2:

Tindakan Kata Kerja HTTP Jalur URL Deskripsi
Buat POST /api/people URL untuk membuat orang baru
Baca GET /api/people URL untuk membaca kumpulan orang
Baca GET /api/people/{person_id} URL untuk membaca satu orang dengan person_id
Perbarui PUT /api/people/{person_id} URL untuk memperbarui orang yang ada dengan person_id
Hapus DELETE /api/people/{person_id} URL untuk menghapus orang yang ada dengan person_id

REST API di atas menyediakan jalur URL HTTP ke kumpulan benda, dan ke benda itu sendiri. Anda bisa mendapatkan daftar orang atau berinteraksi dengan satu orang dari daftar orang tersebut. Gaya jalur ini menyempurnakan apa yang dikembalikan dengan cara kiri-ke-kanan, menjadi lebih terperinci seiring berjalannya waktu.

Anda akan melanjutkan pola kiri-ke-kanan ini untuk mendapatkan lebih banyak rincian dan mengakses koleksi catatan. Inilah REST API yang diperluas yang akan Anda buat untuk memberikan catatan ke aplikasi web mini-blog:

Tindakan Kata Kerja HTTP Jalur URL Deskripsi
Buat POST /api/people/{person_id}/notes URL untuk membuat catatan baru
Baca GET /api/people/{person_id}/notes/{note_id} URL untuk membaca catatan satu orang
Pembaruan PUT api/people/{person_id}/notes/{note_id} URL untuk memperbarui catatan satu orang
Hapus DELETE api/people/{person_id}/notes/{note_id} URL untuk menghapus catatan satu orang
Baca GET /api/notes URL untuk mendapatkan semua catatan untuk semua orang yang diurutkan berdasarkan note.timestamp

Ada dua variasi dalam note bagian dari REST API dibandingkan dengan konvensi yang digunakan dalam people bagian:

  1. Tidak ada URL yang ditentukan untuk mendapatkan semua note terkait dengan seseorang, hanya URL untuk mendapatkan satu catatan. Ini akan membuat REST API selesai dengan cara tertentu, tetapi aplikasi web yang akan Anda buat nanti tidak memerlukan fungsi ini. Oleh karena itu, telah ditinggalkan.

  2. There is the inclusion of the last URL /api/notes . This is a convenience method created for the web application. It will be used in the mini-blog on the home page to show all the notes in the system. There isn’t a way to get this information readily using the REST API pathing style as designed, so this shortcut has been added.

As in Part 2, the REST API is configured in the swagger.yml file.

Catatan:

The idea of designing a REST API with a path that gets more and more granular as you move from left to right is very useful. Thinking this way can help clarify the relationships between different parts of a database. Just be aware that there are realistic limits to how far down a hierarchical structure this kind of design should be taken.

For example, what if the Note object had a collection of its own, something like comments on the notes. Using the current design ideas, this would lead to a URL that went something like this:/api/people/{person_id}/notes/{note_id}/comments/{comment_id}

There is no practical limit to this kind of design, but there is one for usefulness. In actual use in real applications, a long, multilevel URL like that one is hardly ever needed. A more common pattern is to get a list of intervening objects (like notes) and then use a separate API entry point to get a single comment for an application use case.



Implement the API

With the updated REST API defined in the swagger.yml file, you’ll need to update the implementation provided by the Python modules. This means updating existing module files, like models.py and people.py , and creating a new module file called notes.py to implement support for Notes in the extended REST API.


Update Response JSON

The purpose of the REST API is to get useful JSON data out of the database. Now that you’ve updated the SQLAlchemy Person and created the Note models, you’ll need to update the Marshmallow schema models as well. As you may recall from Part 2, Marshmallow is the module that translates the SQLAlchemy objects into Python objects suitable for creating JSON strings.

The updated and newly created Marshmallow schemas are in the models.py module, which are explained below, and look like this:

 1class PersonSchema(ma.ModelSchema):
 2    class Meta:
 3        model = Person
 4        sqla_session = db.session
 5    notes = fields.Nested('PersonNoteSchema', default=[], many=True)
 6
 7class PersonNoteSchema(ma.ModelSchema):
 8    """
 9    This class exists to get around a recursion issue
10    """
11    note_id = fields.Int()
12    person_id = fields.Int()
13    content = fields.Str()
14    timestamp = fields.Str()
15
16class NoteSchema(ma.ModelSchema):
17    class Meta:
18        model = Note
19        sqla_session = db.session
20    person = fields.Nested('NotePersonSchema', default=None)
21
22class NotePersonSchema(ma.ModelSchema):
23    """
24    This class exists to get around a recursion issue
25    """
26    person_id = fields.Int()
27    lname = fields.Str()
28    fname = fields.Str()
29    timestamp = fields.Str()

There are some interesting things going on in the above definitions. The PersonSchema class has one new entry:the notes attribute defined in line 5. This defines it as a nested relationship to the PersonNoteSchema . It will default to an empty list if nothing is present in the SQLAlchemy notes relationship. The many=True parameter indicates that this is a one-to-many relationship, so Marshmallow will serialize all the related notes .

The PersonNoteSchema class defines what a Note object looks like as Marshmallow serializes the notes list. The NoteSchema defines what a SQLAlchemy Note object looks like in terms of Marshmallow. Notice that it has a person attribute. This attribute comes from the SQLAlchemy db.relationship(...) definition parameter backref='person' . The person Marshmallow definition is nested, but because it doesn’t have the many=True parameter, there is only a single person connected.

The NotePersonSchema class defines what is nested in the NoteSchema.person attribute.

Catatan:

You might be wondering why the PersonSchema class has its own unique PersonNoteSchema class to define the notes collection attribute. By the same token, the NoteSchema class has its own unique NotePersonSchema class to define the person attribute. You may be wondering whether the PersonSchema class could be defined this way:

class PersonSchema(ma.ModelSchema):
    class Meta:
        model = Person
        sqla_session = db.session
    notes = fields.Nested('NoteSchema', default=[], many=True)

Additionally, couldn’t the NoteSchema class be defined using the PersonSchema to define the person attribute? A class definition like this would each refer to the other, and this causes a recursion error in Marshmallow as it will cycle from PersonSchema to NoteSchema until it runs out of stack space. Using the unique schema references breaks the recursion and allows this kind of nesting to work.



People

Now that you’ve got the schemas in place to work with the one-to-many relationship between Person and Note , you need to update the person.py and create the note.py modules in order to implement a working REST API.

The people.py module needs two changes. The first is to import the Note class, along with the Person class at the top of the module. Then only read_one(person_id) needs to change in order to handle the relationship. That function will look like this:

 1def read_one(person_id):
 2    """
 3    This function responds to a request for /api/people/{person_id}
 4    with one matching person from people
 5
 6    :param person_id:   Id of person to find
 7    :return:            person matching id
 8    """
 9    # Build the initial query
10    person = (
11        Person.query.filter(Person.person_id == person_id)
12        .outerjoin(Note)
13        .one_or_none()
14    )
15
16    # Did we find a person?
17    if person is not None:
18
19        # Serialize the data for the response
20        person_schema = PersonSchema()
21        data = person_schema.dump(person).data
22        return data
23
24    # Otherwise, nope, didn't find that person
25    else:
26        abort(404, f"Person not found for Id: {person_id}")

The only difference is line 12:.outerjoin(Note) . An outer join (left outer join in SQL terms) is necessary for the case where a user of the application has created a new person object, which has no notes related to it. The outer join ensures that the SQL query will return a person object, even if there are no note rows to join with.

At the start of this article, you saw how person and note data could be represented in a single, flat table, and all of the disadvantages of that approach. You also saw the advantages of breaking that data up into two tables, person and note , with a relationship between them.

Until now, we’ve been working with the data as two distinct, but related, items in the database. But now that you’re actually going to use the data, what we essentially want is for the data to be joined back together. This is what a database join does. It combines data from two tables together using the primary key to foreign key relationship.

A join is kind of a boolean and operation because it only returns data if there is data in both tables to combine. If, for example, a person row exists but has no related note row, then there is nothing to join, so nothing is returned. This isn’t what you want for read_one(person_id) .

This is where the outer join comes in handy. It’s a kind of boolean or operation. It returns person data even if there is no associated note data to combine with. This is the behavior you want for read_one(person_id) to handle the case of a newly created Person object that has no notes yet.

You can see the complete people.py in the article repository.



Notes

You’ll create a notes.py module to implement all the Python code associated with the new note related REST API definitions. In many ways, it works like the people.py module, except it must handle both a person_id and a note_id as defined in the swagger.yml configuration file. As an example, here is read_one(person_id, note_id) :

 1def read_one(person_id, note_id):
 2    """
 3    This function responds to a request for
 4    /api/people/{person_id}/notes/{note_id}
 5    with one matching note for the associated person
 6
 7    :param person_id:       Id of person the note is related to
 8    :param note_id:         Id of the note
 9    :return:                json string of note contents
10    """
11    # Query the database for the note
12    note = (
13        Note.query.join(Person, Person.person_id == Note.person_id)
14        .filter(Person.person_id == person_id)
15        .filter(Note.note_id == note_id)
16        .one_or_none()
17    )
18
19    # Was a note found?
20    if note is not None:
21        note_schema = NoteSchema()
22        data = note_schema.dump(note).data
23        return data
24
25    # Otherwise, nope, didn't find that note
26    else:
27        abort(404, f"Note not found for Id: {note_id}")

The interesting parts of the above code are lines 12 to 17:

  • Line 13 begins a query against the Note SQLAlchemy objects and joins to the related Person SQLAlchemy object comparing person_id from both Person and Note .
  • Line 14 filters the result down to the Note objects that has a Person.person_id equal to the passed in person_id parameter.
  • Line 15 filters the result further to the Note object that has a Note.note_id equal to the passed in note_id parameter.
  • Line 16 returns the Note object if found, or None if nothing matching the parameters is found.

You can check out the complete notes.py .




Updated Swagger UI

The Swagger UI has been updated by the action of updating the swagger.yml file and creating the URL endpoint implementations. Below is a screenshot of the updated UI showing the Notes section with the GET /api/people/{person_id}/notes/{note_id} expanded:



Mini-Blogging Web Application

The web application has been substantially changed to show its new purpose as a mini-blogging application. It has three pages:

  1. The home page (localhost:5000/ ) , which shows all of the blog messages (notes) sorted from newest to oldest

  2. The people page (localhost:5000/people ) , which shows all the people in the system, sorted by last name, and also allows the user to create a new person and update or delete an existing one

  3. The notes page (localhost:5000/people/{person_id}/notes ) , which shows all the notes associated with a person, sorted from newest to oldest, and also allows the user to create a new note and update or delete an existing one


Navigation

There are two buttons on every page of the application:

  1. The Home button will navigate to the home screen.
  2. The People button navigates to the /people screen, showing all people in the database.

These two buttons are present on every screen in the application as a way to get back to a starting point.



Home Page

Below is a screenshot of the home page showing the initialized database contents:

The functionality of this page works like this:

  • Double-clicking on a person’s name will take the user to the /people/{person_id} page, with the editor section filled in with the person’s first and last names and the update and reset buttons enabled.

  • Double-clicking on a person’s note will take the user to the /people/{person_id}/notes/{note_id} page, with the editor section filled in with the note’s contents and the Update and Reset buttons enabled.



People Page

Below is a screenshot of the people page showing the people in the initialized database:

The functionality of this page works like this:

  • Single-clicking on a person’s name will populate the editor section of the page with the person’s first and last name, disabling the Create button, and enabling the Update and Delete buttons.

  • Double clicking on a person’s name will navigate to the notes pages for that person.

The functionality of the editor works like this:

  • If the first and last name fields are empty, the Create and Reset buttons are enabled. Entering a new name in the fields and clicking Create will create a new person and update the database and re-render the table below the editor. Clicking Reset will clear the editor fields.

  • If the first and last name fields have data, the user navigated here by double-clicking the person’s name from the home screen. In this case, the Update , Delete , and Reset buttons are enabled. Changing the first or last name and clicking Update will update the database and re-render the table below the editor. Clicking Delete will remove the person from the database and re-render the table.



Notes Page

Below is a screenshot of the notes page showing the notes for a person in the initialized database:

The functionality of this page works like this:

  • Single-clicking on a note will populate the editor section of the page with the notes content, disabling the Create button, and enabling the Update and Delete buttons.

  • All other functionality of this page is in the editor section.

The functionality of the editor works like this:

  • If the note content field is empty, then the Create and Reset buttons are enabled. Entering a new note in the field and clicking Create will create a new note and update the database and re-render the table below the editor. Clicking Reset will clear the editor fields.

  • If the note field has data, the user navigated here by double-clicking the person’s note from the home screen. In this case, the Update , Delete , and Reset buttons are enabled. Changing the note and clicking Update will update the database and re-render the table below the editor. Clicking Delete will remove the note from the database and re-render the table.



Web Application

This article is primarily focused on how to use SQLAlchemy to create relationships in the database, and how to extend the REST API to take advantage of those relationships. As such, the code for the web application didn’t get much attention. When you look at the web application code, keep an eye out for the following features:

  • Each page of the application is a fully formed single page web application.

  • Each page of the application is driven by JavaScript following an MVC (Model/View/Controller) style of responsibility delegation.

  • The HTML that creates the pages takes advantage of the Jinja2 inheritance functionality.

  • The hardcoded JavaScript table creation has been replaced by using the Handlebars.js templating engine.

  • The timestamp formating in all of the tables is provided by Moment.js.

You can find the following code in the repository for this article:

  • The HTML for the web application
  • The CSS for the web application
  • The JavaScript for the web application

All of the example code for this article is available in the GitHub repository for this article. This contains all of the code related to this article, including all of the web application code.




Kesimpulan

Congratulations are in order for what you’ve learned in this article! Knowing how to build and use database relationships gives you a powerful tool to solve many difficult problems. There are other relationship besides the one-to-many example from this article. Other common ones are one-to-one, many-to-many, and many-to-one. All of them have a place in your toolbelt, and SQLAlchemy can help you tackle them all!

For more information about databases, you can check out these tutorials. You can also set up Flask to use SQLAlchemy. You can check out Model-View-Controller (MVC) more information about the pattern used in the web application JavaScript code.

In Part 4 of this series, you’ll focus on the HTML, CSS, and JavaScript files used to create the web application.

« Part 2:Database PersistencePart 3:Database RelationshipsPart 4:Simple Web Applications »

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Salesforce SOQL dari Microsoft Office

  2. Pertemuan di Tempat Kerja:Mendapatkan Kembali Ruang dari Database yang Terlalu Besar

  3. Bagaimana Menjumlahkan Nilai Kolom dalam SQL?

  4. Menggunakan Fungsi DATEADD, DATEDIFF dan DATEPART T-SQL dalam Istilah Sederhana

  5. SQL MIN() untuk Pemula