分支机构的项目管理,基于Django

This commit is contained in:
2025-11-11 11:09:01 +08:00
commit f0cbf2c2ec
284 changed files with 9472 additions and 0 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

3816
.idea/CopilotChatHistory.xml generated Normal file

File diff suppressed because one or more lines are too long

8
.idea/fzjg-activation.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,27 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E722" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N802" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="news.models.News.*" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

10
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fzjg-activation.iml" filepath="$PROJECT_DIR$/.idea/fzjg-activation.iml" />
</modules>
</component>
</project>

66
count_lines.py Normal file
View File

@@ -0,0 +1,66 @@
import os
import sys
def count_lines_in_file(file_path):
"""统计单个文件的行数"""
try:
with open(file_path, 'r', encoding='utf-8') as file:
return sum(1 for _ in file)
except UnicodeDecodeError:
try:
with open(file_path, 'r', encoding='gbk') as file:
return sum(1 for _ in file)
except UnicodeDecodeError:
print(f"无法解码文件: {file_path}")
return 0
except Exception as e:
print(f"读取文件 {file_path} 时出错: {e}")
return 0
def count_lines_in_directory(directory, extensions):
"""统计目录中特定扩展名文件的总行数"""
total_lines = 0
file_count = 0
for root, dirs, files in os.walk(directory):
# 跳过__pycache__目录
if '__pycache__' in root:
continue
for file in files:
if any(file.endswith(ext) for ext in extensions):
file_path = os.path.join(root, file)
lines = count_lines_in_file(file_path)
total_lines += lines
file_count += 1
print(f"{file_path}: {lines}")
return total_lines, file_count
def main():
project_dir = os.path.dirname(os.path.abspath(__file__))
# 统计Python文件
print("=== Python文件行数统计 ===")
py_lines, py_files = count_lines_in_directory(project_dir, ['.py'])
print(f"\nPython文件总数: {py_files}")
print(f"Python文件总行数: {py_lines}")
# 统计前端文件 (HTML, CSS, JS)
print("\n=== 前端文件行数统计 ===")
frontend_extensions = ['.html', '.css', '.js']
frontend_lines, frontend_files = count_lines_in_directory(project_dir, frontend_extensions)
print(f"\n前端文件总数: {frontend_files}")
print(f"前端文件总行数: {frontend_lines}")
# 总计
total_files = py_files + frontend_files
total_lines = py_lines + frontend_lines
print("\n=== 项目总计 ===")
print(f"文件总数: {total_files}")
print(f"代码总行数: {total_lines}")
print(f"Python文件占比: {py_lines/total_lines*100:.2f}%")
print(f"前端文件占比: {frontend_lines/total_lines*100:.2f}%")
if __name__ == "__main__":
main()

BIN
fzjgact/db.sqlite3 Normal file

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
fzjgact/fzjgact/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for fzjgact project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'fzjgact.settings')
application = get_asgi_application()

136
fzjgact/fzjgact/settings.py Normal file
View File

@@ -0,0 +1,136 @@
"""
Django settings for fzjgact project.
Generated by 'django-admin startproject' using Django 5.0.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-y_ky#j@s(-ayj+5yo)02&f%w-(flx5p)2sv3yl5j_az4$&c)ih'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['10.124.107.72', '127.0.0.1', 'localhost']
# Application definition
INSTALLED_APPS = [
"unfold", # before django.contrib.admin
"unfold.contrib.filters", # optional, if special filters are needed
"unfold.contrib.forms", # optional, if special form elements are needed
"unfold.contrib.inlines", # optional, if special inlines are needed
"unfold.contrib.import_export", # optional, if django-import-export package is used
"unfold.contrib.guardian", # optional, if django-guardian package is used
"unfold.contrib.simple_history", # optional, if django-simple-history package is used
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'huodong',
'rest_framework',
'django_select2',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'fzjgact.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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',
],
},
},
]
WSGI_APPLICATION = 'fzjgact.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = 'static/'
# 媒体文件配置
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

25
fzjgact/fzjgact/urls.py Normal file
View File

@@ -0,0 +1,25 @@
"""
URL configuration for fzjgact project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("huodong.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

16
fzjgact/fzjgact/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for fzjgact project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'fzjgact.settings')
application = get_wsgi_application()

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

