Zapraszam do serii postów opisujących modele w Django. Opiszę dobre praktyki, które warto stosować przy tworzeniu modeli, zastosowanie dekoratorów, dodatkowych metod, i wiele więcej.
Podstawowe informacje o modelach.
Model jest wbudowaną funkcjonalnością, której Django używa do zarządzania bazą danych (ORM — Object Relational Mapping). Jeden model odpowiada za jedną tabelę w bazie. Upraszcza sposób tworzenia pól w bazie danych, pozwala opisać własności takie jak dodatkowe metody sprawdzające poprawność danych (walidatory) i opisy pól.
Wspaniałą funkcjonalnością są zapytania (Querysets). Nie trzeba pisać skomplikowanego kodu SQL, wystarczy skorzystać z funkcji modelu. Na przykład pobranie pierwszego rekordu z tabeli users można zapisać jako: first_user = Users.objects.first()
Na tym obiekcie first_user
można już pracować! Na przykład wyświetlić jego imię: first_user.name
.
Zapytania są LENIWE.
Tak. I lenistwo jest cnotą.
Tworzenie zapytania do bazy danych, np. takiego jak wyżej, nie powoduje wykonania tego zapytania. Dopóki nie pobierzesz jakiejś wartości z first_user
, żadna operacja na bazie danych nie zostanie wykonana.
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
(ref: https://docs.djangoproject.com/en/4.1/topics/db/queries/#querysets-are-lazy)
Ta konstrukcja wygląda jak trzy zapytania do bazy. W rzeczywistości zapytanie jest tylko jedno, wywołane przez print(q)
. Dlaczego? Zapytanie nie jest wykonywane do momentu, w którym poprosisz o dane. To bardzo ważne. Oznacza, że możemy konstruować dowolnie skomplikowane zapytania bezkarnie. Dopóki nie "poprosimy o dane" baza danych nie będzie odpytywana.
A kiedy zapytanie będzie wykonane?
- W momencie odczytywania pól modelu, np.
print(q)
,first_user.name
. Co więcej,first_user.name
spowoduje zapytanie tylko o wartość kolumny name, a nie pobranie całego wiersza z bazy! - Podczas konwersji zapytania na listę:
entry_list = list(Entry.objects.all())
. To spowoduje pobranie wszystkich wierszy z bazy danych dla tabeliEntry
. - Przy wywołaniu funkcji
len()
, na przykład:len(Entry.objects.all())
- spowoduje… spore obciążenie bazy danych. Wyciąga wszystkie obiekty, tylko po to, by policzyć, ile ich jest. Poprawne w tym przypadku jest użycie zapytaniacount
:Entry.objects.count()
.
Więcej na temat tworzenia zaawansowanych zapytań będzie w kolejnych postach. Zainteresowanym tematem polecam zajrzeć do dokumentacji Django: https://docs.djangoproject.com/en/4.1/ref/models/querysets/#when-querysets-are-evaluated.
Tworzenie modelu
- Każdy model w Django dziedziczy z klasy
django.db.models.Model
- Każdy pole modelu jest polem w bazie danych
- Django automatycznie mapuje pola modelu na pola w bazie danych.
- Dodatkowo Django wykorzystuje pola modeli do wyświetlania formularzy i panelu administracyjnego.
- Django obsługuje ogromną ilość pól modelowych — polecam dokumentacje: https://docs.djangoproject.com/en/4.1/ref/models/fields/#field-types
from django.db import models
# Create your models here.
class ContactMessage(models.Model):
name = models.CharField(verbose_name="imię", max_length=50, blank=False, null=False)
email = models.EmailField(verbose_name="adres email", help_text="help text do pola adres email", max_length=255, blank=False, null=False)
message = models.TextField(verbose_name="wiadomość", blank=False, null=False)
def __str__(self):
return f"wiadomość od {self.name}({self.email})"
powyższy model zmapuje się do takiej tabeli:

Samo napisanie modelu nic jeszcze nie daje. Należy uruchomić komendę:
python manage.py makemigrations
która przetłumaczy model na serię zapytań do bazy danych, które stworzą odpowiednie tabele.
Dopiero:
python manage.py migrate
stworzy tabele w bazie.
Atrybuty pól modelu
Każde pole w modelu ma dodatkowe atrybuty. Do najważniejszych należą:
help_text
- dodatkowy tekst pomocniczy wykorzystywany podczas wyświetlania formularza oraz panelu administracyjnymverbose_name
- nazwa pola czytelna dla człowieka. Przykład:email = models.EmailField(verbose_name="adres email" max_length=255, blank=False, null=False)
spowoduje wyświetlenie nazwy "adres email" jako opis pola. Jeśliverbose_name
nie zostało zdefiniowane, Django wygeneruje je samo z nazwy pola.max_length
- maksymalna długość pola dla CharFieldblank = False
— wymaga wartości przy zapisienull = False
— podobnie jak blank nie pozwala na nullerror_messages
— słownik pozwalający zdefiniować własne komunikaty o błędachvalidators
— lista metod sprawdzających, które będą zastosowane na polu. Django robi swoje sprawdzenia, to pozwala na większe dostosowanie. Warto zajrzeć do dokumentacji: https://docs.djangoproject.com/en/4.1/ref/validators/. Oczywiście można pisać własne walidacje. Więcej o walidatorach będzie w kolejnych wpisach.
Wykorzystanie modelu
Model możemy wykorzystać do stworzenia widoku formularza kontaktowego. W tym celu tworzymy plik views.py
:
# Create your views here.
from django.views.generic.edit import CreateView
from .models import ContactMessage
class ContactFormView(CreateView):
template_name = 'contact.html'
model = ContactMessage
fields = ["name", "email", "message"]
success_url = "/contact"
Tworzę formularz na podstawie modelu ContactMessage z konkretnymi polami. Dodatkowo korzystam z Class Based Views, więc nie muszę tworzyć dodatkowo pliku forms.py
z definicją formularza (potrzebujesz więcej informacji o Class Based Views? Napisz mi wiadomość! )
Tutaj ważna informacja. Django pozwala na łatwe stworzenie formularza ze wszystkimi polami poprzez
fields = "all".
Fajne, prawda?
Nie.
Wykorzystaniefields = "all"
doprowadzi do sytuacji, w której pole niedostępne dla użytkownika znajdzie się w formularzu. Na przykładis_active
w edycji użytkownika. Wtedy użytkownik będzie mógł siebie sam wyłączyć lub, co gorsza, zrobić psikusa innemu użytkownikowi. Można też "przypadkiem" wyświetlić pole z numerem pesel czy innymi danymi poufnymi. Nigdy nie stosujfields = "all"
czy to w formularzach, czy serializerach. Byłem tam, to boli.
Żeby wyświetlić formularz potrzebna jest ścieżka w urls.py oraz template.
# urls.py
"""URL Configuration
The urlpatterns
list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from accounts.views import ContactFormView
urlpatterns = [
path('admin/', admin.site.urls),
path('contact', ContactFormView.as_view(), name='contact'),
]
I szablon html. W celu zachowania czytelności jest to sam formularz.
<form action="{{request.path}}" method="post">
{% csrf_token %}
<ul>
{{ form.as_p }}
</ul>
<input type="submit" value="Submit">
</form>

Powyżej widać wygenerowany formularz. Specjalnie nie ma żadnych styli. Zwróćcie uwagę na pole "Adres email" - nazwa jest zawartością verbose_name
.
A jak dodać dodatkowe opisy do pól formularza? Należy wykorzystać atrybut help_text
.
Zmienię definicje pola email:email = models.EmailField(verbose_name="adres email", help_text="help text do pola adres email", max_length=255, blank=False, null=False)

To już od nas zależy, jak ostylujemy ten dodatkowy tekst.
Fajnie to wygląda w adminie:

Help tekst wygląda ładnie, tylko nazwa obiektu jest brzydka: ContactMessage object (6)
. Poprawić to można poprzez dodanie metody __str__
do modelu ContacMessage
. Metoda ta zwraca reprezentacje obiektu w postaci stringa, czyli ciągu znaków.
def __str__(self):
return f"wiadomość od {self.name}({self.email})"

Od razu czytelniej.
A jak wysłać tą wiadomość mailem?
Wysyłka maili jest bardziej skomplikowana. Na sam początek trzeba zdefiniować konto mailowe w settings.py. Trzeba ustawić następujące parametry:
- EMAIL_HOST
- EMAIL_HOST_PASSWORD
- EMAIL_HOST_USER
- EMAIL_PORT
- EMAIL_USE_TLS
- EMAIL_USE_SSL
- DEFAULT_FROM_EMAIL
i ewentualnie inne: https://docs.djangoproject.com/en/4.1/ref/settings/#email
Najlepiej założyć oddzielne konto do wysyłki maili na jednym z darmowych serwerów - wp, Gmail czy inne. Mając te dane pozostaje wykorzystać funkcje send_email:
from django.core.mail import send_mail
send_mail(
'Subject here',
'Here is the message.',
'from@example.com',
['to@example.com'],
fail_silently=False,
)
Funkcje tą trzeba dodać do metody save w modelu:
def save(self, *args, **kwargs):
result = super().save(*args, **kwargs) # zapis wiadomości przed wysyłką, gdyby coś padło.
message = f"Wiadmość email od {self} o treści: \r\n {self.message}"
send_mail(
str(self),
message,
settings.DEFAULT_FROM_EMAIL,
["nasz@adres.email.pl"],
fail_silently=False,
)
return result
Teraz w momencie zapisu formularza zostanie wysłany email na adres podany w "to" funkcji send_email. Co ważne, email jest wysyłany po zapisie wiadomości. Jak coś się popsuje (np. serwer nie zadziała, niepoprawne hasło itp.) wiadomość zostaje zapisana w bazie. Można się pokusić o rozbudowanie modelu wiadomości o pola: is_sent (boolean), które pozwolą zaznaczyć, że wiadomość została wysłana. Można też użyć pola sent_on (datetime) - do zapisania daty i godziny wysyłki. Takie dodatkowe wartości pozwolą na przykład na ustawienie wysyłki maili raz dziennie lub na ponowną próbę wysłania maila w przypadku niepowodzenia. To już wychodzi poza zakres dzisiejszego posta — jeśli chcesz wiedzieć więcej: Napisz mi wiadomość!
Zapisz się na newsletter by otrzymać informacje, kiedy pojawi się nowy post w tej serii. Zapraszam!
Ps. Spodobał Ci się post? Udostępnij go na swoich kanałach.
PS2. Masz uwagi do posta, chcesz porozmawiać, szukasz pomocy z Pythonem i Django? Napisz do mnie!
1 thought on “Zrozumieć Django: wprowadzenie do modeli”
Comments are closed.