commit 7634e66541d07c0a12a7a775394a8d1246a15e1d Author: xiaji Date: Sun Jan 5 10:45:32 2025 +0800 Initial commit diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..52a4fe5 Binary files /dev/null and b/db.sqlite3 differ diff --git a/fileshare/__init__.py b/fileshare/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fileshare/__pycache__/__init__.cpython-310.pyc b/fileshare/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..c03a7d9 Binary files /dev/null and b/fileshare/__pycache__/__init__.cpython-310.pyc differ diff --git a/fileshare/__pycache__/settings.cpython-310.pyc b/fileshare/__pycache__/settings.cpython-310.pyc new file mode 100644 index 0000000..88e7894 Binary files /dev/null and b/fileshare/__pycache__/settings.cpython-310.pyc differ diff --git a/fileshare/__pycache__/urls.cpython-310.pyc b/fileshare/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..94eb857 Binary files /dev/null and b/fileshare/__pycache__/urls.cpython-310.pyc differ diff --git a/fileshare/__pycache__/wsgi.cpython-310.pyc b/fileshare/__pycache__/wsgi.cpython-310.pyc new file mode 100644 index 0000000..809e7fb Binary files /dev/null and b/fileshare/__pycache__/wsgi.cpython-310.pyc differ diff --git a/fileshare/asgi.py b/fileshare/asgi.py new file mode 100644 index 0000000..5833107 --- /dev/null +++ b/fileshare/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for fileshare 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.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fileshare.settings") + +application = get_asgi_application() diff --git a/fileshare/settings.py b/fileshare/settings.py new file mode 100644 index 0000000..5d6aa50 --- /dev/null +++ b/fileshare/settings.py @@ -0,0 +1,139 @@ +""" +Django settings for fileshare project. + +Generated by 'django-admin startproject' using Django 5.1.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path +import os + +# 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.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-c$znmkzi@t&!x)r!7(-g6h++fu3hi!ml&_nqmvcigt2$x=6xi&" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "main", +] + +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 = "fileshare.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / 'templates'], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "fileshare.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/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.1/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.1/howto/static-files/ + +STATIC_URL = "static/" +STATICFILES_DIRS = [ + BASE_DIR / "static", +] + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# Login redirect +LOGIN_REDIRECT_URL = 'dashboard' +LOGOUT_REDIRECT_URL = 'home' + +# CSRF settings +CSRF_COOKIE_SECURE = False # Set to True in production with HTTPS +CSRF_COOKIE_HTTPONLY = False +CSRF_COOKIE_SAMESITE = 'Lax' +CSRF_TRUSTED_ORIGINS = ['http://localhost:8000'] + +SESSION_COOKIE_SECURE = False # Set to True in production with HTTPS +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SAMESITE = 'Lax' diff --git a/fileshare/urls.py b/fileshare/urls.py new file mode 100644 index 0000000..141d3ae --- /dev/null +++ b/fileshare/urls.py @@ -0,0 +1,31 @@ +""" +URL configuration for fileshare project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/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.contrib.auth import views as auth_views +from main import views + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('main.urls')), + path('accounts/', include([ + path('login/', auth_views.LoginView.as_view(template_name='registration/login.html'), name='login'), + path('logout/', auth_views.LogoutView.as_view(template_name='registration/logout.html'), name='logout'), + path('register/', views.register, name='register'), + ])), +] diff --git a/fileshare/wsgi.py b/fileshare/wsgi.py new file mode 100644 index 0000000..0fd22ef --- /dev/null +++ b/fileshare/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for fileshare 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.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fileshare.settings") + +application = get_wsgi_application() diff --git a/input.css b/input.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/main/__init__.py b/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/__pycache__/__init__.cpython-310.pyc b/main/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..7426a2d Binary files /dev/null and b/main/__pycache__/__init__.cpython-310.pyc differ diff --git a/main/__pycache__/admin.cpython-310.pyc b/main/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..4a3d68c Binary files /dev/null and b/main/__pycache__/admin.cpython-310.pyc differ diff --git a/main/__pycache__/apps.cpython-310.pyc b/main/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..6cd32d5 Binary files /dev/null and b/main/__pycache__/apps.cpython-310.pyc differ diff --git a/main/__pycache__/forms.cpython-310.pyc b/main/__pycache__/forms.cpython-310.pyc new file mode 100644 index 0000000..34448a7 Binary files /dev/null and b/main/__pycache__/forms.cpython-310.pyc differ diff --git a/main/__pycache__/models.cpython-310.pyc b/main/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..2f9b937 Binary files /dev/null and b/main/__pycache__/models.cpython-310.pyc differ diff --git a/main/__pycache__/urls.cpython-310.pyc b/main/__pycache__/urls.cpython-310.pyc new file mode 100644 index 0000000..8851bca Binary files /dev/null and b/main/__pycache__/urls.cpython-310.pyc differ diff --git a/main/__pycache__/views.cpython-310.pyc b/main/__pycache__/views.cpython-310.pyc new file mode 100644 index 0000000..0d4bc97 Binary files /dev/null and b/main/__pycache__/views.cpython-310.pyc differ diff --git a/main/admin.py b/main/admin.py new file mode 100644 index 0000000..c40effe --- /dev/null +++ b/main/admin.py @@ -0,0 +1,34 @@ +from django.contrib import admin +from .models import File, Message, PublicMessage, PublicFile, Friendship + +@admin.register(File) +class FileAdmin(admin.ModelAdmin): + list_display = ('file', 'owner', 'created_at') + list_filter = ('owner', 'created_at') + search_fields = ('file', 'description') + raw_id_fields = ('shared_with',) + +@admin.register(Message) +class MessageAdmin(admin.ModelAdmin): + list_display = ('sender', 'created_at') + list_filter = ('sender', 'created_at') + search_fields = ('content',) + raw_id_fields = ('recipients',) + +@admin.register(PublicMessage) +class PublicMessageAdmin(admin.ModelAdmin): + list_display = ('name', 'created_at', 'ip_address') + list_filter = ('created_at',) + search_fields = ('content',) + +@admin.register(PublicFile) +class PublicFileAdmin(admin.ModelAdmin): + list_display = ('name', 'file', 'created_at', 'ip_address') + list_filter = ('created_at',) + search_fields = ('file', 'description') + +@admin.register(Friendship) +class FriendshipAdmin(admin.ModelAdmin): + list_display = ('from_user', 'to_user', 'status', 'created_at') + list_filter = ('status', 'created_at') + search_fields = ('from_user__username', 'to_user__username') diff --git a/main/apps.py b/main/apps.py new file mode 100644 index 0000000..34f1451 --- /dev/null +++ b/main/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MainConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "main" diff --git a/main/forms.py b/main/forms.py new file mode 100644 index 0000000..6f7d11d --- /dev/null +++ b/main/forms.py @@ -0,0 +1,112 @@ +from django import forms +from django.core import validators +from .models import File, Message, PublicMessage, PublicFile, Friendship +from django.contrib.auth import get_user_model +from django.contrib.auth.forms import UserCreationForm + +class CustomUserCreationForm(UserCreationForm): + class Meta(UserCreationForm.Meta): + labels = { + 'username': '用户名', + 'password1': '密码', + 'password2': '确认密码' + } + help_texts = { + 'username': '150个字符以内。可以包含中文、字母、数字和 @/./+/-/_。', + 'password1': '您的密码不能与其他个人信息太相似。\n您的密码必须包含至少 8 个字符。\n您的密码不能是常用密码。\n您的密码不能全部是数字。', + 'password2': '再次输入密码以确认。' + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # 修改用户名字段的正则验证 + self.fields['username'].validators = [ + validators.RegexValidator( + r'^[\w\u4e00-\u9fa5@.+-_]+$', + '用户名只能包含中文、字母、数字和 @/./+/-/_。' + ) + ] + +class FileUploadForm(forms.ModelForm): + is_public = forms.BooleanField( + required=False, + label='设为公开文件', + help_text='如果勾选,此文件将对所有用户可见' + ) + + class Meta: + model = File + fields = ['file', 'description', 'shared_with', 'is_public'] + labels = { + 'file': '文件', + 'description': '描述', + 'shared_with': '共享用户' + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['shared_with'].required = False + +class MessageForm(forms.ModelForm): + class Meta: + model = Message + fields = ['recipients', 'content', 'file'] + labels = { + 'recipients': '收件人', + 'content': '消息内容', + 'file': '附件' + } + +class PublicMessageForm(forms.ModelForm): + class Meta: + model = PublicMessage + fields = ['content'] + labels = { + 'content': '留言内容' + } + widgets = { + 'content': forms.Textarea(attrs={'rows': 3, 'placeholder': '您的留言...'}), + } + +class FriendRequestForm(forms.Form): + username = forms.CharField( + label='用户名', + max_length=150, + widget=forms.TextInput(attrs={ + 'placeholder': '输入要添加的好友用户名', + 'class': 'w-full px-3 py-2 border rounded-md' + }) + ) + + def clean_username(self): + username = self.cleaned_data['username'] + User = get_user_model() + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + raise forms.ValidationError('用户不存在') + return user + +class FriendActionForm(forms.Form): + ACTION_CHOICES = [ + ('accept', '接受'), + ('reject', '拒绝'), + ] + + action = forms.ChoiceField( + choices=ACTION_CHOICES, + widget=forms.RadioSelect, + label='操作' + ) + +class PublicFileForm(forms.ModelForm): + class Meta: + model = PublicFile + fields = ['file', 'description'] + labels = { + 'file': '文件', + 'description': '描述' + } + widgets = { + 'description': forms.Textarea(attrs={'rows': 3, 'placeholder': '文件描述...'}), + } diff --git a/main/migrations/0001_initial.py b/main/migrations/0001_initial.py new file mode 100644 index 0000000..6d6b85e --- /dev/null +++ b/main/migrations/0001_initial.py @@ -0,0 +1,92 @@ +# Generated by Django 5.1.4 on 2025-01-03 14:10 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="File", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("file", models.FileField(upload_to="uploads/")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("description", models.TextField(blank=True)), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="files", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "shared_with", + models.ManyToManyField( + blank=True, + related_name="shared_files", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="Message", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField()), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "file", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="main.file", + ), + ), + ( + "recipients", + models.ManyToManyField( + related_name="received_messages", to=settings.AUTH_USER_MODEL + ), + ), + ( + "sender", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="sent_messages", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + }, + ), + ] diff --git a/main/migrations/0002_publicmessage.py b/main/migrations/0002_publicmessage.py new file mode 100644 index 0000000..4c2a0c0 --- /dev/null +++ b/main/migrations/0002_publicmessage.py @@ -0,0 +1,36 @@ +# Generated by Django 5.1.4 on 2025-01-04 12:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="PublicMessage", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(blank=True, max_length=100)), + ("content", models.TextField()), + ( + "file", + models.FileField( + blank=True, null=True, upload_to="public_uploads/" + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/main/migrations/0003_publicfile_remove_publicmessage_file.py b/main/migrations/0003_publicfile_remove_publicmessage_file.py new file mode 100644 index 0000000..159a155 --- /dev/null +++ b/main/migrations/0003_publicfile_remove_publicmessage_file.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.4 on 2025-01-04 13:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0002_publicmessage"), + ] + + operations = [ + migrations.CreateModel( + name="PublicFile", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(blank=True, max_length=100)), + ("file", models.FileField(upload_to="public_uploads/")), + ("description", models.TextField(blank=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.RemoveField( + model_name="publicmessage", + name="file", + ), + ] diff --git a/main/migrations/0004_publicfile_ip_address_publicmessage_ip_address_and_more.py b/main/migrations/0004_publicfile_ip_address_publicmessage_ip_address_and_more.py new file mode 100644 index 0000000..d85cd26 --- /dev/null +++ b/main/migrations/0004_publicfile_ip_address_publicmessage_ip_address_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.1.4 on 2025-01-04 14:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0003_publicfile_remove_publicmessage_file"), + ] + + operations = [ + migrations.AddField( + model_name="publicfile", + name="ip_address", + field=models.GenericIPAddressField(blank=True, null=True), + ), + migrations.AddField( + model_name="publicmessage", + name="ip_address", + field=models.GenericIPAddressField(blank=True, null=True), + ), + migrations.AlterField( + model_name="publicfile", + name="name", + field=models.CharField(default="公共", editable=False, max_length=100), + ), + migrations.AlterField( + model_name="publicmessage", + name="name", + field=models.CharField(default="公共", editable=False, max_length=100), + ), + ] diff --git a/main/migrations/0005_friendship.py b/main/migrations/0005_friendship.py new file mode 100644 index 0000000..0b9939c --- /dev/null +++ b/main/migrations/0005_friendship.py @@ -0,0 +1,63 @@ +# Generated by Django 5.1.4 on 2025-01-04 15:21 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0004_publicfile_ip_address_publicmessage_ip_address_and_more"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Friendship", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "status", + models.CharField( + choices=[ + ("pending", "等待中"), + ("accepted", "已接受"), + ("rejected", "已拒绝"), + ], + default="pending", + max_length=10, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "from_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="friendship_requests_sent", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "to_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="friendship_requests_received", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + "unique_together": {("from_user", "to_user")}, + }, + ), + ] diff --git a/main/migrations/__init__.py b/main/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/migrations/__pycache__/0001_initial.cpython-310.pyc b/main/migrations/__pycache__/0001_initial.cpython-310.pyc new file mode 100644 index 0000000..558d734 Binary files /dev/null and b/main/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/main/migrations/__pycache__/0002_publicmessage.cpython-310.pyc b/main/migrations/__pycache__/0002_publicmessage.cpython-310.pyc new file mode 100644 index 0000000..f8fd38e Binary files /dev/null and b/main/migrations/__pycache__/0002_publicmessage.cpython-310.pyc differ diff --git a/main/migrations/__pycache__/0003_publicfile_remove_publicmessage_file.cpython-310.pyc b/main/migrations/__pycache__/0003_publicfile_remove_publicmessage_file.cpython-310.pyc new file mode 100644 index 0000000..a80cd90 Binary files /dev/null and b/main/migrations/__pycache__/0003_publicfile_remove_publicmessage_file.cpython-310.pyc differ diff --git a/main/migrations/__pycache__/0004_publicfile_ip_address_publicmessage_ip_address_and_more.cpython-310.pyc b/main/migrations/__pycache__/0004_publicfile_ip_address_publicmessage_ip_address_and_more.cpython-310.pyc new file mode 100644 index 0000000..ba8db38 Binary files /dev/null and b/main/migrations/__pycache__/0004_publicfile_ip_address_publicmessage_ip_address_and_more.cpython-310.pyc differ diff --git a/main/migrations/__pycache__/0005_friendship.cpython-310.pyc b/main/migrations/__pycache__/0005_friendship.cpython-310.pyc new file mode 100644 index 0000000..24cc420 Binary files /dev/null and b/main/migrations/__pycache__/0005_friendship.cpython-310.pyc differ diff --git a/main/migrations/__pycache__/__init__.cpython-310.pyc b/main/migrations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..df11657 Binary files /dev/null and b/main/migrations/__pycache__/__init__.cpython-310.pyc differ diff --git a/main/models.py b/main/models.py new file mode 100644 index 0000000..f87bd7a --- /dev/null +++ b/main/models.py @@ -0,0 +1,63 @@ +from django.db import models +from django.contrib.auth.models import User + +class File(models.Model): + owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='files') + file = models.FileField(upload_to='uploads/') + shared_with = models.ManyToManyField(User, related_name='shared_files', blank=True) + created_at = models.DateTimeField(auto_now_add=True) + description = models.TextField(blank=True) + + def __str__(self): + return f"{self.file.name}({self.owner.username})" + +class Message(models.Model): + sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages') + recipients = models.ManyToManyField(User, related_name='received_messages') + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + file = models.ForeignKey(File, on_delete=models.SET_NULL, null=True, blank=True) + + def __str__(self): + return f"来自 {self.sender.username} 的消息" + + class Meta: + ordering = ['-created_at'] + +class PublicMessage(models.Model): + name = models.CharField(max_length=100, default="公共", editable=False) + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + ip_address = models.GenericIPAddressField(null=True, blank=True) + + def __str__(self): + return f"来自 {self.name or '匿名用户'} 的公开留言" + +class PublicFile(models.Model): + name = models.CharField(max_length=100, default="公共", editable=False) + file = models.FileField(upload_to='public_uploads/') + description = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + ip_address = models.GenericIPAddressField(null=True, blank=True) + + def __str__(self): + return f"来自 {self.name or '匿名用户'} 的公开文件" + +class Friendship(models.Model): + STATUS_CHOICES = [ + ('pending', '等待中'), + ('accepted', '已接受'), + ('rejected', '已拒绝'), + ] + + from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_requests_sent') + to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_requests_received') + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending') + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ('from_user', 'to_user') + ordering = ['-created_at'] + + def __str__(self): + return f"{self.from_user.username} -> {self.to_user.username} ({self.status})" diff --git a/main/templates/main/dashboard.html b/main/templates/main/dashboard.html new file mode 100644 index 0000000..7047322 --- /dev/null +++ b/main/templates/main/dashboard.html @@ -0,0 +1,89 @@ +{% extends 'base.html' %} + +{% block content %} +
+