194
fzjgact/huodong/admin.py Normal file
View File

@@ -0,0 +1,194 @@
from django.contrib import admin
from django.contrib.admin.actions import delete_selected
from django.urls import reverse
from django.utils.html import format_html
from .models import Branch, EquipmentImage, Drawing, PublicScreen
from .models import Activity, Branch, Event, Contact, VideoTerminal
from django_select2.forms import Select2Widget
from unfold.admin import ModelAdmin
from django.contrib.admin import AdminSite
from django.contrib.auth.models import User, Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
from django.db.models import Count, Q
from django.utils.html import format_html
from django.urls import reverse
from django import forms
# 定义Activity的内联admin类
class ActivityInline(admin.TabularInline): # 改为更紧凑的表格形式内联原StackedInline为堆叠形式
model = Activity
extra = 1 # 在Branch表单中默认显示1个额外的Activity表单可根据需求调整数值
fields = ('name', 'scope', 'start_time', 'end_time') # 限制内联表单显示的字段
readonly_fields = ('start_time',) # 可选:将开始时间设为只读字段(避免误修改)
# 可选:添加帮助文本提示
help_texts = {
'name': '请输入活动名称(必填)',
'scope': '选择活动范围(内部/外部)'
}
admin.site.unregister(User)
admin.site.unregister(Group)
@admin.register(User)
class UserAdmin(BaseUserAdmin, ModelAdmin):
pass
@admin.register(Group)
class GroupAdmin(BaseGroupAdmin, ModelAdmin):
pass
@admin.register(Branch)
class BranchAdmin(ModelAdmin):
inlines = [ActivityInline]
list_display = ('name', 'location', 'category', 'display_mature_status', 'background_color')
search_fields = ['name', 'location']
fieldsets = (
(None, {
'fields': ('name', 'location', 'contact_info', 'description', 'category', 'is_mature', 'background_color')
}),
)
def display_mature_status(self, obj):
return '💼' if obj.is_mature else '📒'
display_mature_status.short_description = '是否成熟'
def activity_count(self, obj):
return obj.activity_set.count()
activity_count.short_description = '活动数量'
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
inspection_person_count=Count('contact', filter=Q(contact__category='机房/设备间巡检人')))
return queryset
actions = ['set_branches_to_type_b']
def set_branches_to_type_b(self, request, queryset):
queryset.update(category='B型')
set_branches_to_type_b.short_description = '将选中的分支机构统一改为B型'
# 注册Activity模型如果需要的话虽然在这个示例中我们主要关注Branch
@admin.register(Activity)
class ActivityAdmin(ModelAdmin):
list_display = ('branch', 'scope', 'name', 'start_time', 'end_time')
search_fields = ["branch__name"] # 改为关联Branch模型的name字段
add_form_template = 'admin/huodong/add_form.html'
autocomplete_fields = ['branch']
@admin.register(Event)
class EventAdmin(ModelAdmin):
list_display = ('name', 'start_time', 'end_time', 'description')
filter_horizontal = ('branches',)
class ContactAdminForm(forms.ModelForm):
class Meta:
model = Contact
fields = '__all__'
widgets = {
# 使用复选框实现多选
'category': forms.CheckboxSelectMultiple(choices=Contact.CATEGORY_CHOICES)
}
class EquipmentImageAdmin(admin.ModelAdmin):
def bulk_delete_selected(self, request, queryset):
count = queryset.count()
queryset.delete()
self.message_user(request, f'已成功删除{count}条设备图片记录')
bulk_delete_selected.short_description = '批量删除选中的设备图片'
actions = ['bulk_delete_selected']
list_display = ['id', 'branch', 'uploaded_at', 'delete_link']
def delete_link(self, obj):
url = reverse('admin:huodong_equipmentimage_delete', args=[obj.id])
return format_html('<a class="deletelink" href="{}">删除</a>', url)
delete_link.short_description = '操作'
list_filter = ['branch']
search_fields = ['branch__name']
autocomplete_fields = ['branch']
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
# 设置默认分支机构为最后一个设备间图片的分支机构
try:
latest_image = EquipmentImage.objects.latest('uploaded_at')
form.base_fields['branch'].initial = latest_image.branch_id
except EquipmentImage.DoesNotExist:
pass
return form
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
# Count images for the current branch
count = EquipmentImage.objects.filter(branch=obj.branch).count()
self.message_user(request, f"{obj.branch.name}】已经有{count}张设备间图片。")
admin.site.register(EquipmentImage, EquipmentImageAdmin)
class DrawingAdmin(admin.ModelAdmin):
def bulk_delete_selected(self, request, queryset):
count = queryset.count()
queryset.delete()
self.message_user(request, f'已成功删除{count}条图纸记录')
bulk_delete_selected.short_description = '批量删除选中的图纸'
actions = ['bulk_delete_selected']
list_display = ('id', 'branch', 'uploaded_at', 'delete_link')
def delete_link(self, obj):
url = reverse('admin:huodong_drawing_delete', args=[obj.id])
return format_html('<a class="deletelink" href="{}">删除</a>', url)
delete_link.short_description = '操作'
list_filter = ('branch', 'uploaded_at')
search_fields = ('branch__name',)
autocomplete_fields = ['branch']
admin.site.register(Drawing, DrawingAdmin)
class PublicScreenAdmin(ModelAdmin):
def delete_link(self, obj):
if obj.id:
delete_url = reverse('admin:huodong_publicscreen_delete', args=[obj.id])
return format_html('<a href="{}" class="text-red-600 hover:text-red-900">删除</a>', delete_url)
return '-'
delete_link.short_description = '操作'
def bulk_delete_selected(self, request, queryset):
count = queryset.count()
queryset.delete()
self.message_user(request, f'已成功删除{count}条公共电子屏记录')
bulk_delete_selected.short_description = '批量删除选中的公共电子屏'
list_display = ('id', 'branch', 'screen_type', 'last_drill', 'delete_link')
list_filter = ['branch', 'screen_type']
autocomplete_fields = ['branch']
actions = ['bulk_delete_selected']
admin.site.register(PublicScreen, PublicScreenAdmin)
@admin.register(Contact)
class ContactAdmin(ModelAdmin):
form = ContactAdminForm # 指定自定义表单
list_display = ('branch', 'category', 'name', 'phone', 'email') # 可选:显示分类字段
autocomplete_fields = ['branch']
@admin.register(VideoTerminal)
class VideoTerminalAdmin(ModelAdmin):
list_display = ('branch', 'terminal_type', 'description', 'created_at')
list_filter = ('terminal_type', 'branch')
search_fields = ('branch__name', 'description')
autocomplete_fields = ['branch']

