Compare commits
No commits in common. 'main' and 'y_branch' have entirely different histories.
|
Before Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 478 KiB |
|
Before Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 586 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 2.4 MiB |
@ -1,8 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="django" name="Django">
|
||||
<configuration>
|
||||
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||
<option name="settingsModule" value="learning_platform/settings.py" />
|
||||
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
||||
<option name="environment" value="<map/>" />
|
||||
<option name="doNotUseTestRunner" value="false" />
|
||||
<option name="trackFilePattern" value="migrations" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/templates" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</module>
|
||||
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="Django default" uuid="d1281534-9e1d-4d3f-b11f-84fe6706a605">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<remarks>$PROJECT_DIR$/learning_platform/settings.py</remarks>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:C:\Users\杨新硕\Desktop\Knowledge_Atlas\db.sqlite3</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.8" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Knowledge_Atlas.iml" filepath="$PROJECT_DIR$/.idea/Knowledge_Atlas.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@ -1,44 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from .models import User, UserFollow
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
"""用户管理"""
|
||||
list_display = ['username', 'email', 'phone', 'school', 'major', 'upload_count', 'following_count', 'followers_count', 'is_active', 'date_joined']
|
||||
list_filter = ['is_active', 'is_staff', 'school', 'major', 'grade']
|
||||
search_fields = ['username', 'email', 'phone', 'school']
|
||||
ordering = ['-date_joined']
|
||||
|
||||
fieldsets = BaseUserAdmin.fieldsets + (
|
||||
('扩展信息', {
|
||||
'fields': ('phone', 'avatar', 'bio', 'birth_date', 'location', 'school', 'major', 'grade')
|
||||
}),
|
||||
('统计信息', {
|
||||
'fields': ('upload_count', 'download_count'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def following_count(self, obj):
|
||||
return obj.following_count
|
||||
following_count.short_description = '关注数'
|
||||
|
||||
def followers_count(self, obj):
|
||||
return obj.followers_count
|
||||
followers_count.short_description = '粉丝数'
|
||||
|
||||
|
||||
@admin.register(UserFollow)
|
||||
class UserFollowAdmin(admin.ModelAdmin):
|
||||
"""用户关注关系管理"""
|
||||
list_display = ['follower', 'following', 'created_at']
|
||||
list_filter = ['created_at']
|
||||
search_fields = ['follower__username', 'following__username']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj: # 编辑时只读
|
||||
return ['follower', 'following', 'created_at']
|
||||
return ['created_at']
|
||||
@ -1,7 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'accounts'
|
||||
verbose_name = '用户管理'
|
||||
@ -1,98 +0,0 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
|
||||
from django.contrib.auth import get_user_model
|
||||
from .models import College, EnrollmentYear
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class CustomUserCreationForm(UserCreationForm):
|
||||
"""自定义用户注册表单"""
|
||||
email = forms.EmailField(
|
||||
required=True,
|
||||
widget=forms.EmailInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '请输入邮箱地址'
|
||||
})
|
||||
)
|
||||
username = forms.CharField(
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '请输入用户名'
|
||||
})
|
||||
)
|
||||
password1 = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '请输入密码'
|
||||
})
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '请确认密码'
|
||||
})
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email', 'password1', 'password2')
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=False)
|
||||
user.email = self.cleaned_data['email']
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class CustomAuthenticationForm(AuthenticationForm):
|
||||
"""自定义用户登录表单"""
|
||||
username = forms.CharField(
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '用户名或邮箱'
|
||||
})
|
||||
)
|
||||
password = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '密码'
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
"""用户资料编辑表单"""
|
||||
enrollment_year = forms.ChoiceField(
|
||||
choices=[],
|
||||
required=False,
|
||||
widget=forms.Select(attrs={'class': 'form-control'}),
|
||||
label='入学年份'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 从数据库读取启用的入学年份选项
|
||||
year_choices = [('', '请选择入学年份')]
|
||||
enrollment_years = EnrollmentYear.objects.filter(is_active=True).order_by('-display_order', '-year')
|
||||
for enrollment_year in enrollment_years:
|
||||
year_choices.append((enrollment_year.year, f'{enrollment_year.year}级'))
|
||||
self.fields['enrollment_year'].choices = year_choices
|
||||
|
||||
# 如果用户已有入学年份,设置初始值
|
||||
if self.instance and self.instance.enrollment_year:
|
||||
self.fields['enrollment_year'].initial = self.instance.enrollment_year
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'email', 'phone', 'bio', 'school', 'college', 'major', 'enrollment_year']
|
||||
widgets = {
|
||||
'username': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
||||
'phone': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
'school': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'college': forms.Select(attrs={'class': 'form-control'}),
|
||||
'major': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
from django.urls import path
|
||||
from . import html_views
|
||||
|
||||
urlpatterns = [
|
||||
path('register/', html_views.register_view, name='register'),
|
||||
path('login/', html_views.login_view, name='login'),
|
||||
path('logout/', html_views.logout_view, name='logout'),
|
||||
path('profile/', html_views.profile_view, name='profile'),
|
||||
path('dashboard/', html_views.dashboard_view, name='dashboard'),
|
||||
|
||||
# 关注功能
|
||||
path('following/', html_views.following_list_view, name='following_list'),
|
||||
path('followers/', html_views.followers_list_view, name='followers_list'),
|
||||
path('discover-users/', html_views.discover_users_view, name='discover_users'),
|
||||
path('follow/<int:user_id>/', html_views.follow_user_view, name='follow_user'),
|
||||
path('unfollow/<int:user_id>/', html_views.unfollow_user_view, name='unfollow_user'),
|
||||
|
||||
# 资料管理
|
||||
path('delete-material/<int:material_id>/', html_views.delete_material_view, name='delete_material'),
|
||||
]
|
||||
@ -1,53 +0,0 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from accounts.models import College
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '初始化学院数据'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
colleges_data = [
|
||||
{'name': '计算机科学与技术学院', 'code': 'CS', 'description': '计算机科学与技术相关专业'},
|
||||
{'name': '软件工程学院', 'code': 'SE', 'description': '软件工程相关专业'},
|
||||
{'name': '信息工程学院', 'code': 'IE', 'description': '信息工程相关专业'},
|
||||
{'name': '电子工程学院', 'code': 'EE', 'description': '电子工程相关专业'},
|
||||
{'name': '机械工程学院', 'code': 'ME', 'description': '机械工程相关专业'},
|
||||
{'name': '土木工程学院', 'code': 'CE', 'description': '土木工程相关专业'},
|
||||
{'name': '化学工程学院', 'code': 'CHE', 'description': '化学工程相关专业'},
|
||||
{'name': '材料科学与工程学院', 'code': 'MSE', 'description': '材料科学与工程相关专业'},
|
||||
{'name': '数学与统计学院', 'code': 'MATH', 'description': '数学与统计学相关专业'},
|
||||
{'name': '物理学院', 'code': 'PHY', 'description': '物理学相关专业'},
|
||||
{'name': '化学学院', 'code': 'CHEM', 'description': '化学相关专业'},
|
||||
{'name': '生命科学学院', 'code': 'BIO', 'description': '生命科学相关专业'},
|
||||
{'name': '经济管理学院', 'code': 'EM', 'description': '经济管理相关专业'},
|
||||
{'name': '外国语学院', 'code': 'FL', 'description': '外国语言文学相关专业'},
|
||||
{'name': '文学院', 'code': 'LIT', 'description': '中国语言文学相关专业'},
|
||||
{'name': '法学院', 'code': 'LAW', 'description': '法学相关专业'},
|
||||
{'name': '艺术学院', 'code': 'ART', 'description': '艺术相关专业'},
|
||||
{'name': '体育学院', 'code': 'PE', 'description': '体育相关专业'},
|
||||
{'name': '医学院', 'code': 'MED', 'description': '医学相关专业'},
|
||||
{'name': '教育学院', 'code': 'EDU', 'description': '教育学相关专业'},
|
||||
]
|
||||
|
||||
created_count = 0
|
||||
for college_data in colleges_data:
|
||||
college, created = College.objects.get_or_create(
|
||||
code=college_data['code'],
|
||||
defaults={
|
||||
'name': college_data['name'],
|
||||
'description': college_data['description']
|
||||
}
|
||||
)
|
||||
if created:
|
||||
created_count += 1
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'创建学院: {college.name}')
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(f'学院已存在: {college.name}')
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'成功创建 {created_count} 个学院')
|
||||
)
|
||||
@ -1,50 +0,0 @@
|
||||
"""
|
||||
将现有用户设置为管理员账号
|
||||
使用方法: python manage.py make_admin <username>
|
||||
"""
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '将现有用户设置为管理员账号'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('username', type=str, help='要设置为管理员的用户名')
|
||||
parser.add_argument(
|
||||
'--email',
|
||||
type=str,
|
||||
help='管理员邮箱(可选)',
|
||||
default=None
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
username = options['username']
|
||||
email = options['email']
|
||||
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
|
||||
# 设置为管理员
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
if email:
|
||||
user.email = email
|
||||
user.save()
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f'成功将用户 "{username}" 设置为管理员!\n'
|
||||
f'用户名: {user.username}\n'
|
||||
f'邮箱: {user.email}\n'
|
||||
f'是否管理员: {user.is_staff}\n'
|
||||
f'是否超级用户: {user.is_superuser}'
|
||||
)
|
||||
)
|
||||
except User.DoesNotExist:
|
||||
raise CommandError(f'用户 "{username}" 不存在!')
|
||||
except Exception as e:
|
||||
raise CommandError(f'设置管理员失败: {str(e)}')
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2025-09-17 12:30
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = []
|
||||
@ -1,185 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2025-09-17 13:05
|
||||
|
||||
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 = [
|
||||
("accounts", "0001_initial"),
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(max_length=254, unique=True, verbose_name="邮箱"),
|
||||
),
|
||||
(
|
||||
"phone",
|
||||
models.CharField(
|
||||
blank=True, max_length=11, null=True, verbose_name="手机号"
|
||||
),
|
||||
),
|
||||
(
|
||||
"avatar",
|
||||
models.ImageField(
|
||||
blank=True, null=True, upload_to="avatars/", verbose_name="头像"
|
||||
),
|
||||
),
|
||||
(
|
||||
"bio",
|
||||
models.TextField(
|
||||
blank=True, max_length=500, verbose_name="个人简介"
|
||||
),
|
||||
),
|
||||
(
|
||||
"birth_date",
|
||||
models.DateField(blank=True, null=True, verbose_name="出生日期"),
|
||||
),
|
||||
(
|
||||
"location",
|
||||
models.CharField(blank=True, max_length=100, verbose_name="所在地"),
|
||||
),
|
||||
(
|
||||
"school",
|
||||
models.CharField(blank=True, max_length=100, verbose_name="学校"),
|
||||
),
|
||||
(
|
||||
"major",
|
||||
models.CharField(blank=True, max_length=100, verbose_name="专业"),
|
||||
),
|
||||
(
|
||||
"grade",
|
||||
models.CharField(blank=True, max_length=20, verbose_name="年级"),
|
||||
),
|
||||
(
|
||||
"upload_count",
|
||||
models.PositiveIntegerField(default=0, verbose_name="上传资料数"),
|
||||
),
|
||||
(
|
||||
"download_count",
|
||||
models.PositiveIntegerField(default=0, verbose_name="下载次数"),
|
||||
),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||
),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to.",
|
||||
related_name="custom_user_set",
|
||||
related_query_name="custom_user",
|
||||
to="auth.group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="custom_user_set",
|
||||
related_query_name="custom_user",
|
||||
to="auth.permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "用户",
|
||||
"verbose_name_plural": "用户",
|
||||
"db_table": "accounts_user",
|
||||
},
|
||||
managers=[
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,36 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2025-09-20 00:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserFollow',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='关注时间')),
|
||||
('follower', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='following_relations', to=settings.AUTH_USER_MODEL, verbose_name='关注者')),
|
||||
('following', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='follower_relations', to=settings.AUTH_USER_MODEL, verbose_name='被关注者')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户关注',
|
||||
'verbose_name_plural': '用户关注',
|
||||
'db_table': 'accounts_user_follow',
|
||||
'ordering': ['-created_at'],
|
||||
'unique_together': {('follower', 'following')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='following',
|
||||
field=models.ManyToManyField(blank=True, related_name='followers', through='accounts.UserFollow', to=settings.AUTH_USER_MODEL, verbose_name='关注的用户'),
|
||||
),
|
||||
]
|
||||
@ -1,13 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2025-09-20 00:03
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0003_userfollow_user_following'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
@ -1,62 +0,0 @@
|
||||
# Generated by Django 4.2.7 on 2025-10-24 08:30
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0004_auto_20250920_0803"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="College",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
max_length=100, unique=True, verbose_name="学院名称"
|
||||
),
|
||||
),
|
||||
(
|
||||
"code",
|
||||
models.CharField(
|
||||
max_length=20, unique=True, verbose_name="学院代码"
|
||||
),
|
||||
),
|
||||
("description", models.TextField(blank=True, verbose_name="学院描述")),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "学院",
|
||||
"verbose_name_plural": "学院",
|
||||
"db_table": "accounts_college",
|
||||
"ordering": ["name"],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="college",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="accounts.college",
|
||||
verbose_name="学院",
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -1,20 +0,0 @@
|
||||
# Generated by Django 4.2.25 on 2025-11-19 05:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0006_user_enrollment_year"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="coins",
|
||||
field=models.PositiveIntegerField(
|
||||
default=0, help_text="用户金币余额", verbose_name="金币"
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -1,142 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from .models import User, UserFollow, College
|
||||
|
||||
|
||||
class CollegeSerializer(serializers.ModelSerializer):
|
||||
"""学院序列化器"""
|
||||
class Meta:
|
||||
model = College
|
||||
fields = ['id', 'name', 'code', 'description']
|
||||
|
||||
|
||||
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||
"""用户注册序列化器"""
|
||||
password = serializers.CharField(write_only=True, validators=[validate_password])
|
||||
password_confirm = serializers.CharField(write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'email', 'password', 'password_confirm', 'phone', 'college', 'major']
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs['password'] != attrs['password_confirm']:
|
||||
raise serializers.ValidationError("两次输入的密码不一致")
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data.pop('password_confirm')
|
||||
user = User.objects.create_user(**validated_data)
|
||||
return user
|
||||
|
||||
|
||||
class UserLoginSerializer(serializers.Serializer):
|
||||
"""用户登录序列化器"""
|
||||
email = serializers.EmailField()
|
||||
password = serializers.CharField()
|
||||
|
||||
def validate(self, attrs):
|
||||
email = attrs.get('email')
|
||||
password = attrs.get('password')
|
||||
|
||||
if email and password:
|
||||
user = authenticate(username=email, password=password)
|
||||
if not user:
|
||||
raise serializers.ValidationError('邮箱或密码错误')
|
||||
if not user.is_active:
|
||||
raise serializers.ValidationError('账户已被禁用')
|
||||
attrs['user'] = user
|
||||
else:
|
||||
raise serializers.ValidationError('必须提供邮箱和密码')
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class UserProfileSerializer(serializers.ModelSerializer):
|
||||
"""用户资料序列化器"""
|
||||
college = CollegeSerializer(read_only=True)
|
||||
following_count = serializers.ReadOnlyField()
|
||||
followers_count = serializers.ReadOnlyField()
|
||||
is_following = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'id', 'username', 'email', 'phone', 'avatar', 'bio',
|
||||
'birth_date', 'location', 'school', 'college', 'major', 'grade',
|
||||
'upload_count', 'download_count', 'following_count', 'followers_count',
|
||||
'is_following', 'date_joined', 'last_login'
|
||||
]
|
||||
read_only_fields = [
|
||||
'id', 'upload_count', 'download_count', 'following_count', 'followers_count',
|
||||
'is_following', 'date_joined', 'last_login'
|
||||
]
|
||||
|
||||
def get_is_following(self, obj):
|
||||
"""获取当前用户是否关注了这个用户"""
|
||||
request = self.context.get('request')
|
||||
if request and request.user.is_authenticated and request.user != obj:
|
||||
return request.user.is_following(obj)
|
||||
return False
|
||||
|
||||
|
||||
class UserUpdateSerializer(serializers.ModelSerializer):
|
||||
"""用户信息更新序列化器"""
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'username', 'phone', 'avatar', 'bio', 'birth_date',
|
||||
'location', 'school', 'college', 'major', 'grade'
|
||||
]
|
||||
|
||||
|
||||
class PasswordChangeSerializer(serializers.Serializer):
|
||||
"""密码修改序列化器"""
|
||||
old_password = serializers.CharField()
|
||||
new_password = serializers.CharField(validators=[validate_password])
|
||||
new_password_confirm = serializers.CharField()
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs['new_password'] != attrs['new_password_confirm']:
|
||||
raise serializers.ValidationError("两次输入的新密码不一致")
|
||||
return attrs
|
||||
|
||||
def validate_old_password(self, value):
|
||||
user = self.context['request'].user
|
||||
if not user.check_password(value):
|
||||
raise serializers.ValidationError("原密码错误")
|
||||
return value
|
||||
|
||||
|
||||
class UserFollowSerializer(serializers.ModelSerializer):
|
||||
"""用户关注关系序列化器"""
|
||||
follower = UserProfileSerializer(read_only=True)
|
||||
following = UserProfileSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = UserFollow
|
||||
fields = ['id', 'follower', 'following', 'created_at']
|
||||
read_only_fields = ['id', 'created_at']
|
||||
|
||||
|
||||
class UserSimpleSerializer(serializers.ModelSerializer):
|
||||
"""简化的用户信息序列化器(用于关注列表)"""
|
||||
following_count = serializers.ReadOnlyField()
|
||||
followers_count = serializers.ReadOnlyField()
|
||||
is_following = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
'id', 'username', 'avatar', 'bio', 'school', 'major',
|
||||
'following_count', 'followers_count', 'is_following', 'date_joined'
|
||||
]
|
||||
read_only_fields = ['id', 'following_count', 'followers_count', 'is_following', 'date_joined']
|
||||
|
||||
def get_is_following(self, obj):
|
||||
"""获取当前用户是否关注了这个用户"""
|
||||
request = self.context.get('request')
|
||||
if request and request.user.is_authenticated and request.user != obj:
|
||||
return request.user.is_following(obj)
|
||||
return False
|
||||