您的仪表板

+ +
+

您的文件

+
+ {% if files %} +
    + {% for file in files %} +
  • +
    + + {{ file.file.name }} + +

    {{ file.description }}

    +
    +
    + {% csrf_token %} + +
    +
  • + {% endfor %} +
+ {% else %} +

尚未上传文件。

+ {% endif %} + +
+
+ +
+

共享文件

+
+ {% if shared_files %} +
    + {% for file in shared_files %} +
  • + + {{ file.file.name }} + +

    Shared by {{ file.owner.username }}

    +

    {{ file.description }}

    +
  • + {% endfor %} +
+ {% else %} +

没有共享文件。

+ {% endif %} +
+
+ +
+

消息

+
+ {% if messages %} +
    + {% for message in messages %} +
  • +

    {{ message.content }}

    +

    From {{ message.sender.username }}

    + {% if message.file %} +

    + File: + {{ message.file.file.name }} + +

    + {% endif %} +
  • + {% endfor %} +
+ {% else %} +

没有消息。

+ {% endif %} +
+
+
+{% endblock %} diff --git a/main/templates/main/home.html b/main/templates/main/home.html new file mode 100644 index 0000000..0f74bac --- /dev/null +++ b/main/templates/main/home.html @@ -0,0 +1,132 @@ +{% extends 'base.html' %} + +{% block content %} +
+

