iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0
Modern Web

Python x Django 網站實作&學習記錄系列 第 6

D6 allauth 採坑日記 Extending & Substituting User model (1)

我本來只是想在註冊頁面(Signup)增加手機號碼(不需簡訊驗證)
結果被Allauth跟django.contrib.auth搞得差點起笑
這是原本的註冊畫面
Imgur
這是最終的註冊畫面
Imgur

先講我最終使用的方法 Extending user model
我重開一個專案叫做docsystem_5並新增一個叫auth_info的app
專案的樹狀如下
Imgur
一樣在docsystem_5/setting.py加入installed_apps
templates部分 把allauth package中的templates整個複製到docsystem_5內
並修改'DIRS': [BASE_DIR / 'templates']
這樣可以讓我們在資料夾內直接改我們需要的網頁樣式而不會去動到其他有在使用allauth的專案

INSTALLED_APPS = [
    # other app
    'django.contrib.auth',
    'django.contrib.messages',
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.weixin',
    'auth_info',
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                # allauth need
                'django.template.context_processors.request',
            ],
        },
    },
]

# allauth
AUTHENTICATION_BACKENDS = [
    # Needed to login by username in Django admin, regardless of `allauth`
    'django.contrib.auth.backends.ModelBackend',
    # `allauth` specific authentication methods, such as login by e-mail
    'allauth.account.auth_backends.AuthenticationBackend',
]
SITE_ID = 1
SOCIALACCOUNT_PROVIDERS = {
    'google': {
        # For each OAuth based provider, either add a ``SocialApp``
        # (``socialaccount`` app) containing the required client
        # credentials, or list them here:
        'APP': {
            'client_id': '123',
            'secret': '456',
            'key': ''
        }
    }
}
## this to avoid email verification and shows at console
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
ACCOUNT_AUTHENTICATION_METHOD ='email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_USERNAME_REQUIRED = False
### to use custom form
ACCOUNT_SIGNUP_FORM_CLASS = 'auth_info.forms.SignupForm'
LOGIN_REDIRECT_URL = '/accounts/profile/'

allauth的templates位置
Imgur
由以上的ACCOUNT_SIGNUP_FORM_CLASS可以知道我們會將註冊頁面改由auth_info之下的form.py內的SignupForm類別來執行
因此我們創建一個auth_info/form.py
first_name 跟 last_name 是django.auth本來就預設的資料 所以本來就建立在資料庫的auth_user table內
而phone_number則是要靠auth_info app 的model建立一個新的table來存放 並且對應到auth_user.id

from django import forms
from .models import UserProfile


class ProfileForm(forms.Form):
    first_name = forms.CharField(label='First Name', max_length=50, required=False)
    last_name = forms.CharField(label='Last Name', max_length=50, required=False)
    phone_number = forms.CharField(label='Phone number', max_length=10, required=False)


class SignupForm(forms.Form):
    first_name = forms.CharField(
        max_length=30,
        label="First Name",
        widget=forms.TextInput(attrs={"placeholder":"小明"}),
    )
    last_name = forms.CharField(
        max_length=30,
        label="Last Name",
        widget=forms.TextInput(attrs={"placeholder":"王"}),
    )
    phone_number = forms.CharField(
        max_length=10,
        label="Phone number",
        widget=forms.TextInput(attrs={"placeholder":"0987654321"}),
        required=False,
    )

    def signup(self, request, user):
        user_profile = UserProfile()
        user_profile.user = user
        user.save()
        user_profile.phone_number = self.cleaned_data['phone_number']
        user_profile.save()

修改auth_info/model.py
django會根據model內的內容修改、建立或是刪除SQLite資料庫的table
建立一個新的類別叫做UserProfile 這裡未來要建立一個auth_info_userprofile的table到資料庫內(資料庫的圖我放在最下面,可以參考圖片中的訊息了解一下資料庫之間的相關)
我把user用OneToOneField的方法將auth_info_userprofile的user_id欄對應到auth_user的id欄

from django.db import models
from django.contrib.auth.models import User
from allauth.account.models import EmailAddress
# Create your models here.

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='userprofile')
    phone_number = models.CharField('phone_number', max_length=10,blank=True)
    mod_date = models.DateTimeField('Last modified', auto_now=True)

    class Meta:
        verbose_name = 'User Profile'

    def __str__(self):
        return "{}'s profile".format(self.user.__str__())
    
    def account_verified(self):
        if self.user.is_authenticated:
            result = EmailAddress.objects.filter(email=self.user.email)
            if len(result):
                return result[0].verified
        return False

