This commit is contained in:
pacnpal
2024-10-29 01:09:14 -04:00
parent 74a1b730b9
commit 158f25b24b
195 changed files with 5000 additions and 1213 deletions

BIN
.DS_Store vendored

Binary file not shown.

2
.gitignore vendored
View File

@@ -4,4 +4,4 @@
venv
/venv
./venv
venv/
venv/sour

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,85 @@
from django.core.management.base import BaseCommand
from django.db import connection
from django.contrib.auth.hashers import make_password
import uuid
class Command(BaseCommand):
help = 'Reset database and create admin user'
def handle(self, *args, **options):
self.stdout.write('Resetting database...')
# Drop all tables
with connection.cursor() as cursor:
cursor.execute("""
DO $$ DECLARE
r RECORD;
BEGIN
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
END LOOP;
END $$;
""")
# Reset sequences
cursor.execute("""
DO $$ DECLARE
r RECORD;
BEGIN
FOR r IN (SELECT sequencename FROM pg_sequences WHERE schemaname = current_schema()) LOOP
EXECUTE 'ALTER SEQUENCE ' || quote_ident(r.sequencename) || ' RESTART WITH 1';
END LOOP;
END $$;
""")
self.stdout.write('All tables dropped and sequences reset.')
# Run migrations
from django.core.management import call_command
call_command('migrate')
self.stdout.write('Migrations applied.')
# Create superuser using raw SQL
try:
with connection.cursor() as cursor:
# Create user
user_id = str(uuid.uuid4())[:10]
cursor.execute("""
INSERT INTO accounts_user (
username, password, email, is_superuser, is_staff,
is_active, date_joined, user_id, first_name,
last_name, role, is_banned, ban_reason,
theme_preference
) VALUES (
'admin', %s, 'admin@thrillwiki.com', true, true,
true, NOW(), %s, '', '', 'SUPERUSER', false, '',
'light'
) RETURNING id;
""", [make_password('admin'), user_id])
user_db_id = cursor.fetchone()[0]
# Create profile
profile_id = str(uuid.uuid4())[:10]
cursor.execute("""
INSERT INTO accounts_userprofile (
profile_id, display_name, pronouns, bio,
twitter, instagram, youtube, discord,
coaster_credits, dark_ride_credits,
flat_ride_credits, water_ride_credits,
user_id, avatar
) VALUES (
%s, 'Admin', 'they/them', 'ThrillWiki Administrator',
'', '', '', '',
0, 0, 0, 0,
%s, ''
);
""", [profile_id, user_db_id])
self.stdout.write('Superuser created.')
except Exception as e:
self.stdout.write(self.style.ERROR(f'Error creating superuser: {str(e)}'))
raise
self.stdout.write(self.style.SUCCESS('Database reset complete.'))

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-28 20:17
# Generated by Django 5.1.2 on 2024-10-28 21:50
import django.contrib.auth.models
import django.contrib.auth.validators
@@ -13,121 +13,345 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
("auth", "0012_alter_user_first_name_max_length"),
("contenttypes", "0002_remove_content_type_name"),
]
operations = [
migrations.CreateModel(
name='User',
name="User",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('user_id', models.CharField(editable=False, help_text='Unique identifier for this user that remains constant even if the username changes', max_length=10, unique=True)),
('first_name', models.CharField(default='', max_length=150, verbose_name='first name')),
('last_name', models.CharField(default='', max_length=150, verbose_name='last name')),
('role', models.CharField(choices=[('USER', 'User'), ('MODERATOR', 'Moderator'), ('ADMIN', 'Admin'), ('SUPERUSER', 'Superuser')], default='USER', max_length=10)),
('is_banned', models.BooleanField(default=False)),
('ban_reason', models.TextField(blank=True)),
('ban_date', models.DateTimeField(blank=True, null=True)),
('pending_email', models.EmailField(blank=True, max_length=254, null=True)),
('theme_preference', models.CharField(choices=[('light', 'Light'), ('dark', 'Dark')], default='light', max_length=5)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
(
"user_id",
models.CharField(
editable=False,
help_text="Unique identifier for this user that remains constant even if the username changes",
max_length=10,
unique=True,
),
),
(
"first_name",
models.CharField(
default="", max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
default="", max_length=150, verbose_name="last name"
),
),
(
"role",
models.CharField(
choices=[
("USER", "User"),
("MODERATOR", "Moderator"),
("ADMIN", "Admin"),
("SUPERUSER", "Superuser"),
],
default="USER",
max_length=10,
),
),
("is_banned", models.BooleanField(default=False)),
("ban_reason", models.TextField(blank=True)),
("ban_date", models.DateTimeField(blank=True, null=True)),
(
"pending_email",
models.EmailField(blank=True, max_length=254, null=True),
),
(
"theme_preference",
models.CharField(
choices=[("light", "Light"), ("dark", "Dark")],
default="light",
max_length=5,
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
("objects", django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='EmailVerification',
name="EmailVerification",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=64, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('last_sent', models.DateTimeField(auto_now_add=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("token", models.CharField(max_length=64, unique=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("last_sent", models.DateTimeField(auto_now_add=True)),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
'verbose_name': 'Email Verification',
'verbose_name_plural': 'Email Verifications',
"verbose_name": "Email Verification",
"verbose_name_plural": "Email Verifications",
},
),
migrations.CreateModel(
name='PasswordReset',
name="PasswordReset",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=64)),
('created_at', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField()),
('used', models.BooleanField(default=False)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("token", models.CharField(max_length=64)),
("created_at", models.DateTimeField(auto_now_add=True)),
("expires_at", models.DateTimeField()),
("used", models.BooleanField(default=False)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
'verbose_name': 'Password Reset',
'verbose_name_plural': 'Password Resets',
"verbose_name": "Password Reset",
"verbose_name_plural": "Password Resets",
},
),
migrations.CreateModel(
name='TopList',
name="TopList",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('category', models.CharField(choices=[('RC', 'Roller Coaster'), ('DR', 'Dark Ride'), ('FR', 'Flat Ride'), ('WR', 'Water Ride'), ('PK', 'Park')], max_length=2)),
('description', models.TextField(blank=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='top_lists', to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=100)),
(
"category",
models.CharField(
choices=[
("RC", "Roller Coaster"),
("DR", "Dark Ride"),
("FR", "Flat Ride"),
("WR", "Water Ride"),
("PK", "Park"),
],
max_length=2,
),
),
("description", models.TextField(blank=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="top_lists",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
'ordering': ['-updated_at'],
"ordering": ["-updated_at"],
},
),
migrations.CreateModel(
name='UserProfile',
name="UserProfile",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('profile_id', models.CharField(editable=False, help_text='Unique identifier for this profile that remains constant', max_length=10, unique=True)),
('display_name', models.CharField(help_text='This is the name that will be displayed on the site', max_length=50, unique=True)),
('avatar', models.ImageField(blank=True, upload_to='avatars/')),
('pronouns', models.CharField(blank=True, max_length=50)),
('bio', models.TextField(blank=True, max_length=500)),
('twitter', models.URLField(blank=True)),
('instagram', models.URLField(blank=True)),
('youtube', models.URLField(blank=True)),
('discord', models.CharField(blank=True, max_length=100)),
('coaster_credits', models.IntegerField(default=0)),
('dark_ride_credits', models.IntegerField(default=0)),
('flat_ride_credits', models.IntegerField(default=0)),
('water_ride_credits', models.IntegerField(default=0)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"profile_id",
models.CharField(
editable=False,
help_text="Unique identifier for this profile that remains constant",
max_length=10,
unique=True,
),
),
(
"display_name",
models.CharField(
help_text="This is the name that will be displayed on the site",
max_length=50,
unique=True,
),
),
("avatar", models.ImageField(blank=True, upload_to="avatars/")),
("pronouns", models.CharField(blank=True, max_length=50)),
("bio", models.TextField(blank=True, max_length=500)),
("twitter", models.URLField(blank=True)),
("instagram", models.URLField(blank=True)),
("youtube", models.URLField(blank=True)),
("discord", models.CharField(blank=True, max_length=100)),
("coaster_credits", models.IntegerField(default=0)),
("dark_ride_credits", models.IntegerField(default=0)),
("flat_ride_credits", models.IntegerField(default=0)),
("water_ride_credits", models.IntegerField(default=0)),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="profile",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.CreateModel(
name='TopListItem',
name="TopListItem",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('rank', models.PositiveIntegerField()),
('notes', models.TextField(blank=True)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('top_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='accounts.toplist')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("object_id", models.PositiveIntegerField()),
("rank", models.PositiveIntegerField()),
("notes", models.TextField(blank=True)),
(
"content_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
),
),
(
"top_list",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="items",
to="accounts.toplist",
),
),
],
options={
'ordering': ['rank'],
'unique_together': {('top_list', 'rank')},
"ordering": ["rank"],
"unique_together": {("top_list", "rank")},
},
),
]

120
assets/css/src/input.css Normal file
View File

@@ -0,0 +1,120 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply inline-flex items-center px-6 py-2.5 border border-transparent rounded-full shadow-md text-sm font-medium text-white bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary/50 transform hover:scale-105 transition-all;
}
.btn-secondary {
@apply inline-flex items-center px-6 py-2.5 border border-gray-200 dark:border-gray-700 rounded-full shadow-md text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary/50 transform hover:scale-105 transition-all;
}
.nav-link {
@apply flex items-center px-6 py-2.5 rounded-lg font-medium text-gray-700 dark:text-gray-200 hover:bg-primary/10 dark:hover:bg-primary/20 hover:text-primary dark:hover:text-primary transition-all border border-transparent hover:border-primary/20 dark:hover:border-primary/30;
}
.nav-link i {
@apply mr-3 text-lg text-gray-500 transition-colors dark:text-gray-400;
}
.nav-link:hover i {
@apply text-primary;
}
.mobile-nav-link {
@apply flex items-center px-6 py-3 text-gray-700 transition-all border border-transparent rounded-lg dark:text-gray-200 hover:bg-primary/10 dark:hover:bg-primary/20 hover:text-primary dark:hover:text-primary hover:border-primary/20 dark:hover:border-primary/30;
}
.mobile-nav-link i {
@apply mr-3 text-lg text-gray-500 dark:text-gray-400;
}
.menu-item {
@apply flex items-center w-full px-4 py-3 text-sm text-gray-700 transition-all dark:text-gray-200 hover:bg-primary/10 dark:hover:bg-primary/20 hover:text-primary dark:hover:text-primary first:rounded-t-lg last:rounded-b-lg;
}
.menu-item i {
@apply mr-3 text-base text-gray-500 dark:text-gray-400;
}
.form-input {
@apply w-full px-4 py-3 text-gray-900 transition-all border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 bg-white/70 dark:bg-gray-800/70 backdrop-blur-sm dark:text-white focus:ring-2 focus:ring-primary/50 focus:border-primary;
}
.form-label {
@apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5;
}
.form-hint {
@apply mt-2 space-y-1 text-sm text-gray-500 dark:text-gray-400;
}
.form-error {
@apply mt-2 text-sm text-red-600 dark:text-red-400;
}
/* Auth Card Styles */
.auth-card {
@apply w-full max-w-md p-8 mx-auto border shadow-xl bg-white/90 dark:bg-gray-800/90 rounded-2xl backdrop-blur-sm border-gray-200/50 dark:border-gray-700/50;
}
.auth-title {
@apply mb-8 text-2xl font-bold text-center text-transparent bg-gradient-to-r from-primary to-secondary bg-clip-text;
}
.auth-divider {
@apply relative my-6 text-center;
}
.auth-divider::before,
.auth-divider::after {
@apply absolute top-1/2 w-1/3 border-t border-gray-200 dark:border-gray-700 content-[''];
}
.auth-divider::before {
@apply left-0;
}
.auth-divider::after {
@apply right-0;
}
.auth-divider span {
@apply px-4 text-sm text-gray-500 dark:text-gray-400 bg-white/90 dark:bg-gray-800/90;
}
/* Social Login Buttons */
.btn-social {
@apply w-full flex items-center justify-center px-6 py-3 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary/50 transform hover:scale-[1.02] transition-all mb-3;
}
.btn-discord {
@apply bg-[#5865F2] hover:bg-[#4752C4] text-white border-transparent shadow-[#5865F2]/20;
}
.btn-google {
@apply text-gray-700 bg-white border-gray-200 hover:bg-gray-50 shadow-gray-200/50 dark:shadow-gray-900/50;
}
.alert {
@apply p-4 mb-4 shadow-lg rounded-xl backdrop-blur-sm;
}
.alert-success {
@apply text-green-800 border border-green-200 bg-green-100/90 dark:bg-green-800/30 dark:text-green-100 dark:border-green-700;
}
.alert-error {
@apply text-red-800 border border-red-200 bg-red-100/90 dark:bg-red-800/30 dark:text-red-100 dark:border-red-700;
}
.alert-warning {
@apply text-yellow-800 border border-yellow-200 bg-yellow-100/90 dark:bg-yellow-800/30 dark:text-yellow-100 dark:border-yellow-700;
}
.alert-info {
@apply text-blue-800 border border-blue-200 bg-blue-100/90 dark:bg-blue-800/30 dark:text-blue-100 dark:border-blue-700;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -4,14 +4,14 @@ from .models import Company, Manufacturer
@admin.register(Company)
class CompanyAdmin(SimpleHistoryAdmin):
list_display = ('name', 'headquarters', 'website', 'created_at')
list_display = ('id', 'name', 'headquarters', 'website', 'created_at')
search_fields = ('name', 'headquarters', 'description')
prepopulated_fields = {'slug': ('name',)}
readonly_fields = ('created_at', 'updated_at')
@admin.register(Manufacturer)
class ManufacturerAdmin(SimpleHistoryAdmin):
list_display = ('name', 'headquarters', 'website', 'created_at')
list_display = ('id', 'name', 'headquarters', 'website', 'created_at')
search_fields = ('name', 'headquarters', 'description')
prepopulated_fields = {'slug': ('name',)}
readonly_fields = ('created_at', 'updated_at')

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-28 20:25
# Generated manually
from django.db import migrations, models
@@ -31,22 +31,22 @@ class Migration(migrations.Migration):
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='historicalmanufacturer',
model_name='manufacturer',
name='total_rides',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='historicalmanufacturer',
model_name='manufacturer',
name='total_roller_coasters',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='manufacturer',
model_name='historicalmanufacturer',
name='total_rides',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='manufacturer',
model_name='historicalmanufacturer',
name='total_roller_coasters',
field=models.PositiveIntegerField(default=0),
),

View File

@@ -0,0 +1,13 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('companies', '0002_stats_fields'),
]
operations = [
migrations.RemoveField(
model_name='company',
name='total_parks',
),
]

View File

@@ -0,0 +1,14 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('companies', '0003_remove_total_parks'),
]
operations = [
migrations.AddField(
model_name='company',
name='total_parks',
field=models.PositiveIntegerField(default=0),
),
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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