7
fzjgact/huodong/apps.py Normal file
View File

@@ -0,0 +1,7 @@
from django.apps import AppConfig
class HuodongConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'huodong'
verbose_name = '运营活动'

View File

View File

@@ -0,0 +1,26 @@
import random
from django.core.management.base import BaseCommand
from huodong.models import Branch
class Command(BaseCommand):
help = '随机修改所有branch实例的背景色'
def handle(self, *args, **kwargs):
colors = ['#f8fafc', '#f1f5f9', '#f9fafb', '#f3f4f6', '#fafafa',
'#f4f4f5', '#fafafa', '#f5f5f5', '#fafaf9', '#f5f5f4',
'#fef2f2', '#fee2e2', '#fff7ed', '#ffedd5', '#fffbeb',
'#fef3c7', '#fefce8', '#fef9c3', '#f7fee7', '#ecfccb',
'#f0fdf4', '#dcfce7', '#ecfdf5', '#d1fae5', '#f0fdfa',
'#ccfbf1', '#ecfeff', '#cffafe', '#f0f9ff', '#e0f2fe',
'#eff6ff', '#dbeafe', '#eef2ff', '#e0e7ff', '#f5f3ff',
'#ede9fe', '#faf5ff', '#f3e8ff', '#fdf4ff', '#fae8ff',
'#fff1f2', '#ffe4e6']
branches = Branch.objects.all()
for branch in branches:
branch.background_color = random.choice(colors)
branch.save()
self.stdout.write(self.style.SUCCESS('已成功修改所有branch实例的背景色'))
# 在终端运行此命令
# python manage.py your_command_name

