local_settings.py
 db.sqlite3
 db.sqlite3-journal
+staticfiles/
 
 # Flask stuff:
 instance/
 
--- /dev/null
+How to deploy on Debian 11
+==========================
+
+
+Dependencies:
+==========================
+Python
+--------------------------
+python3
+
+Django
+--------------------------
+python3-django
+python3-django-allauth
+python3-psycopg2
+python3-whitenoise
+libjs-jquery
+
+Serving
+--------------------------
+gunicorn
+nginx
+
+SSL
+--------------------------
+certbot
+python3-certbot-nginx
+
+Postgres
+--------------------------
+postgresql
+postgresql-contrib
+
+
+Installation Instructions:
+==========================
+ 1. Install the necessary dependencies
+ 2. Create the directories; change example.com to your domain
+    a. /var/log/gunicorn/
+    b. /var/run/gunicorn/
+    c. /var/www/example.com/
+    d. /usr/local/etc/gunicorn/
+ 3. Copy example.com.service to /etc/systemd/system/ and change the name to your domain
+ 4. Edit the values of example.com.service by changing example.com to your domain
+ 5. Copy example.com.env to /usr/local/etc/gunicorn/ and change the name to your domain
+ 6. Edit the values of example.com.env
+    a. Generate a secret key by running generate_random_key.sh
+ 7. Change the user and group ownership of (2.abd) directories to www-data recursively
+ 8. In postgres create a user with a password and a database owned by the user
+ 9. Export your environment variables and inside core, run `python3 manage.py migrate`
+10. Inside core, run `python3 manage.py createsuperuser`
+11. Inside core, run `python3 manage.py collectstatic`
+12. Copy the contents of core/ into /var/www/example.com/
+13. Copy example.com.nginx to /etc/nginx/sites-available/ and change the name
+14. Edit the file example.com.nginx replacing example.com with your domain
+15. Run certbot to verify your domain
+16. Enable gunicorn `systemctl enable --now example.com.service`
+17. Enable nginx `systemctl enable --now example.com.service`
+
+
+Finalising Deployment:
+==========================
+1. Log into the admin panel of your website and edit / create site values in "Sites"
+2. Create a Social Application.
+3. Create a ResetData object.
 
 
 - Display clip info more clearly
 -- Clip comments?
+
+- Allow users to delete clips they've added during a block
+
+- Add left and right buttons on clips during ranking
+
+- Show a list of all clips during ranking with a button to quickly go to
+
+- A button to quickly go to the specific clip ranking from the results
+
+- Let users view clips uploaded by others and let them see the final page
+
+- Show historical rankings
+
+- If a clip has been ranked, show its ranking during the ranking phase
 
 # Quick-start development settings - unsuitable for production
 # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
 
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = "django-insecure-!nzce=4gwub(n+!6@lh^vjl+m(*ibo42&gav2kvr@d4!$3ausi"
 
 # SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
+DEBUG = os.getenv("CLIP_RANKER_DEBUG", "") != "False"
 
 if DEBUG:
     import dotenv
 
     dotenv.load_dotenv()
 
-ALLOWED_HOSTS = []
 
+ALLOWED_HOSTS = os.getenv("CLIP_RANKER_ALLOWED_HOSTS", "").split(";")
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = os.getenv(
+    "CLIP_RANKER_SECRET_KEY", "04t8gx%o4!^1ae_@%3r@vb^=lx^r$%u*5&+88568$s%w)u1b@f"
+)
 
 # Application definition
 
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    "whitenoise.middleware.WhiteNoiseMiddleware",
 ]
 
 ROOT_URLCONF = "core.urls"
 
 DATABASES = {
     "default": {
-        "ENGINE": "django.db.backends.sqlite3",
-        "NAME": BASE_DIR / "db.sqlite3",
+        "ENGINE": "django.db.backends.postgresql",
+        "NAME": os.getenv("CLIP_RANKER_DB_NAME"),
+        "USER": os.getenv("CLIP_RANKER_DB_USER"),
+        "PASSWORD": os.getenv("CLIP_RANKER_DB_PASS"),
+        "HOST": os.getenv("CLIP_RANKER_DB_HOST"),
+        "PORT": os.getenv("CLIP_RANKER_DB_PORT"),
     }
 }
 
