initial commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
__pycache__
|
||||
*.sqlite3
|
||||
media/
|
||||
*.csv
|
||||
.env
|
||||
124
boxofficefantasy.code-workspace
Normal file
124
boxofficefantasy.code-workspace
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"launch": {
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Django Server",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/manage.py",
|
||||
"args": ["runserver"],
|
||||
"django": true,
|
||||
"console": "integratedTerminal",
|
||||
"envFile": "${workspaceFolder}/.env"
|
||||
},
|
||||
{
|
||||
"name": "Launch Chrome",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://127.0.0.1:8000", // adjust based on your local server
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"sourceMaps": true,
|
||||
"trace": true
|
||||
},
|
||||
{
|
||||
"name": "Debug: Import 2014-2019.csv",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/manage.py",
|
||||
"args": ["import_legacy", "./data/2014-2019.csv"],
|
||||
"django": true,
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
],
|
||||
"compounds":[{
|
||||
"name": "Django + Chrome",
|
||||
"configurations": ["Run Django Server", "Launch Chrome"],
|
||||
"type": "compound"
|
||||
}]
|
||||
},
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "🗑️ Delete all Movies",
|
||||
"type": "shell",
|
||||
"command": "${config:python.defaultInterpreterPath}",
|
||||
"args": [
|
||||
"manage.py",
|
||||
"shell",
|
||||
"-c",
|
||||
"from boxofficefantasy.models import Movie; Movie.objects.all().delete()"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Import 2014-2019.csv",
|
||||
"type": "shell",
|
||||
"command": "${config:python.defaultInterpreterPath}",
|
||||
"args": ["manage.py", "import_legacy", "./data/2014-2019.csv"],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "📦 Make Migrations",
|
||||
"type": "shell",
|
||||
"command": "${config:python.defaultInterpreterPath}",
|
||||
"args": ["manage.py", "makemigrations"],
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "🔄 Apply Migrations",
|
||||
"type": "shell",
|
||||
"command": "${config:python.defaultInterpreterPath}",
|
||||
"args": ["manage.py", "migrate"],
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Import ",
|
||||
"type": "shell",
|
||||
"command": "${config:python.defaultInterpreterPath}",
|
||||
"args": ["manage.py", "migrate"],
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "📦 🔄 Make & Apply Migratations",
|
||||
"dependsOn": ["📦 Make Migrations", "🔄 Apply Migrations"],
|
||||
"dependsOrder": "sequence",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"[django-html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.quickSuggestions": {
|
||||
"other": true,
|
||||
"comments": true,
|
||||
"strings": true
|
||||
}
|
||||
},
|
||||
"files.associations": {
|
||||
"*.dj.html": "django-html"
|
||||
},
|
||||
"emmet.includeLanguages": {
|
||||
"django-html": "html"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
boxofficefantasy/__init__.py
Normal file
0
boxofficefantasy/__init__.py
Normal file
11
boxofficefantasy/admin.py
Normal file
11
boxofficefantasy/admin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from .models import League, Season, UserSeasonEntry, Movie, MovieMetric, ScoringRule, Pick
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(League)
|
||||
admin.site.register(Season)
|
||||
admin.site.register(UserSeasonEntry)
|
||||
admin.site.register(Movie)
|
||||
admin.site.register(MovieMetric)
|
||||
admin.site.register(ScoringRule)
|
||||
admin.site.register(Pick)
|
||||
6
boxofficefantasy/apps.py
Normal file
6
boxofficefantasy/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BoxofficefantasyConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'boxofficefantasy'
|
||||
51
boxofficefantasy/integrations/tmdb.py
Normal file
51
boxofficefantasy/integrations/tmdb.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import requests
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
from tmdbv3api import TMDb, Movie
|
||||
|
||||
# Configure TMDb API client
|
||||
tmdb = TMDb()
|
||||
tmdb.api_key = settings.TMDB_API_KEY
|
||||
tmdb.language = "en"
|
||||
|
||||
TMDB_IMAGE_BASE = "https://image.tmdb.org/t/p/w500"
|
||||
|
||||
def get_tmdb_movie_by_imdb(imdb_id):
|
||||
"""
|
||||
Fetch TMDb metadata by IMDb ID, using cache to avoid redundant API calls.
|
||||
"""
|
||||
cache_key = f"tmdb:movie:{imdb_id}"
|
||||
cached = cache.get(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
results = Movie().external(external_id=imdb_id, external_source="imdb_id")
|
||||
if not results:
|
||||
return None
|
||||
|
||||
movie_data = results.movie_results[0]
|
||||
cache.set(cache_key, movie_data, timeout=60 * 60 * 24) # 1 day
|
||||
return movie_data
|
||||
|
||||
|
||||
def cache_tmdb_poster(poster_path):
|
||||
"""
|
||||
Download and cache the TMDb poster locally, returning a local URL.
|
||||
"""
|
||||
if not poster_path:
|
||||
return None
|
||||
|
||||
filename = f"tmdb_posters/{poster_path.strip('/')}"
|
||||
if default_storage.exists(filename):
|
||||
return default_storage.url(filename)
|
||||
|
||||
url = f"{TMDB_IMAGE_BASE}{poster_path}"
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
content = ContentFile(response.content)
|
||||
default_storage.save(filename, content)
|
||||
return default_storage.url(filename)
|
||||
|
||||
return None
|
||||
0
boxofficefantasy/management/__init__.py
Normal file
0
boxofficefantasy/management/__init__.py
Normal file
0
boxofficefantasy/management/commands/__init__.py
Normal file
0
boxofficefantasy/management/commands/__init__.py
Normal file
64
boxofficefantasy/management/commands/import_legacy.py
Normal file
64
boxofficefantasy/management/commands/import_legacy.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import csv
|
||||
from django.core.management.base import BaseCommand
|
||||
from boxofficefantasy.models import Movie, MovieMetric, UserSeasonEntry, Pick, Season
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Import legacy draft/score CSV into boxofficefantasy app"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("csv_path", type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
path = options["csv_path"]
|
||||
with open(path, newline="", encoding="utf-8") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
# 1. Get or create the Movie
|
||||
movie, _ = Movie.objects.get_or_create(
|
||||
title=row["Title"] if row["Title"] != 'NULL' else row["draftFilmTitle"],
|
||||
defaults={
|
||||
"imdb_id": row["imdbID"] if row["imdbID"] != 'NULL' else None,
|
||||
"bom_legacy_id": row["mojoID"] if row["mojoID"] != 'NULL' else None,
|
||||
"bom_id": None,
|
||||
},
|
||||
)
|
||||
|
||||
# 2. Add the MovieStat for domestic gross (if present)
|
||||
if row.get("domesticgross"):
|
||||
MovieMetric.objects.get_or_create(
|
||||
movie=movie,
|
||||
key="domestic_gross",
|
||||
value=float(row["domesticgross"]) if row["domesticgross"] != 'NULL' else 0,
|
||||
)
|
||||
|
||||
# 3. Create the Season (if needed)
|
||||
season, _ = Season.objects.get_or_create(
|
||||
year=row["year"],
|
||||
defaults={"league_id": 1}, # Change if you have multiple leagues
|
||||
)
|
||||
|
||||
# 3.5 Create the User (if needed)
|
||||
user, did_create_new_user = User.objects.get_or_create(
|
||||
username=row["ownerId"], email=f"{row['ownerId']}@not_an_email.com"
|
||||
)
|
||||
if did_create_new_user:
|
||||
user.set_unusable_password()
|
||||
|
||||
# 4. Create the UserSeasonEntry
|
||||
season_entry, _ = UserSeasonEntry.objects.get_or_create(
|
||||
user=user, # Assumes user with that ID exists
|
||||
season=season,
|
||||
defaults={"team_name": row["teamname"]},
|
||||
)
|
||||
|
||||
# # 5. Create the Pick
|
||||
Pick.objects.get_or_create(
|
||||
season_entry=season_entry,
|
||||
season=season,
|
||||
movie=movie,
|
||||
defaults={"bid_amount": float(row["purchaseprice"])},
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Import completed."))
|
||||
99
boxofficefantasy/migrations/0001_initial.py
Normal file
99
boxofficefantasy/migrations/0001_initial.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-18 20:01
|
||||
|
||||
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='Movie',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255)),
|
||||
('imdb_id', models.CharField(max_length=20, unique=True)),
|
||||
('bom_id', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('bom_legacy_id', models.CharField(blank=True, max_length=50, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='League',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('commissioner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Season',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('year', models.IntegerField()),
|
||||
('start_date', models.DateField()),
|
||||
('end_date', models.DateField()),
|
||||
('league', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boxofficefantasy.league')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('league', 'year')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ScoringRule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('formula', models.TextField(help_text="Python expression using keys like 'domestic_gross', 'oscars', 'multiplier'.")),
|
||||
('season', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='boxofficefantasy.season')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserSeasonEntry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('team_name', models.CharField(max_length=100)),
|
||||
('season', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boxofficefantasy.season')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Pick',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('bid_amount', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boxofficefantasy.movie')),
|
||||
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boxofficefantasy.userseasonentry')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MovieMetric',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=100)),
|
||||
('value', models.FloatField()),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boxofficefantasy.movie')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('movie', 'key')},
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='userseasonentry',
|
||||
constraint=models.UniqueConstraint(fields=('season', 'team_name'), name='unique_team_name_per_season'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='userseasonentry',
|
||||
unique_together={('user', 'season')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='pick',
|
||||
unique_together={('entry', 'movie')},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-18 20:11
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxofficefantasy', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='userseasonentry',
|
||||
options={'verbose_name': 'User Season Entry', 'verbose_name_plural': 'User Season Entries'},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-19 00:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxofficefantasy', '0002_alter_userseasonentry_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='season',
|
||||
name='end_date',
|
||||
field=models.DateField(null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='season',
|
||||
name='start_date',
|
||||
field=models.DateField(null=True),
|
||||
),
|
||||
]
|
||||
18
boxofficefantasy/migrations/0004_league_slug.py
Normal file
18
boxofficefantasy/migrations/0004_league_slug.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-19 14:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxofficefantasy', '0003_alter_season_end_date_alter_season_start_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='league',
|
||||
name='slug',
|
||||
field=models.SlugField(blank=True, unique=True),
|
||||
),
|
||||
]
|
||||
19
boxofficefantasy/migrations/0005_season_label.py
Normal file
19
boxofficefantasy/migrations/0005_season_label.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-19 14:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxofficefantasy', '0004_league_slug'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='season',
|
||||
name='label',
|
||||
field=models.CharField(default='Summer', max_length=50),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,52 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-19 15:59
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def set_season_from_season_entry(apps, schema_editor):
|
||||
Pick = apps.get_model("boxofficefantasy", "Pick")
|
||||
|
||||
for pick in Pick.objects.all():
|
||||
pick.season = pick.season_entry.season
|
||||
pick.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("boxofficefantasy", "0005_season_label"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="pick",
|
||||
old_name="entry",
|
||||
new_name="season_entry",
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="pick",
|
||||
unique_together={("season_entry", "movie")},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="pick",
|
||||
name="season",
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="boxofficefantasy.season",
|
||||
null=True,
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(set_season_from_season_entry),
|
||||
migrations.AlterField(
|
||||
model_name="pick",
|
||||
name="season",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="boxofficefantasy.season",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
19
boxofficefantasy/migrations/0007_alter_pick_season.py
Normal file
19
boxofficefantasy/migrations/0007_alter_pick_season.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-19 16:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxofficefantasy', '0006_rename_entry_pick_season_entry_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='pick',
|
||||
name='season',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boxofficefantasy.season'),
|
||||
),
|
||||
]
|
||||
18
boxofficefantasy/migrations/0008_alter_movie_imdb_id.py
Normal file
18
boxofficefantasy/migrations/0008_alter_movie_imdb_id.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-19 16:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxofficefantasy', '0007_alter_pick_season'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='movie',
|
||||
name='imdb_id',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
]
|
||||
0
boxofficefantasy/migrations/__init__.py
Normal file
0
boxofficefantasy/migrations/__init__.py
Normal file
106
boxofficefantasy/models.py
Normal file
106
boxofficefantasy/models.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.text import slugify
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
# Create your models here.
|
||||
class League(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
commissioner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Season(models.Model):
|
||||
league = models.ForeignKey(League, on_delete=models.CASCADE)
|
||||
year = models.IntegerField()
|
||||
start_date = models.DateField(null=True)
|
||||
end_date = models.DateField(null=True)
|
||||
label = models.CharField(max_length=50)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('league', 'year')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.league.name} - {self.year}"
|
||||
|
||||
@property
|
||||
def slug(self):
|
||||
return f"{slugify(self.label)}-{self.year}"
|
||||
|
||||
|
||||
class UserSeasonEntry(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
season = models.ForeignKey(Season, on_delete=models.CASCADE)
|
||||
team_name = models.CharField(max_length=100)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'season')
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['season', 'team_name'], name='unique_team_name_per_season')
|
||||
]
|
||||
verbose_name = "User Season Entry"
|
||||
verbose_name_plural = "User Season Entries"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.team_name} ({self.user.username})"
|
||||
|
||||
|
||||
class Movie(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
imdb_id = models.CharField(max_length=20, blank=True, null=True)
|
||||
bom_id = models.CharField(max_length=50, blank=True, null=True)
|
||||
bom_legacy_id = models.CharField(max_length=50, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def clean(self):
|
||||
for field in ["imdb_id", "bom_id", "bom_legacy_id"]:
|
||||
if getattr(self, field):
|
||||
if Movie.objects.exclude(pk=self.pk).filter(**{field:getattr(self,field)}).exists():
|
||||
raise ValidationError({field: f'{field} must be unique.'})
|
||||
|
||||
|
||||
class Pick(models.Model):
|
||||
season_entry = models.ForeignKey(UserSeasonEntry, on_delete=models.CASCADE)
|
||||
season = models.ForeignKey(Season, on_delete=models.CASCADE)
|
||||
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
|
||||
bid_amount = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
|
||||
|
||||
class Meta:
|
||||
unique_together = ('season_entry', 'movie')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.season_entry} - {self.movie}"
|
||||
|
||||
|
||||
class MovieMetric(models.Model):
|
||||
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=100)
|
||||
value = models.FloatField()
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('movie', 'key')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.movie.title} - {self.key}: {self.value}"
|
||||
|
||||
|
||||
class ScoringRule(models.Model):
|
||||
season = models.OneToOneField(Season, on_delete=models.CASCADE)
|
||||
formula = models.TextField(
|
||||
help_text="Python expression using keys like 'domestic_gross', 'oscars', 'multiplier'."
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"Scoring for {self.season}"
|
||||
34
boxofficefantasy/templates/base.dj.html
Normal file
34
boxofficefantasy/templates/base.dj.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}My Site{% endblock %}</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.datatables.net/2.3.2/css/dataTables.bootstrap5.css"
|
||||
/>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/2.3.2/js/dataTables.js"></script>
|
||||
<script src="https://cdn.datatables.net/2.3.2/js/dataTables.bootstrap5.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="p-3 bg-dark text-white">
|
||||
<div class="container">My Site Header</div>
|
||||
</header>
|
||||
|
||||
<main class="container mt-4">
|
||||
{% block content %}
|
||||
<!-- Default content -->
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="text-muted text-center mt-5">
|
||||
<small>© 2025 MySite</small>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
15
boxofficefantasy/templates/movie.dj.html
Normal file
15
boxofficefantasy/templates/movie.dj.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends "base.dj.html" %} {% load humanize %} {% block title %}Scoreboard –
|
||||
{{ season.label }} {{season.year }}{% endblock %} {% block content %}
|
||||
{{scoreboard|json_script:"scoreboard-data" }}
|
||||
<h1>
|
||||
{{tmdb_data.title}}
|
||||
</h1>
|
||||
<script>
|
||||
{{tmdb_data}}
|
||||
</script>
|
||||
|
||||
|
||||
<div><img src="{{poster_path}}"></div>
|
||||
<div>{{tmdb_data.overview}}</div>
|
||||
|
||||
{%endblock%}
|
||||
34
boxofficefantasy/templates/movies.dj.html
Normal file
34
boxofficefantasy/templates/movies.dj.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends "base.dj.html" %} {% load humanize %} {% block title %}Scoreboard –
|
||||
{{ season.label }} {{season.year }}{% endblock %} {% block content %}
|
||||
{{movies|json_script:"movie_data" }}
|
||||
<h1>
|
||||
{{ season.league.name }} {{ season.label }} {{ season.year }} Movies
|
||||
</h1>
|
||||
<table id="datatable" class="table"></table>
|
||||
<script>
|
||||
window.addEventListener("load", () => {
|
||||
const columns = [
|
||||
{ title: "Title", data: "title" },
|
||||
{ title: "Team", data:"team_name"},
|
||||
{ title: "Score", data: "score",render: function (data, type, row) {
|
||||
// Only format for display type
|
||||
if (type === "display" || type === "filter") {
|
||||
return `$${parseFloat(data).toLocaleString("en-US", {
|
||||
minimumFractionDigits: 0,
|
||||
})}`;
|
||||
}
|
||||
return data;
|
||||
},},
|
||||
];
|
||||
const data = JSON.parse(
|
||||
document.getElementById("movie_data").textContent
|
||||
);
|
||||
const dataTableEl = document.querySelector("#datatable");
|
||||
const dataTable = $("#datatable").DataTable({
|
||||
data,
|
||||
columns,
|
||||
order: [[2, 'desc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{%endblock%}
|
||||
52
boxofficefantasy/templates/scoreboard.dj.html
Normal file
52
boxofficefantasy/templates/scoreboard.dj.html
Normal file
@@ -0,0 +1,52 @@
|
||||
{% extends "base.dj.html" %} {% load humanize %} {% block title %}Scoreboard –
|
||||
{{ season.label }} {{season.year }}{% endblock %} {% block content %}
|
||||
{{scoreboard|json_script:"scoreboard-data" }}
|
||||
<h1>
|
||||
{{ season.league.name }} {{ season.label }} {{ season.year }} Scoreboard
|
||||
</h1>
|
||||
<table id="datatable" class="table"></table>
|
||||
|
||||
{% for entry in scoreboard %}
|
||||
<h3>{{ entry.team }} ({{ entry.user }})</h3>
|
||||
<ul>
|
||||
{% for pick in entry.picks %}
|
||||
<li>
|
||||
<a href="{% url 'season:movie' league.slug season.slug pick.imdb_id %}">{{ pick.movie.title }}</a> – ${{ pick.score|floatformat:0|intcomma }} (Bid:
|
||||
${{pick.bid | floatformat:0}}M)
|
||||
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
<script>
|
||||
window.addEventListener("load", () => {
|
||||
const columns = [
|
||||
{ title: "Team", data: "team" },
|
||||
{ title: "User", data: "user" },
|
||||
{
|
||||
title: "Total Score",
|
||||
data: "total",
|
||||
class: "font-monospace",
|
||||
render: function (data, type, row) {
|
||||
// Only format for display type
|
||||
if (type === "display" || type === "filter") {
|
||||
return `$${parseFloat(data).toLocaleString("en-US", {
|
||||
minimumFractionDigits: 0,
|
||||
})}`;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
];
|
||||
const data = JSON.parse(
|
||||
document.getElementById("scoreboard-data").textContent
|
||||
);
|
||||
const dataTableEl = document.querySelector("#datatable");
|
||||
const dataTable = $("#datatable").DataTable({
|
||||
data,
|
||||
columns,
|
||||
order: [[2, 'desc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{%endblock%}
|
||||
14
boxofficefantasy/templates/seasons.dj.html
Normal file
14
boxofficefantasy/templates/seasons.dj.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "base.dj.html" %} {% load humanize %} {% block content%}
|
||||
<script id="season-data" type="application/json">
|
||||
{{ seasons|json_script:"seasons-data" }}
|
||||
</script>
|
||||
<ul>
|
||||
{% for season in seasons %}
|
||||
<li>
|
||||
<a href="{% url 'season:scoreboard' season.league.slug season.slug %}">
|
||||
{{ season.league.name }} – {{ season.label }} {{ season.year }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{%endblock%}
|
||||
39
boxofficefantasy/templates/teams.dj.html
Normal file
39
boxofficefantasy/templates/teams.dj.html
Normal file
@@ -0,0 +1,39 @@
|
||||
{% extends "base.dj.html" %} {% load humanize %} {% block title %}Scoreboard –
|
||||
{{ season.label }} {{season.year }}{% endblock %} {% block content %}
|
||||
{{movies|json_script:"movie_data" }}
|
||||
<h1>
|
||||
Teams
|
||||
</h1>
|
||||
<table id="datatable" class="table d-none"></table>
|
||||
<ul>
|
||||
{%for entry in user_season_entries%}
|
||||
<li>{{entry.username}} - {{entry.team_name}}</li>
|
||||
{%endfor%}
|
||||
</ul>
|
||||
<script>
|
||||
window.addEventListener("xxx", () => {
|
||||
const columns = [
|
||||
{ title: "Title", data: "title" },
|
||||
{ title: "Team", data:"team_name"},
|
||||
{ title: "Score", data: "score",render: function (data, type, row) {
|
||||
// Only format for display type
|
||||
if (type === "display" || type === "filter") {
|
||||
return `$${parseFloat(data).toLocaleString("en-US", {
|
||||
minimumFractionDigits: 0,
|
||||
})}`;
|
||||
}
|
||||
return data;
|
||||
},},
|
||||
];
|
||||
const data = JSON.parse(
|
||||
document.getElementById("movie_data").textContent
|
||||
);
|
||||
const dataTableEl = document.querySelector("#datatable");
|
||||
const dataTable = $("#datatable").DataTable({
|
||||
data,
|
||||
columns,
|
||||
order: [[2, 'desc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{%endblock%}
|
||||
3
boxofficefantasy/tests.py
Normal file
3
boxofficefantasy/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
29
boxofficefantasy/urls.py
Normal file
29
boxofficefantasy/urls.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
|
||||
league_patterns = [
|
||||
# path("", views.league_view, name="league" ),
|
||||
path("season/", views.season_view, name="seasons"),
|
||||
path("movie/", views.movie_view, name="movies"),
|
||||
path("team/", views.team_view, name="teams"),
|
||||
]
|
||||
|
||||
season_patterns = [
|
||||
path("", views.season_view, name="season"),
|
||||
path("scoreboard/", views.scoreboard_view, name="scoreboard"),
|
||||
path("team/<str:username>/", views.team_view, name="team"),
|
||||
path("team/", views.team_view, name="teams"),
|
||||
path("movie/<str:imdb_id>/", views.movie_view, name="movie"),
|
||||
path("movie/", views.movie_view, name="movies")
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"league/<slug:league_slug>/season/<slug:season_slug>/",
|
||||
include((season_patterns, "boxofficefantasy"), namespace="season")
|
||||
),
|
||||
path(
|
||||
"league/<slug:league_slug>/",
|
||||
include((league_patterns, "boxofficefantasy"), namespace="league")
|
||||
),
|
||||
]
|
||||
221
boxofficefantasy/views.py
Normal file
221
boxofficefantasy/views.py
Normal file
@@ -0,0 +1,221 @@
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http.response import Http404, HttpResponse
|
||||
from boxofficefantasy.models import League, Season, UserSeasonEntry, Movie, Pick
|
||||
from .integrations.tmdb import get_tmdb_movie_by_imdb, cache_tmdb_poster
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
def parse_season_slug(season_slug: str) -> tuple[str, str]:
|
||||
try:
|
||||
label, year = season_slug.rsplit("-", 1)
|
||||
year = int(year)
|
||||
return (label, year)
|
||||
except ValueError:
|
||||
raise Http404("Invalid season format.")
|
||||
|
||||
|
||||
# Create your views here.
|
||||
def scoreboard_view(request, league_slug, season_slug):
|
||||
# season_slug is something like "summer-2025"
|
||||
|
||||
season_label, season_year = parse_season_slug(season_slug)
|
||||
|
||||
league = get_object_or_404(League, slug=league_slug)
|
||||
season = get_object_or_404(
|
||||
Season, league=league, year=season_year, label__iexact=season_label
|
||||
)
|
||||
entries = UserSeasonEntry.objects.filter(season=season).select_related("user")
|
||||
scoreboard = []
|
||||
|
||||
for season_entry in entries:
|
||||
picks = Pick.objects.filter(season_entry=season_entry).select_related("movie")
|
||||
total_score = 0
|
||||
pick_data = []
|
||||
|
||||
for pick in picks:
|
||||
movie = pick.movie
|
||||
metrics = {m.key: m.value for m in movie.moviemetric_set.all()}
|
||||
score = metrics.get("domestic_gross", 0)
|
||||
total_score += score
|
||||
pick_data.append(
|
||||
{
|
||||
"imdb_id": movie.imdb_id,
|
||||
"movie": movie.title,
|
||||
"score": score,
|
||||
"bid": pick.bid_amount,
|
||||
}
|
||||
)
|
||||
pick_data.sort(key=lambda e: e["score"], reverse=True)
|
||||
scoreboard.append(
|
||||
{
|
||||
"team": season_entry.team_name,
|
||||
"user": season_entry.user.username,
|
||||
"total": total_score,
|
||||
"picks": pick_data,
|
||||
}
|
||||
)
|
||||
|
||||
scoreboard.sort(key=lambda e: e["total"], reverse=True)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"scoreboard.dj.html",
|
||||
{
|
||||
"league": league,
|
||||
"season": season,
|
||||
"scoreboard": scoreboard,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def team_view(request, league_slug=None, season_slug=None, username=None):
|
||||
if not league_slug:
|
||||
return HttpResponse("Team View: League not specified", content_type="text/plain")
|
||||
|
||||
league = get_object_or_404(League, slug=league_slug)
|
||||
|
||||
# 1️⃣ League only – all teams across all seasons in the league
|
||||
if not season_slug:
|
||||
entries = UserSeasonEntry.objects.filter(season__league=league).select_related("user", "season")
|
||||
return render(request, "teams.dj.html", {
|
||||
"entries": entries,
|
||||
"league": league,
|
||||
})
|
||||
|
||||
# 2️⃣ League + Season – all teams in that season
|
||||
season_label, season_year = parse_season_slug(season_slug)
|
||||
season = get_object_or_404(Season, league=league, year=season_year, label__iexact=season_label)
|
||||
|
||||
if not username:
|
||||
entries = UserSeasonEntry.objects.filter(season=season).select_related("user")
|
||||
return render(request, "teams.dj.html", {
|
||||
"user_season_entries": [{
|
||||
"name": user_season_entry.user.get_full_name(),
|
||||
"team_name": user_season_entry.team_name,
|
||||
"username": user_season_entry.user.username
|
||||
} for user_season_entry in entries],
|
||||
"league": {'name': league.name},
|
||||
"season": {'label':season.label, 'year':season.year},
|
||||
})
|
||||
|
||||
# 3️⃣ League + Season + Username – one team and its picks
|
||||
user = get_object_or_404(User, username=username)
|
||||
entry = get_object_or_404(UserSeasonEntry, season=season, user=user)
|
||||
|
||||
picks = Pick.objects.filter(season_entry=entry).select_related("movie").prefetch_related("movie__moviemetric_set")
|
||||
movie_data = []
|
||||
for pick in picks:
|
||||
metrics = {m.key: m.value for m in pick.movie.moviemetric_set.all()}
|
||||
movie_data.append({
|
||||
"movie": pick.movie,
|
||||
"bid": pick.bid_amount,
|
||||
"score": metrics.get("domestic_gross", 0)
|
||||
})
|
||||
|
||||
return render(request, "team_detail.dj.html", {
|
||||
"entry": entry,
|
||||
"picks": movie_data,
|
||||
"league": league,
|
||||
"season": season,
|
||||
"user": user,
|
||||
})
|
||||
|
||||
|
||||
def movie_view(request, league_slug=None, season_slug=None, imdb_id=None):
|
||||
if not league_slug:
|
||||
return HttpResponse("Movie View: No league provided", content_type="text/plain")
|
||||
|
||||
# 1️⃣ League only — all movies across seasons
|
||||
if league_slug and not season_slug and not imdb_id:
|
||||
league = get_object_or_404(League, slug=league_slug)
|
||||
picks = Pick.objects.filter(season__league=league)
|
||||
movie_data = [
|
||||
{
|
||||
"title": pick.movie.title,
|
||||
"score": pick.movie.moviemetric_set.filter(key="domestic_gross").first().value if pick.movie else 0,
|
||||
"bid": pick.bid_amount,
|
||||
"team_name": pick.season_entry.team_name,
|
||||
"user": pick.season_entry.user.username,
|
||||
}
|
||||
for pick in picks
|
||||
]
|
||||
return render(request, "movies.dj.html", {"movies": movie_data, "league": league})
|
||||
|
||||
# 2️⃣ League + Season — all movies in that season
|
||||
if league_slug and season_slug and not imdb_id:
|
||||
season_label, season_year = parse_season_slug(season_slug)
|
||||
league = get_object_or_404(League, slug=league_slug)
|
||||
season = get_object_or_404(
|
||||
Season, league=league, year=season_year, label__iexact=season_label
|
||||
)
|
||||
picks = season.pick_set.select_related("movie", "season_entry", "season_entry__user")
|
||||
movie_data = [
|
||||
{
|
||||
"id": pick.movie.bom_legacy_id,
|
||||
"title": pick.movie.title,
|
||||
"score": pick.movie.moviemetric_set.filter(key="domestic_gross").first().value if pick.movie else 0,
|
||||
"bid": pick.bid_amount,
|
||||
"team_name": pick.season_entry.team_name,
|
||||
"user": pick.season_entry.user.username,
|
||||
}
|
||||
for pick in picks
|
||||
]
|
||||
return render(request, "movies.dj.html", {"movies": movie_data, "season": season, "league": league})
|
||||
|
||||
# 3️⃣ League + Season + Movie — show movie details
|
||||
if league_slug and season_slug and imdb_id:
|
||||
season_label, season_year = parse_season_slug(season_slug)
|
||||
league = get_object_or_404(League, slug=league_slug)
|
||||
season = get_object_or_404(Season, league=league, year=season_year, label__iexact=season_label)
|
||||
movie = get_object_or_404(Movie, imdb_id=imdb_id)
|
||||
|
||||
picks = movie.pick_set.filter(season=season).select_related("season_entry", "season_entry__user")
|
||||
metrics = {m.key: m.value for m in movie.moviemetric_set.all()}
|
||||
tmdb_data = get_tmdb_movie_by_imdb(movie.imdb_id)
|
||||
data = {
|
||||
"movie": movie,
|
||||
"tmdb_data": tmdb_data,
|
||||
"poster_path": cache_tmdb_poster(tmdb_data.poster_path),
|
||||
"metrics": metrics,
|
||||
"picks": picks,
|
||||
"season": season,
|
||||
"league": league,
|
||||
}
|
||||
return render(request, "movie.dj.html", data)
|
||||
|
||||
return HttpResponse("Invalid parameter combination.", content_type="text/plain")
|
||||
|
||||
|
||||
def season_view(request, league_slug, season_slug=None):
|
||||
if not league_slug:
|
||||
return HttpResponse("League not specified", content_type="text/plain")
|
||||
|
||||
league = get_object_or_404(League, slug=league_slug)
|
||||
|
||||
# 1️⃣ League only – list all seasons in the league
|
||||
if not season_slug:
|
||||
seasons = [
|
||||
{
|
||||
"label": season.label,
|
||||
"year": season.year,
|
||||
"slug": season.slug,
|
||||
"league": {"name": season.league.name, "slug": season.league.slug},
|
||||
}
|
||||
for season in league.season_set.all()
|
||||
]
|
||||
return render(request, "seasons.dj.html", {
|
||||
"seasons": seasons,
|
||||
"league": league,
|
||||
})
|
||||
|
||||
# 2️⃣ League + season – show a basic detail page or placeholder
|
||||
season_label, season_year = parse_season_slug(season_slug)
|
||||
season = get_object_or_404(
|
||||
Season, league=league, year=season_year, label__iexact=season_label
|
||||
)
|
||||
|
||||
return render(request, "scoreboard.dj.html", {
|
||||
"season": season,
|
||||
"league": league,
|
||||
})
|
||||
0
boxofficefantasy_project/__init__.py
Normal file
0
boxofficefantasy_project/__init__.py
Normal file
16
boxofficefantasy_project/asgi.py
Normal file
16
boxofficefantasy_project/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for boxofficefantasy_project 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', 'boxofficefantasy_project.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
131
boxofficefantasy_project/settings.py
Normal file
131
boxofficefantasy_project/settings.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
Django settings for boxofficefantasy_project 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-_rrxhe5i6uqap!52u(1zi8x$820duvf5s_!9!bc4ghbyyktol0'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
# TMDB API KEY
|
||||
TMDB_API_KEY = os.environ.get("TMDB_API_KEY")
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'boxofficefantasy',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize'
|
||||
]
|
||||
|
||||
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 = 'boxofficefantasy_project.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 = 'boxofficefantasy_project.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 = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
29
boxofficefantasy_project/urls.py
Normal file
29
boxofficefantasy_project/urls.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
URL configuration for boxofficefantasy_project 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.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path("", include("boxofficefantasy.urls"))
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
16
boxofficefantasy_project/wsgi.py
Normal file
16
boxofficefantasy_project/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for boxofficefantasy_project 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', 'boxofficefantasy_project.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
22
manage.py
Executable file
22
manage.py
Executable 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', 'boxofficefantasy_project.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()
|
||||
Reference in New Issue
Block a user