@ -0,0 +1,5 @@ | |||
from django.contrib import admin | |||
# Register your models here. | |||
from .models import CustomUser | |||
admin.site.register(CustomUser) |
@ -0,0 +1,10 @@ | |||
from django.apps import AppConfig | |||
class UsersConfig(AppConfig): | |||
default_auto_field = "django.db.models.BigAutoField" | |||
name = "users" | |||
# 登陆状态信号 | |||
def ready(self): | |||
import users.signals |
@ -0,0 +1,85 @@ | |||
from django import forms | |||
from .models import CustomUser | |||
from django.contrib.auth.hashers import make_password | |||
import re | |||
from django.core.exceptions import ValidationError | |||
class LoginForm(forms.Form): | |||
username = forms.CharField( | |||
label="用户名", | |||
max_length=150, | |||
widget=forms.TextInput(attrs={ | |||
'class': 'form-control', | |||
'placeholder': '请输入用户名', | |||
}) | |||
) | |||
password = forms.CharField( | |||
label="密码", | |||
widget=forms.PasswordInput(attrs={ | |||
'class': 'form-control', | |||
'placeholder': '请输入密码', | |||
}) | |||
) | |||
class RegistrationForm(forms.ModelForm): | |||
password_confirm = forms.CharField( | |||
label="确认密码", | |||
widget=forms.PasswordInput(attrs={ | |||
'class': 'form-control', | |||
'placeholder': '请再次输入密码' | |||
}) | |||
) | |||
email = forms.EmailField( | |||
label="邮箱", | |||
widget=forms.EmailInput(attrs={ | |||
'class': 'form-control', | |||
'placeholder': '请输入邮箱' | |||
}) | |||
) | |||
agreed_terms = forms.BooleanField( | |||
required=True, | |||
label="我已阅读并同意服务条款", | |||
error_messages={"required": "必须同意服务条款"}, | |||
widget=forms.CheckboxInput(attrs={ | |||
'class': 'form-check-input' | |||
}) | |||
) | |||
class Meta: | |||
model = CustomUser | |||
fields = ['username', 'email', 'password'] | |||
widgets = { | |||
'username': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入用户名'}), | |||
'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': '请输入邮箱'}), | |||
'password': forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': '请输入密码'}), | |||
} | |||
def clean_password(self): | |||
password = self.cleaned_data['password'] | |||
pattern = r'^[A-Za-z\d]{8,}$' # 至少8位,只包含英文字母和数字 | |||
if not re.match(pattern, password): | |||
raise ValidationError("密码必须为至少8位的字母和数字组合") | |||
return password | |||
def clean(self): | |||
cleaned_data = super().clean() | |||
password = cleaned_data.get('password') | |||
password_confirm = cleaned_data.get('password_confirm') | |||
if password and password_confirm and password != password_confirm: | |||
raise forms.ValidationError("两次输入的密码不一致") | |||
return cleaned_data | |||
def save(self, commit=True): | |||
user = super().save(commit=False) | |||
user.password = make_password(password=self.cleaned_data['password']) | |||
if commit: | |||
user.save() | |||
return user | |||
def clean_email(self): | |||
email = self.cleaned_data['email'] | |||
if CustomUser.objects.filter(email=email).exists(): | |||
raise forms.ValidationError("该邮箱已被注册") | |||
return email |
@ -0,0 +1,146 @@ | |||
# Generated by Django 5.2 on 2025-05-30 01:17 | |||
import django.contrib.auth.models | |||
import django.contrib.auth.validators | |||
import django.utils.timezone | |||
from django.db import migrations, models | |||
class Migration(migrations.Migration): | |||
initial = True | |||
dependencies = [ | |||
("auth", "0012_alter_user_first_name_max_length"), | |||
] | |||
operations = [ | |||
migrations.CreateModel( | |||
name="CustomUser", | |||
fields=[ | |||
( | |||
"id", | |||
models.BigAutoField( | |||
auto_created=True, | |||
primary_key=True, | |||
serialize=False, | |||
verbose_name="ID", | |||
), | |||
), | |||
("password", models.CharField(max_length=128, verbose_name="password")), | |||
( | |||
"last_login", | |||
models.DateTimeField( | |||
blank=True, null=True, verbose_name="last login" | |||
), | |||
), | |||
( | |||
"is_superuser", | |||
models.BooleanField( | |||
default=False, | |||
help_text="Designates that this user has all permissions without explicitly assigning them.", | |||
verbose_name="superuser status", | |||
), | |||
), | |||
( | |||
"username", | |||
models.CharField( | |||
error_messages={ | |||
"unique": "A user with that username already exists." | |||
}, | |||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", | |||
max_length=150, | |||
unique=True, | |||
validators=[ | |||
django.contrib.auth.validators.UnicodeUsernameValidator() | |||
], | |||
verbose_name="username", | |||
), | |||
), | |||
( | |||
"first_name", | |||
models.CharField( | |||
blank=True, max_length=150, verbose_name="first name" | |||
), | |||
), | |||
( | |||
"last_name", | |||
models.CharField( | |||
blank=True, max_length=150, verbose_name="last name" | |||
), | |||
), | |||
( | |||
"email", | |||
models.EmailField( | |||
blank=True, max_length=254, verbose_name="email address" | |||
), | |||
), | |||
( | |||
"is_staff", | |||
models.BooleanField( | |||
default=False, | |||
help_text="Designates whether the user can log into this admin site.", | |||
verbose_name="staff status", | |||
), | |||
), | |||
( | |||
"is_active", | |||
models.BooleanField( | |||
default=True, | |||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", | |||
verbose_name="active", | |||
), | |||
), | |||
( | |||
"date_joined", | |||
models.DateTimeField( | |||
default=django.utils.timezone.now, verbose_name="date joined" | |||
), | |||
), | |||
( | |||
"avatar", | |||
models.ImageField( | |||
blank=True, | |||
default="avatars/default_avatar.png", | |||
null=True, | |||
upload_to="avatars/", | |||
verbose_name="头像", | |||
), | |||
), | |||
( | |||
"agreed_terms", | |||
models.BooleanField(default=False, verbose_name="同意条款"), | |||
), | |||
("activation_token", models.CharField(blank=True, max_length=40)), | |||
( | |||
"groups", | |||
models.ManyToManyField( | |||
blank=True, | |||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", | |||
related_name="user_set", | |||
related_query_name="user", | |||
to="auth.group", | |||
verbose_name="groups", | |||
), | |||
), | |||
( | |||
"user_permissions", | |||
models.ManyToManyField( | |||
blank=True, | |||
help_text="Specific permissions for this user.", | |||
related_name="user_set", | |||
related_query_name="user", | |||
to="auth.permission", | |||
verbose_name="user permissions", | |||
), | |||
), | |||
], | |||
options={ | |||
"verbose_name": "用户", | |||
"verbose_name_plural": "用户管理", | |||
}, | |||
managers=[ | |||
("objects", django.contrib.auth.models.UserManager()), | |||
], | |||
), | |||
] |
@ -0,0 +1,25 @@ | |||
from django.db import models | |||
from django.conf import settings | |||
from django.contrib.auth.models import AbstractUser | |||
class CustomUser(AbstractUser): | |||
# 基础字段(已继承 username、email、password 等) | |||
avatar = models.ImageField( | |||
upload_to='avatars/', | |||
verbose_name="头像", | |||
blank=True, | |||
null=True, | |||
default='avatars/default_avatar.png' # ✅ 指定默认图片路径 | |||
) | |||
agreed_terms = models.BooleanField(default=False, verbose_name="同意条款") | |||
activation_token = models.CharField(max_length=40, blank=True) # 激活令牌字段 | |||
class Meta: | |||
verbose_name = "用户" | |||
verbose_name_plural = "用户管理" | |||
def __str__(self): | |||
return self.username | |||
# Create your models here. |
@ -0,0 +1,10 @@ | |||
from django.db.models.signals import post_save | |||
from django.dispatch import receiver | |||
from .models import CustomUser | |||
from MyPage.models import UserProfile | |||
# 注册时同时创建userprofile | |||
@receiver(post_save, sender=CustomUser) | |||
def create_user_profile(sender, instance, created, **kwargs): | |||
if created: | |||
UserProfile.objects.create(user=instance) |
@ -0,0 +1,3 @@ | |||
from django.test import TestCase | |||
# Create your tests here. |
@ -0,0 +1,8 @@ | |||
from django.urls import path | |||
from . import views | |||
urlpatterns = [ | |||
path('login/', views.login_view, name='login'), | |||
path('register/', views.register_view, name='register'), | |||
path('logout/', views.logout_view, name='logout'), | |||
] |
@ -0,0 +1,58 @@ | |||
import uuid | |||
from django.views.generic.edit import CreateView | |||
from .forms import RegistrationForm, LoginForm | |||
from .models import CustomUser | |||
from django.urls import reverse_lazy | |||
from django.contrib import messages # 可选:添加注册成功提示 | |||
from django.shortcuts import render,redirect | |||
from django.contrib.auth import authenticate, login | |||
from django.contrib.auth import logout | |||
def logout_view(request): | |||
logout(request) | |||
return redirect('home') | |||
def register_view(request): | |||
if request.method == 'POST': | |||
form = RegistrationForm(request.POST) | |||
if form.is_valid(): | |||
user = form.save() | |||
user.activation_token = uuid.uuid4().hex | |||
user.save() | |||
messages.success(request, "注册成功!请登录") | |||
return redirect('login') | |||
else: | |||
form = RegistrationForm() | |||
return render(request, 'register.html', {'form': form}) | |||
def login_view(request): | |||
if request.user.is_authenticated: | |||
return redirect('home') | |||
form = LoginForm(request.POST or None) | |||
if request.method == 'POST': | |||
if form.is_valid(): | |||
username = form.cleaned_data['username'].strip() | |||
password = form.cleaned_data['password'].strip() | |||
# 检查用户名是否存在 | |||
if not CustomUser.objects.filter(username=username).exists(): | |||
messages.error(request, "该用户名未注册") | |||
else: | |||
user = authenticate(request, username=username, password=password) | |||
if user is not None: | |||
if user.is_active: | |||
login(request, user) | |||
return redirect('home') | |||
else: | |||
messages.error(request, "账户已被禁用,请联系管理员") | |||
else: | |||
messages.error(request, "密码错误,请重新输入") | |||
else: | |||
messages.error(request, "请输入完整的用户名和密码") | |||
return render(request, 'Login.html', {'form': form}) |