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