Skip to content

Latest commit

 

History

History
298 lines (213 loc) · 17.2 KB

lekce_04.md

File metadata and controls

298 lines (213 loc) · 17.2 KB

Obnovení hesla

Lidé jsou zapomnětliví a může se stát, že někdo z uživatelů zapomene heslo. Djagno umožňuje použít standardní mechanismus změny hesla po ověřené e-mailu. My si zatím připravíme formulář na žádost uživatele o obnovení hesla. Formulář uložíme do šablony password_reset_form.html.

{% extends "base.html" %}
{% block content %}
<h2>Password Reset</h2>
<p>Please fillin the e-mail address of your account</p>
<form method="post">
    {% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">Odeslat</button>
</form>
{% endblock %}

Jako druhý krok přidáme stránku, kam chceme uživatele přesměrové poté, co vyplní e-mail. Kód uložíme do šablony password_reset_done.html.

{% extends "base.html" %}
{% block content %}
<h2>Password Reset Finished</h2>
{% endblock %}

E-mail zatím odeslán není, protože nemáme nastavené připojení na server, který e-mail může odeslat.

Nastavení služby Mailtrap

Ve fázi vývoje je jednodušší a bezpečnější e-maily neodesílat, ale pouze použít nějakou službu, která simuluje odesílání mailů. Příkladem takové služby je služba Mailtrap. Pro použití služby je nutné se nejprve zaregistrovat a oveřit e-mail.

Po přihlášení do aplikace je potřeba vytvořit novou mailovou schránku. K tomu slouží tlačítko Add Inbox. Té přiřadíme nějaké jméno (např. Django).

create_group

Po kliknutí na název schránky vidíme na záložce SMTP Settings nastavení schránky pro různá prostředí, která můžeme rovou překopírovat. Ve dlouhém seznamu nechybí Djano. Nastavení má pouze čtyři řádky, které zkopírujeme do souboru settings.py.

create_group

Nyní můžeme znovu vyzkoušet reset mailu. Pokud máme správně zkopírované nastavení a použijeme reset na adresu, kterou má nastavenou nějaký uživatel, zobrazí se v naší schránce e-mail. V něm vidíme text, který by byl odeslán uživateli. E-mail obsahuje stručný text a unikátní odkaz, který umožní zapomnětlivému uživateli nastavit nové heslo.

create_group

Rozšíření modelu uživatele

U uživatele chceme často evidovat více informací, než kolik umožňuje klasický User model. U firemních aplikací je často potřeba uložit oddělení, telefonní číslo, pobočku či nadřízeného zaměstnance, u běžných komerčních aplikací (např. e-shopu) pak třeba doručovací adresu, odkazy na profily na sociálních sítích atd.

Django nabízí několik možností, jak rozšířit možnosti modelu User. Jednou z nich je vytvoření vlastní verze modelu, to je ale používáno především v situaci, kdy máme specifické požadavky (např. provádíme import uživatelů z jiné databáze). Pokud náme jde o přidání dodatečných polí, je možné přidat samostatný model (např. Employee) a k němu přidat vazbu OneToOne na model Employee. Dále k modelu Employee přidáme oddělení (jako řetězec).

class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    department = models.CharField(max_length=100, blank=True)

Záznam modelu Employee by v ideálním případě měl být vytvořen při vytvoření záznamu mudelu User. To můžeme zařídit automaticky pomocí signálů. Signály jsou vysílány při různých operacích (např. uložení záznamu) a pokud se přihlásíme k odběru signálu, můžeme při operaci vždy provést nějakou akci. Vytvoříme tedy funkci create_user_profile, kterou označíme tzv. dekorátorem @receiver (přijímač). Funkce create_user_profile() bude mít parametry sender (odesílatel signálu), instance (upravovaný záznam), created (pravdivostní hodnota, která udává, jestli došlo k vytvoření nebo úpravě již existujícího záznamu) a pro případné další parametry je přítomen **kwargs.

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Employee.objects.create(user=instance)

Protože jsme vytvořili nový model, musíme provést migraci databáze.

python manage.py makemigrations
python manage.py migrate

Dále zaregistrujeme model pro zobrazení v administrátorském rozhraní.

admin.site.register(crm.models.Employee)

Úprava informací o uživateli

Nejčastější případ úpravy dat o uživateli je, že si data upravuje sám uživatel. Aby si uživatel mohl své údaje upravit, přidáme na úpravu pohled EmployeeUpdateView. Využijeme UpdateView, přidáme ale metodu get_object(). Tato metoda je u třídy UpdateView čte ID záznamu, který upravujeme, z adresy. To by v našem případě nebylo správně, protože uživatel by měl mít možnost upravovat pouze své údaje (pokud není administrátor). Upravíme tedy metodu get_object(). Využijeme atribut request, což je objekt třídy HttpRequest a obsahuje různé informace o požadavku uživatele. Kompletní popis této třídy je možné najít v dokumentaci. Jedním z klíčových atrbitů požadavku je uživatel, který požadavek vyvolal. S uživatelem již umíme pracovat na úrovni šablony (např. při kontrole, zda je uživatel přihlášen), nyní ho využijeme v metodě get_object(). Atribut user je záznam třídy User a můžeme tak přistoupit přes jeho atribut employee k záznamu třídy Employee, což je přesně to, co potřebujeme.

class EmployeeUpdateView(LoginRequiredMixin, UpdateView):
    template_name = "employee/update_employee.html"
    fields = ['department']
    success_url = reverse_lazy("employee_update")

    def get_object(self, queryset=None):
        return self.request.user.employee

Nepovinné čtení na doma - kombinace úprav obou modelů

Uvažujme trochu složitější zadání. Nyní chceme umožnit uživateli úpravu údajů v modelu User i Employee na jedné stránce. V takovém případě už za nás UpdateView celý problém nevyřeší, protože to počítá s úpravou jednoho záznamu.

Začneme s tím, že si vytvoříme formuláře jako třídy v souboru forms.py. Vytvoříme samostatný formulář pro úpravu údajů v modelu User a samostatný formulář pro úpravu údajů v modelu Employee. V Django můžeme používat dva typy formulářů:

  • obecné, které dědí od třídy Form a nemusí být napojeny na žádný model,
  • formuláře zaměřené na úpravu dat v modelech, které dědí od třídy ModelForm.

V našem případě je vhodnější použití ModelForm. Vytvoříme nejprve třídu UserForm a do ní vložíme "vnořenou" třídu Meta, které nastavíme atributy model a fields. Nastavení hodnot těchto atributů má stejnou logiku, jako mělo jejich nastavení přímo na pohled.

from django.forms import ModelForm
from crm.models import Employee
from django.contrib.auth.models import User


class UserForm(ModelForm):
    class Meta:
        model = User
        fields = ['first_name', 'last_name', 'email']

class EmployeeForm(ModelForm):
    class Meta:
        model = Employee
        fields = ['department']

Nyní upravíme pohled EmployeeUpdateView. Odebereme nejprve atribut fields a nahradíme ho atributem form_class, kde použijeme formulář EmployeeForm. Tím máme vyřešené úpravy dat v modelu Employee. Následně musíme zařídit zobrazení formuláře na úpravu dat v modelu User.

Přidáme metodu get_context_data(). Kontext je vlastně jen slovník, který obsahuje seznam proměnných, které jsou k dispozici uvnitř šablony. My nejprve necháme vytvořit kontext metodou get_context_data() mateřské třídy UpdateView (abychom nepřišli o proměnnou form, kde bude vložen formulář EmployeeForm) a následně vložíme klíč user_form, který bude obsahovat formulář UserForm. Formuláři UserForm při vytváření přiřadíme jako parametr instance aktuálně přihlášeného uživatele, kterého opět zjistíme z atributu request.user.

Dále se musíme postarat o uložení výsledných hodnot. K tomu můžeme využít metodu post(), která je zavolána vždy, když uživatel uloží formulář. Při ukládání opět nejprve vytvoříme instanci třídy UserForm, které navíc kromě parametru instance dáme slovník hodnot, který obsahuje data zadaná uživatelem. Slovník je opět součástí objektu request, konkrétně atributu request.POST. Dále provedeme kontrolu, že je formulář vyplněný korektně, k čemuž slouží metody is_valid(). Pokud tato metoda vrátí hodnotu True, provedeme uložení změn pomocí metody save().

from crm.forms import EmployeeForm, UserForm

class EmployeeUpdateView(LoginRequiredMixin, UpdateView):
    template_name = "employee/update_employee.html"
    form_class = EmployeeForm
    success_url = reverse_lazy("employee_update")

    def get_object(self, queryset=None):
        return self.request.user.employee

    def get_context_data(self, **kwargs):
        context = super().get_context_data()
        context["user_form"] = UserForm(instance=self.request.user)
        return context

    def post(self, request, *args, **kwargs):
        user_form = UserForm(request.POST, instance=request.user)
        if user_form.is_valid():
            user_form.save()
        return super().post(request, *args, **kwargs)

Poslední změna se týká šablony update_employee.html, do které přidáme vložení formuláře pomocí {{ user_form.as_p }}.

{% extends "base.html" %}
{% block content %}
<h1>Create new company</h1>
<form method="post">
    {% csrf_token %}
    {{ user_form.as_p }}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Uložit</button>
</form>
{% endblock %}

Zprávy

Pokud uživatel provede nějakou akci (např. upraví nějaký záznam), určitě uvítá, pokud ho informujeme o úspěšném uložení jím provedených změn. Tyto zprávy v rámci Django označujeme jako messages. To, aby pohled uměl se zprávami pracovat, zařídíme tím, že mu mezi mateřské třídy přidáme SuccessMessageMixin. Mateřský pohled UpdateView umí se zprávami již pracovat, pouze mu tedy musíme říct, jako zprávu má zobrazit, pokud došlo k úspěšné úpravě záznamu. To uděláme pomocí atributu success_message.

from django.contrib.messages.views import SuccessMessageMixin

class EmployeeUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    template_name = "employee/update_employee.html"
    fields = ['department']
    success_url = reverse_lazy("employee_update")
    success_message = "Data was updated successfully"

    def get_object(self, queryset=None):
        return self.request.user.employee

Následně musíme zařídit zobrazení zprávy v šabloně, a to především její správné naformátování. Framework Bootstrap má komponentu Alerts, která slouží ke stejnému účelu, tj. k zobrazení zpráv. U zpráv rozlišujeme především jejich typ, podle kterého je zpráva barevně vyznačena. Správnou třídu pak zvolíme pomocí třídy.

Zpráv teoreticky může být více, proto jsou uloženy v seznamu messages. Začneme kontrolou, zda v seznamu vůbec nějaká zpráva je, což zařídíme pomocí podmínky {% if messages %}. Poté pomocí cyklu projdeme všechny zprávy k zobrazení pomocí cyklu for. Každou právu vložíme do tagu <div>, který musí mít nastavenou třídu alert a následně třídu pro příslušný typ zprávy. Ten je uložený v atributu tag dané zprávy, tj. zobrazíme ho pomocí {{ message.tags }}.

{% if messages %}
{% for message in messages %}
<div class="alert {{ message.tags }}" role="alert">
    {{ message }}
</div>
{% endfor %}
{% endif %}

Správná třída není vložena automaticky, ale na základě nastavení. Do souboru settings.py vložíme slovník MESSAGE_TAGS, který má jako klíče typy zpráv a jako hodnoty tagy, které jsou následně "přilepeny" na každou ze zpráv dle jejího typu. Zatím používáme pouze typ messages.SUCCESS a tomu nastavíme hodnotu alert-success, což je vhodná třída dle dokumentace frameworku Bootstrap.

from django.contrib.messages import constants as messages

MESSAGE_TAGS = {
    messages.SUCCESS: 'alert-success',
}

Pokud byste rovnou chtěli přidat i další třídy, můžete si zkopírovat slovník například ze článku How to use Django Messages Framework.

Úkoly

Zpráva o vytvoření

Stejně jako UpdateView je i CreateView vybaven na odesílání zpráv. Přidej zprávu o vytvoření k pohledu OpportunityCreateView a CompanyCreateView.

Řešení

Je potřeba k rodičovským třídám přidat SuccessMessageMixin a poté atribut success_message s nějakým textem.

class OpportunityCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
    permission_required = "crm.add_opportunity"
    model = models.Opportunity
    template_name = "company/create_company.html"
    fields = ["company", "sales_manager", "description", "status", "value"]
    success_url = reverse_lazy("index")
    success_message = "Opportunity was created successfully."

class CompanyCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
    model = models.Company
    template_name = "company/create_company.html"
    fields = ["name", "status", "phone_number", "email", "identification_number"]
    success_url = reverse_lazy("index")
    success_message = "Company was created successfully."

Další údaje o zaměstnanci

Přidej k zaměstnanci další údaje, které o něm chceme uchovávat. Jedním z nich bude telefonní čislo (atribut pojmenuj jako phone_number a vytvoř ho jako textové pole), číslo kanceláře (office_number, opět textová hodnota) a nadřízeného (manager, vazba na model Employee, název modelu musí být v uvozovkách). Umožni uživatelu úpravy všech těchto atributů.

Řešení

Ke třídě Employee přidáme jednotlivé atributy.

class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    department = models.CharField(max_length=100, blank=True, null=True)
    phone_number = models.CharField(max_length=20, blank=True, null=True)
    office_number = models.CharField(max_length=10, blank=True, null=True)
    manager = models.ForeignKey("Employee", on_delete=models.SET_NULL, null=True, blank=True)

Následně je potřeba spustit tyto příkazy python manage.py makemigrations a python manage.py migrate. Aby si uživatel mohl hodnoty upravit, je potřeba je přidat do seznamu v atributu fields.

class EmployeeUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    fields = ["department", "phone_number", "office_number", "manager"]
    template_name = "employee/update_employee.html"
    success_url = reverse_lazy("index")
    success_message = "Data was updated successfully."

    def get_object(self, queryset=None):
        return self.request.user.employee

Bonus: Notifikace

Často uživatelé chtějí dostávat notifikace o nějaké události, např. zákazník e-shopu chce dostat zprávu o odeslání balíku. Uvažujme, že budeme chtít posílat informace o nově založených obchodních příležitostech na mail vedoucího obchodního oddělení. K tomu využijeme opět signály. Přidej signál, jehož odesílatelem (sender) bude model Opportunity a vytvoř vhodně pojmenovanou metodu (např. create_opportunity, ale název není důležitý). Podmínku if created: můžeš klidně překopírovat, protože chceme informovat pouze o založení, nikoli o úpravách. Do podmínky vlož funkci send_email. Je nutné provést její import takto:

from django.core.mail import send_mail

Funkci zadej 4 parametry:

  1. předmět e-mailu,
  2. obsah e-mailu - zadej ho jako prostý text, můžeš tam třeba vložit název firmy, které se obchodní případ týká, k tomu se dostateš přes proměnnou instance,
  3. adresu odesílatele, tu nastav třeba na robot@mojefirma.cz nebo libovolnou jinou,
  4. seznam příjemců - do něj vlož jednoho příjemce, a to sales_manager@czechitas.cz (pozor, musí to opravdu být seznam!).

Poté zkus založit obchodní případ a zkontroluj, že ve službě Mailtrap se e-mail zobrazí.

Řešení

Řešení bonusového příkladu vypadá takto:

  • Odesílatelem je modul Opportunity, protože na jeho vytvoření reagujeme.
  • Uvnitř metody create_opportunity získáme název firmy (či jakoukoli jinou informaci z modelu Opportunity) prostřednictvím hodnoty parametru instance. Pokud chceme název firmy, napíšeme instance.company.name.
@receiver(post_save, sender=Opportunity)
def create_opportunity(sender, instance, created, **kwargs):
    if created:
        send_mail("Opporunity was created", instance.company.name, "test@mojefirma.cz", ["manazer@mojefirma.cz"])