Django: Vartotojo profilio puslapis (v2) - robotautas/kursas GitHub Wiki

Sukuriame vartotojo profilio puslapį

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>

Prie vartotojo pridedame papildomus laukus

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į)

Automatiškai sumažiname ir atvaizduojame nuotraukas:

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 %}

ALTERNATYVA:

Š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

Užduotis

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ų.

Atsakymas

⚠️ **GitHub.com Fallback** ⚠️