View File

@@ -0,0 +1,50 @@
# Generated by Django 5.0.6 on 2024-07-09 06:35
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Branch',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('location', models.CharField(max_length=255)),
('contact_info', models.CharField(max_length=255)),
('description', models.TextField()),
],
),
migrations.CreateModel(
name='Activity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('scope', models.CharField(max_length=255)),
('start_time', models.DateTimeField()),
('end_time', models.DateTimeField()),
('location', models.CharField(max_length=255)),
('description', models.TextField()),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.branch')),
],
),
migrations.CreateModel(
name='Evaluation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('score', models.DecimalField(decimal_places=2, max_digits=4)),
('comment', models.TextField()),
('file_path', models.CharField(blank=True, max_length=255, null=True)),
('status', models.CharField(choices=[('pending', '待审核'), ('approved', '已通过'), ('rejected', '已拒绝')], default='pending', max_length=20)),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.activity')),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.branch')),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-07-17 09:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='end_time',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-07-24 02:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0002_alter_activity_end_time'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='scope',
field=models.CharField(choices=[('新建', '新建'), ('搬迁', '搬迁'), ('装修', '装修'), ('其他技术问题', '其他技术问题')], max_length=255),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-07-24 09:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0003_alter_activity_scope'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='scope',
field=models.CharField(choices=[('新建', '新建'), ('搬迁', '搬迁'), ('原址装修', '原址装修'), ('其他技术问题', '其他技术问题')], max_length=255),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.0.6 on 2024-07-30 08:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0004_alter_activity_scope'),
]
operations = [
migrations.CreateModel(
name='Event',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('start_time', models.DateTimeField()),
('end_time', models.DateTimeField(blank=True, null=True)),
('description', models.TextField()),
('branches', models.ManyToManyField(to='huodong.branch')),
],
),
]

View File