欢迎使用文件共享系统

+ +
+

公共留言

+
+ {% if public_messages %} +
    + {% for message in public_messages %} +
  • +
    +
    +

    {{ message.name|default:"匿名" }}

    +

    {{ message.content }}

    +

    IP: {{ message.ip_address }}

    + {% if message.file %} + + 下载附件: {{ message.file.name }} + + {% endif %} +
    + {{ message.created_at|date:"Y-m-d H:i" }} +
    +
  • + {% endfor %} +
+ {% else %} +

还没有留言。

+ {% endif %} +
+ +

公共文件

+
+ {% if public_files %} +
    + {% for file in public_files %} +
  • +
    +
    + + {{ file.file.name }} + +

    上传者:{{ file.name|default:"匿名用户" }}

    +

    {{ file.description }}

    +

    IP: {{ file.ip_address }}

    +
    + {{ file.created_at|date:"Y-m-d H:i" }} +
    +
  • + {% endfor %} +
+ {% else %} +

没有可用的公共文件。

+ {% endif %} +
+ +

提交留言

+
+
+ {% csrf_token %} + + +
+ + +
+ +
+ + +
+ + +
+
+ +

提交文件

+
+
+ {% csrf_token %} + + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+
+{% endblock %} diff --git a/main/templates/main/manage_friends.html b/main/templates/main/manage_friends.html new file mode 100644 index 0000000..a9fb8a0 --- /dev/null +++ b/main/templates/main/manage_friends.html @@ -0,0 +1,77 @@ +{% extends 'base.html' %} + +{% block content %} +
+

