[Mobilar] 02 - Multiple Users: user profile

Ref: Python Django Tutorial: Full-Featured Web App Part 8 - User Profile and Picture

Ref: Python Django Tutorial: Full-Featured Web App Part 9 - Update User Profile

 

 

User Profile

一、view --> template

文件 users/views.py

这里有权限 checker。

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm


def register(request):
    if request.method == 'POST':
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            messages.success(request, f'Your account has been created! You are now able to log in')
            return redirect('login')
    else:
        form = UserRegisterForm()
    return render(request, 'users/register.html', {'form': form})


#
# 该装饰器作为 permission checker
#
@login_required
def profile(request):
    return render(request, 'users/profile.html')

 

 

二、注册 model 到 admin 

  • 定义 Profile 模型

文件 users/models.py

from django.db import models
from django.contrib.auth.models import User


class Profile(models.Model):
    # 如何 user 被删除,profile 也会跟着被删除
    user  = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(default='default.jpg', upload_to='profile_pics')  # 记得执行 pip install Pillow

    def __str__(self):
        return f'{self.user.username} Profile'

 

如下可见,user 已直接跟 profile 关联了起来。

>>> from django.contrib.auth.models import User
>>> user = User.objects.filter(username='CoreyMS').first()
>>> user
<User: CoreyMS>
>>> user.profile <Profile: CoreyMS Profile> >>> user.profile.image <ImageFieldFile: profile_pics/pic.jpg> >>> user.profile.image.width 1536 >>> user.profile.image.url '/media/profile_pics/pic.jpg'

 

user 和 image 的效果如下:

__str__ 作为添加完毕后的tip如下:

 

  • 注册 Profile 模型

文件 users/admin.py

from django.contrib import admin
from .models import Profile

admin.site.register(Profile)

 

  • 设置 media

文件 settings.py

media 的文件系统的路径,以及url上的位置。

STATIC_URL = '/static/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL  = '/media/'

CRISPY_TEMPLATE_PACK = 'bootstrap4'

LOGIN_REDIRECT_URL = 'blog-home'
LOGIN_URL = 'login'

 

Managing static files (e.g. images, JavaScript, CSS)

文件 urls.py

Ref: https://docs.djangoproject.com/en/3.1/howto/static-files/#serving-files-uploaded-by-a-user-during-development

from django.conf import settings
from django.conf.urls.static import static

#
# urlpatterns 也定义了路由
#
if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

 

 

三、Django信号

Ref: Django基础(31): 如何理解和正确使用Django信号(Signals)

 

通俗而讲Django信号的工作原理就是:

当某个事件发生的时候会发出一个信号 (signals),而监听这个信号的函数 (receivers) 就会立即执行。

 

文件 users/signals.py

如下代码意思:监听 User 模型发出的 post_save 信号。也就是说:profile的创建是被动地收到 user 的控制。

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile


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

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

 

 

 

更新 user profile

一、View 获取 Form

文件 users/views.py

获得 request,其中主要是两个 form 的内容,而 form 的内容在 forms.py 中定义。

@login_required
def profile(request):
if request.method == 'POST': u_form = UserUpdateForm(request.POST, instance=request.user) # ['username', 'email'] p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile) # ['image']
if u_form.is_valid() and p_form.is_valid(): u_form.save() p_form.save() messages.success(request, f'Your account has been updated!') return redirect('profile') # (2) 如果是更新过程,最后定向到 profile else: u_form = UserUpdateForm(instance=request.user) p_form = ProfileUpdateForm(instance=request.user.profile) context = { 'u_form': u_form, 'p_form': p_form } return render(request, 'users/profile.html', context) # (1) 如果是将要更新前的界面,就是先提供基本的 u_form and p_form。

-------- form 的单独定义 ---------

文件 users/forms.py

这里只定义了 “提交” 的信息部分。

from django import forms
from django.contrib.auth.models import User
from .models import Profile


class UserUpdateForm(forms.ModelForm):
    email = forms.EmailField()

    class Meta:
        model = User
        fields = ['username', 'email']


class ProfileUpdateForm(forms.ModelForm):

    class Meta:
        model = Profile
        fields = ['image']

 

 

二、HTML 模板

Ref: form表单中的enctype=“multipart/form-data“什么意思?

enctype 就是 encodetype,编码类型的意思。

multipart/form-data 是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思。

{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
      <div class="media">
        <img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
        <div class="media-body">
          <h2 class="account-heading">{{ user.username }}</h2>
          <p class="text-secondary">{{ user.email }}</p>
        </div>
      </div>
<form method="POST" enctype="multipart/form-data"> {% csrf_token %} <fieldset class="form-group"> <legend class="border-bottom mb-4">Profile Info</legend> {{ u_form|crispy }} {{ p_form|crispy }} </fieldset> <div class="form-group"> <button class="btn btn-outline-info" type="submit">Update</button> </div> </form> </div> {% endblock content %}

UI如下,update成功后,还是重定向到 原本的 profile,只是多了些 “更新成功” 的提示。

 

 

三、Thumbnail

  • 设置

在 Admin User Profile 中,需要考虑 thumbnail 的效果。 可通过安装 pillow 来实现。

文件 users/models.py

from django.db import models
from django.contrib.auth.models import User
from PIL import Image


class Profile(models.Model):
    user  = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(default='default.jpg', upload_to='profile_pics')

    def __str__(self):
        return f'{self.user.username} Profile'

    def save(self):  # <---- 可能有点问题
        super().save()

        img = Image.open(self.image.path)

        if img.height > 300 or img.width > 300:
            output_size = (300, 300)
            img.thumbnail(output_size)  # <---- 保存的 过程中 会对图片进行缩放。
            img.save(self.image.path)

 

(1) 可能的问题:

save() got an unexpected keyword argument 'force_insert'

(2) 解决方法如下:

Ref: Django - TypeError - save() got an unexpected keyword argument 'force_insert'

from django.db import models
from django.contrib.auth.models import User
from PIL import Image


class Profile(models.Model):
    user  = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(default='default.jpg', upload_to='profile_pics')

    def __str__(self):
        return f'{self.user.username} Profile'

    def save(self, *args, **kwargs):  # <---- 添加参数2,3
        super(Profile, self).save(*args, **kwargs)  # <---- 添加参数

        img = Image.open(self.image.path)

        if img.height > 300 or img.width > 300:
            output_size = (300, 300)
            img.thumbnail(output_size)
            img.save(self.image.path)

 

  • 使用

如此,有了“小图片”,再在其他页面使用。

文件 blog/templates/blog/home.html

{% extends "blog/base.html" %}
{% block content %}
    {% for post in posts %}
        <article class="media content-section">
<img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">  # 增加此行,添加了圆形头像功能。
<div class="media-body"> <div class="article-metadata"> <a class="mr-2" href="#">{{ post.author }}</a> <small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small> </div> <h2><a class="article-title" href="#">{{ post.title }}</a></h2> <p class="article-content">{{ post.content }}</p> </div> </article> {% endfor %} {% endblock content %}

UI 实现效果:

 

 

 

数据库

可见,在数据库中,并没有权限概念。权限是在读数据库之前就进行了check。

 

End.

posted @ 2021-01-27 18:00  郝壹贰叁  阅读(96)  评论(0编辑  收藏  举报