05 - Formset

May 06, 2018 5 minute read

Palette uygulamasıyla MTV Katmanları ve Admin paneli ve CRUD ile ilgili giriş seviyesinde bilgiler edindik. Yeni uygulamamız Uptime. Bu uygulamayla yerelleştirme, sayfa isteklerini yönetme ve yetkilendirme, eposta veya sayfa içi bildirimlerle kullanıcıyı bilgilendirme, unittest, komut satırında çalışan scriptler ve bunlarla ilişkili birçok konuya değineceğiz. Formset ile başlıyoruz.

Özet

  • Uptime uygulamasını yazmaya başlıyoruz. URL’leri girmek için Formset kullanıyoruz.
  • İhtiyacımızı görecek bir Django eklentisini yükleyip kullanacağız.
  • Bu yazıyla ilgili kodlara buradan erişebilirsiniz.

Yeni Uygulamamız, Uptime

Uygulamamızın en temel işlevi, belirlediğimiz URL veya IP adreslerinin erişilebilirliğini belirli aralıklarla kontrol etmek. Piyasa bu işi yapan uptimerobot, pingdom gibi çeşitli hizmetler var. Biz de Django’yu öğrenmek için basit bir prototipini yapmış olacağız. Daha önce öğrendiklerimizi kullanarak şöyle bir başlangıç yapıyoruz:

  • hello_uptime adında yeni bir uygulama oluşturuyoruz (startapp).
  • templates/uptime/ içinde Template katmanımızı oluşturuyoruz.
  • urls.py ile uygulama url’lerimizi tanımlıyoruz, bunun için gerekli View katmanlarımızı hazırlıyoruz.
  • Monitor adında ilk modelimizi oluşturuyoruz, model üzerinde denemelerimizi yapmak için Admin class’ımızı hazırlıyoruz.

Yeni bir uygulama yazarken önce bir kabaca başlangıç yapmak size yardımcı olur. Yani hemen Template katmanının içini doldurmaya başlamayın, önce basit bir form olsun yeter. Modelinizi denemek mi istiyorsunuz? Admin panelini kullanın. Şimdi, depodan Article-05 dizinine girin, orada hello_uptime uygulamasını kullanıp hızlı bir başlangıç yapabilirsiniz.

Uygulamamızı basit bir şekilde tasarlayalım. Sadece bir dashboard’ımız olsun. Orada URL veya IP girebileceğimiz bir formumuz olsun, formu kaydedebilelim ve yine aynı sayfada sonuçları izleyebileceğimiz bir ekranımız olsun.

# hello_uptime/views.py
...
class UptimeDashboardView(FormView):
    template_name = 'uptime/dashboard.html'  # Template dosyamız.
    form_class = MonitorForm  # Monitor modeli için form.

    def get_success_url(self):
        # Form başarıyla kaydedilirse aynı sayfaya yönlendirilecek.
        # Tek bir View katmanımız olacak. Her şey bir Dashboard'dan ibaret.
        return reverse('uptime:dashboard')

    def get_form_kwargs(self):
        # Tekrar hatırlatıyorum, tek bir View katmanımız var. Daha önce kaydettiğimiz
        # Bir Monitor object (instance) varsa onu kullanmaya devam edeceğiz.
        # İstediğimizde tekrar düzenleyebileceğiz. Hepsi tek bir View katmanında.
        kwargs = super().get_form_kwargs()
        instance = Monitor.objects.first()
        kwargs.update({
            'instance': instance,
        })
        return kwargs

    def form_valid(self, form):
        # ModelForm'u mutlaka inceleyin: hello_uptime/forms.py
        # save metodunun nereden geldiğini ve nasıl çalıştığını anlayın.
        form.save()
        return super().form_valid(form)

View kodumuzu üzerine yazdığım yorumlarla birlikte inceleyin. Şimdi burada bir sorun var. Bir sayfada tek bir Form kullanabiliyoruz. Birden fazla URL girmek istediğimizde ne yapacağız? http://localhost:8000/uptime/ üzerinden bir bakalım:

Uptime form