好友管理

+ +
+ +
+

待处理的好友请求

+ {% if pending_requests %} +
    + {% for request in pending_requests %} +
  • +
    +
    +

    {{ request.from_user.username }}

    +

    {{ request.created_at|date:"Y-m-d H:i" }}

    +
    +
    + {% csrf_token %} +
    + + +
    +
    +
    +
  • + {% endfor %} +
+ {% else %} +

没有待处理的好友请求。

+ {% endif %} +
+ + +
+

好友列表

+ {% if friends %} +
    + {% for friend in friends %} +
  • +
    +
    +

    + {% if friend.from_user == request.user %} + {{ friend.to_user.username }} + {% else %} + {{ friend.from_user.username }} + {% endif %} +

    +

    成为好友时间:{{ friend.created_at|date:"Y-m-d H:i" }}

    +
    +
    + {% csrf_token %} + +
    +
    +
  • + {% endfor %} +
+ {% else %} +

您还没有好友。

+ {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/main/templates/main/search.html b/main/templates/main/search.html new file mode 100644 index 0000000..ea63220 --- /dev/null +++ b/main/templates/main/search.html @@ -0,0 +1,37 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Search Files

+ +
+
+ + +
+
+ + {% if results %} +
+
    + {% for file in results %} +
  • + + {{ file.file.name }} + +

    Uploaded by {{ file.owner.username }}

    +

    {{ file.description }}

    +
  • + {% endfor %} +
+
+ {% elif query %} +

No files found matching "{{ query }}".

+ {% endif %} +
+{% endblock %} diff --git a/main/templates/main/send_friend_request.html b/main/templates/main/send_friend_request.html new file mode 100644 index 0000000..319a11f --- /dev/null +++ b/main/templates/main/send_friend_request.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% block content %} +
+

