Django: Vartotojo profilio puslapis (v2) - robotautas/kursas GitHub Wiki
Sukuriame formą vartotojo redagavimui faile forms.py:
from django import forms
from django.contrib.auth.models import User
class UserChangeForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name', 'email']
Galima būtų jos neaprašinėti ir naudoti defaultinę UserCreationForm, bet tada formoje būtų rodomi tik vartotojo vardo ir slaptažodžio pakeitimo laukai (o mums reikia vardo, pavardės, el. pašto adreso).
Aprašome redagavimo rodinį views.py faile:
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.urls import reverse_lazy
class ProfileUpdateView(LoginRequiredMixin, generic.UpdateView):
model = User
form_class = UserChangeForm
template_name = "profile.html"
success_url = reverse_lazy('profile')
context_object_name = "user"
def get_object(self, queryset=None):
return self.request.user
Pridedame kelią į library/urls.py:
from . import views
...
path('profile/', views.ProfileUpdateView.as_view(), name='profile'),
Sukuriame profilio redagavimo šabloną profile.html:
{% extends "base.html" %}
{% block "title" %}Profile{% endblock %}
{% block "content" %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md">
<p><strong>Username: </strong>{{ user.username }}</p>
<p><strong>First Name: </strong>{{ user.first_name }}</p>
<p><strong>Last Name: </strong>{{ user.last_name }}</p>
<p><strong>Email: </strong>{{ user.email }}</p>
<h2 class="mb-4 text-center">Profile</h2>
<form method="post">
{% csrf_token %}
{% load crispy_forms_tags %}
{{ form | crispy }}
<button type="submit" class="btn btn-primary w-100">Update</button>
</form>
</div>
</div>
</div>
{% endblock %}
Įdedame naują nuorodą (href="{% url 'profile' %}") į meniu, paveikslėlio ir vartotojo vardo tage, faile base.html:
<li class="nav-item"><a class="nav-link" href="{% url 'profile' %}">
<svg class="bi bi-person" width="1.5em" height="1.5em" viewBox="0 0 16 16" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M13 14s1 0 1-1-1-4-6-4-6 3-6 4 1 1 1 1h10zm-9.995-.944v-.002.002zM3.022 13h9.956a.274.274 0 00.014-.002l.008-.002c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664a1.05 1.05 0 00.022.004zm9.974.056v-.002.002zM8 7a2 2 0 100-4 2 2 0 000 4zm3-2a3 3 0 11-6 0 3 3 0 016 0z"
clip-rule="evenodd"/>
</svg>
{{ user.get_username }}</a></li>
Padarysime, kad prie vartotojo leistų prisegti nuotrauką. Ši informacija bus atvaizduojama naujame profilio puslapyje.
Papildome Django User modelį nuotraukos lauku models.py:
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
photo = models.ImageField(default="profile_pics/default.png", upload_to="profile_pics")
Įvykdome migracijas
Paredaguojame vartotojo redagavimo formą forms.py:
from django import forms
from .models import CustomUser
class CustomUserChangeForm(forms.ModelForm):
class Meta:
model = CustomUser
fields = ['first_name', 'last_name', 'email', 'photo']
Sutikriname, ar niekas nepapasikeitė (neturėtų) views.py:
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Task, CustomUser
from .forms import CustomUserChangeForm
class ProfileUpdateView(LoginRequiredMixin, generic.UpdateView):
model = CustomUser
form_class = CustomUserChangeForm
template_name = 'profile.html'
success_url = reverse_lazy('profile')
context_object_name = "user"
def get_object(self, queryset=None):
return self.request.user
Ir library/urls:
path('profile/', views.ProfileUpdateView.as_view(), name='profile'),
Taip pat profile.html:
{% extends "base.html" %}
{% block "title" %}Profile{% endblock %}
{% block "content" %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md">
<p><strong>Username: </strong>{{ user.username }}</p>
<p><strong>First Name: </strong>{{ user.first_name }}</p>
<p><strong>Last Name: </strong>{{ user.last_name }}</p>
<p><strong>Email: </strong>{{ user.email }}</p>
<h2 class="mb-4 text-center">Profile</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% load crispy_forms_tags %}
{{ form | crispy }}
<button type="submit" class="btn btn-primary w-100">Update</button>
</form>
</div>
</div>
</div>
{% endblock %}
Atkreipkite dėmesį, kad tam, kad forma priimtu nuotrauką, į form tag'ą reikia įrašyti enctype="multipart/form-data".
Ir base.html:
<li class="nav-item"><a class="nav-link" href="{% url 'profile' %}">
<svg class="bi bi-person" width="1.5em" height="1.5em" viewBox="0 0 16 16" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M13 14s1 0 1-1-1-4-6-4-6 3-6 4 1 1 1 1h10zm-9.995-.944v-.002.002zM3.022 13h9.956a.274.274 0 00.014-.002l.008-.002c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664a1.05 1.05 0 00.022.004zm9.974.056v-.002.002zM8 7a2 2 0 100-4 2 2 0 000 4zm3-2a3 3 0 11-6 0 3 3 0 016 0z"
clip-rule="evenodd"/>
</svg>
{{ user.get_username }}</a></li>
Kad programa dabar prisijungimui naudotų mūsų naują vartotoją, settings.py įrašome:
AUTH_USER_MODEL = 'library.CustomUser'
Taip pat teks patobulinti vartotojo registraciją, kad naudotų mūsų naują vartotoją.
Tam faile įdedame naują vartotojo kūrimo formą forms.py:
from .models import CustomUser
from django.contrib.auth.forms import UserCreationForm
class CustomUserCreateForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ['username', 'email', 'password1', 'password2']
Taip pat perrašome registracijos view'są, kad naudotų naują formą:
from .forms import CustomUserCreateForm
class SignUpView(generic.CreateView):
form_class = CustomUserCreateForm
template_name = "signup.html"
success_url = reverse_lazy("login")
Taip pat reikės pakoreguoti modelius, kurie turi ryšį su vartotoju models.py:
class BookInstance(models.Model):
uuid = models.UUIDField(default=uuid.uuid4)
book = models.ForeignKey(to="Book", on_delete=models.SET_NULL, null=True, blank=True, related_name="instances")
due_back = models.DateField("Available On", null=True, blank=True)
reader = models.ForeignKey(to="library.CustomUser", verbose_name="Reader", on_delete=models.SET_NULL, null=True, blank=True)
LOAN_STATUS = (
('d', 'Administered'),
('t', 'Taken'),
('a', 'Available'),
('r', 'Reserved'),
)
status = models.CharField(max_length=1, choices=LOAN_STATUS, blank=True, default="d")
def is_overdue(self):
return self.due_back and timezone.now().date() > self.due_back
def __str__(self):
return str(self.uuid)
class BookReview(models.Model):
book = models.ForeignKey(to="Book", verbose_name="Book", on_delete=models.SET_NULL, null=True, blank=True,
related_name="reviews")
reviewer = models.ForeignKey(to="library.CustomUser", verbose_name="Reviewer", on_delete=models.SET_NULL, null=True, blank=True)
date_created = models.DateTimeField(verbose_name="Date Created", auto_now_add=True)
content = models.TextField(verbose_name="Content", max_length=2000)
class Meta:
verbose_name = "Book Review"
verbose_name_plural = 'Book Reviews'
ordering = ['-date_created']
Nuo šio momento gali nepavykti įvykdyti migracijų, nes visi ryšiai į vartotoją dabar turi rodyti į naują vartotoją. Paprasčiausias sprendimas - iš naujo sukurti duomenų bazę (taip prarasime visus joje esančius duomenis). Tam reikės ištrinti patį duomenų bazės failą (db.sqlite3) ir failus, esančius kataloge library/migrations(išskyrus init.py failą!). Taip pat pridėtus failus media kataloge.
Dabar atliekame migracijas ir iš naujo įrašome (importuojame) duomenis į duomenų bazę.
Kad matytume naujo vartotojo rodinį admin puslapyje, reikės registruoti jį admin.py, :
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
fieldsets = UserAdmin.fieldsets + (
('Additional Info', {'fields': ('photo',)}),
)
admin.site.register(CustomUser, CustomUserAdmin)
Tam, kad profilyje matytume ir priskirtą nuotrauką:
<img class="rounded-circle account-img" style="width: 300px" src="{{ user.photo.url }}">
Kad vartotojui nepriskyrus jokios nuotraukos, būtų rodoma numatytoji, į media/profile_pics katalogą įdėkite failą default.png (numatytąjį paveikslėlį)
Profilio modelyje perrašome save metodą. Faile models.py:
from PIL import Image
class CustomUser(AbstractUser):
photo = models.ImageField(default="profile_pics/default.png", upload_to="profile_pics")
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
img = Image.open(self.photo.path)
min_side = min(img.width, img.height)
left = (img.width - min_side) // 2
top = (img.height - min_side) // 2
right = left + min_side
bottom = top + min_side
img = img.crop((left, top, right, bottom))
img = img.resize((300, 300), Image.LANCZOS)
img.save(self.photo.path)
Dabar, prisegus naują failą profilyje, jis bus automatiškai sumažinamas iki 300x300 taškų.
Dabar galime vartotojo nuotraukas atvaizduoti ir puslapiuose, kurie turi ryšį su vartotoju, pavyzdžiui faile book.html, prie kiekvieno komentaro:
<h2>Komentarai:</h2>
<br/>
{% if book.reviews.all %}
{% for review in book.reviews.all %}
<p>
<img class="rounded-circle account-img" style="width: 30px" src="{{ review.reviewer.photo.url }}">
<strong>{{ review.reviewer }}</strong>, <em>{{ review.date_created }}</em>
</p>
<p>{{ review.content }}</p>
<hr>
{% endfor %}
{% else %}
<p>Komentarų nėra</p>
{% endif %}
Šioje paskaitoje pateiktus žingsnius galima išspręsti ir kitaip: sukūrus papildomą Profilio modelį, turintį OneToOne ryšį su vartotoju: https://github.com/robotautas/kursas/wiki/Django:-Vartotojo-profilio-puslapis
Tęsti kurti Django užduotį – Autoservisas:
- Papildyti Django vartotojo modelį photo lauku (per Abstract User).
- Sukurti naują profilio puslapį, kuriame galima redaguoti vartotojo informaciją (el. paštą, vardą, pavardę), pakeisti nuotrauką.
- Padaryti, kad prisegta profilio nuotrauka būtų automatiškai sumažinama iki norimo dydžio ir būtų kvadratinė (pagal dizainą).
- Padaryti, kad vartotojo nuotrauka būtų matoma profilio puslapyje.
- Padaryti, kad vartotojo nuotrauka būtų matoma prie kiekvieno užsakymo ir vartotojo komentarų.