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


<<:  [Day 07] 透过 DAO 和资料库进行互动

>>:  Day 7 Compose UI Image Layout

D14 - 彭彭的课程# Python 函式参数详解:参数预设值、名称对应、任意长度参数(1)

今天也是一个爆炸累 天气颇好早上出门没那麽热了 秋天感觉终於要来了~~~ 今天就是来一个函式参数说明...

Football Betting - Making Sense of the Odds

Football Betting - Making Sense of the Odds Footba...

[Tableau Public] day 17:试着分析appstore资料集-2

某类型的App价格愈高,使用者平均评价也会有正相关吗? 这是个蛮有趣的议题,照理来说愈贵的app功能...

人脸辨识-day22

在处理资料时,有些资料需要需要做转换,如在做分类时直接将每个类别都直接丢下去做训练,这样比较难训练外...

Day_09 有线网路应用(二)

前面提到的都是串接主路由,让装openwrt的树梅派成为次级设备,但如果今天想要成为主路由进行ppp...