在docsystem_5/urls.py 新增如下

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('allauth.urls')),
    path('accounts/', include('auth_info.urls')),
]

然後在auth_info/urls.py 新增如下

from django.urls import path
from . import views

app_name = "auth_info"
urlpatterns = [
    path('profile/', views.profile, name='profile'),
    path('profile/update/', views.profile_update, name='profile_update'),
]

這樣會將http://127.0.0.1:8000/accounts/profile 轉到templates/account/profile.html
以及會將http://127.0.0.1:8000/accounts/profile_update 轉到templates/account/profile_update.html

來把view修改一下
auth_info/view.py

from django.shortcuts import render, get_object_or_404
from .models import UserProfile
from .forms import ProfileForm
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required


@login_required
def profile(request):
    user = request.user
    return render(request, 'account/profile.html', {'user': user})

@login_required
def profile_update(request):
    user = request.user
    user_profile = get_object_or_404(UserProfile, user=user)
    if request.method == "POST":
        form = ProfileForm(request.POST)
        # form表單驗證提交資料的正確性
        if form.is_valid():
            # 獲取篩選後的資料,參考django的form表單
            user.first_name = form.cleaned_data['first_name'] 
            user.last_name = form.cleaned_data['last_name']
            user.save()
            user_profile.phone_number = form.cleaned_data['phone_number']
            user_profile.save()
            return HttpResponseRedirect(reverse('auth_info:profile'))
    else:
        default_data = {
            'first_name': user.first_name,
            'last_name': user.last_name,
            'phone_number': user_profile.phone_number,
        }
        form = ProfileForm(default_data)

    return render(request, 'account/profile_update.html', {'form': form, 'user': user})

新增templates/account/profile.html

{% block content %}
{% if user.is_authenticated %}
<a href="{% url 'auth_info:profile_update' %}">Update Profile</a> | <a href="{% url 'account_email' %}">Manage Email</a>  | <a href="{% url 'account_change_password' %}">Change Password</a> |
<a href="{% url 'account_logout' %}">Logout</a>
{% endif %}
<p>Welcome, {{ user.first_name }} {{ user.last_name }}.
    {% if not user.is_superuser %}
    (User)
    {% elif user.is_superuser %}
    (Admin)
    {% endif %}
</p>


<h2>My Profile</h2>

<ul>
    <li>First Name: {{ user.first_name }} </li>
    <li>Last Name: {{ user.last_name }} </li>
    <li>Email: {{ user.email }} </li>
    <li>Phone number: {{ user.userprofile.phone_number }} </li>
</ul>


{% endblock %}

新增templates/account/profile_update.html

{% block content %}
{% if user.is_authenticated %}
<a href="{% url 'auth_info:profile_update' %}">Update Profile</a> | <a href="{% url 'account_email' %}">Manage Email</a>  | <a href="{% url 'account_change_password' %}">Change Password</a> |
<a href="{% url 'account_logout' %}">Logout</a>
{% endif %}
<h2>Update My Profile</h2>

<div class="form-wrapper">
   <form method="post" action="" enctype="multipart/form-data">
      {% csrf_token %}
      {% for field in form %}
           <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
        {% if field.help_text %}
             <p class="help">{{ field.help_text|safe }}</p>
        {% endif %}
           </div>
        {% endfor %}
      <div class="button-wrapper submit">
         <input type="submit" value="Update" />
      </div>
   </form>
</div>


{% endblock %}

OK 最後回到docsystem_5 執行

python manage.py makemigrations #告訴django依據model跟installed_app要該棟那些table
python manage.py migrate        #執行以上的變動
python manage.py runserver      #執行server

現在跳回http://127.0.0.1:8000/accounts/signup 看看有沒有長得像最終註冊畫面

最後應該會產生這些table
Imgur
這是django.contrib.auth本來就會產生的user表
Imgur
這是我用model讓django幫我建立的userprofile表
Imgur


上一篇
D5 allauth 測試
下一篇
D7 allauth 採坑日記 Extending & Substituting User model (2)
系列文
Python x Django 網站實作&學習記錄30

尚未有邦友留言

立即登入留言