-
 # Password validation
 # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
 
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/4.1/howto/static-files/
 
-STATIC_URL = "static/"
+STATIC_URL = "/static/"
+
 STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
 
+STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
+
 # Default primary key field type
 # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
 
 
-# Generated by Django 4.1.5 on 2023-01-22 12:47
+# Generated by Django 4.1.5 on 2023-01-25 14:32
 
+import datetime
+from django.conf import settings
 from django.db import migrations, models
 import django.db.models.deletion
 
     initial = True
 
     dependencies = [
-        ("socialaccount", "0003_extra_data_default_dict"),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
     operations = [
+        migrations.CreateModel(
+            name="ResetData",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("date_time", models.DateTimeField()),
+                (
+                    "end_date_time",
+                    models.DateTimeField(default=datetime.datetime(2038, 1, 19, 3, 14)),
+                ),
+                ("max_clips", models.IntegerField(default=2)),
+                ("ranks", models.IntegerField(default=5)),
+                ("user_created_clip", models.BooleanField(default=True)),
+                (
+                    "clip_newer_than",
+                    models.DateTimeField(
+                        default=datetime.datetime(1970, 1, 1, 0, 0, 1)
+                    ),
+                ),
+            ],
+        ),
         migrations.CreateModel(
             name="Clip",
             fields=[
                 (
                     "id",
-                    models.CharField(max_length=100, primary_key=True, serialize=False),
+                    models.CharField(
+                        max_length=100, primary_key=True, serialize=False, unique=True
+                    ),
                 ),
                 ("url", models.URLField(null=True)),
                 ("embed_url", models.URLField(null=True)),
                 ("thumbnail_url", models.URLField(null=True)),
                 ("duration", models.FloatField(null=True)),
                 ("vod_offset", models.IntegerField(null=True)),
+                ("date_added", models.DateTimeField(null=True)),
+                ("rank", models.IntegerField(null=True)),
+                (
+                    "account",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="AllowedChannel",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("broadcaster_id", models.CharField(max_length=100)),
                 (
-                    "user",
+                    "reset_data",
                     models.ForeignKey(
                         on_delete=django.db.models.deletion.CASCADE,
-                        to="socialaccount.socialaccount",
+                        to="manager.resetdata",
                     ),
                 ),
             ],
+            options={
+                "unique_together": {("broadcaster_id", "reset_data")},
+            },
         ),
     ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 12:48
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0001_initial"),
-    ]
-
-    operations = [
-        migrations.RenameField(
-            model_name="clip",
-            old_name="user",
-            new_name="account",
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 12:52
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ("manager", "0002_rename_user_clip_account"),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name="clip",
-            name="account",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
-            ),
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 13:10
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0003_alter_clip_account"),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name="clip",
-            name="id",
-            field=models.CharField(
-                max_length=100, primary_key=True, serialize=False, unique=True
-            ),
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 13:30
-
-from django.conf import settings
-import django.contrib.auth.models
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("auth", "0012_alter_user_first_name_max_length"),
-        ("manager", "0004_alter_clip_id"),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name="MyUser",
-            fields=[
-                (
-                    "user_ptr",
-                    models.OneToOneField(
-                        auto_created=True,
-                        on_delete=django.db.models.deletion.CASCADE,
-                        parent_link=True,
-                        primary_key=True,
-                        serialize=False,
-                        to=settings.AUTH_USER_MODEL,
-                    ),
-                ),
-                ("clip_amount", models.IntegerField(default=0)),
-            ],
-            options={
-                "verbose_name": "user",
-                "verbose_name_plural": "users",
-                "abstract": False,
-            },
-            bases=("auth.user",),
-            managers=[
-                ("objects", django.contrib.auth.models.UserManager()),
-            ],
-        ),
-        migrations.AlterField(
-            model_name="clip",
-            name="account",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE, to="manager.myuser"
-            ),
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 13:42
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ("manager", "0005_myuser_alter_clip_account"),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name="clip",
-            name="date_added",
-            field=models.DateTimeField(null=True),
-        ),
-        migrations.AlterField(
-            model_name="clip",
-            name="account",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
-            ),
-        ),
-        migrations.DeleteModel(
-            name="MyUser",
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 13:50
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0006_clip_date_added_alter_clip_account_delete_myuser"),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name="ResetTime",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("date_time", models.DateTimeField()),
-            ],
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 13:58
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0007_resettime"),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name="resettime",
-            name="clip_amount",
-            field=models.IntegerField(default=2),
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 14:00
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0008_resettime_clip_amount"),
-    ]
-
-    operations = [
-        migrations.RenameField(
-            model_name="resettime",
-            old_name="clip_amount",
-            new_name="max_clips",
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 14:02
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0009_rename_clip_amount_resettime_max_clips"),
-    ]
-
-    operations = [
-        migrations.RenameModel(
-            old_name="ResetTime",
-            new_name="ResetData",
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-22 16:24
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0010_rename_resettime_resetdata"),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name="clip",
-            name="rank",
-            field=models.IntegerField(null=True),
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-23 00:22
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0011_clip_rank"),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name="resetdata",
-            name="ranks",
-            field=models.IntegerField(default=5),
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-23 11:51
-
-import datetime
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0012_resetdata_ranks"),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name="resetdata",
-            name="clip_limit_to_channels",
-            field=models.JSONField(null=True),
-        ),
-        migrations.AddField(
-            model_name="resetdata",
-            name="clip_newer_than",
-            field=models.DateTimeField(default=datetime.datetime(1970, 1, 1, 0, 0, 1)),
-        ),
-        migrations.AddField(
-            model_name="resetdata",
-            name="user_created_clip",
-            field=models.BooleanField(default=True),
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-23 12:03
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0013_resetdata_clip_limit_to_channels_and_more"),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name="AllowedChannel",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("broadcaster_id", models.CharField(max_length=100)),
-                (
-                    "reset_data",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        to="manager.resetdata",
-                    ),
-                ),
-            ],
-        ),
-    ]
 
