admin 0Comment

Jednym z użytecznych elementów, z którego możemy skorzystać przy okazji pracy z Django, jest system uwierzytelniania użytkowników. Potrzebujesz stworzyć system rejestracji, logowania i nie tylko? Nie ma problemu, framework pomoże Ci w tym w bardzo wygodny i szybki sposób. Mój konkursowy projekt aplikacji to jednocześnie jeden z tych przypadków, w których konieczne jest rozszerzenie domyślnego modelu użytkownika, z którego korzystamy w Django. Nie chcę, aby użytkownik mógł korzystać tylko z podstawowych ustawień, oferowanych przez aplikację – powinien być w stanie w pewnym zakresie konfigurować jej poszczególne elementy, takie jak np. nazwy miejsc, w których gromadzi swoje środki pieniężne (dla przypomnienia – moja konkursowa aplikacja ma pomagać w zarządzaniu budżetem). Domyślnie, podczas rejestracji użytkownika, wspomniane elementy są ustawione pod nazwami ‘Bank account’, ‘Savings’, ‘Wallet’ oraz ‘Others’, chciałbym jednak by możliwe było nie tylko zmienianie powyższych nazw, ale też swobodne zwiększanie lub zmniejszanie samej liczby tych elementów.

Oczywiście każdy użytkownik powinien być w stanie dowolnie konfigurować te, i kilka innych, elementów. Oznacza to, że są to dane, które muszą być powiązane z konkretnym obiektem User, który jednak w podstawowej wersji oferuje nam tylko kilka pól opisujących go (są to m. in. pola first_name, last_name, email czy password, po dokładniejsze informacje polecam sięgnąć do dokumentacji).

W tym momencie pojawia się więc konieczność rozszerzenia funkcjonalności modelu użytkownika o dodatkowe informacje, które będziemy mogli konfigurować w samym programie (innym rozwiązaniem byłoby stworzenie kolejnego modelu reprezentującego te właśnie nazwy miejsc, w których użytkownik gromadzi swoje środki pieniężne i przypisanie jego instancji do modelu użytkownika). Jest kilka sposobów na osiągnięcie tego celu, dzisiaj skupię się na tym, który zdecydowałem się wykorzystać.

Sposób ten polega na stworzeniu modelu zajmującego się gromadzeniem dodatkowych informacji oraz powiązaniu go z modelem użytkownika (User). Kluczem do poprawnego działania tego sposobu jest użycie relacji One-To-One między modelami, co oznacza, że dokładnie jeden obiekt danego modelu może być powiązany z dokładnie jednym modelem drugiego modelu. Mój dodatkowy model tworzę pod nazwą Profile:

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    stashNames = models.TextField(default='Bank account\nSavings\nWallet\nOthers\n')
    costNames = models.TextField(default='Rent and other charges\nTransportation\n'
                                         'Clothes\nFood\nHobby\nOthers\n')

Jak widać na początku określam tutaj relację do modelu User (wspomniane One-To-One), a także akcję w przypadku skasowania obiektu modelu, do którego przyporządkowany jest dany obiekt (zostanie on także skasowany, bo i po co nam obiekt Profile jeśli użytkownik już nie istnieje).

Dalsze zmienne określają nam elementy, które użytkownik będzie mógł konfigurować (opisują one nazwy miejsc, w których gromadzone są środki pieniężne oraz nazwy dla kategorii wydatków). Jak widać, są one zapisane jako pola typu TextField, a nie, co wydawałoby się najsensowniejsze, jako listy. Powodem jest to, że Django, by być w stanie mapować obiekty do relacyjnej bazy danych, musi korzystać z predefiniowanych typów pól, takich jak właśnie TextField. Z czego Django korzysta i jak zamienia obiekty (tworzone jako instancje klas) na dane w tabelach bazy – to temat na inny, długi wpis, który wymaga od was poznania podstaw takich zagadnień jak ORM, SQL oraz relacyjnych baz danych (normalizacji). W przyszłości poruszę na blogu i te tematy, w tym jednak momencie napiszę, że na tę chwilę zdecydowałem się na proste zapisanie danych w postaci łańcuchów znaków (innym sposobem byłoby np. stworzenie własnego typu pola dla modelu, lub skorzystanie z PostgreSQL zamiast domyślnej bazy SQLite, co ułatwiłoby pracę na listach) i zamianie ich do formy listy niejako „w biegu”, lub poprzez dodatkowe funkcje pomocnicze. Na szczęście Python bardzo ułatwia tego typu działania i nie wymaga pisania dużej ilości dodatkowego kodu.

Wróćmy jednak do naszego modelu Profile i rozszerzania funkcjonalności. Brakuje nam tutaj bardzo ważnego elementu – wiemy, że oba modele są ze sobą powiązane, wiemy też, że zostaną razem usunięte jednak chcielibyśmy by tworzenie i zmiana obiektu modelu Profile dokonywała sie ‘automatycznie’, podczas zmian obiektu użytkownika. Pomoże nam w tym tzw. dekorowanie  i dodanie odpowiednich funkcji:

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    stashNames = models.TextField(default='Bank account\nSavings\nWallet\nOthers\n')
    costNames = models.TextField(default='Rent and other charges\nTransportation\n'
                                         'Clothes\nFood\nHobby\nOthers\n')

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

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

W ten prosty sposób nasze obiekty modelu Profile są jeszcze ściślej powiązane z obiektami modelu User.  Jak widzicie, dzięki wspólnym siłom języka Python oraz frameworka Django, nie wymaga to dużej ilości dodatkowego kodu.

Pozostaje jeszcze pytanie jak korzystać z tego dodatkowego modelu w widokach? Jest to (jak przystało na Pythona) bardzo proste. Przykładowe pozyskanie obiektu Profile dla danego użytkownika wygląda tak:

userProfile = Profile.objects.get(user=request.user)

Moglibyśmy też dotrzeć do obiektu Profile (i jego pól) w ten sposób:

user = User.objects.get(pk=user_id)
user.profile.stashNames = Bank account\nSavings account\nWallet\nSecret stash\nOthers\n'
user.save()

Proste? Proste!

Dodaj komentarz