@@ -0,0 +1,41 @@
# Generated by Django 5.0.6 on 2024-09-09 09:26
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0005_event'),
]
operations = [
migrations.AlterModelOptions(
name='activity',
options={'verbose_name': '运营活动内容', 'verbose_name_plural': '运营活动内容(新建搬迁装修和技术)'},
),
migrations.AlterModelOptions(
name='branch',
options={'verbose_name': '分支机构', 'verbose_name_plural': '分支机构(基础信息)'},
),
migrations.AlterModelOptions(
name='event',
options={'verbose_name': '运营事件', 'verbose_name_plural': '运营事件(其它)'},
),
migrations.CreateModel(
name='Contact',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('category', models.CharField(choices=[('信息安全联系人', '信息安全联系人'), ('巡检人', '巡检人')], max_length=50)),
('name', models.CharField(max_length=255)),
('phone', models.CharField(max_length=20)),
('email', models.EmailField(blank=True, max_length=254)),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.branch')),
],
options={
'verbose_name': '联系人群',
'verbose_name_plural': '联系人群',
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-09-10 01:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0006_alter_activity_options_alter_branch_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='branch',
name='name',
field=models.CharField(max_length=255, unique=True),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.0.6 on 2024-09-10 02:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0007_alter_branch_name'),
]
operations = [
migrations.AddField(
model_name='contact',
name='description',
field=models.TextField(blank=True),
),
migrations.AlterField(
model_name='contact',
name='category',
field=models.CharField(choices=[('机房/设备间巡检人', '机房/设备间巡检人'), ('信息安全联系人', '信息安全联系人')], max_length=50),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.0.6 on 2024-09-10 02:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0008_contact_description_alter_contact_category'),
]
operations = [
migrations.AlterField(
model_name='contact',
name='description',
field=models.TextField(blank=True, verbose_name='描述,可不填'),
),
migrations.AlterField(
model_name='contact',
name='email',
field=models.EmailField(blank=True, max_length=254, verbose_name='邮箱,可不填'),
),
migrations.AlterField(
model_name='contact',
name='name',
field=models.CharField(max_length=255, verbose_name='姓名'),
),
migrations.AlterField(
model_name='contact',
name='phone',
field=models.CharField(max_length=20, verbose_name='电话'),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.0.6 on 2024-09-10 02:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0009_alter_contact_description_alter_contact_email_and_more'),
]
operations = [
migrations.AlterField(
model_name='branch',
name='contact_info',
field=models.CharField(max_length=255, verbose_name='主要人的联系方式'),
),
migrations.AlterField(
model_name='branch',
name='description',
field=models.TextField(blank=True, verbose_name='备注,可不填'),
),
migrations.AlterField(
model_name='branch',
name='location',
field=models.CharField(max_length=255, verbose_name='所在省份'),
),
migrations.AlterField(
model_name='branch',
name='name',
field=models.CharField(max_length=255, unique=True, verbose_name='分支机构名称'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.0.6 on 2024-09-10 02:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0010_alter_branch_contact_info_alter_branch_description_and_more'),
]
operations = [
migrations.AlterField(
model_name='branch',
name='contact_info',
field=models.CharField(max_length=255, verbose_name='主要联系人'),
),
migrations.AlterField(
model_name='branch',
name='description',
field=models.TextField(blank=True, verbose_name='备注'),
),
]

View File

@@ -0,0 +1,79 @@
# Generated by Django 5.0.6 on 2024-09-10 07:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0011_alter_branch_contact_info_alter_branch_description'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='branch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.branch', verbose_name='分支机构'),
),
migrations.AlterField(
model_name='activity',
name='description',
field=models.TextField(verbose_name='其它内容'),
),
migrations.AlterField(
model_name='activity',
name='end_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='结束日期'),
),
migrations.AlterField(
model_name='activity',
name='location',
field=models.CharField(max_length=255, verbose_name='所在地点'),
),
migrations.AlterField(
model_name='activity',
name='name',
field=models.CharField(max_length=255, verbose_name='活动名称'),
),
migrations.AlterField(
model_name='activity',
name='start_time',
field=models.DateTimeField(verbose_name='开始日期'),
),
migrations.AlterField(
model_name='contact',
name='branch',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='huodong.branch', verbose_name='分支机构'),
),
migrations.AlterField(
model_name='contact',
name='category',
field=models.CharField(choices=[('机房/设备间巡检人', '机房/设备间巡检人'), ('信息安全联系人', '信息安全联系人')], max_length=50, verbose_name='联系人分类'),
),
migrations.AlterField(
model_name='event',
name='branches',
field=models.ManyToManyField(to='huodong.branch', verbose_name='分支机构'),
),
migrations.AlterField(
model_name='event',
name='description',
field=models.TextField(verbose_name='事件描述'),
),
migrations.AlterField(
model_name='event',
name='end_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='结束时间'),
),
migrations.AlterField(
model_name='event',
name='name',
field=models.CharField(max_length=255, verbose_name='事件名称'),
),
migrations.AlterField(
model_name='event',
name='start_time',
field=models.DateTimeField(verbose_name='开始时间'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-09-20 02:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0012_alter_activity_branch_alter_activity_description_and_more'),
]
operations = [
migrations.AddField(
model_name='branch',
name='background_color',
field=models.CharField(default='#EFF6FF', help_text='使用#RRGGBB格式的颜色代码', max_length=7, verbose_name='背景色'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-09-23 07:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0013_branch_background_color'),
]
operations = [
migrations.AddField(
model_name='branch',
name='category',
field=models.CharField(choices=[('A型', 'A型'), ('B型', 'B型'), ('C型', 'C型'), ('不适用', '不适用')], default='C型', max_length=10, verbose_name='分类'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-10-28 05:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0014_branch_category'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='scope',
field=models.CharField(choices=[('新建', '新建'), ('搬迁', '搬迁'), ('原址装修', '原址装修'), ('撤销', '撤销'), ('其他技术问题', '其他技术问题')], max_length=255),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-06-10 05:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0015_alter_activity_scope'),
]
operations = [
migrations.AlterField(
model_name='contact',
name='category',
field=models.CharField(choices=[('机房/设备间巡检人', '机房/设备间巡检人'), ('信息安全联系人', '信息安全联系人'), ('兼岗', '兼岗'), ('安全员', '安全员')], help_text='按住 Ctrl/Command 键多选(值将以逗号分隔存储)', max_length=255, verbose_name='联系人分类'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-06-12 09:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0016_alter_contact_category'),
]
operations = [
migrations.AddField(
model_name='branch',
name='equipment_image',
field=models.ImageField(blank=True, null=True, upload_to='equipment_room_images/', verbose_name='设备间图片'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-06-12 09:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0017_branch_equipment_image'),
]
operations = [
migrations.AlterField(
model_name='branch',
name='equipment_image',
field=models.ImageField(blank=True, default=None, null=True, upload_to='equipment_room_images/', verbose_name='设备间图片'),
),
]

View File

@@ -0,0 +1,27 @@
# Generated by Django 5.0.6 on 2025-06-13 06:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0018_alter_branch_equipment_image'),
]
operations = [
migrations.RemoveField(
model_name='branch',
name='equipment_image',
),
migrations.CreateModel(
name='EquipmentImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='equipment_room_images/')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='equipment_images', to='huodong.branch')),
],
),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.0.6 on 2025-06-13 09:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0019_remove_branch_equipment_image_equipmentimage'),
]
operations = [
migrations.AlterModelOptions(
name='equipmentimage',
options={'verbose_name': '设备间图', 'verbose_name_plural': '设备间图'},
),
migrations.CreateModel(
name='Drawing',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='drawings/')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='drawings', to='huodong.branch')),
],
options={
'verbose_name': '图纸',
'verbose_name_plural': '图纸',
},
),
]

View File

@@ -0,0 +1,27 @@
# Generated by Django 5.0.6 on 2025-06-18 06:29
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0020_alter_equipmentimage_options_drawing'),
]
operations = [
migrations.CreateModel(
name='PublicScreen',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='public_screen_images/')),
('screen_type', models.CharField(choices=[('marquee', '跑马灯'), ('advertisement', '广告屏'), ('information', '信息发布屏')], max_length=20, verbose_name='功能类型')),
('description', models.TextField(blank=True, null=True, verbose_name='功能描述')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='public_screens', to='huodong.branch')),
('last_drill', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='public_screens', to='huodong.event', verbose_name='最后演练事件')),
],
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.0.6 on 2025-06-18 07:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('huodong', '0021_publicscreen'),
]
operations = [
migrations.RemoveField(
model_name='publicscreen',
name='created_at',
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.0.6 on 2025-06-18 08:43
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0022_remove_publicscreen_created_at'),
]
operations = [
migrations.AddField(
model_name='publicscreen',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-06-19 08:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0023_publicscreen_created_at'),
]
operations = [
migrations.AlterField(
model_name='event',
name='branches',
field=models.ManyToManyField(related_name='events', to='huodong.branch', verbose_name='分支机构'),
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.0.6 on 2025-06-25 06:37
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0024_alter_event_branches'),
]
operations = [
migrations.CreateModel(
name='VideoTerminal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('terminal_type', models.CharField(choices=[('polycom', '宝利通终端'), ('zte', '中兴终端'), ('logitech', '罗技摄像头'), ('laptop_tv', '笔记本加电视'), ('laptop_projector', '笔记本加投影仪'), ('other', '其它')], max_length=20, verbose_name='设备类型')),
('description', models.TextField(blank=True, verbose_name='设备描述')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='video_terminals', to='huodong.branch', verbose_name='分支机构')),
],
options={
'verbose_name': '视频设备终端',
'verbose_name_plural': '视频设备终端',
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-06-25 07:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0025_videoterminal'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='scope',
field=models.CharField(choices=[('新建', '新建'), ('搬迁', '搬迁'), ('原址装修', '原址装修'), ('撤销', '撤销'), ('其他技术问题', '其他技术问题')], max_length=255, verbose_name='活动类型'),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.0.6 on 2025-06-27 09:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0026_alter_activity_scope'),
]
operations = [
migrations.AlterModelOptions(
name='publicscreen',
options={'verbose_name': '公共电子屏', 'verbose_name_plural': '公共电子屏'},
),
migrations.AddField(
model_name='branch',
name='is_mature',
field=models.BooleanField(default=False, verbose_name='是否成熟'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.0.6 on 2025-07-02 07:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0027_alter_publicscreen_options_branch_is_mature'),
]
operations = [
migrations.AlterField(
model_name='publicscreen',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='public_screen_images/'),
),
migrations.AlterField(
model_name='publicscreen',
name='screen_type',
field=models.CharField(blank=True, choices=[('marquee', '跑马灯'), ('advertisement', '广告屏'), ('information', '信息发布屏')], max_length=20, null=True, verbose_name='功能类型'),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.0.6 on 2025-09-01 06:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('huodong', '0028_alter_publicscreen_image_and_more'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='end_time',
field=models.DateField(blank=True, null=True, verbose_name='结束日期'),
),
migrations.AlterField(
model_name='activity',
name='start_time',
field=models.DateField(verbose_name='开始日期'),
),
migrations.AlterField(
model_name='event',
name='end_time',
field=models.DateField(blank=True, null=True, verbose_name='结束时间'),
),
migrations.AlterField(
model_name='event',
name='start_time',
field=models.DateField(verbose_name='开始时间'),
),
]

View File

182
fzjgact/huodong/models.py Normal file
View File

@@ -0,0 +1,182 @@
from django.db import models
from django.utils import timezone
class Branch(models.Model):
CATEGORY_CHOICES = (
('A型', 'A型'),
('B型', 'B型'),
('C型', 'C型'),
('不适用', '不适用'),
)
name = models.CharField(max_length=255, unique=True, verbose_name='分支机构名称')
location = models.CharField(max_length=255, verbose_name='所在省份')
contact_info = models.CharField(max_length=255, verbose_name='主要联系人')
description = models.TextField(blank=True, verbose_name='备注')
background_color = models.CharField(max_length=7, default='#EFF6FF', verbose_name='背景色',
help_text='使用#RRGGBB格式的颜色代码')
category = models.CharField(max_length=10, choices=CATEGORY_CHOICES, default='C型', verbose_name='分类')
is_mature = models.BooleanField(default=False, verbose_name='是否成熟')
def __str__(self):
return f'{self.name} 💼' if self.is_mature else self.name
class Meta:
verbose_name = '分支机构'
verbose_name_plural = '分支机构(基础信息)'
class Contact(models.Model):
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构')
CATEGORY_CHOICES = [
('机房/设备间巡检人', '机房/设备间巡检人'),
('信息安全联系人', '信息安全联系人'),
('兼岗', '兼岗'),
('安全员', '安全员')
# 可以添加更多类别
]
# 修改为支持多选的 CharField
category = models.CharField(
max_length=255, # 增大长度原50可能不足
choices=CATEGORY_CHOICES,
verbose_name='联系人分类',
help_text='按住 Ctrl/Command 键多选(值将以逗号分隔存储)'
)
name = models.CharField(max_length=255, verbose_name='姓名')
phone = models.CharField(max_length=20, verbose_name='电话')
email = models.EmailField(blank=True, verbose_name='邮箱,可不填')
description = models.TextField(blank=True, verbose_name='描述,可不填')
def __str__(self):
return self.name
class Meta:
verbose_name = '联系人群'
verbose_name_plural = '联系人群'
class Activity(models.Model):
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构')
name = models.CharField(max_length=255, verbose_name='活动名称')
scope = models.CharField(max_length=255, choices=(
('新建', '新建'),
('搬迁', '搬迁'),
('原址装修', '原址装修'),
('撤销', '撤销'),
('其他技术问题', '其他技术问题')
), verbose_name='活动类型')
start_time = models.DateField(verbose_name='开始日期')
end_time = models.DateField(blank=True, null=True, verbose_name='结束日期') # 可以为空,表示活动尚未结束
location = models.CharField(max_length=255, verbose_name='所在地点')
description = models.TextField(verbose_name='其它内容')
def __str__(self):
return self.name
class Meta:
verbose_name = '运营活动内容'
verbose_name_plural = '运营活动内容(新建搬迁装修和技术)'
class EquipmentImage(models.Model):
branch = models.ForeignKey(Branch, related_name='equipment_images', on_delete=models.CASCADE)
image = models.ImageField(upload_to='equipment_room_images/')
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"设备间图片 {self.id} - {self.branch.name}"
class Meta:
verbose_name = '设备间图'
verbose_name_plural = '设备间图'
# 图纸的类
class Drawing(models.Model):
branch = models.ForeignKey(Branch, related_name='drawings', on_delete=models.CASCADE)
image = models.ImageField(upload_to='drawings/')
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"图纸 {self.id} - {self.branch.name}"
class Meta:
verbose_name = '图纸'
verbose_name_plural = '图纸'
# 公共电子屏
class PublicScreen(models.Model):
SCREEN_TYPES = (
('marquee', '跑马灯'),
('advertisement', '广告屏'),
('information', '信息发布屏'),
)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='public_screens')
image = models.ImageField(upload_to='public_screen_images/', null=True, blank=True)
screen_type = models.CharField(max_length=20, choices=SCREEN_TYPES, verbose_name='功能类型', null=True, blank=True)
description = models.TextField(blank=True, null=True, verbose_name='功能描述')
last_drill = models.ForeignKey('Event', on_delete=models.SET_NULL, blank=True, null=True, related_name='public_screens', verbose_name='最后演练事件')
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.branch.name} - {self.get_screen_type_display()} {self.id}'
class Meta:
verbose_name = '公共电子屏'
verbose_name_plural = '公共电子屏'
class Event(models.Model):
branches = models.ManyToManyField(Branch, related_name='events', verbose_name='分支机构')
name = models.CharField(max_length=255, verbose_name='事件名称')
start_time = models.DateField(verbose_name='开始时间')
end_time = models.DateField(blank=True, null=True, verbose_name='结束时间') # 可以为空,表示活动尚未结束
description = models.TextField(verbose_name='事件描述')
def __str__(self):
return self.name
class Meta:
verbose_name = '运营事件'
verbose_name_plural = '运营事件(其它)'
class VideoTerminal(models.Model):
TERMINAL_TYPES = (
('polycom', '宝利通终端'),
('zte', '中兴终端'),
('logitech', '罗技摄像头'),
('laptop_tv', '笔记本加电视'),
('laptop_projector', '笔记本加投影仪'),
('other', '其它'),
)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='video_terminals', verbose_name='分支机构')
terminal_type = models.CharField(max_length=20, choices=TERMINAL_TYPES, verbose_name='设备类型')
description = models.TextField(blank=True, verbose_name='设备描述')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
def __str__(self):
return f"{self.branch.name} - {self.get_terminal_type_display()}"
class Meta:
verbose_name = '视频设备终端'
verbose_name_plural = '视频设备终端'
class Evaluation(models.Model):
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
score = models.DecimalField(max_digits=4, decimal_places=2)
comment = models.TextField()
file_path = models.CharField(max_length=255, blank=True, null=True)
status = models.CharField(max_length=20,
choices=(('pending', '待审核'), ('approved', '已通过'), ('rejected', '已拒绝')),
default='pending')
def __str__(self):
return f"{self.activity.name} - {self.branch.name}"

View File

@@ -0,0 +1,20 @@
from rest_framework import serializers
from .models import Branch, Activity, Evaluation
class BranchSerializer(serializers.ModelSerializer):
class Meta:
model = Branch
fields = '__all__'
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
fields = '__all__'
class EvaluationSerializer(serializers.ModelSerializer):
class Meta:
model = Evaluation
fields = '__all__'

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{% extends "admin/change_form.html" %}
{% load i18n admin_urls static admin_modify %}
{% block extrahead %}
{{ block.super }}
<script src="{% static 'admin/js/custom.js' %}"></script>
{% endblock %}
{% block content %}
<h1>自定义的添加表单</h1>
{{ block.super }}
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More