发送好友请求

+ +
+
+ {% csrf_token %} +
+ {{ form.as_p }} +
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/main/templates/main/send_message.html b/main/templates/main/send_message.html new file mode 100644 index 0000000..1c70776 --- /dev/null +++ b/main/templates/main/send_message.html @@ -0,0 +1,34 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Send Message

+
+ {% csrf_token %} +
+ + {{ form.recipients }} +
+
+ + {{ form.content }} +
+
+ + {{ form.file }} +
+
+ +
+
+
+{% endblock %} diff --git a/main/templates/main/upload.html b/main/templates/main/upload.html new file mode 100644 index 0000000..0f90e1a --- /dev/null +++ b/main/templates/main/upload.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Upload File

+
+ {% csrf_token %} +
+ + {{ form.file }} +
+
+ + {{ form.description }} +
+
+ + {{ form.shared_with }} +
+
+ +

公开文件对所有用户可见

+
+
+ +
+
+
+{% endblock %} diff --git a/main/tests.py b/main/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/main/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/main/urls.py b/main/urls.py new file mode 100644 index 0000000..03dd3b3 --- /dev/null +++ b/main/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name='home'), + path('dashboard/', views.dashboard, name='dashboard'), + path('upload/', views.upload_file, name='upload'), + path('delete//', views.delete_file, name='delete_file'), + path('send-message/', views.send_message, name='send_message'), + path('search/', views.search_files, name='search'), + path('friends/', views.manage_friends, name='manage_friends'), + path('friends/send-request/', views.send_friend_request, name='send_friend_request'), + path('friends/handle-request//', views.handle_friend_request, name='handle_friend_request'), +] diff --git a/main/views.py b/main/views.py new file mode 100644 index 0000000..e5459b0 --- /dev/null +++ b/main/views.py @@ -0,0 +1,176 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth import login +from django.contrib.auth.forms import UserCreationForm +from .forms import CustomUserCreationForm +from django.contrib.auth.decorators import login_required +from .models import File, Message, PublicMessage, PublicFile, Friendship +from .forms import FileUploadForm, MessageForm, PublicMessageForm, PublicFileForm, FriendRequestForm, FriendActionForm + +def home(request): + public_files = PublicFile.objects.all().order_by('-created_at') + public_messages = PublicMessage.objects.all().order_by('-created_at') + + if request.method == 'POST': + if 'submit_message' in request.POST: + form = PublicMessageForm(request.POST) + if form.is_valid(): + message = form.save(commit=False) + message.ip_address = request.META.get('REMOTE_ADDR') + message.save() + return redirect('home') + elif 'submit_file' in request.POST: + file_form = PublicFileForm(request.POST, request.FILES) + if file_form.is_valid(): + public_file = file_form.save(commit=False) + public_file.ip_address = request.META.get('REMOTE_ADDR') + public_file.save() + return redirect('home') + else: + form = PublicMessageForm() + file_form = PublicFileForm() + + return render(request, 'main/home.html', { + 'public_files': public_files, + 'public_messages': public_messages, + 'form': form, + 'file_form': file_form + }) + +@login_required +def dashboard(request): + files = File.objects.filter(owner=request.user) + shared_files = File.objects.filter(shared_with=request.user) + messages = Message.objects.filter(recipients=request.user) + return render(request, 'main/dashboard.html', { + 'files': files, + 'shared_files': shared_files, + 'messages': messages + }) + +@login_required +def upload_file(request): + if request.method == 'POST': + form = FileUploadForm(request.POST, request.FILES) + if form.is_valid(): + file = form.save(commit=False) + file.owner = request.user + + # Handle public file sharing + if form.cleaned_data['is_public']: + file.shared_with.clear() + file.save() + + form.save_m2m() + return redirect('dashboard') + else: + form = FileUploadForm() + return render(request, 'main/upload.html', {'form': form}) + +@login_required +def delete_file(request, file_id): + file = get_object_or_404(File, id=file_id, owner=request.user) + if request.method == 'POST': + file.delete() + return redirect('dashboard') + +@login_required +def send_message(request): + if request.method == 'POST': + form = MessageForm(request.POST) + if form.is_valid(): + message = form.save(commit=False) + message.sender = request.user + message.save() + form.save_m2m() + return redirect('dashboard') + else: + form = MessageForm() + return render(request, 'main/send_message.html', {'form': form}) + +def register(request): + if request.method == 'POST': + form = CustomUserCreationForm(request.POST) + if form.is_valid(): + user = form.save() + login(request, user) + return redirect('dashboard') + else: + # 添加错误提示 + return render(request, 'registration/register.html', { + 'form': form, + 'error': '注册失败,请检查输入信息' + }) + else: + form = CustomUserCreationForm() + return render(request, 'registration/register.html', {'form': form}) + +@login_required +def search_files(request): + query = request.GET.get('q') + results = [] + if query: + results = File.objects.filter( + file__icontains=query, + owner=request.user + ) | File.objects.filter( + file__icontains=query, + shared_with=request.user + ) + return render(request, 'main/search.html', { + 'results': results, + 'query': query + }) + +@login_required +def send_friend_request(request): + if request.method == 'POST': + form = FriendRequestForm(request.POST) + if form.is_valid(): + to_user = form.cleaned_data['username'] + # 检查是否已经存在好友请求 + if not Friendship.objects.filter( + from_user=request.user, + to_user=to_user + ).exists(): + Friendship.objects.create( + from_user=request.user, + to_user=to_user, + status='pending' + ) + return redirect('manage_friends') + else: + form = FriendRequestForm() + return render(request, 'main/send_friend_request.html', {'form': form}) + +@login_required +def manage_friends(request): + # 获取所有待处理的好友请求 + pending_requests = Friendship.objects.filter( + to_user=request.user, + status='pending' + ) + + # 获取已接受的好友列表 + friends = Friendship.objects.filter( + models.Q(from_user=request.user) | models.Q(to_user=request.user), + status='accepted' + ).select_related('from_user', 'to_user') + + return render(request, 'main/manage_friends.html', { + 'pending_requests': pending_requests, + 'friends': friends + }) + +@login_required +def handle_friend_request(request, request_id): + friendship = get_object_or_404(Friendship, id=request_id, to_user=request.user) + if request.method == 'POST': + form = FriendActionForm(request.POST) + if form.is_valid(): + action = form.cleaned_data['action'] + if action == 'accept': + friendship.status = 'accepted' + else: + friendship.status = 'rejected' + friendship.save() + return redirect('manage_friends') diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..b9f0855 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fileshare.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/output.css b/output.css new file mode 100644 index 0000000..c2905a9 --- /dev/null +++ b/output.css @@ -0,0 +1,568 @@ +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +/* +! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden]:where(:not([hidden="until-found"])) { + display: none; +} + +.static { + position: static; +} + +.block { + display: block; +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} diff --git a/public_uploads/AideUserSetup-x64-1.96.2.25003.exe b/public_uploads/AideUserSetup-x64-1.96.2.25003.exe new file mode 100644 index 0000000..9c5f185 Binary files /dev/null and b/public_uploads/AideUserSetup-x64-1.96.2.25003.exe differ diff --git a/static/css/output.css b/static/css/output.css new file mode 100644 index 0000000..dc62942 --- /dev/null +++ b/static/css/output.css @@ -0,0 +1,856 @@ +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +/* +! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden]:where(:not([hidden="until-found"])) { + display: none; +} + +.visible { + visibility: visible; +} + +.static { + position: static; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.w-full { + width: 100%; +} + +.max-w-4xl { + max-width: 56rem; +} + +.max-w-6xl { + max-width: 72rem; +} + +.max-w-md { + max-width: 28rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.items-center { + align-items: center; +} + +.justify-between { + justify-content: space-between; +} + +.space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} + +.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-r-lg { + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +.border { + border-width: 1px; +} + +.bg-blue-500 { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} + +.bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); +} + +.p-6 { + padding: 1.5rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-semibold { + font-weight: 600; +} + +.leading-tight { + line-height: 1.25; +} + +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity, 1)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity, 1)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity, 1)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity, 1)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity, 1)); +} + +.text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity, 1)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.hover\:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1)); +} + +.hover\:bg-green-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(21 128 61 / var(--tw-bg-opacity, 1)); +} + +.hover\:text-blue-700:hover { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity, 1)); +} + +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity, 1)); +} + +.hover\:text-red-700:hover { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity, 1)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..fb115eb --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './templates/**/*.html', + './main/templates/**/*.html', + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/tailwindcss-windows-x64.exe b/tailwindcss-windows-x64.exe new file mode 100644 index 0000000..398de02 Binary files /dev/null and b/tailwindcss-windows-x64.exe differ diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..0c1c8bc --- /dev/null +++ b/templates/base.html @@ -0,0 +1,57 @@ +{% load static %} + + + + + + File Share System + + + + + + {% block content %} + {% endblock %} + + diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 0000000..5e1064a --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% block content %} +
+

登录

+ {% if form.errors %} +
+ 用户名或密码错误,请重试 +
+ {% endif %} +
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ +
+
+
+{% endblock %} diff --git a/templates/registration/logout.html b/templates/registration/logout.html new file mode 100644 index 0000000..38a1ce4 --- /dev/null +++ b/templates/registration/logout.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} + +{% block content %} +
+

您已成功退出

+

正在跳转到首页...

+
+
+ + + + +{% endblock %} \ No newline at end of file diff --git a/templates/registration/register.html b/templates/registration/register.html new file mode 100644 index 0000000..aee7cde --- /dev/null +++ b/templates/registration/register.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} + +{% block content %} +
+

注册

+ {% if error %} +
+ {{ error }} +
+ {% endif %} +
+ {% csrf_token %} +
+ + {{ form.username }} +
+
+ + {{ form.password1 }} +
+
+ + {{ form.password2 }} +
+
+ +
+
+
+{% endblock %}