Initial commit
This commit is contained in:
BIN
db.sqlite3
Normal file
BIN
db.sqlite3
Normal file
Binary file not shown.
0
fileshare/__init__.py
Normal file
0
fileshare/__init__.py
Normal file
BIN
fileshare/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
fileshare/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
fileshare/__pycache__/settings.cpython-310.pyc
Normal file
BIN
fileshare/__pycache__/settings.cpython-310.pyc
Normal file
Binary file not shown.
BIN
fileshare/__pycache__/urls.cpython-310.pyc
Normal file
BIN
fileshare/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
fileshare/__pycache__/wsgi.cpython-310.pyc
Normal file
BIN
fileshare/__pycache__/wsgi.cpython-310.pyc
Normal file
Binary file not shown.
16
fileshare/asgi.py
Normal file
16
fileshare/asgi.py
Normal file
@@ -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()
|
||||
139
fileshare/settings.py
Normal file
139
fileshare/settings.py
Normal file
@@ -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'
|
||||
31
fileshare/urls.py
Normal file
31
fileshare/urls.py
Normal file
@@ -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'),
|
||||
])),
|
||||
]
|
||||
16
fileshare/wsgi.py
Normal file
16
fileshare/wsgi.py
Normal file
@@ -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()
|
||||
3
input.css
Normal file
3
input.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
0
main/__init__.py
Normal file
0
main/__init__.py
Normal file
BIN
main/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
main/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/__pycache__/admin.cpython-310.pyc
Normal file
BIN
main/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/__pycache__/apps.cpython-310.pyc
Normal file
BIN
main/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/__pycache__/forms.cpython-310.pyc
Normal file
BIN
main/__pycache__/forms.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/__pycache__/models.cpython-310.pyc
Normal file
BIN
main/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/__pycache__/urls.cpython-310.pyc
Normal file
BIN
main/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/__pycache__/views.cpython-310.pyc
Normal file
BIN
main/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
34
main/admin.py
Normal file
34
main/admin.py
Normal file
@@ -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')
|
||||
6
main/apps.py
Normal file
6
main/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MainConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "main"
|
||||
112
main/forms.py
Normal file
112
main/forms.py
Normal file
@@ -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': '文件描述...'}),
|
||||
}
|
||||
92
main/migrations/0001_initial.py
Normal file
92
main/migrations/0001_initial.py
Normal file
@@ -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"],
|
||||
},
|
||||
),
|
||||
]
|
||||
36
main/migrations/0002_publicmessage.py
Normal file
36
main/migrations/0002_publicmessage.py
Normal file
@@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
35
main/migrations/0003_publicfile_remove_publicmessage_file.py
Normal file
35
main/migrations/0003_publicfile_remove_publicmessage_file.py
Normal file
@@ -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",
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
63
main/migrations/0005_friendship.py
Normal file
63
main/migrations/0005_friendship.py
Normal file
@@ -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")},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
main/migrations/__init__.py
Normal file
0
main/migrations/__init__.py
Normal file
BIN
main/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
BIN
main/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/migrations/__pycache__/0002_publicmessage.cpython-310.pyc
Normal file
BIN
main/migrations/__pycache__/0002_publicmessage.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
main/migrations/__pycache__/0005_friendship.cpython-310.pyc
Normal file
BIN
main/migrations/__pycache__/0005_friendship.cpython-310.pyc
Normal file
Binary file not shown.
BIN
main/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
main/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
63
main/models.py
Normal file
63
main/models.py
Normal file
@@ -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})"
|
||||
89
main/templates/main/dashboard.html
Normal file
89
main/templates/main/dashboard.html
Normal file
@@ -0,0 +1,89 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto py-8">
|
||||
<h1 class="text-3xl font-bold mb-6">您的仪表板</h1>
|
||||
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">您的文件</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
{% if files %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for file in files %}
|
||||
<li class="py-4 flex items-center justify-between">
|
||||
<div>
|
||||
<a href="{{ file.file.url }}" class="text-blue-500 hover:text-blue-700">
|
||||
{{ file.file.name }}
|
||||
</a>
|
||||
<p class="text-gray-600 text-sm">{{ file.description }}</p>
|
||||
</div>
|
||||
<form action="{% url 'delete_file' file.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="text-red-500 hover:text-red-700">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">尚未上传文件。</p>
|
||||
{% endif %}
|
||||
<div class="mt-4 flex space-x-4">
|
||||
<a href="{% url 'upload' %}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||
上传新文件
|
||||
</a>
|
||||
<a href="{% url 'send_message' %}" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
|
||||
发送消息
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">共享文件</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
{% if shared_files %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for file in shared_files %}
|
||||
<li class="py-4">
|
||||
<a href="{{ file.file.url }}" class="text-blue-500 hover:text-blue-700">
|
||||
{{ file.file.name }}
|
||||
</a>
|
||||
<p class="text-gray-600 text-sm">Shared by {{ file.owner.username }}</p>
|
||||
<p class="text-gray-600 text-sm">{{ file.description }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">没有共享文件。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold mb-4">消息</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
{% if messages %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for message in messages %}
|
||||
<li class="py-4">
|
||||
<p class="text-gray-800">{{ message.content }}</p>
|
||||
<p class="text-gray-600 text-sm">From {{ message.sender.username }}</p>
|
||||
{% if message.file %}
|
||||
<p class="text-gray-600 text-sm">
|
||||
File: <a href="{{ message.file.file.url }}" class="text-blue-500 hover:text-blue-700">
|
||||
{{ message.file.file.name }}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">没有消息。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
132
main/templates/main/home.html
Normal file
132
main/templates/main/home.html
Normal file
@@ -0,0 +1,132 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto py-8">
|
||||
<h1 class="text-3xl font-bold mb-6">欢迎使用文件共享系统</h1>
|
||||
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">公共留言</h2>
|
||||
<div class="bg-gray-50 p-6 rounded-lg shadow mb-8">
|
||||
{% if public_messages %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for message in public_messages %}
|
||||
<li class="py-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<p class="font-medium">{{ message.name|default:"匿名" }}</p>
|
||||
<p class="text-gray-600">{{ message.content }}</p>
|
||||
<p class="text-xs text-gray-400 mt-1">IP: {{ message.ip_address }}</p>
|
||||
{% if message.file %}
|
||||
<a href="{{ message.file.url }}" class="text-blue-500 hover:text-blue-700 mt-2 inline-block">
|
||||
下载附件: {{ message.file.name }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">还没有留言。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold mb-4">公共文件</h2>
|
||||
<div class="bg-gray-50 p-6 rounded-lg shadow">
|
||||
{% if public_files %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for file in public_files %}
|
||||
<li class="py-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<a href="{{ file.file.url }}" class="text-blue-500 hover:text-blue-700">
|
||||
{{ file.file.name }}
|
||||
</a>
|
||||
<p class="text-gray-600 text-sm">上传者:{{ file.name|default:"匿名用户" }}</p>
|
||||
<p class="text-gray-600 text-sm">{{ file.description }}</p>
|
||||
<p class="text-xs text-gray-400 mt-1">IP: {{ file.ip_address }}</p>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">{{ file.created_at|date:"Y-m-d H:i" }}</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">没有可用的公共文件。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold mb-4">提交留言</h2>
|
||||
<div class="bg-blue-50 p-6 rounded-lg shadow mb-8">
|
||||
<form method="post" class="space-y-4">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="submit_message" value="1">
|
||||
|
||||
<div>
|
||||
<label for="id_name" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
姓名
|
||||
</label>
|
||||
<input type="text" name="name" id="id_name" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_content" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
留言内容
|
||||
</label>
|
||||
<textarea name="content" id="id_content" rows="4" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors duration-300">
|
||||
提交留言
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold mb-4">提交文件</h2>
|
||||
<div class="bg-blue-50 p-6 rounded-lg shadow mb-8">
|
||||
<form method="post" enctype="multipart/form-data" class="space-y-4">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="submit_file" value="1">
|
||||
|
||||
<div>
|
||||
<label for="id_name" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
姓名
|
||||
</label>
|
||||
<input type="text" name="name" id="id_name" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_description" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
文件描述
|
||||
</label>
|
||||
<textarea name="description" id="id_description" rows="3" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_file" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
选择文件
|
||||
</label>
|
||||
<input type="file" name="file" id="id_file" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500
|
||||
file:mr-4 file:py-2 file:px-4
|
||||
file:rounded-md file:border-0
|
||||
file:text-sm file:font-semibold
|
||||
file:bg-blue-50 file:text-blue-700
|
||||
hover:file:bg-blue-100">
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors duration-300">
|
||||
提交文件
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
77
main/templates/main/manage_friends.html
Normal file
77
main/templates/main/manage_friends.html
Normal file
@@ -0,0 +1,77 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">好友管理</h1>
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- 待处理的好友请求 -->
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">待处理的好友请求</h2>
|
||||
{% if pending_requests %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for request in pending_requests %}
|
||||
<li class="py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="font-medium">{{ request.from_user.username }}</p>
|
||||
<p class="text-sm text-gray-500">{{ request.created_at|date:"Y-m-d H:i" }}</p>
|
||||
</div>
|
||||
<form method="post" action="{% url 'handle_friend_request' request.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="flex space-x-2">
|
||||
<button type="submit" name="action" value="accept"
|
||||
class="bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600">
|
||||
接受
|
||||
</button>
|
||||
<button type="submit" name="action" value="reject"
|
||||
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
|
||||
拒绝
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">没有待处理的好友请求。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 好友列表 -->
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">好友列表</h2>
|
||||
{% if friends %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for friend in friends %}
|
||||
<li class="py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="font-medium">
|
||||
{% if friend.from_user == request.user %}
|
||||
{{ friend.to_user.username }}
|
||||
{% else %}
|
||||
{{ friend.from_user.username }}
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">成为好友时间:{{ friend.created_at|date:"Y-m-d H:i" }}</p>
|
||||
</div>
|
||||
<form method="post" action="{% url 'remove_friend' friend.id %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
|
||||
删除好友
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-gray-600">您还没有好友。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
37
main/templates/main/search.html
Normal file
37
main/templates/main/search.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">Search Files</h1>
|
||||
|
||||
<form method="get" class="mb-8">
|
||||
<div class="flex">
|
||||
<input type="text" name="q" value="{{ query }}"
|
||||
class="flex-1 px-4 py-2 border rounded-l-lg focus:outline-none"
|
||||
placeholder="搜索文件名...">
|
||||
<button type="submit"
|
||||
class="bg-blue-500 hover:bg-blue-700 text-white font-bold px-4 rounded-r-lg">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if results %}
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for file in results %}
|
||||
<li class="py-4">
|
||||
<a href="{{ file.file.url }}" class="text-blue-500 hover:text-blue-700">
|
||||
{{ file.file.name }}
|
||||
</a>
|
||||
<p class="text-gray-600 text-sm">Uploaded by {{ file.owner.username }}</p>
|
||||
<p class="text-gray-600 text-sm">{{ file.description }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% elif query %}
|
||||
<p class="text-gray-600">No files found matching "{{ query }}".</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
20
main/templates/main/send_friend_request.html
Normal file
20
main/templates/main/send_friend_request.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">发送好友请求</h1>
|
||||
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="space-y-4">
|
||||
{{ form.as_p }}
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
||||
发送请求
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
34
main/templates/main/send_message.html
Normal file
34
main/templates/main/send_message.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">Send Message</h1>
|
||||
<form method="post" class="bg-white p-6 rounded-lg shadow">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
收件人
|
||||
</label>
|
||||
{{ form.recipients }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
消息内容
|
||||
</label>
|
||||
{{ form.content }}
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
附加文件(可选)
|
||||
</label>
|
||||
{{ form.file }}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
||||
type="submit">
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
41
main/templates/main/upload.html
Normal file
41
main/templates/main/upload.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">Upload File</h1>
|
||||
<form method="post" enctype="multipart/form-data" class="bg-white p-6 rounded-lg shadow">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
文件
|
||||
</label>
|
||||
{{ form.file }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
描述
|
||||
</label>
|
||||
{{ form.description }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">
|
||||
与特定用户共享(可选)
|
||||
</label>
|
||||
{{ form.shared_with }}
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="flex items-center space-x-2">
|
||||
{{ form.is_public }}
|
||||
<span class="text-gray-700 text-sm">设为公开文件</span>
|
||||
</label>
|
||||
<p class="text-gray-500 text-xs mt-1">公开文件对所有用户可见</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
||||
type="submit">
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
3
main/tests.py
Normal file
3
main/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
14
main/urls.py
Normal file
14
main/urls.py
Normal file
@@ -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/<int:file_id>/', 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/<int:request_id>/', views.handle_friend_request, name='handle_friend_request'),
|
||||
]
|
||||
176
main/views.py
Normal file
176
main/views.py
Normal file
@@ -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')
|
||||
22
manage.py
Normal file
22
manage.py
Normal file
@@ -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()
|
||||
568
output.css
Normal file
568
output.css
Normal file
@@ -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));
|
||||
}
|
||||
BIN
public_uploads/AideUserSetup-x64-1.96.2.25003.exe
Normal file
BIN
public_uploads/AideUserSetup-x64-1.96.2.25003.exe
Normal file
Binary file not shown.
856
static/css/output.css
Normal file
856
static/css/output.css
Normal file
@@ -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;
|
||||
}
|
||||
11
tailwind.config.js
Normal file
11
tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./templates/**/*.html',
|
||||
'./main/templates/**/*.html',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
BIN
tailwindcss-windows-x64.exe
Normal file
BIN
tailwindcss-windows-x64.exe
Normal file
Binary file not shown.
57
templates/base.html
Normal file
57
templates/base.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>File Share System</title>
|
||||
<link href="{% static 'css/output.css' %}" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<nav class="bg-white shadow">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex space-x-4">
|
||||
<div>
|
||||
<a href="{% url 'home' %}" class="flex items-center py-5 px-2 text-gray-700 hover:text-gray-900">
|
||||
首页
|
||||
</a>
|
||||
</div>
|
||||
{% if user.is_authenticated %}
|
||||
<div>
|
||||
<a href="{% url 'search' %}" class="flex items-center py-5 px-2 text-gray-700 hover:text-gray-900">
|
||||
搜索
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'dashboard' %}" class="py-5 px-3 text-gray-700 hover:text-gray-900">仪表盘</a>
|
||||
<a href="{% url 'upload' %}" class="py-5 px-3 text-gray-700 hover:text-gray-900">上传文件</a>
|
||||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'admin:index' %}" class="py-5 px-3 text-gray-700 hover:text-gray-900">管理后台</a>
|
||||
{% endif %}
|
||||
<span class="py-5 px-3 text-gray-700">{{ user.username }}</span>
|
||||
<form action="{% url 'logout' %}" method="post" class="inline" onsubmit="return confirmLogout()">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="py-5 px-3 text-gray-700 hover:text-gray-900">退出</button>
|
||||
</form>
|
||||
<script>
|
||||
function confirmLogout() {
|
||||
return confirm('确定要退出登录吗?');
|
||||
}
|
||||
</script>
|
||||
{% else %}
|
||||
<a href="{% url 'login' %}" class="py-5 px-3 text-gray-700 hover:text-gray-900">登录</a>
|
||||
<a href="{% url 'register' %}" class="py-5 px-3 text-gray-700 hover:text-gray-900">注册</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
35
templates/registration/login.html
Normal file
35
templates/registration/login.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-md mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">登录</h1>
|
||||
{% if form.errors %}
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
用户名或密码错误,请重试
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" class="bg-white p-6 rounded-lg shadow">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="username">
|
||||
用户名
|
||||
</label>
|
||||
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
type="text" name="username" required>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="password">
|
||||
密码
|
||||
</label>
|
||||
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
|
||||
type="password" name="password" required>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
||||
type="submit">
|
||||
登录
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
32
templates/registration/logout.html
Normal file
32
templates/registration/logout.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-md mx-auto py-8 text-center">
|
||||
<h1 class="text-2xl font-bold mb-6">您已成功退出</h1>
|
||||
<p class="mb-6">正在跳转到首页...</p>
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
window.location.href = "{% url 'home' %}";
|
||||
}, 2000);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
39
templates/registration/register.html
Normal file
39
templates/registration/register.html
Normal file
@@ -0,0 +1,39 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-md mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-6">注册</h1>
|
||||
{% if error %}
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" class="bg-white p-6 rounded-lg shadow">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="username">
|
||||
用户名
|
||||
</label>
|
||||
{{ form.username }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="password1">
|
||||
密码
|
||||
</label>
|
||||
{{ form.password1 }}
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2" for="password2">
|
||||
确认密码
|
||||
</label>
|
||||
{{ form.password2 }}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
||||
type="submit">
|
||||
注册
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user