🔖 Day12 - Extend User Profile, OneToOneField, Signal, SelectDateWidget

2018 - 06 - 26
🔖 Day12 - Extend User Profile, OneToOneField, Signal, SelectDateWidget
上一篇新增的MyAccount頁面,只是把Django完有的User Models 以表單形式顯示出來,該用戶使用。 如果需要增加多點欄位,e.g. Gender、DOB、Address等等profile,我們需要把預設的User Models extend。 在SQL,兩個獨立的tables可以被合體:
SELECT * 
FROM User
INNER JOIN Profile ON User.id=Profile.id

在Django裡面也可以把兩個models連接,並同步更新,要使用的工具包括:
  • OneToOneField,類似SQL中把Models連上。 詳細內容可以到DjangoProject參考
  • django.db.models.signals.post_save,作為model A 發出save()時的signal類別
  • Decorator: django.dispatch.receiver,接收Signals,讓model B 跟隨model A同步更新

1. [Create new model] 首先到<app>.models.py新增一個model - Profile
from django.contrib.auth.models import User
from django import models
from django.dispatch import receiver
from django.db.models.signals import post_save

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    gender = models.CharField(null=True, blank=True, max_length=1)
    birthday = models.DateField(null=True, blank=True)
    City = models.TextField(null=True, blank=True)

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

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

這個class的內容重點:
  • 建立新model class - Profile
  • 使用了OneToOneField把Profile連接到auth.models.User
  • on_delete=models.CASCADE的意思是當該user被刪除時,profile同時淹沒
  • 當有新User被建立時,利用django.dispatch.receiver接收signal - post_save,新建一項Profile object至該User
  • 同樣,當用戶更新auth.models.User資料時,再save一下profile

2. [Create new form]<app>.forms.py 建立新form:
from django import forms
from .models import Profile

class ProfileForm(forms.modelForm):
    gender = forms.ChoiceField(required=False, choices=(('',''),('M','M'),('F','F')))
    birthday = forms.DateField(required=False, widget=forms.widget.SelectDateWidget(years=range(1917, 2018)))
    city = forms.CharField(required=False)

    class Meta:
        model = Profile
        fields = ('gender', 'birthday', 'city')

建立FORM跟之前其他沒什麼分別,只是這裡有兩種特別forms.fields使用了
  • forms.ChoiceField,可以使用set自定有甚麼choices。 每一個小tuple裡面,第一格是顯示在database GUI的名字 第二格才是顯示在頁面form內的選項
  • forms.widget.SelectDateWidget出來的效果就是三個selections,年月日。 假如沒有指定年份,它會顯示出今年至未來的年份。 因為這項form是birthday,所以故意限定years=range(1917,2018)這100年

3. [Modify view function]<app>.views.myaccount,加上ProfileForm:
from .forms imort UserAccount, ProfileForm

def myaccount(request):
    if request.method == "POST":
        form = UserAccount(request.POST, instance=request.user)
        profile_form = ProfileForm(request.POST, instance=request.user.profile)
        if form.is_valid() and profile_form.is_valid():
            form.save()
            profile_form.save()
            return appindex(request)
        else:
            error = 'There is something wrong about your information.'
            form = UserAccount(instance=request.user)
            profile_form = ProfileForm(instance=request.user.profile)
            return render(request, 'registration/myaccount.html', {'form':form, 'profile_form': profile_form, 'error':error})
    else:
        form = UserAccount(instance=request.user)
        profile_form = ProfileForm(instance=request.user.profile)
        return render(request, 'registration/myaccount.html', {'form':form, 'profile_form': profile_form})

在白色地方,加上ProfileForm,以便用戶在同一頁一次過save
4. [Modify template]templates/registration/myaccount.html,修改頁面,加入profile_form
{% extends 'base.html' %}
{% block content %}

<form method="POST">
    {% csrf_token %}
    {% for field in form %}
        <label for='{{ field.name }}'>{{ field.label_tag }}</label>: 
        {% if field.name != "password" %}
            {{ field }}
        {% endif %}
        {% if field.help_text %}
            {{ field.help_text | safe }}
        {% endif %}
    {% endfor %}

    {% for field in profile_form %}
        <label for='{{ field.name }}'>{{ field.label_tag }}</label>:
        {{ field }}
        {% if field.help_text %}
            {{ field.help_text }}
        {% endif %}
    {% endfor %}

    <input type='submit' value='Submit'>
</form>

{% endblock %}

這樣在同一頁內,可以讓用戶同時修改基本User資料,以及增加Profile資料
5. [No profile for existing users] 由於Profile只是在新增用戶時,才會被建立表單, 所以exiting users在登入時,會引起step 1內的instance.profile.save()錯誤。 最簡單的解決方法,是manually在shell為該用戶增建profile。 在terminal打開pipenv run python manage.py shell
$from django.contrib.auth.models import User
$u = User.objects.get(username='<username>')
$from <app>.models import Profile
$ = Profile(user=u, gender='M')
$p.save()

這樣就會在database加上了一行新的項目給該用戶 當該用戶再登入時,就不會再出error。

Comments

There is no comment yet

New Comment

Please Login to comment