Tek form, tek monitor :-(. Şimdi Formset1 zamanı.

Formset Kullanımı ve Don’t Repeat Yourself

Uygulamamız henüz buna hazır değil; ama diyelim ki kullanıcılar projemizde hesap açsınlar ve her kullanıcının en fazla 10 URL’i izleme hakkı olsun. Yani bizim uygulama arayüzümüzde 10 form içeren bir form kümesi olsun ve kullanıcılar tek seferde düzenleyebilsin.

UptimeDashboardView için miras aldığımız FormView, tek bir formu yönetebilmemiz için hazırlanmış kapsamlı bir View class’ıdır. Bu durumda elimizde iki seçenek var. Bunlardan birincisi, FormView’i kendi ihtiyacımıza göre özelleştirip yeni bir genel View class’ı ve bu tip özel ihtiyaçlarda hep aynı class’ı kullanmak. Mesela bu özelleştirilmiş class’ın adı FormSetView olabilir.

Ama bir seçeneğimiz daha var. Django büyük bir topluluk ve bizim ihtiyaç duyduğumuz şeye başkaları da ihtiyaç duymuş olabilir, hatta bu ihtiyacı birisi Django eklentisi haline getirmiş bile olabilir. PyPI‘den arattığımda django-extra-views2‘in aradığımız eklenti olduğunu gördüm. Hemen requirements.txt‘ye yeni bağımlılığımızı ekliyoruz, kuruyoruz ve View katmanımızı güncelliyoruz:

# hello_uptime/views.py
from django.urls import reverse_lazy
from extra_views import ModelFormSetView

from hello_uptime.models import Monitor
from hello_uptime.utils import USER_MONITOR_LIMIT


class UptimeDashboardView(ModelFormSetView):
    # django-extra-views belgesinde deniliyor ki, ayrıca Form veya FormSet
    # oluşturmaya gerek yok, bana modelini, kullanacağın field'ları, success_url'i
    # söyle, gerisini ben hallederim. Halletsin bir zahmet, güzel bir eklenti olmuş.
    fields = ('url', 'interval', 'is_active')
    model = Monitor
    success_url = reverse_lazy('uptime:dashboard')  # Neden reverse değil de reverse_lazy? Bana yazın. ;-)
    template_name = 'uptime/dashboard.html'

    def get_queryset(self):
        return Monitor.objects.order_by('created_at')  # Sıra bozulmasın diye, oluşturulma sırasına göre aldık.

    def get_factory_kwargs(self):
        kwargs = super().get_factory_kwargs()
        kwargs.update({
            'extra': USER_MONITOR_LIMIT,  # Dashboard'da 10 input'un hepsini tek seferde göster.
            'max_num': USER_MONITOR_LIMIT,  # Dashboard'da en fazla 10 input olabilir.
        })
        return kwargs

    def formset_valid(self, formset):  # form_valid formset_valid oldu.
        formset.save()
        return super().formset_valid(formset)

Eklenti kullanmasaydık, bu kod bu kadar basit ve temiz olmayacaktı. İyi ki topluluk var! Son bir işimiz kaldı, Template katmanımızı da formset kullanacak şekilde düzenlemeliyiz:

<!-- templates/uptime/dashboard.html -->
...
{% block content %}
    <form action="{% url 'uptime:dashboard' %}" method="POST">
        {% csrf_token %}  <!-- Sahte istekleri önleyen CSRF template tag'ımız -->
        {{ formset.management_form }} <!-- Formset'imiz ile ilgili max_num, total gibi gizli inputları ekledik. -->
        <table>
            {% for form in formset %}
                <tr>
                    <th></th>
                    <td>
                        <!-- blocktrans i18n template modülünden geliyor. Çeviriler için. -->
                        <!-- Örnek bir blocktrans ve forloop.counter kullanımı -->
                        {% blocktrans with number=forloop.counter %}Monitor {{ number }}{% endblocktrans %}
                    </td>
                </tr>
                {{ form.as_table }}
            {% endfor %}
            <tr>
                <th></th>
                <td><button type="submit">{% trans "Save" %}</button></td>
            </tr>
        </table>
    </form>
{% endblock %}

Formsetimiz içinde 10 tane form var ve başlangıç datasıyla birlikte artık kullanıma hazır:

Uptime formset

Yalnız bir eksiklik var. Bu uptime uygulamasını kullanıcılarımızın bir hesap açarak kullanmasını istiyoruz ve her kullanıcının 10 ekran izleme hakkı olsun istiyoruz. Bunun için hesap oluşturma, email ile hesap doğrulama, parola yenileme gibi ekranlara ihtiyacımız var ve bu uptime panelini giriş yapan kullanıcıya göre biraz özelleştirmemiz gerekiyor. Sonraki yazımızda görüşmek üzere.


  1. Daha önce söylediğim gibi, bilmediğiniz veya hatırlamadığınız konular için Django belgelerine sık sık bakmayı alışkanlık hale getirmeniz size çok şey kazandırır. Formsets belgesine mutlaka bakın. [return]
  2. Bir eklentiyi bağımlılık olarak eklemeye karar vermeden önce bakımı iyi yapılıyor mu, unittestleri var mı, belgesi nasıl, kodu temiz mi bir bakmakta fayda var. [return]