+++ /dev/null
-# Generated by Django 4.1.5 on 2023-01-23 12:14
-
-import datetime
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ("manager", "0014_allowedchannel"),
-    ]
-
-    operations = [
-        migrations.RemoveField(
-            model_name="resetdata",
-            name="clip_limit_to_channels",
-        ),
-        migrations.AddField(
-            model_name="resetdata",
-            name="end_date_time",
-            field=models.DateTimeField(default=datetime.datetime(2038, 1, 19, 3, 14)),
-        ),
-    ]
 
         if not allowed_channels.exists():
             raise ChannelNotAllowedError
 
-    if (
-            reset_data.clip_newer_than
-            > datetime.datetime.strptime(data["created_at"], "%Y-%m-%dT%H:%M:%S%z")
+    if reset_data.clip_newer_than > datetime.datetime.strptime(
+        data["created_at"], "%Y-%m-%dT%H:%M:%S%z"
     ):
         raise ClipTooOldError
 
 
 <div id="group">
     <h1>uploaded by: {{ video.account.username }}</h1>
     <iframe
-            src="https://clips.twitch.tv/embed?clip={{ video.id }}&parent=localhost"
+            src="https://clips.twitch.tv/embed?clip={{ video.id }}{% for site in sites %}&parent={{ site.domain }}{% endfor %}"
             height="600"
             width="1000"
             preload="none"
 
         {% if video.rank == i %}
         <td>
             <iframe
-                    src="https://clips.twitch.tv/embed?clip={{ video.id }}&parent=localhost"
+                    src="https://clips.twitch.tv/embed?clip={{ video.id }}{% for site in sites %}&parent={{ site.domain }}{% endfor %}"
                     height="135"
                     width="240"
                     preload="none"
 
 from django.shortcuts import render, redirect
 from django.core.exceptions import ValidationError
 from django.contrib.admin.views.decorators import staff_member_required
+from django.contrib.sites.models import Site
 
 from .request_clip import request_clip
 from .forms import ClipForm, RankForm
     NotFoundError,
 )
 from .models import Clip, ResetData
