Menyimpan file di Google Drive pada projek Django
Artikel ini merupakan refleksi saya dalam membuat fitur “Simpan file materi ke Google Drive” pada projek kelas Penjaminan Mutu Perangkat Lunak Semester Gasal 2020/2021 Fasilkom UI, sekaligus tutorial menggunakan Google Drive API dengan mudah dengan PyDrive.
Google menyediakan Google Drive API untuk digunakan pengembang pada aplikasinya. Agar lebih mudah, Anda dapat menggunakan Pydrive yang merupakan library wrapper Google Drive API dalam bahasa Python.
Saya akan memberikan contoh penggunaannya pada projek Django yang sudah ada sebelumnya.
Setup
Buat dan aktifkan virtual environment terlebih dahulu.
python -m venv env
source env/bin/activate
Install depedencies dan Pydrive dan mock (untuk keperluan testing).
pip install -r requirements.txt
pip install pydrive mock
pip freeze > requirements.txt
Jika melihat dokumentasinya, anda sudah bisa menggunakan mengakses Google Drive API hanya dengan dua baris.
Buat file quickstart.py
, dan jalankan kode ini.
from pydrive.auth import GoogleAuthgauth = GoogleAuth()
gauth.LocalWebserverAuth() # Creates local webserver and auto handles authentication.
Apakah tidak bisa? Itu karena Anda memerlukan client secrets untuk dapat menggunakan menggunakan API dari Google.
- Pergi ke Google APIs console dan buat projek baru.
- Cari Google Drive API, pilih dan klik ‘Enable’.
- Pilih ‘Credentials’ pada menu di samping kiri, klik ‘Create Credentials’, lalu klik ‘OAuth client ID’.
- Klik ‘Configure consent screen’ dan ikuti instruksinya. Setelah selesai:
- Pada kolom ‘Application type’, pilih ‘Web application’.
- Masukkan nama yang sesuai.
- Masukkan http://localhost:8080 pada ‘Authorized JavaScript origins’.
- Masukkan http://localhost:8080/ pada ‘Authorized redirect URIs’.
- Klik ‘Save’.
5. Klik ‘Download JSON’ di sebelah kanan Client ID untuk mengunduh client_secret_<really long ID>.json.
Ubah nama file tersebut menjadi “client_secrets.json” dan letakkan pada direktori projek Anda.
Setelah itu, coba jalankan kembali quickstart.py
dan Anda akan dibawa ke halaman login Google.
Implementasi
Pada app/views.py
tambahkan fungsi yang mengunggah file ke Google Drive pengguna dan fungsi yang menjadi handler tombol Simpan ke Google Drive.
# app/views.pydef upload_to_gdrive(file_path, title):
gauth = GoogleAuth()
gauth.LocalWebserverAuth() drive = GoogleDrive(gauth)
file1 = drive.CreateFile()
file1.SetContentFile(file_path)
file1["title"] = title
print("title: %s, mimeType: %s" % (file1["title"], file1["mimeType"]))
file1.Upload()
def save_to_gdrive(request, pk):
materi = get_object_or_404(Materi, pk=pk)
path = materi.content.path
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
upload_to_gdrive(file_path, materi.title)
else:
raise Http404("File tidak dapat ditemukan.") return HttpResponseRedirect(reverse('detail-materi', kwargs={'pk': pk}))
Tambahkan url yang nantinya akan dipanggil saat tombol ditekan.
# app/urls.pyfrom django.urls import path, re_path
from app import views
[...]urlpatterns = [
[...]
path("materi/<int:pk>/save-to-gdrive/", views.save_to_gdrive, name="save-to-gdrive"),
]
Pada template detail materi, tambahkan tombol simpan ke Google Drive.
# app/templates/app/detail_materi.html<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">[...] <a class="dropdown-item" href="{% url 'save-to-gdrive' materi_data.id %}">Google Drive</a>[...]</div>
save_to_gdrive(request, pk)
merupakan handler tombol Simpan ke Google Drive. Ketika tombol ditekan, pengguna akan dibawa pada halaman login akun Google. Setelah pengguna login, file materi akan otomatis tersimpan pada Google Drive pengguna.
upload_to_gdrive(file_path, title)
merupakan fungsi yang khusus berurusan dengan library PyDrive. Fungsi ini lah yang meminta pengguna untuk login dan mengunggah file materi ke Google Drive.
Testing
Lebih baik menerapkan TDD sehingga seharusnya testing dilakukan di awal. Namun, untuk keperluan tutorial, saya meletakkan bagian testing di akhir.
Berikut setup dari unit test detail materi yang sudah ada sebelumnya.
# app/tests.pyclass DetailMateriTest(TestCase):
def setUp(self):
self.client = Client()
self.admin_credential = {"email": "admin@gov.id", "password": "passwordtest"}
self.contributor_credential = {"email": "kontributor@gov.id", "password": "passwordtest"}
self.anonymous_credential = {"email": "anonymous@gov.id", "password": "passwordtest"}
self.admin = get_user_model().objects.create_user(**self.admin_credential, name="Admin", is_admin=True)
self.contributor = get_user_model().objects.create_user(
**self.contributor_credential, name="Kontributor", is_contributor=True
)
self.anonymous = get_user_model().objects.create_user(**self.anonymous_credential, name="Anonymous")
self.cover = SimpleUploadedFile("ExampleCover921.jpg", b"Test file")
self.content = SimpleUploadedFile("ExampleFile921.pdf", b"Test file") Materi(
title="Materi 1",
author="Agas",
uploader=self.contributor,
publisher="Kelas SC",
descriptions="Deskripsi Materi 1",
status="APPROVE",
cover=self.cover,
content=self.content,
).save()
self.materi1 = Materi.objects.first()
self.url = "/materi/" + str(self.materi1.id) + "/" self.materi_with_published_date = Materi.objects.create(
title="Materi 1",
author="Agas",
uploader=self.contributor,
publisher="Kelas SC",
descriptions="Deskripsi Materi 1",
status="APPROVE",
cover=self.cover,
content=self.content,
date_modified=datetime.now(),
date_created=datetime.now(),
)
self.materi_with_published_date_url = "/materi/" + str(self.materi_with_published_date.id) + "/"
VerificationReport.objects.create(
report='{"feedback": "Something", "kriteria": [{"title": "Kriteria 1", "status": true},'
+ ' {"title": "Kriteria 2", "status": true}, {"title": "Kriteria 3", "status": true}]}',
timestamp="2020-10-09 06:21:33",
status="Diterima",
materi=self.materi_with_published_date,
user=self.materi_with_published_date.uploader,
)[...]
Saya tinggal menambahkan fungsi test baru pada kelas tersebut.
# app/tests.py > DetailMateriTest def test_tombol_bagikan_google_drive(self):
response = Client().get(self.url)
self.assertContains(response, "Google Drive") @mock.patch("app.views.upload_to_gdrive")
def test_save_to_gdrive_with_nonexistent_materi(self, mock_upload_to_gdrive):
response = self.client.get("/materi/%s/save-to-gdrive/" % 0)
mock_upload_to_gdrive.assert_not_called()
self.assertEqual(response.status_code, 404) @mock.patch("app.views.upload_to_gdrive")
def test_save_to_gdrive_with_valid_materi(self, mock_upload_to_gdrive):
response = self.client.get("/materi/%s/save-to-gdrive/" % self.materi1.id, follow=True)
last_url, status_code = response.redirect_chain[-1]
mock_upload_to_gdrive.assert_called_once()
self.assertEqual(last_url, "/materi/%d/" % self.materi1.id)
self.assertEqual(status_code, 302)
test_tombol_bagikan_google_drive
memastikan apakah tombol Google Drive sudah ada pada halaman detail materi.
Karena Google Drive API dan Pydrive sulit di-test (dan saya yakin library tersebut pasti berfungsi), maka fungsi upload_to_gdrive
terpaksa saya mock.
test_save_to_gdrive_with_nonexistent_materi
memastikan jika url dipanggil dengan ID materi yang tidak ada, url akan mengembalikan status 404 danupload_to_gdrive
tidak akan dipanggil.
test_save_to_gdrive_with_valid_materi
memastikan jika url dipanggil dengan ID materi yang tersedia, upload_to_gdrive
akan dipanggil sekali, url akan mengembalikan status 302, dan halaman dialihkan kembali ke halaman materi.
Refleksi
Saat mengerjakan fitur ini, awalnya saya kewalahan dalam memahami Google Drive API, untungnya sudah ada library python yang sangat mudah digunakan. Setelah itu, awalnya saya juga kesulitan dalam setup clients secret karena tautan dokumentasi Pydrive pada halaman github nya tidak dapat dibuka. Namun, setelah saya mencari kembali di google, ternyata ada dokumentasi resmi Pydrive yang memuat cara setup client_secrets.
Selain itu, ini pertama kalinya saya belajar cara mock dalam test. Ternyata tidak sesulit itu implementasinya. Mungkin karena hal yang saya mock masih cukup sederhana.