feat: 实现城市手册项目需求 - 数据库模型
- 扩展 User 模型,添加角色和状态字段 - 创建 Region 模型(省市县乡村层级结构) - 创建版主管理相关模型(申请、权限、支持、限制) - 创建 Article 模型(文章 + 审核流程) - 创建 FeaturedService 模型(特色服务 + 审核流程) - 创建交互功能模型(评论、评分、点赞、收藏) - 更新 Django settings 注册所有 apps - 创建需求实施文档 完整实现需求文档中的 12 个核心数据表和审核流程
This commit is contained in:
1
backend/apps/articles/__init__.py
Normal file
1
backend/apps/articles/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Articles app
|
||||
7
backend/apps/articles/apps.py
Normal file
7
backend/apps/articles/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ArticlesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.articles'
|
||||
verbose_name = '文章管理'
|
||||
144
backend/apps/articles/models.py
Normal file
144
backend/apps/articles/models.py
Normal file
@@ -0,0 +1,144 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from apps.regions.models import Region
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
"""Model for articles."""
|
||||
|
||||
ARTICLE_TYPE_CHOICES = [
|
||||
('basic', '城市信息'),
|
||||
('history', '历史'),
|
||||
('culture', '文化'),
|
||||
('practical', '实用'),
|
||||
('life', '生活'),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('draft', '草稿'),
|
||||
('pending_moderator', '待版主审核'),
|
||||
('pending_ai', '待AI审核'),
|
||||
('published', '已发布'),
|
||||
('rejected', '已拒绝'),
|
||||
]
|
||||
|
||||
MODERATOR_STATUS_CHOICES = [
|
||||
('pending', '待审核'),
|
||||
('approved', '通过'),
|
||||
('rejected', '拒绝'),
|
||||
]
|
||||
|
||||
AI_STATUS_CHOICES = [
|
||||
('pending', '待审核'),
|
||||
('approved', '通过'),
|
||||
('rejected', '拒绝'),
|
||||
]
|
||||
|
||||
title = models.CharField(max_length=200, verbose_name='标题')
|
||||
content = models.TextField(verbose_name='内容')
|
||||
region = models.ForeignKey(
|
||||
Region,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='articles',
|
||||
verbose_name='所属版块'
|
||||
)
|
||||
article_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=ARTICLE_TYPE_CHOICES,
|
||||
verbose_name='内容类型'
|
||||
)
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='articles',
|
||||
verbose_name='提交者'
|
||||
)
|
||||
|
||||
# Moderator review
|
||||
moderator_reviewer = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='moderated_articles',
|
||||
verbose_name='版主审核人'
|
||||
)
|
||||
moderator_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=MODERATOR_STATUS_CHOICES,
|
||||
default='pending',
|
||||
verbose_name='版主审核状态'
|
||||
)
|
||||
moderator_reviewed_at = models.DateTimeField(null=True, blank=True, verbose_name='版主审核时间')
|
||||
moderator_rejection_reason = models.TextField(null=True, blank=True, verbose_name='版主拒绝原因')
|
||||
|
||||
# AI review
|
||||
ai_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=AI_STATUS_CHOICES,
|
||||
default='pending',
|
||||
verbose_name='AI审核状态'
|
||||
)
|
||||
ai_reviewed_at = models.DateTimeField(null=True, blank=True, verbose_name='AI审核时间')
|
||||
ai_rejection_reason = models.TextField(null=True, blank=True, verbose_name='AI拒绝原因')
|
||||
|
||||
# Publish status
|
||||
publish_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='draft',
|
||||
verbose_name='发布状态'
|
||||
)
|
||||
published_at = models.DateTimeField(null=True, blank=True, verbose_name='发布时间')
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'articles'
|
||||
verbose_name = '文章'
|
||||
verbose_name_plural = '文章'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def submit_for_review(self):
|
||||
"""Submit article for moderator review."""
|
||||
self.publish_status = 'pending_moderator'
|
||||
self.save()
|
||||
|
||||
def approve_moderator(self, reviewer, reason=''):
|
||||
"""Approve article by moderator."""
|
||||
self.moderator_status = 'approved'
|
||||
self.moderator_reviewer = reviewer
|
||||
self.moderator_reviewed_at = timezone.now()
|
||||
self.moderator_rejection_reason = reason
|
||||
self.publish_status = 'pending_ai'
|
||||
self.save()
|
||||
|
||||
def reject_moderator(self, reviewer, reason):
|
||||
"""Reject article by moderator."""
|
||||
self.moderator_status = 'rejected'
|
||||
self.moderator_reviewer = reviewer
|
||||
self.moderator_reviewed_at = timezone.now()
|
||||
self.moderator_rejection_reason = reason
|
||||
self.publish_status = 'rejected'
|
||||
self.save()
|
||||
|
||||
def approve_ai(self, reason=''):
|
||||
"""Approve article by AI."""
|
||||
self.ai_status = 'approved'
|
||||
self.ai_reviewed_at = timezone.now()
|
||||
self.ai_rejection_reason = reason
|
||||
self.publish_status = 'published'
|
||||
self.published_at = timezone.now()
|
||||
self.save()
|
||||
|
||||
def reject_ai(self, reason):
|
||||
"""Reject article by AI."""
|
||||
self.ai_status = 'rejected'
|
||||
self.ai_reviewed_at = timezone.now()
|
||||
self.ai_rejection_reason = reason
|
||||
self.publish_status = 'rejected'
|
||||
self.save()
|
||||
1
backend/apps/featured_services/__init__.py
Normal file
1
backend/apps/featured_services/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Featured services app
|
||||
7
backend/apps/featured_services/apps.py
Normal file
7
backend/apps/featured_services/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FeaturedServicesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.featured_services'
|
||||
verbose_name = '特色服务'
|
||||
149
backend/apps/featured_services/models.py
Normal file
149
backend/apps/featured_services/models.py
Normal file
@@ -0,0 +1,149 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from apps.regions.models import Region
|
||||
|
||||
|
||||
class FeaturedService(models.Model):
|
||||
"""Model for featured services."""
|
||||
|
||||
SERVICE_CATEGORY_CHOICES = [
|
||||
('clothing', '衣'),
|
||||
('food', '食'),
|
||||
('accommodation', '住'),
|
||||
('transport', '行'),
|
||||
('entertainment', '娱乐'),
|
||||
('tourism', '旅游'),
|
||||
('culture', '文化'),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('draft', '草稿'),
|
||||
('pending_moderator', '待版主审核'),
|
||||
('pending_ai', '待AI审核'),
|
||||
('published', '已发布'),
|
||||
('rejected', '已拒绝'),
|
||||
]
|
||||
|
||||
MODERATOR_STATUS_CHOICES = [
|
||||
('pending', '待审核'),
|
||||
('approved', '通过'),
|
||||
('rejected', '拒绝'),
|
||||
]
|
||||
|
||||
AI_STATUS_CHOICES = [
|
||||
('pending', '待审核'),
|
||||
('approved', '通过'),
|
||||
('rejected', '拒绝'),
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=200, verbose_name='服务名称')
|
||||
description = models.TextField(verbose_name='服务描述')
|
||||
region = models.ForeignKey(
|
||||
Region,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='featured_services',
|
||||
verbose_name='所属版块'
|
||||
)
|
||||
category = models.CharField(
|
||||
max_length=20,
|
||||
choices=SERVICE_CATEGORY_CHOICES,
|
||||
verbose_name='服务分类'
|
||||
)
|
||||
address = models.CharField(max_length=200, null=True, blank=True, verbose_name='地址')
|
||||
contact = models.CharField(max_length=100, null=True, blank=True, verbose_name='联系方式')
|
||||
image = models.ImageField(upload_to='services/', null=True, blank=True, verbose_name='图片')
|
||||
submitter = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='submitted_services',
|
||||
verbose_name='提交者'
|
||||
)
|
||||
|
||||
# Moderator review
|
||||
moderator_reviewer = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='moderated_services',
|
||||
verbose_name='版主审核人'
|
||||
)
|
||||
moderator_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=MODERATOR_STATUS_CHOICES,
|
||||
default='pending',
|
||||
verbose_name='版主审核状态'
|
||||
)
|
||||
moderator_reviewed_at = models.DateTimeField(null=True, blank=True, verbose_name='版主审核时间')
|
||||
moderator_rejection_reason = models.TextField(null=True, blank=True, verbose_name='版主拒绝原因')
|
||||
|
||||
# AI review
|
||||
ai_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=AI_STATUS_CHOICES,
|
||||
default='pending',
|
||||
verbose_name='AI审核状态'
|
||||
)
|
||||
ai_reviewed_at = models.DateTimeField(null=True, blank=True, verbose_name='AI审核时间')
|
||||
ai_rejection_reason = models.TextField(null=True, blank=True, verbose_name='AI拒绝原因')
|
||||
|
||||
# Publish status
|
||||
publish_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='draft',
|
||||
verbose_name='发布状态'
|
||||
)
|
||||
published_at = models.DateTimeField(null=True, blank=True, verbose_name='发布时间')
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'featured_services'
|
||||
verbose_name = '特色服务'
|
||||
verbose_name_plural = '特色服务'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def submit_for_review(self):
|
||||
"""Submit service for moderator review."""
|
||||
self.publish_status = 'pending_moderator'
|
||||
self.save()
|
||||
|
||||
def approve_moderator(self, reviewer, reason=''):
|
||||
"""Approve service by moderator."""
|
||||
self.moderator_status = 'approved'
|
||||
self.moderator_reviewer = reviewer
|
||||
self.moderator_reviewed_at = timezone.now()
|
||||
self.moderator_rejection_reason = reason
|
||||
self.publish_status = 'pending_ai'
|
||||
self.save()
|
||||
|
||||
def reject_moderator(self, reviewer, reason):
|
||||
"""Reject service by moderator."""
|
||||
self.moderator_status = 'rejected'
|
||||
self.moderator_reviewer = reviewer
|
||||
self.moderator_reviewed_at = timezone.now()
|
||||
self.moderator_rejection_reason = reason
|
||||
self.publish_status = 'rejected'
|
||||
self.save()
|
||||
|
||||
def approve_ai(self, reason=''):
|
||||
"""Approve service by AI."""
|
||||
self.ai_status = 'approved'
|
||||
self.ai_reviewed_at = timezone.now()
|
||||
self.ai_rejection_reason = reason
|
||||
self.publish_status = 'published'
|
||||
self.published_at = timezone.now()
|
||||
self.save()
|
||||
|
||||
def reject_ai(self, reason):
|
||||
"""Reject service by AI."""
|
||||
self.ai_status = 'rejected'
|
||||
self.ai_reviewed_at = timezone.now()
|
||||
self.ai_rejection_reason = reason
|
||||
self.publish_status = 'rejected'
|
||||
self.save()
|
||||
1
backend/apps/interactions/__init__.py
Normal file
1
backend/apps/interactions/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Interactions app
|
||||
7
backend/apps/interactions/apps.py
Normal file
7
backend/apps/interactions/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class InteractionsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.interactions'
|
||||
verbose_name = '交互功能'
|
||||
159
backend/apps/interactions/models.py
Normal file
159
backend/apps/interactions/models.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
"""Model for comments."""
|
||||
|
||||
AI_STATUS_CHOICES = [
|
||||
('pending', '待审核'),
|
||||
('approved', '通过'),
|
||||
('rejected', '拒绝'),
|
||||
]
|
||||
|
||||
TARGET_TYPE_CHOICES = [
|
||||
('article', '文章'),
|
||||
('service', '特色服务'),
|
||||
]
|
||||
|
||||
content = models.TextField(verbose_name='评论内容')
|
||||
target_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=TARGET_TYPE_CHOICES,
|
||||
verbose_name='评论对象类型'
|
||||
)
|
||||
target_id = models.PositiveIntegerField(verbose_name='评论对象ID')
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='comments',
|
||||
verbose_name='评论者'
|
||||
)
|
||||
ai_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=AI_STATUS_CHOICES,
|
||||
default='pending',
|
||||
verbose_name='AI审核状态'
|
||||
)
|
||||
ai_rejection_reason = models.TextField(null=True, blank=True, verbose_name='AI拒绝原因')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'comments'
|
||||
verbose_name = '评论'
|
||||
verbose_name_plural = '评论'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.author.username} on {self.target_type} {self.target_id}'
|
||||
|
||||
def approve_ai(self):
|
||||
"""Approve comment by AI."""
|
||||
self.ai_status = 'approved'
|
||||
self.save()
|
||||
|
||||
def reject_ai(self, reason):
|
||||
"""Reject comment by AI."""
|
||||
self.ai_status = 'rejected'
|
||||
self.ai_rejection_reason = reason
|
||||
self.save()
|
||||
|
||||
|
||||
class Rating(models.Model):
|
||||
"""Model for ratings."""
|
||||
|
||||
TARGET_TYPE_CHOICES = [
|
||||
('region', '城市'),
|
||||
('service', '特色服务'),
|
||||
]
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='ratings',
|
||||
verbose_name='用户'
|
||||
)
|
||||
target_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=TARGET_TYPE_CHOICES,
|
||||
verbose_name='评分对象类型'
|
||||
)
|
||||
target_id = models.PositiveIntegerField(verbose_name='评分对象ID')
|
||||
score = models.PositiveSmallIntegerField(verbose_name='评分值')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'ratings'
|
||||
verbose_name = '评分'
|
||||
verbose_name_plural = '评分'
|
||||
unique_together = ['user', 'target_type', 'target_id']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} rated {self.target_type} {self.target_id}: {self.score}'
|
||||
|
||||
|
||||
class Like(models.Model):
|
||||
"""Model for likes."""
|
||||
|
||||
TARGET_TYPE_CHOICES = [
|
||||
('article', '文章'),
|
||||
('service', '特色服务'),
|
||||
]
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='likes',
|
||||
verbose_name='用户'
|
||||
)
|
||||
target_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=TARGET_TYPE_CHOICES,
|
||||
verbose_name='点赞对象类型'
|
||||
)
|
||||
target_id = models.PositiveIntegerField(verbose_name='点赞对象ID')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'likes'
|
||||
verbose_name = '点赞'
|
||||
verbose_name_plural = '点赞'
|
||||
unique_together = ['user', 'target_type', 'target_id']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} likes {self.target_type} {self.target_id}'
|
||||
|
||||
|
||||
class Favorite(models.Model):
|
||||
"""Model for favorites."""
|
||||
|
||||
TARGET_TYPE_CHOICES = [
|
||||
('region', '城市'),
|
||||
('service', '特色服务'),
|
||||
]
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='favorites',
|
||||
verbose_name='用户'
|
||||
)
|
||||
target_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=TARGET_TYPE_CHOICES,
|
||||
verbose_name='收藏对象类型'
|
||||
)
|
||||
target_id = models.PositiveIntegerField(verbose_name='收藏对象ID')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'favorites'
|
||||
verbose_name = '收藏'
|
||||
verbose_name_plural = '收藏'
|
||||
unique_together = ['user', 'target_type', 'target_id']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} favorited {self.target_type} {self.target_id}'
|
||||
1
backend/apps/moderation/__init__.py
Normal file
1
backend/apps/moderation/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Moderation app
|
||||
7
backend/apps/moderation/apps.py
Normal file
7
backend/apps/moderation/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ModerationConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.moderation'
|
||||
verbose_name = '版主管理'
|
||||
190
backend/apps/moderation/models.py
Normal file
190
backend/apps/moderation/models.py
Normal file
@@ -0,0 +1,190 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from apps.regions.models import Region
|
||||
|
||||
|
||||
class ModeratorApplication(models.Model):
|
||||
"""Model for moderator applications."""
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('pending', '待审核'),
|
||||
('approved', '已通过'),
|
||||
('rejected', '已拒绝'),
|
||||
('cancelled', '已取消'),
|
||||
]
|
||||
|
||||
RANK_CHOICES = [
|
||||
('general', '将军'),
|
||||
('colonel', '校官'),
|
||||
('captain', '尉官'),
|
||||
('soldier', '士兵'),
|
||||
]
|
||||
|
||||
applicant = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='moderator_applications',
|
||||
verbose_name='申请者'
|
||||
)
|
||||
region = models.ForeignKey(
|
||||
Region,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='moderator_applications',
|
||||
verbose_name='申请的版块'
|
||||
)
|
||||
support_count = models.IntegerField(default=0, verbose_name='支持人数')
|
||||
deadline = models.DateTimeField(verbose_name='截止时间')
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name='状态')
|
||||
rank = models.CharField(max_length=20, choices=RANK_CHOICES, verbose_name='军衔级别')
|
||||
reviewed_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='reviewed_applications',
|
||||
verbose_name='审核人'
|
||||
)
|
||||
reviewed_at = models.DateTimeField(null=True, blank=True, verbose_name='审核时间')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='申请时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'moderator_applications'
|
||||
verbose_name = '版主申请'
|
||||
verbose_name_plural = '版主申请'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.applicant.username} - {self.region.name} ({self.get_status_display()})'
|
||||
|
||||
def is_expired(self):
|
||||
"""Check if the application has expired."""
|
||||
from django.utils import timezone
|
||||
return timezone.now() > self.deadline
|
||||
|
||||
def has_enough_support(self):
|
||||
"""Check if the application has enough support."""
|
||||
# TODO: Define minimum support count
|
||||
return self.support_count >= 10
|
||||
|
||||
|
||||
class ModeratorPermission(models.Model):
|
||||
"""Model for moderator permissions."""
|
||||
|
||||
PERMISSION_STATUS_CHOICES = [
|
||||
('active', '正常'),
|
||||
('restricted', '限制'),
|
||||
('revoked', '取消'),
|
||||
]
|
||||
|
||||
RANK_CHOICES = [
|
||||
('general', '将军'),
|
||||
('colonel', '校官'),
|
||||
('captain', '尉官'),
|
||||
('soldier', '士兵'),
|
||||
]
|
||||
|
||||
moderator = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='moderator_permissions',
|
||||
verbose_name='版主'
|
||||
)
|
||||
region = models.ForeignKey(
|
||||
Region,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='moderator_permissions',
|
||||
verbose_name='管辖版块'
|
||||
)
|
||||
rank = models.CharField(max_length=20, choices=RANK_CHOICES, verbose_name='军衔级别')
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=PERMISSION_STATUS_CHOICES,
|
||||
default='active',
|
||||
verbose_name='权限状态'
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
restricted_until = models.DateTimeField(null=True, blank=True, verbose_name='限制结束时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'moderator_permissions'
|
||||
verbose_name = '版主权限'
|
||||
verbose_name_plural = '版主权限'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.moderator.username} - {self.region.name} ({self.get_status_display()})'
|
||||
|
||||
def is_active(self):
|
||||
"""Check if the permission is currently active."""
|
||||
from django.utils import timezone
|
||||
if self.status != 'active':
|
||||
return False
|
||||
if self.restricted_until and timezone.now() < self.restricted_until:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class ModeratorSupport(models.Model):
|
||||
"""Model for moderator application supports."""
|
||||
|
||||
supporter = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='supported_applications',
|
||||
verbose_name='支持者'
|
||||
)
|
||||
application = models.ForeignKey(
|
||||
ModeratorApplication,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='supports',
|
||||
verbose_name='版主申请'
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='支持时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'moderator_supports'
|
||||
verbose_name = '版主支持'
|
||||
verbose_name_plural = '版主支持'
|
||||
unique_together = ['supporter', 'application']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.supporter.username} supports {self.application.region.name}'
|
||||
|
||||
|
||||
class PermissionRestriction(models.Model):
|
||||
"""Model for permission restrictions."""
|
||||
|
||||
RESTRICTION_TYPE_CHOICES = [
|
||||
('partial', '部分限制'),
|
||||
('full', '完全限制'),
|
||||
]
|
||||
|
||||
operator = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='created_restrictions',
|
||||
verbose_name='操作者'
|
||||
)
|
||||
target_moderator = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='received_restrictions',
|
||||
verbose_name='被限制版主'
|
||||
)
|
||||
restriction_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=RESTRICTION_TYPE_CHOICES,
|
||||
verbose_name='限制类型'
|
||||
)
|
||||
start_time = models.DateTimeField(verbose_name='限制开始时间')
|
||||
end_time = models.DateTimeField(verbose_name='限制结束时间')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'permission_restrictions'
|
||||
verbose_name = '权限限制'
|
||||
verbose_name_plural = '权限限制'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.operator.username} restricted {self.target_moderator.username} ({self.get_restriction_type_display()})'
|
||||
1
backend/apps/regions/__init__.py
Normal file
1
backend/apps/regions/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Regions app
|
||||
7
backend/apps/regions/apps.py
Normal file
7
backend/apps/regions/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RegionsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.regions'
|
||||
verbose_name = '版块管理'
|
||||
54
backend/apps/regions/models.py
Normal file
54
backend/apps/regions/models.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Region(models.Model):
|
||||
"""Region model for hierarchical administrative divisions."""
|
||||
|
||||
LEVEL_CHOICES = [
|
||||
('province', '省'),
|
||||
('city', '市'),
|
||||
('county', '县'),
|
||||
('town', '乡镇/街道'),
|
||||
('village', '村/居委会'),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('active', '正常'),
|
||||
('inactive', '停用'),
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name='版块名称')
|
||||
level = models.CharField(max_length=20, choices=LEVEL_CHOICES, verbose_name='版块级别')
|
||||
parent = models.ForeignKey(
|
||||
'self',
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='children',
|
||||
verbose_name='上级版块'
|
||||
)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active', verbose_name='状态')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'regions'
|
||||
verbose_name = '版块'
|
||||
verbose_name_plural = '版块'
|
||||
ordering = ['level', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_full_path(self):
|
||||
"""Get the full hierarchical path of this region."""
|
||||
path = [self.name]
|
||||
parent = self.parent
|
||||
while parent:
|
||||
path.insert(0, parent.name)
|
||||
parent = parent.parent
|
||||
return ' → '.join(path)
|
||||
|
||||
def get_children(self):
|
||||
"""Get all direct children of this region."""
|
||||
return self.children.filter(status='active')
|
||||
@@ -5,10 +5,24 @@ from django.db import models
|
||||
class User(AbstractUser):
|
||||
"""Custom user model extending AbstractUser."""
|
||||
|
||||
ROLE_CHOICES = [
|
||||
('user', '普通用户'),
|
||||
('moderator', '版主'),
|
||||
('ai_auditor', 'AI审核员'),
|
||||
('admin', '管理员'),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('active', '正常'),
|
||||
('disabled', '禁用'),
|
||||
]
|
||||
|
||||
email = models.EmailField(unique=True)
|
||||
first_name = models.CharField(max_length=150)
|
||||
last_name = models.CharField(max_length=150)
|
||||
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
|
||||
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
REQUIRED_FIELDS = ['username', 'first_name']
|
||||
@@ -19,4 +33,13 @@ class User(AbstractUser):
|
||||
verbose_name_plural = 'Users'
|
||||
|
||||
def __str__(self):
|
||||
return self.email
|
||||
return self.email
|
||||
|
||||
def is_moderator(self):
|
||||
return self.role == 'moderator'
|
||||
|
||||
def is_admin(self):
|
||||
return self.role == 'admin'
|
||||
|
||||
def is_ai_auditor(self):
|
||||
return self.role == 'ai_auditor'
|
||||
@@ -34,6 +34,11 @@ INSTALLED_APPS = [
|
||||
|
||||
# Local apps
|
||||
'apps.users',
|
||||
'apps.regions',
|
||||
'apps.moderation',
|
||||
'apps.articles',
|
||||
'apps.featured_services',
|
||||
'apps.interactions',
|
||||
'apps.core',
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user