-import traceback
 
 
 @staff_member_required
     reset_data = ResetData.objects.latest("date_time")
     reset_time = reset_data.date_time
     try:
-        video = Clip.objects.filter(
-            date_added__gt=reset_time
-        ).order_by("date_added")[id - 1]
+        video = Clip.objects.filter(date_added__gt=reset_time).order_by("date_added")[
+            id - 1
+        ]
     except IndexError:
         return redirect(final_ranking)
     if request.method == "POST":
         context={
             "video": video,
             "ranks": range(reset_data.ranks, 0, -1),
+            "sites": Site.objects.all(),
         },
     )
 
         context={
             "videos": Clip.objects.filter(date_added__gt=reset_time),
             "ranks": range(reset_data.ranks, 0, -1),
+            "sites": Site.objects.all(),
         },
     )
 
 
--- /dev/null
+CLIP_RANKER_DEBUG="False"
+CLIP_RANKER_ALLOWED_HOSTS="example.com;www.example.com"
+CLIP_RANKER_SECRET_KEY="yoursecretkey"
+CLIP_RANKER_STATIC_ROOT="/var/www/example.com"
+CLIP_RANKER_DB_USER="username"
+CLIP_RANKER_DB_PASS="password"
+CLIP_RANKER_DB_HOST="localhost"
+CLIP_RANKER_DB_PORT="5432"
+CLIP_RANKER_DB_NAME="dbname"
 
--- /dev/null
+server {
+    server_name example.com www.example.com;
+
+    location / {
+        include proxy_params;
+        proxy_pass http://unix:/var/run/gunicorn/example.com.sock;
+    }
+
+    location = /favicon.ico {
+        access_log off;
+        log_not_found off;
+    }
+
+    location /static/ {
+        alias /var/www/example.com/staticfiles/;
+    }
+}
 
--- /dev/null
+[Unit]
+Description=example.com gunicorn daemon
+After=network.target
+
+[Service]
+User=www-data
+Group=www-data
+WorkingDirectory=/var/www/example.com/
+EnvironmentFile=/usr/local/etc/gunicorn/example.com.env
+StandardOutput=file:/var/log/gunicorn/example.com.log
+StandardError=file:/var/log/gunicorn/example.com.log
+ExecStart=gunicorn3 --access-logfile - --workers 3 --bind unix:/var/run/gunicorn/example.com.sock core.wsgi:application
+
+[Install]
+WantedBy=multi-user.target
 
--- /dev/null
+#!/bin/sh
+
+python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
 
--- /dev/null
+asgiref==3.6.0
+certifi==2022.12.7
+cffi==1.15.1
+charset-normalizer==3.0.1
+cryptography==39.0.0
+defusedxml==0.7.1
+dj-database-url==1.2.0
+Django==4.1.5
+django-allauth==0.52.0
+gunicorn==20.1.0
+idna==3.4
+oauthlib==3.2.2
+psycopg2==2.9.5
+pycparser==2.21
+PyJWT==2.6.0
+python-dotenv==0.21.0
+python3-openid==3.2.0
+pytz==2022.7.1
+requests==2.28.2
+requests-oauthlib==1.3.1
+sqlparse==0.4.3
+urllib3==1.26.14
+whitenoise==6.3.0