Browse Source

redesigned database and added a charts app

sleepytaco 3 years ago
parent
commit
e2a6996f7b
36 changed files with 2062 additions and 1256 deletions
  1. 2 1
      UnTube/secrets.py
  2. 1 0
      UnTube/settings.py
  3. 1 0
      UnTube/urls.py
  4. 0 0
      apps/charts/__init__.py
  5. 3 0
      apps/charts/admin.py
  6. 6 0
      apps/charts/apps.py
  7. 0 0
      apps/charts/migrations/__init__.py
  8. 3 0
      apps/charts/models.py
  9. 3 0
      apps/charts/tests.py
  10. 6 0
      apps/charts/urls.py
  11. 20 0
      apps/charts/views.py
  12. 110 0
      apps/main/migrations/0026_auto_20210715_0212.py
  13. 18 0
      apps/main/migrations/0027_rename_video_item_id_playlistitem_playlist_video_id.py
  14. 23 0
      apps/main/migrations/0028_auto_20210715_0219.py
  15. 24 0
      apps/main/migrations/0029_auto_20210715_1229.py
  16. 20 0
      apps/main/migrations/0030_alter_tag_created_by.py
  17. 35 0
      apps/main/migrations/0031_auto_20210715_1244.py
  18. 18 0
      apps/main/migrations/0032_rename_channel_title_playlistitem_channel_name.py
  19. 32 0
      apps/main/migrations/0033_auto_20210715_2154.py
  20. 18 0
      apps/main/migrations/0034_rename_added_on_playlistitem_published_at.py
  21. 21 0
      apps/main/migrations/0035_alter_tag_created_by.py
  22. 18 0
      apps/main/migrations/0036_playlist_is_yt_mix.py
  23. 467 319
      apps/main/models.py
  24. 4 0
      apps/main/static/BackgroundCheck/BackgroundCheck.min.js
  25. 316 136
      apps/main/templates/home.html
  26. 13 13
      apps/main/templates/intercooler/search_untube_results.html
  27. 42 42
      apps/main/templates/intercooler/videos.html
  28. 1 1
      apps/main/templates/manage_playlists.html
  29. 628 535
      apps/main/templates/view_playlist.html
  30. 1 3
      apps/main/util.py
  31. 141 155
      apps/main/views.py
  32. 10 3
      apps/users/templates/index.html
  33. 7 1
      apps/users/templates/intercooler/user_playlist_updates.html
  34. 25 25
      apps/users/templates/profile.html
  35. 14 11
      apps/users/views.py
  36. 11 11
      templates/base.html

+ 2 - 1
UnTube/secrets.py

@@ -2,4 +2,5 @@ SECRETS = {"SECRET_KEY": 'django-insecure-ycs22y+20sq67y(6dm6ynqw=dlhg!)%vuqpd@$
            "YOUTUBE_V3_API_KEY": 'AIzaSyCBOucAIJ5PdLeqzTfkTQ_6twsjNaMecS8',
                     "GOOGLE_OAUTH_CLIENT_ID": "901333803283-1lscbdmukcjj3qp0t3relmla63h6l9k6.apps.googleusercontent.com",
            "GOOGLE_OAUTH_CLIENT_SECRET": "ekdBniL-_mAnNPwCmugfIL2q",
-           "GOOGLE_OAUTH_SCOPES": ['https://www.googleapis.com/auth/youtube']}
+           "GOOGLE_OAUTH_SCOPES": ['https://www.googleapis.com/auth/youtube'],
+           "UNSPLASH_API_ACCESS_KEY": "fGf0hsfg24Hj1mUrwvtETl1Dl2-TvaMP3w1moGEb1hs"}

+ 1 - 0
UnTube/settings.py

@@ -51,6 +51,7 @@ INSTALLED_APPS = [
     'crispy_forms',
     'apps.users',  # has stuff related to user management in it (login, signup, show homepage, import)
     'apps.main',  # main app, shows user their homepage
+    'apps.charts'
 ]
 
 CRISPY_TEMPLATE_PACK = 'bootstrap4'

+ 1 - 0
UnTube/urls.py

@@ -20,4 +20,5 @@ urlpatterns = [
     path('admin/', admin.site.urls),
     path('', include("apps.users.urls")),
     path('', include("apps.main.urls")),
+    path('charts/', include("apps.charts.urls"))
 ]

+ 0 - 0
apps/charts/__init__.py


+ 3 - 0
apps/charts/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
apps/charts/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ChartsConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'apps.charts'

+ 0 - 0
apps/charts/migrations/__init__.py


+ 3 - 0
apps/charts/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 3 - 0
apps/charts/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 6 - 0
apps/charts/urls.py

@@ -0,0 +1,6 @@
+from django.urls import path
+from apps.charts import views
+
+urlpatterns = [
+    path('channel-videos-distribution/<slug:playlist_id>', views.channel_videos_distribution, name='channel_videos_distribution'),
+]

+ 20 - 0
apps/charts/views.py

@@ -0,0 +1,20 @@
+from django.db.models import Count, Q
+from django.http import JsonResponse
+
+
+def channel_videos_distribution(request, playlist_id):
+    labels = []
+    data = []
+
+    playlist_items = request.user.playlists.get(playlist_id=playlist_id).playlist_items.all()
+
+    queryset = playlist_items.filter(Q(video__is_unavailable_on_yt=False) & Q(video__was_deleted_on_yt=False)).values('video__channel_name').annotate(channel_videos_count=Count('video_position')).order_by(
+        '-channel_videos_count')
+    for entry in queryset:
+        labels.append(entry['video__channel_name'])
+        data.append(entry['channel_videos_count'])
+
+    return JsonResponse(data={
+        'labels': labels,
+        'data': data,
+    })

+ 110 - 0
apps/main/migrations/0026_auto_20210715_0212.py

@@ -0,0 +1,110 @@
+# Generated by Django 3.2.3 on 2021-07-15 07:12
+
+import datetime
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0009_untube'),
+        ('main', '0025_playlist_last_accessed_on'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Channel',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('channel_id', models.CharField(default='', max_length=420)),
+                ('name', models.CharField(default='', max_length=420)),
+                ('description', models.CharField(default='No description', max_length=420)),
+                ('thumbnail_url', models.CharField(blank=True, max_length=420)),
+                ('published_at', models.DateTimeField(blank=True)),
+                ('view_count', models.IntegerField(default=0)),
+                ('subscriberCount', models.IntegerField(default=0)),
+                ('hidden_subscriber_count', models.BooleanField(null=True)),
+                ('video_ount', models.IntegerField(default=0)),
+                ('is_private', models.BooleanField(null=True)),
+                ('created_at', models.DateTimeField(auto_now_add=True)),
+                ('updated_at', models.DateTimeField(auto_now=True)),
+            ],
+        ),
+        migrations.RemoveField(
+            model_name='playlistitem',
+            name='is_favorite',
+        ),
+        migrations.RemoveField(
+            model_name='playlistitem',
+            name='user_label',
+        ),
+        migrations.RemoveField(
+            model_name='playlistitem',
+            name='user_notes',
+        ),
+        migrations.RemoveField(
+            model_name='playlistitem',
+            name='video_details_modified',
+        ),
+        migrations.RemoveField(
+            model_name='playlistitem',
+            name='video_details_modified_at',
+        ),
+        migrations.RemoveField(
+            model_name='video',
+            name='is_duplicate',
+        ),
+        migrations.RemoveField(
+            model_name='video',
+            name='playlist',
+        ),
+        migrations.RemoveField(
+            model_name='video',
+            name='playlist_item_id',
+        ),
+        migrations.RemoveField(
+            model_name='video',
+            name='video_position',
+        ),
+        migrations.AddField(
+            model_name='playlistitem',
+            name='added_on',
+            field=models.DateTimeField(default=datetime.datetime.now),
+        ),
+        migrations.AddField(
+            model_name='playlistitem',
+            name='channel_id',
+            field=models.CharField(max_length=250, null=True),
+        ),
+        migrations.AddField(
+            model_name='playlistitem',
+            name='channel_title',
+            field=models.CharField(max_length=250, null=True),
+        ),
+        migrations.AddField(
+            model_name='playlistitem',
+            name='video_item_id',
+            field=models.CharField(max_length=100, null=True),
+        ),
+        migrations.AddField(
+            model_name='video',
+            name='untube_user',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='users.profile'),
+        ),
+        migrations.AlterField(
+            model_name='playlist',
+            name='channel_id',
+            field=models.CharField(blank=True, max_length=420),
+        ),
+        migrations.AlterField(
+            model_name='playlist',
+            name='channel_name',
+            field=models.CharField(blank=True, max_length=420),
+        ),
+        migrations.AlterField(
+            model_name='playlistitem',
+            name='video',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='playlist_items', to='main.video'),
+        ),
+    ]

+ 18 - 0
apps/main/migrations/0027_rename_video_item_id_playlistitem_playlist_video_id.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2021-07-15 07:12
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0026_auto_20210715_0212'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='playlistitem',
+            old_name='video_item_id',
+            new_name='playlist_video_id',
+        ),
+    ]

+ 23 - 0
apps/main/migrations/0028_auto_20210715_0219.py

@@ -0,0 +1,23 @@
+# Generated by Django 3.2.3 on 2021-07-15 07:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0027_rename_video_item_id_playlistitem_playlist_video_id'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='video',
+            name='comment_count',
+            field=models.IntegerField(default=0),
+        ),
+        migrations.AddField(
+            model_name='video',
+            name='public_stats_viewable',
+            field=models.BooleanField(default=True),
+        ),
+    ]

+ 24 - 0
apps/main/migrations/0029_auto_20210715_1229.py

@@ -0,0 +1,24 @@
+# Generated by Django 3.2.3 on 2021-07-15 17:29
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0028_auto_20210715_0219'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='playlist',
+            name='videos',
+            field=models.ManyToManyField(related_name='playlists', to='main.Video'),
+        ),
+        migrations.AlterField(
+            model_name='playlistitem',
+            name='video',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.video'),
+        ),
+    ]

+ 20 - 0
apps/main/migrations/0030_alter_tag_created_by.py

@@ -0,0 +1,20 @@
+# Generated by Django 3.2.3 on 2021-07-15 17:32
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0009_untube'),
+        ('main', '0029_auto_20210715_1229'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='tag',
+            name='created_by',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='playlist_tags', to='users.profile'),
+        ),
+    ]

+ 35 - 0
apps/main/migrations/0031_auto_20210715_1244.py

@@ -0,0 +1,35 @@
+# Generated by Django 3.2.3 on 2021-07-15 17:44
+
+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),
+        ('main', '0030_alter_tag_created_by'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='playlistitem',
+            old_name='playlist_video_id',
+            new_name='vid_id',
+        ),
+        migrations.RemoveField(
+            model_name='playlist',
+            name='user',
+        ),
+        migrations.AddField(
+            model_name='playlist',
+            name='untube_user',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='playlists', to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AlterField(
+            model_name='video',
+            name='untube_user',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='videos', to=settings.AUTH_USER_MODEL),
+        ),
+    ]

+ 18 - 0
apps/main/migrations/0032_rename_channel_title_playlistitem_channel_name.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2021-07-15 17:54
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0031_auto_20210715_1244'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='playlistitem',
+            old_name='channel_title',
+            new_name='channel_name',
+        ),
+    ]

+ 32 - 0
apps/main/migrations/0033_auto_20210715_2154.py

@@ -0,0 +1,32 @@
+# Generated by Django 3.2.3 on 2021-07-16 02:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0032_rename_channel_title_playlistitem_channel_name'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='playlistitem',
+            name='vid_id',
+        ),
+        migrations.AddField(
+            model_name='playlist',
+            name='is_pinned',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='video',
+            name='is_pinned',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AlterField(
+            model_name='playlistitem',
+            name='num_of_accesses',
+            field=models.IntegerField(default=0),
+        ),
+    ]

+ 18 - 0
apps/main/migrations/0034_rename_added_on_playlistitem_published_at.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2021-07-16 06:17
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0033_auto_20210715_2154'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='playlistitem',
+            old_name='added_on',
+            new_name='published_at',
+        ),
+    ]

+ 21 - 0
apps/main/migrations/0035_alter_tag_created_by.py

@@ -0,0 +1,21 @@
+# Generated by Django 3.2.3 on 2021-07-16 06:33
+
+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),
+        ('main', '0034_rename_added_on_playlistitem_published_at'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='tag',
+            name='created_by',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='playlist_tags', to=settings.AUTH_USER_MODEL),
+        ),
+    ]

+ 18 - 0
apps/main/migrations/0036_playlist_is_yt_mix.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2021-07-16 06:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0035_alter_tag_created_by'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='playlist',
+            name='is_yt_mix',
+            field=models.BooleanField(default=False),
+        ),
+    ]

File diff suppressed because it is too large
+ 467 - 319
apps/main/models.py


File diff suppressed because it is too large
+ 4 - 0
apps/main/static/BackgroundCheck/BackgroundCheck.min.js


+ 316 - 136
apps/main/templates/home.html

@@ -1,62 +1,82 @@
 
 {% extends 'base.html' %}
+{% load humanize %}
 {% block content %}
+    <br>
+    {% if user.playlists.all.count == 0 %}
+        <div class="alert alert-success" role="alert">
+            <h4 class="alert-heading">It's empty in here</h4>
+            <p>
+                There's no playlists in your UnTube right now. You can change that by heading over to <a href="{% url 'manage_playlists' %}" class="btn btn-sm btn-primary">Manage</a> to import some public playlists into your UnTube.
+                {% if not user.profile.imported_yt_playlists %}
+                    Or you could always head over to your <a href="{% url 'profile' %}" class="btn btn-sm btn-primary">Profile</a> to import all of your public/private YouTube playlists.
+                {% else %}
+                    Keep in mind that your own YouTube playlists will automatically be imported into UnTube.
+                {% endif %}
+            </p>
+        </div>
+    {% endif %}
+    {% if import_successful %}
         <br>
-        {% if user.profile.playlists.all.count == 0 %}
-            <div class="alert alert-success" role="alert">
-              <h4 class="alert-heading">It's empty in here</h4>
-              <p>
-                  There's no playlists in your UnTube right now. You can change that by heading over to <a href="{% url 'manage_playlists' %}" class="btn btn-sm btn-primary">Manage</a> to import some public playlists into your UnTube.
-                  {% if not user.profile.imported_yt_playlists %}
-                      Or you could always head over to your <a href="{% url 'profile' %}" class="btn btn-sm btn-primary">Profile</a> to import all of your public/private YouTube playlists.
-                  {% else %}
-                      Keep in mind that your own YouTube playlists will automatically be imported into UnTube.
-                  {% endif %}
-              </p>
-            </div>
-        {% endif %}
-        {% if import_successful %}
-            <br>
-                <br>
-                <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
-                    <h1>Welcome to UnTube, {{ user.username|capfirst }}</h1>
-                </div>
-                  <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
-                            <h2>{{ user.profile.playlists.all.count }} playlists from YouTube have been successfully imported.</h2>
-                  </div>
-
-                  <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
-                        <h3>You'll now be notified on the Dashboard whenever there's any new un-exported playlists on YouTube :)</h3>
-                  </div>
-
+        <br>
+        <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
+            <h1>Welcome to UnTube, {{ user.username|capfirst }}</h1>
+        </div>
+        <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
+            <h2>{{ user.playlists.all.count }} playlists from YouTube have been successfully imported.</h2>
+        </div>
 
-                <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
-                    <a href="{% url 'home' %}" class="btn btn-lg btn-success">Go to Dashboard</a>
-                </div>
+        <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
+            <h3>You'll now be notified on the Dashboard whenever there's any new un-exported playlists on YouTube :)</h3>
+        </div>
 
-            {% else %}
 
-            <!--
-            <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+        <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
+            <a href="{% url 'home' %}" class="btn btn-lg btn-success">Go to Dashboard</a>
+        </div>
 
-                <h1 class="h2">Dashboard</h1>
+    {% else %}
 
-                <span><small>Logged in as <b>{{ user.username }}</b></small></span>
+        {% if user.profile.imported_yt_playlists %}
+            <div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
 
             </div>
-            -->
+        {% endif %}
 
-            {% if user.profile.imported_yt_playlists %}
-            <div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
+        {% if watching %}
+            <div class="border border-5 rounded-3 border-primary p-3">
+                <h3><span style="border-bottom: 3px #ffffff dashed;">Continue Watching</span><i class="fas fa-fire-alt ms-2" style="color: #d24646"></i></h3>
+                <div class="row row-cols-1 row-cols-md-4 g-4 text-dark mt-0" data-masonry='{"percentPosition": true }'>
+                    {% for playlist in watching %}
+                        <div class="col">
 
-            </div>
-            {% endif %}
+                            <div class="card" style="background-color: #EFEFEF;">
+                                <img  class="bd-placeholder-img card-img-top" src="{{ playlist.thumbnail_url }}" style="max-width:100%; height: 200px;   object-fit: cover;" alt="{{ playlist.name }} thumbnail">
+
+                                <div class="card-body">
+                                    <h5 class="card-title"><a href="{% url 'playlist' playlist.playlist_id %}" class="stretched-link" style="text-decoration: none; color: black">{{ playlist.name }}</a></h5>
+                                    <p class="card-text">
+                                        <span class="badge bg-{% if playlist.get_watch_time_left == "0secs." %}success{% else %}primary{% endif %} text-white">{{ playlist.get_watched_videos_count }}/{{ playlist.get_watchable_videos_count }} viewed</span>
+                                        {% if playlist.get_watch_time_left != "0secs." %}<span class="badge bg-dark text-white">{{ playlist.get_watch_time_left }} left</span>{% endif %}
+                                    </p>
+                                    <p class="card-text">
+                                        {% if playlist.tags.all %}
+                                            <small>
+                                                <i class="fas fa-tags fa-sm" style="color: black"></i>
+                                                {% for tag in playlist.tags.all %}
+                                                    <span class="badge rounded-pill bg-primary mb-lg-1">
+                                                        {{ tag.name }}
+                                                    </span>
+                                                {% endfor %}
+                                            </small>
+                                        {% endif %}
+                                    </p>
+                                    <p class="card-text"><small class="text-muted">Last watched {{ playlist.last_watched|naturalday }}</small></p>
+                                </div>
+                            </div>
+                        </div>
 
-            {% if watching %}
-                <div class="border border-5 rounded-3 border-primary p-3">
-                    <h3><span style="border-bottom: 3px #ffffff dashed;">Continue Watching</span><i class="fas fa-fire-alt ms-2" style="color: #d24646"></i></h3>
-           <div class="row row-cols-1 row-cols-md-4 g-4 text-dark mt-0">
-                {% for playlist in watching|slice:"0:3" %}
+                        <!--
                 <div class="col">
                     <div class="card">
                         <a style="background-color: #7e89c2;" href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item list-group-item-action" aria-current="true">
@@ -84,7 +104,8 @@
                         </div>
                         </a>
                     </div>
-                </div>
+                </div> -->
+                        <!--
                     {% if forloop.counter == 3 %}
                         {% if watching.count|add:"-3" != 0 %}
                         <div class="col">
@@ -101,55 +122,211 @@
                         </div>
                         {% endif %}
                     {% endif %}
-                {% endfor %}
+                    -->
+                    {% endfor %}
+                </div>
             </div>
+            <br>
+        {% endif %}
+
+
+        <div class="row" data-masonry='{"percentPosition": true }'>
+            {% for playlist in user_playlists|slice:"0:3" %}
+                <div class="col-sm-6 col-lg-4 mb-4">
+                    <div class="card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" style="">
+                        <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1">
+                            <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold"><a href="{% url 'playlist' playlist.playlist_id %}" class="stretched-link" style="text-decoration: none; color: #fafafa">{{ playlist.name }}</a> </h2>
+                            <ul class="d-flex list-unstyled mt-auto">
+                                <li class="me-auto">
+                                    <img src="{{ playlist.thumbnail_url }}" alt="Bootstrap" width="32" height="32" class="rounded-circle border border-white">
+                                </li>
+                                <li class="d-flex align-items-center me-3">
+                                    <small>by {{ playlist.channel_name }}</small>
+                                </li>
+                                <li class="d-flex align-items-center">
+                                    <i class="fas fa-eye me-1"></i>
+                                    <small>{{ playlist.num_of_accesses }} clicks</small>
+
+                                </li>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+            {% endfor %}
+
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card">
+                    <svg class="bd-placeholder-img card-img-top" width="100%" height="200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Image cap" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#868e96"/><text x="50%" y="50%" fill="#dee2e6" dy=".3em">Image cap</text></svg>
+
+                    <div class="card-body">
+                        <h5 class="card-title">Card title that wraps to a new line</h5>
+                        <p class="card-text">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
+                    </div>
+                </div>
+            </div>
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card p-3">
+                    <figure class="p-3 mb-0">
+                        <blockquote class="blockquote">
+                            <p>A well-known quote, contained in a blockquote element.</p>
+                        </blockquote>
+                        <figcaption class="blockquote-footer mb-0 text-muted">
+                            Someone famous in <cite title="Source Title">Source Title</cite>
+                        </figcaption>
+                    </figure>
+                </div>
             </div>
-                <br>
-            {% endif %}
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card">
+                    <svg class="bd-placeholder-img card-img-top" width="100%" height="200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Image cap" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#868e96"/><text x="50%" y="50%" fill="#dee2e6" dy=".3em">Image cap</text></svg>
+
+                    <div class="card-body">
+                        <h5 class="card-title">Card title</h5>
+                        <p class="card-text">This card has supporting text below as a natural lead-in to additional content.</p>
+                        <p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
+                    </div>
+                </div>
+            </div>
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card bg-dark text-white">
+                    <div class="card-body">
+                        <h6 class="d-flex align-items-center mb-3"><span class="text-info me-2">{{ user.playlists.count }}</span>Playlists Statistics</h6>
+
+                        <small>Public <span class="text-warning ms-1">{{ statistics.public_x }}%</span></small>
+                        <div class="progress mb-3" style="height: 5px">
+                            <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.public_x }}%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
+                        </div>
 
+                        <small>Private <span class="text-warning ms-1">{{ statistics.private_x }}%</span></small>
+                        <div class="progress mb-3" style="height: 5px">
+                            <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.private_x }}%" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100"></div>
+                        </div>
+                        <small>Favorites <span class="text-warning ms-1">{{ statistics.favorites_x }}%</span></small>
+                        <div class="progress mb-3" style="height: 5px">
+                            <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.favorites_x }}%" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100"></div>
+                        </div>
+                        <small>Watching <span class="text-warning ms-1">{{ statistics.watching_x }}%</span></small>
+                        <div class="progress mb-3" style="height: 5px">
+                            <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.watching_x }}%" aria-valuenow="55" aria-valuemin="0" aria-valuemax="100"></div>
+                        </div>
+                        <small>Imported <span class="text-warning ms-1">{{ statistics.imported_x }}%</span></small>
+                        <div class="progress mb-3" style="height: 5px">
+                            <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.imported_x }}%" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card text-center">
+                    <div class="card-body">
+                        <h5 class="card-title">Card title</h5>
+                        <p class="card-text">This card has a regular title and short paragraph of text below it.</p>
+                        <p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
+                    </div>
+                </div>
+            </div>
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card">
+                    <svg class="bd-placeholder-img card-img" width="100%" height="260" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Card image" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#868e96"/><text x="50%" y="50%" fill="#dee2e6" dy=".3em">Card image</text></svg>
 
+                </div>
+            </div>
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card p-3 text-end">
+                    <figure class="mb-0">
+                        <blockquote class="blockquote">
+                            <p>A well-known quote, contained in a blockquote element.</p>
+                        </blockquote>
+                        <figcaption class="blockquote-footer mb-0 text-muted">
+                            Someone famous in <cite title="Source Title">Source Title</cite>
+                        </figcaption>
+                    </figure>
+                </div>
+            </div>
+            <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card">
+                    <div class="card-body">
+                        <h5 class="card-title">Card title</h5>
+                        <p class="card-text">This is another card with title and supporting text below. This card has some additional content to make it slightly taller overall.</p>
+                        <p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
+                    </div>
+                </div>
+            </div>
+        </div>
 
 
 
-           <div class="row text-dark mt-0 align-items-center">
-                <div class="col">
-                    <div class="row">
-                        <div class="col">
-                            <div class="card" style="background: linear-gradient(-45deg, #aaae6b, #7b8eab, #8abc97, #e666f5); background-size: 400% 400%; animation: gradient 7s ease infinite;">
-                                <a href="#" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
-                                    <div class="card-body">
-                                        <div class="d-flex justify-content-center h1">
-                                            <i class="fas fa-map-pin" style="color:#c22929"></i>
-                                        </div>
-                                        <div class="d-flex justify-content-center h1">
-                                            YOUR
-                                        </div>
-                                        <div class="d-flex justify-content-center h1">
-                                            PINS
-                                        </div>
-                                    </div>
-                                </a>
+        <div class="container" id="custom-cards">
+            <h2 class="pb-2 border-bottom">Custom cards</h2>
+
+            <div class="row row-cols-1 row-cols-lg-3 align-items-stretch g-4 py-5">
+                {% for playlist in user_playlists|slice:"0:3" %}
+
+                    <div class="col">
+                        <div class="card card-cover h-100 overflow-hidden text-dark bg-dark rounded-5 shadow-lg" style="background-image: url('{% if playlist.videos.count != 0 %}{{ playlist.thumbnail_url }}{% endif %}'); background-position: center">
+
+                            <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1">
+                                <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold"><a href="{% url 'playlist' playlist.playlist_id %}" class="stretched-link" style="text-decoration: none; color: #100f0f">{{ playlist.name }}</a> </h2>
+                                <ul class="d-flex list-unstyled mt-auto">
+                                    <li class="me-auto">
+                                        <img src="{{ playlist.thumbnail_url }}" alt="Bootstrap" width="32" height="32" class="rounded-circle border border-white">
+                                    </li>
+
+                                    <li class="d-flex align-items-center me-3 text-dark">
+                                        <small>by {{ playlist.channel_name }}</small>
+                                    </li>
+                                    <li class="d-flex align-items-center text-dark">
+                                        <i class="fas fa-eye me-1"></i>
+                                        <small>{{ playlist.num_of_accesses }}</small>
+                                    </li>
+                                </ul>
                             </div>
                         </div>
-                        <div class="col">
-                            <div class="card" style="background: linear-gradient(-45deg, #0645a4, #2480cd, #84bcf3, #b7d6f7); background-size: 400% 400%; animation: gradient 7s ease infinite;">
-                                <a href="#" class="list-group-item list-group-item-action bg-transparent" aria-current="true">
-                                    <div class="card-body">
-                                        <div class="d-flex justify-content-center h1">
-                                            <i class="fas fa-heart" style="color: indianred"></i>
-                                        </div>
-                                        <div class="d-flex justify-content-center h1">
-                                            LIKED
-                                        </div>
-                                        <div class="d-flex justify-content-center h1">
-                                            VIDEOS
-                                        </div>
+                    </div>
+                {% endfor %}
+            </div>
+        </div>
+
+
+        <div class="row text-dark mt-0 align-items-center">
+            <div class="col">
+                <div class="row">
+                    <div class="col">
+                        <div class="card" style="background: linear-gradient(-45deg, #aaae6b, #7b8eab, #8abc97, #e666f5); background-size: 400% 400%; animation: gradient 7s ease infinite;">
+                            <a href="#" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
+                                <div class="card-body">
+                                    <div class="d-flex justify-content-center h1">
+                                        <i class="fas fa-map-pin" style="color:#c22929"></i>
                                     </div>
-                                </a>
-                            </div>
+                                    <div class="d-flex justify-content-center h1">
+                                        YOUR
+                                    </div>
+                                    <div class="d-flex justify-content-center h1">
+                                        PINS
+                                    </div>
+                                </div>
+                            </a>
                         </div>
                     </div>
-                    <!-- Implement later
+                    <div class="col">
+                        <div class="card" style="background: linear-gradient(-45deg, #0645a4, #2480cd, #84bcf3, #b7d6f7); background-size: 400% 400%; animation: gradient 7s ease infinite;">
+                            <a href="#" class="list-group-item list-group-item-action bg-transparent" aria-current="true">
+                                <div class="card-body">
+                                    <div class="d-flex justify-content-center h1">
+                                        <i class="fas fa-heart" style="color: indianred"></i>
+                                    </div>
+                                    <div class="d-flex justify-content-center h1">
+                                        LIKED
+                                    </div>
+                                    <div class="d-flex justify-content-center h1">
+                                        VIDEOS
+                                    </div>
+                                </div>
+                            </a>
+                        </div>
+                    </div>
+                </div>
+                <!-- Implement later
                     <div class="row mt-3">
                         <div class="col">
 
@@ -176,8 +353,9 @@
                         </div>
                     </div>
                     -->
-                </div>
-               <div class="col-8">
+            </div>
+            <div class="col-8">
+                <!--
                   <div class="card bg-transparent text-black border border-5 rounded-3 border-success p-3">
                         <div class="card-body">
                               <h3><span style="border-bottom: 3px #ffffff dashed;">Most viewed playlists</span> <a href="{% url 'all_playlists' 'all' %}" class="pt-1"><i class="fas fa-binoculars"></i> </a></h3>
@@ -215,84 +393,86 @@
 
                         </div>
                       </div>
-                </div>
+                -->
+
             </div>
+        </div>
 
-            <br>
+        <br>
 
-           <div class="row text-dark mt-0 d-flex justify-content-evenly">
-               <div class="col">
+        <div class="row text-dark mt-0 d-flex justify-content-evenly">
+            <div class="col">
 
                 <h3><span style="border-bottom: 3px #ffffff dashed;">Recently Added</span> <i class="fas fa-plus-square" style="color:#972727;"></i></h3>
                 {% if recently_added_playlists %}
                     <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
                         {% for playlist in recently_added_playlists %}
-                        <div class="col">
-                            <div class="card overflow-auto" style="background-color: #958a44;">
-                                <a href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
-                                    <div class="card-body">
-                                <h5 class="card-title">
-                                   {{ playlist.name }}
-                                    {% if playlist.is_private_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
-                                    {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-dark">YT</span></small> {% endif %}
-                                </h5>
-                                <small>
-                                    <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
-                                    <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
-                                    <span class="badge bg-secondary rounded-pill">{{ playlist.num_of_accesses }} clicks </span>
-
-                                </small>
+                            <div class="col">
+                                <div class="card overflow-auto" style="background-color: #958a44;">
+                                    <a href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
+                                        <div class="card-body">
+                                            <h5 class="card-title">
+                                                {{ playlist.name }}
+                                                {% if playlist.is_private_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
+                                                {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-dark">YT</span></small> {% endif %}
+                                            </h5>
+                                            <small>
+                                                <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
+                                                <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
+                                                <span class="badge bg-secondary rounded-pill">{{ playlist.num_of_accesses }} clicks </span>
+
+                                            </small>
+                                        </div>
+                                    </a>
                                 </div>
-                                </a>
                             </div>
-                        </div>
                         {% endfor %}
                     </div>
-                     {% else %}
+                {% else %}
                     <br>
                     <h5>You have no playlists ;-;</h5>
-                    {% endif %}
-               </div>
+                {% endif %}
+            </div>
 
-                <div class="col">
-                    <h3><span style="border-bottom: 3px #ffffff dashed;">Recently Accessed</span> <i class="fas fa-redo fa-sm" style="color: #3c3fd2"></i></h3>
+            <div class="col">
+                <h3><span style="border-bottom: 3px #ffffff dashed;">Recently Accessed</span> <i class="fas fa-redo fa-sm" style="color: #3c3fd2"></i></h3>
 
-                    {% if recently_accessed_playlists %}
+                {% if recently_accessed_playlists %}
                     <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
                         {% for playlist in recently_accessed_playlists %}
-                        <div class="col">
-                            <div class="card overflow-auto" style="background-color: #357779;">
-                                <a  href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
-                                    <div class="card-body">
-                                <h5 class="card-title">
-                                   {{ playlist.name }}
-                                    {% if playlist.is_private_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
-                                    {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-dark">YT</span></small> {% endif %}
-                                </h5>
-                                <small>
-                                    <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
-                                    <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
-                                    <span class="badge bg-secondary rounded-pill">{{ playlist.num_of_accesses }} clicks </span>
-
-                                </small>
+                            <div class="col">
+                                <div class="card overflow-auto" style="background-color: #357779;">
+                                    <a  href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
+                                        <div class="card-body">
+                                            <h5 class="card-title">
+                                                {{ playlist.name }}
+                                                {% if playlist.is_private_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
+                                                {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-dark">YT</span></small> {% endif %}
+                                            </h5>
+                                            <small>
+                                                <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
+                                                <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
+                                                <span class="badge bg-secondary rounded-pill">{{ playlist.num_of_accesses }} clicks </span>
+
+                                            </small>
+                                        </div>
+                                    </a>
                                 </div>
-                                </a>
                             </div>
-                        </div>
                         {% endfor %}
                     </div>
-                    {% else %}
+                {% else %}
                     <br>
                     <h5>Nothing to see here... yet.</h5>
-                    {% endif %}
+                {% endif %}
 
-                </div>
             </div>
+        </div>
 
 
-            <br>
-            <br>
-        {% endif %}
+        <br>
+        <br>
+    {% endif %}
 
 
-{% endblock %}
+{% endblock %}

+ 13 - 13
apps/main/templates/intercooler/search_untube_results.html

@@ -15,35 +15,35 @@
 
         <div>
     <div class="row row-cols-1 row-cols-md-3 g-4">
-        {% if videos %}
-        {% for video in videos %}
+        {% if playlist_items %}
+        {% for playlist_item in playlist_items %}
         <div class="col">
             <div class="card" style="background-color: #1A4464;">
 
             <div class="card-body">
                 <h5 class="card-title text-light">
-                    {{ video.name|truncatewords:"15" }}<br>
+                    {{ playlist_item.video.name|truncatewords:"15" }}<br>
 
                     <small>
-                        <a class="badge bg-white text-black-50" href="{% url 'playlist' video.playlist.playlist_id %}">{{ video.playlist.name }}</a>
+                        <a class="badge bg-white text-black-50" href="{% url 'playlist' playlist_item.playlist.playlist_id %}">{{ playlist_item.playlist.name }}</a>
 
-                        <span class="badge bg-dark text-white-50">{{ video.duration }}</span>
+                        <span class="badge bg-dark text-white-50">{{ playlist_item.video.duration }}</span>
                     </small>
-                    {% if video.is_unavailable_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
-                    {% if video.has_cc %}<small><span class="badge bg-danger text-dark">CC</span></small> {% endif %}
+                    {% if playlist_item.video.is_unavailable_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
+                    {% if playlist_item.video.has_cc %}<small><span class="badge bg-danger text-dark">CC</span></small> {% endif %}
                 </h5>
                                                 <br>
 
                 <span class="d-flex justify-content-center">
-                    <a href="https://www.youtube.com/watch?v={{ video.video_id }}" class="btn btn-info me-1" target="_blank" id="share_link" style=""><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
-                    <input class="form-control me-1 visually-hidden" id="video-{{ video.video_id }}" value="https://www.youtube.com/watch?v={{ video.video_id }}">
-                    <button class="copy-btn btn btn-success  me-1" data-clipboard-target="#video-{{ video.video_id }}">
+                    <a href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}" class="btn btn-info me-1" target="_blank" id="share_link" style=""><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
+                    <input class="form-control me-1 visually-hidden" id="video-{{ video.video_id }}" value="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}">
+                    <button class="copy-btn btn btn-success  me-1" data-clipboard-target="#video-{{ playlist_item.video.video_id }}">
                         <i class="far fa-copy" aria-hidden="true"></i>
                     </button>
-                    <button class="btn btn-dark" type="button" hx-get="{% url 'mark_video_favorite' video.playlist.playlist_id video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
+                    <button class="btn btn-dark" type="button" hx-get="{% url 'mark_video_favorite' playlist_item.playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
                         <div id="video-{{ forloop.counter }}-fav">
-                            {% if video.is_favorite %}
-                                <i class="fas fa-heart"></i>
+                            {% if playlist_item.video.is_favorite %}
+                                <i class="fas fa-heart" style="color: #fafa06"></i>
                             {% else %}
                                 <i class="far fa-heart"></i>
                             {% endif %}

+ 42 - 42
apps/main/templates/intercooler/videos.html

@@ -1,26 +1,26 @@
 {% load humanize %}
-{% if videos_details and videos %}
-    <h4 class="mt-3 mb-3"><span class="badge bg-dark text-white">{{ videos_details }}, Results: {{ videos.count }}</span></h4>
+{% if videos_details and playlist_items %}
+    <h4 class="mt-3 mb-3"><span class="badge bg-dark text-white">{{ videos_details }}, Results: {{ playlist_items.count }}</span></h4>
 {% endif %}
 <div class="list-group" id="video-checkboxes">
-    {% if videos %}
-      {% for video in videos|slice:"0:50" %}
-                <li {% if forloop.last and videos.count > 50 %}hx-get="{% url 'load_more_videos' playlist.playlist_id order_by|default:"all" page|default:"1" %}"
+    {% if playlist_items %}
+      {% for playlist_item in playlist_items|slice:"0:50" %}
+                <li {% if forloop.last and playlist_items.count > 50 %}hx-get="{% url 'load_more_videos' playlist.playlist_id order_by|default:"all" page|default:"1" %}"
     hx-trigger="revealed"
     hx-swap="afterend" hx-indicator="#load-more-videos-spinner"{% endif %} class="list-group-item d-flex justify-content-between align-items-center bg-transparent" style="background-color: #40B3A2">
 
-            {% if video.is_unavailable_on_yt and not video.was_deleted_on_yt %}
+            {% if playlist_item.video.is_unavailable_on_yt and not playlist_item.video.was_deleted_on_yt %}
 
                 <div class="d-flex justify-content-between align-items-center">
                     <div>
-                        <input class="video-checkboxes" style="display: none" type="checkbox" value="{{ video.video_id }}" name="video-id">
+                        <input class="video-checkboxes" style="display: none" type="checkbox" value="{{ playlist_item.video.video_id }}" name="video-id">
                     </div>
                       <div class="ms-4" style="max-width: 115px; max-height: 100px;">
                           <img src="https://i.ytimg.com/vi/9219YrnwDXE/maxresdefault.jpg" class="img-fluid" alt="">
                       </div>
                         <div class="ms-4">
-                            <a class="link-dark" href="https://www.youtube.com/watch?v={{ video.video_id }}&list={{ video.playlist.playlist_id }}" target="_blank">
-                                {{ video.video_position }}. {{ video.name }}
+                            <a class="link-dark" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" target="_blank">
+                                {{ playlist_item.video_position }}. {{ playlist_item.video.name }}
                             </a>
                             <br><br>
                         </div>
@@ -29,34 +29,34 @@
 
                 <div class="d-flex justify-content-between align-items-center" >
                     <div>
-                        <input class="video-checkboxes" style="display: none" type="checkbox" value="{{ video.video_id }}" name="video-id">
+                        <input class="video-checkboxes" style="display: none" type="checkbox" value="{{ playlist_item.video.video_id }}" name="video-id">
                     </div>
                       <div class="ms-4" style="max-width: 115px; max-height: 100px;">
-                          <img src="{% if video.thumbnail_url %}{{ video.thumbnail_url }}{% else %}https://i.ytimg.com/vi/9219YrnwDXE/maxresdefault.jpg{% endif %}" class="img-fluid" alt="">
+                          <img src="{% if playlist_item.video.thumbnail_url %}{{ playlist_item.video.thumbnail_url }}{% else %}https://i.ytimg.com/vi/9219YrnwDXE/maxresdefault.jpg{% endif %}" class="img-fluid" alt="">
                       </div>
                         <div class="ms-4">
 
-                        {% if video.is_unavailable_on_yt and video.was_deleted_on_yt %}
-                            {{ video.video_position }}.
-                            <a class="link-dark" href="https://www.youtube.com/watch?v={{ video.video_id }}&list={{ video.playlist.playlist_id }}" target="_blank">
-                                {{ video.name|truncatewords:"16" }}
+                        {% if playlist_item.video.is_unavailable_on_yt or playlist_item.video.was_deleted_on_yt %}
+                            {{ playlist_item.video_position }}.
+                            <a class="link-dark" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" target="_blank">
+                                {{ playlist_item.video.name|truncatewords:"16" }}
                             </a>
                             <br>
                             <span class="badge bg-dark">VIDEO UNAVAILABLE</span>
-                            {% if video.video_details_modified %}<span class="badge bg-danger">{% if video.was_deleted_on_yt %}WENT PRIVATE/DELETED{% else %}ADDED{% endif %} {{ video.updated_at|naturaltime|upper }}</span>{% endif %}
+                            {% if playlist_item.video.video_details_modified %}<span class="badge bg-danger">{% if playlist_item.video.was_deleted_on_yt %}WENT PRIVATE/DELETED{% else %}ADDED{% endif %} {{ playlist_item.video.updated_at|naturaltime|upper }}</span>{% endif %}
                             <br><br>
                         {% else %}
-                            {{ video.video_position }}.
-                            <a class="link-dark" href="https://www.youtube.com/watch?v={{ video.video_id }}&list={{ video.playlist.playlist_id }}" target="_blank">
-                                {{ video.name|truncatewords:"16" }}
-                            </a> by {{ video.channel_name }} <br>
-                            <span class="badge bg-secondary">{{ video.duration }}</span>
-                            {% if video.has_cc %}<span class="badge bg-secondary">CC</span>{% endif %}
-                            {% if video.published_at %}<span class="badge bg-secondary">{{ video.published_at }}</span>{% endif %}
-                          {% if video.view_count %}<span class="badge bg-info">{{ video.view_count|intword|intcomma }} views</span>{% endif %}
+                            {{ playlist_item.video_position }}.
+                            <a class="link-dark" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" target="_blank">
+                                {{ playlist_item.video.name|truncatewords:"16" }}
+                            </a> by {{ playlist_item.video.channel_name }} <br>
+                            <span class="badge bg-secondary">{{ playlist_item.video.duration }}</span>
+                            {% if playlist_item.video.has_cc %}<span class="badge bg-secondary">CC</span>{% endif %}
+                            {% if playlist_item.video.published_at %}<span class="badge bg-secondary">{{ playlist_item.video.published_at }}</span>{% endif %}
+                          {% if playlist_item.video.view_count %}<span class="badge bg-info">{{ playlist_item.video.view_count|intword|intcomma }} views</span>{% endif %}
 
-                            {% if video.is_duplicate %}<span class="badge bg-primary">duplicate</span>{% endif %}
-                            {% if video.video_details_modified %}<span class="badge bg-danger">{% if video.was_deleted_on_yt %}WENT PRIVATE/DELETED{% else %}ADDED{% endif %} {{ video.created_at|naturaltime|upper }}</span>{% endif %}<br>
+                            {% if playlist_item.video.is_duplicate %}<span class="badge bg-primary">duplicate</span>{% endif %}
+                            {% if playlist_item.video.video_details_modified %}<span class="badge bg-danger">{% if playlist_item.video.was_deleted_on_yt %}WENT PRIVATE/DELETED{% else %}ADDED{% endif %} {{ playlist_item.video.created_at|naturaltime|upper }}</span>{% endif %}<br>
                             <br>
                         {% endif %}
 
@@ -65,12 +65,12 @@
 
                 </div>
                 <div class="ms-5">
-                {% if video.is_unavailable_on_yt and video.was_deleted_on_yt %}
-                <button class="btn btn-sm  btn-warning mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasForVideoNotes" aria-controls="offcanvasBottom" hx-get="{% url 'video_notes' playlist.playlist_id video.video_id %}" hx-trigger="click" hx-target="#video-notes">Notes</button>
-                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist.playlist_id video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
+                {% if playlist_item.video.is_unavailable_on_yt or playlist_item.video.was_deleted_on_yt %}
+                <button class="btn btn-sm  btn-warning mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasForVideoNotes" aria-controls="offcanvasBottom" hx-get="{% url 'video_notes' playlist.playlist_id playlist_item.video.video_id %}" hx-trigger="click" hx-target="#video-notes">Notes</button>
+                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
                     <div id="video-{{ forloop.counter }}-fav">
-                        {% if video.is_favorite %}
-                            <i class="fas fa-heart"></i>
+                        {% if playlist_item.video.is_favorite %}
+                            <i class="fas fa-heart" style="color: #fafa06"></i>
                         {% else %}
                             <i class="far fa-heart"></i>
                         {% endif %}
@@ -78,26 +78,26 @@
                 </button>
                 {% else %}
 
-                <a class="btn btn-sm btn-info mb-1" type="button" href="https://www.youtube.com/watch?v={{ video.video_id }}" class="btn btn-info me-1" target="_blank"><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
-                <input class="form-control me-1 visually-hidden" id="video-{{ video.video_id }}" value="https://www.youtube.com/watch?v={{ video.video_id }}">
-                <button class="copy-btn btn btn-sm  btn-success mb-1" data-clipboard-target="#video-{{ video.video_id }}">
+                <a class="btn btn-sm btn-info mb-1" type="button" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}" class="btn btn-info me-1" target="_blank"><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
+                <input class="form-control me-1 visually-hidden" id="video-{{ playlist_item.video.video_id }}" value="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}">
+                <button class="copy-btn btn btn-sm  btn-success mb-1" data-clipboard-target="#video-{{ playlist_item.video.video_id }}">
                     <i class="far fa-copy" aria-hidden="true"></i>
                 </button>
-                <button class="btn btn-sm  btn-primary mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasWithBackdrop" aria-controls="offcanvasBottom" hx-get="{% url 'video_details' playlist.playlist_id video.video_id %}" hx-trigger="click" hx-target="#video-details"><i class="fas fa-info"></i></button>
-                <button class="btn btn-sm  btn-warning mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasForVideoNotes" aria-controls="offcanvasBottom" hx-get="{% url 'video_notes' playlist.playlist_id video.video_id %}" hx-trigger="click" hx-target="#video-notes">Notes</button>
-                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist.playlist_id video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
+                <button class="btn btn-sm  btn-primary mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasWithBackdrop" aria-controls="offcanvasBottom" hx-get="{% url 'video_details' playlist.playlist_id playlist_item.video.video_id %}" hx-trigger="click" hx-target="#video-details"><i class="fas fa-info"></i></button>
+                <button class="btn btn-sm  btn-warning mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasForVideoNotes" aria-controls="offcanvasBottom" hx-get="{% url 'video_notes' playlist.playlist_id playlist_item.video.video_id %}" hx-trigger="click" hx-target="#video-notes">Notes</button>
+                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
                     <div id="video-{{ forloop.counter }}-fav">
-                        {% if video.is_favorite %}
-                            <i class="fas fa-heart"></i>
+                        {% if playlist_item.video.is_favorite %}
+                            <i class="fas fa-heart" style="color: #fafa06"></i>
                         {% else %}
                             <i class="far fa-heart"></i>
                         {% endif %}
                     </div>
                 </button>
-                    {% if playlist.marked_as == "watching" %}
-                    <button class="btn btn-sm btn-light mb-1" type="button" hx-get="{% url 'mark_video_watched' playlist.playlist_id video.video_id %}" hx-target="#video-{{ forloop.counter }}-watched">
+                    {% if playlist.marked_as == "watching" and not playlist_item.is_duplicate %}
+                    <button class="btn btn-sm btn-light mb-1" type="button" hx-get="{% url 'mark_video_watched' playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-watched">
                         <div id="video-{{ forloop.counter }}-watched">
-                            {% if video.is_marked_as_watched %}
+                            {% if playlist_item.video.is_marked_as_watched %}
                                 <i class="fas fa-check-circle"></i>
                             {% else %}
                                 <i class="far fa-check-circle"></i>

+ 1 - 1
apps/main/templates/manage_playlists.html

@@ -7,7 +7,7 @@
 
     </div>
     {% endif %}
-<div class="row row-cols-1 row-cols-md-3 g-4" >
+<div class="row row-cols-1 row-cols-md-3 g-4" data-masonry='{"percentPosition": true }'>
     <div id="manage-import-playlists-btn" class="col">
         <a  href="{% url 'manage_view_page' 'import' %}" class="text-decoration-none text-white">
         <div class="card" style="background-color: #25641a;">

File diff suppressed because it is too large
+ 628 - 535
apps/main/templates/view_playlist.html


+ 1 - 3
apps/main/util.py

@@ -2,11 +2,9 @@ import datetime
 import humanize
 import re
 
+
 # given amount of seconds makes it into this 54 MINUTES AND 53 SECONDS (from humanize) then
 # perform a bunch of replace operations to make it look like 54mins. 53secs.
-from django.db.models import Q
-
-
 def getHumanizedTimeString(seconds):
     return humanize.precisedelta(
         datetime.timedelta(seconds=seconds)).upper(). \

+ 141 - 155
apps/main/views.py

@@ -1,12 +1,9 @@
 import datetime
 import random
-
-import humanize
 import pytz
 from django.db.models import Q
-from django.http import HttpResponse, HttpResponseRedirect
+from django.http import HttpResponse
 from django.shortcuts import render, redirect, get_object_or_404
-
 import apps
 from apps.main.models import Playlist, Tag
 from django.contrib.auth.decorators import login_required  # redirects user to settings.LOGIN_URL
@@ -20,17 +17,15 @@ from .util import *
 # Create your views here.
 @login_required
 def home(request):
-    user_profile = request.user.profile
-    user_playlists = user_profile.playlists.filter(Q(is_in_db=True) & Q(num_of_accesses__gt=0)).order_by(
-        "-num_of_accesses")
+    user_profile = request.user
     watching = user_profile.playlists.filter(Q(marked_as="watching") & Q(is_in_db=True)).order_by("-num_of_accesses")
     recently_accessed_playlists = user_profile.playlists.filter(is_in_db=True).filter(
-        updated_at__gt=user_profile.updated_at).order_by("-updated_at")[:6]
+        updated_at__gt=user_profile.profile.updated_at).order_by("-updated_at")[:6]
     recently_added_playlists = user_profile.playlists.filter(is_in_db=True).order_by("-created_at")[:6]
 
     #### FOR NEWLY JOINED USERS ######
     channel_found = True
-    if user_profile.show_import_page:
+    if user_profile.profile.show_import_page:
         """
         Logic:
         show_import_page is True by default. When a user logs in for the first time (infact anytime), google 
@@ -41,16 +36,17 @@ def home(request):
         """
         # user_profile.show_import_page = False
 
-        if user_profile.access_token.strip() == "" or user_profile.refresh_token.strip() == "":
+        if user_profile.profile.access_token.strip() == "" or user_profile.profile.refresh_token.strip() == "":
             user_social_token = SocialToken.objects.get(account__user=request.user)
-            user_profile.access_token = user_social_token.token
-            user_profile.refresh_token = user_social_token.token_secret
-            user_profile.expires_at = user_social_token.expires_at
-            request.user.save()
-
-        if user_profile.imported_yt_playlists:
-            user_profile.show_import_page = False  # after user imports all their YT playlists no need to show_import_page again
-            user_profile.save(update_fields=['show_import_page'])
+            user_profile.profile.access_token = user_social_token.token
+            user_profile.profile.refresh_token = user_social_token.token_secret
+            user_profile.profile.expires_at = user_social_token.expires_at
+            user_profile.save()
+            Playlist.objects.getUserYTChannelID(user_profile)
+
+        if user_profile.profile.imported_yt_playlists:
+            user_profile.profile.show_import_page = False  # after user imports all their YT playlists no need to show_import_page again
+            user_profile.profile.save(update_fields=['show_import_page'])
             return render(request, "home.html", {"import_successful": True})
 
         return render(request, "import_in_progress.html")
@@ -62,23 +58,10 @@ def home(request):
         #  return render(request, "home.html", {"import_successful": True})
     ##################################
 
-    if request.method == "POST":
-        print(request.POST)
-        if Playlist.objects.initPlaylist(request.user, request.POST['playlist-id'].strip()) == -1:
-            print("No such playlist found.")
-            playlist = []
-            videos = []
-        else:
-            playlist = user_profile.playlists.get(playlist_id__exact=request.POST['playlist-id'].strip())
-            videos = playlist.videos.all()
-    else:  # GET request
-        videos = []
-        playlist = []
-
-        print("TESTING")
-
-    user_playlists = request.user.profile.playlists.filter(is_in_db=True)
+    user_playlists = request.user.playlists.filter(is_in_db=True)
     total_num_playlists = user_playlists.count()
+    user_playlists = user_playlists.filter(num_of_accesses__gt=0).order_by(
+        "-num_of_accesses")
 
     statistics = {
         "public_x": 0,
@@ -97,8 +80,6 @@ def home(request):
         statistics["imported_x"] = round(user_playlists.filter(is_user_owned=False).count() / total_num_playlists, 1) * 100
 
     return render(request, 'home.html', {"channel_found": channel_found,
-                                         "playlist": playlist,
-                                         "videos": videos,
                                          "user_playlists": user_playlists,
                                          "watching": watching,
                                          "recently_accessed_playlists": recently_accessed_playlists,
@@ -108,14 +89,14 @@ def home(request):
 
 @login_required
 def view_video(request, playlist_id, video_id):
-    video = request.user.profile.playlists.get(playlist_id=playlist_id).videos.get(video_id=video_id)
+    video = request.user.playlists.get(playlist_id=playlist_id).videos.get(video_id=video_id)
     print(video.name)
     return HttpResponse(loader.get_template("intercooler/video_details.html").render({"video": video}))
 
 
 @login_required
 def video_notes(request, playlist_id, video_id):
-    video = request.user.profile.playlists.get(playlist_id=playlist_id).videos.get(video_id=video_id)
+    video = request.user.playlists.get(playlist_id=playlist_id).videos.get(video_id=video_id)
 
     if request.method == "POST":
         if 'video-notes-text-area' in request.POST:
@@ -132,11 +113,11 @@ def video_notes(request, playlist_id, video_id):
 
 @login_required
 def view_playlist(request, playlist_id):
-    user_profile = request.user.profile
+    user_profile = request.user
     user_owned_playlists = user_profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
 
     # specific playlist requested
-    if user_profile.playlists.filter(Q(playlist_id=playlist_id) & Q(is_in_db=True)).count() != 0:
+    if user_profile.playlists.filter(Q(playlist_id=playlist_id) & Q(is_in_db=True)).exists():
         playlist = user_profile.playlists.get(playlist_id__exact=playlist_id)
         # playlist.num_of_accesses += 1
         # only note down that the playlist as been viewed when 5mins has passed since the last access
@@ -157,11 +138,11 @@ def view_playlist(request, playlist_id):
                 video.video_details_modified = False
                 video.save()
 
-        if recently_updated_videos.count() == 0:
+        if not recently_updated_videos.exists():
             playlist.has_new_updates = False
             playlist.save()
 
-    videos = playlist.videos.order_by("video_position")
+    playlist_items = playlist.playlist_items.order_by("video_position")
 
     user_created_tags = Tag.objects.filter(created_by=request.user)
     playlist_tags = playlist.tags.all()
@@ -172,14 +153,10 @@ def view_playlist(request, playlist_id):
 
     unused_tags = user_created_tags.difference(playlist_tags)
 
-    all_videos_unavailable = False
-    if playlist.videos.filter(Q(is_unavailable_on_yt=True) | Q(was_deleted_on_yt=True)).count() == playlist.videos.all().count():
-        all_videos_unavailable = True
-
     return render(request, 'view_playlist.html', {"playlist": playlist,
                                                   "playlist_tags": playlist_tags,
                                                   "unused_tags": unused_tags,
-                                                  "videos": videos,
+                                                  "playlist_items": playlist_items,
                                                   "user_owned_playlists": user_owned_playlists,
                                                   "watching_message": generateWatchingMessage(playlist),
                                                   })
@@ -202,19 +179,19 @@ def all_playlists(request, playlist_type):
     playlist_type = playlist_type.lower()
     watching = False
     if playlist_type == "" or playlist_type == "all":
-        playlists = request.user.profile.playlists.all().filter(is_in_db=True)
+        playlists = request.user.playlists.all().filter(is_in_db=True)
         playlist_type_display = "All Playlists"
     elif playlist_type == "user-owned":  # YT playlists owned by user
-        playlists = request.user.profile.playlists.all().filter(Q(is_user_owned=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_user_owned=True) & Q(is_in_db=True))
         playlist_type_display = "Your YouTube Playlists"
     elif playlist_type == "imported":  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(Q(is_user_owned=False) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_user_owned=False) & Q(is_in_db=True))
         playlist_type_display = "Imported playlists"
     elif playlist_type == "favorites":  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
         playlist_type_display = "Favorites"
     elif playlist_type.lower() in ["watching", "plan-to-watch"]:
-        playlists = request.user.profile.playlists.filter(Q(marked_as=playlist_type.lower()) & Q(is_in_db=True))
+        playlists = request.user.playlists.filter(Q(marked_as=playlist_type.lower()) & Q(is_in_db=True))
         playlist_type_display = playlist_type.lower().replace("-", " ")
         if playlist_type.lower() == "watching":
             watching = True
@@ -224,17 +201,17 @@ def all_playlists(request, playlist_type):
         if request.method == "POST":
             playlists_type = request.POST["playlistsType"]
             if playlists_type == "All":
-                playlists = request.user.profile.playlists.all().filter(is_in_db=True)
+                playlists = request.user.playlists.all().filter(is_in_db=True)
             elif playlists_type == "Favorites":
-                playlists = request.user.profile.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
+                playlists = request.user.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
             elif playlists_type == "Watching":
-                playlists = request.user.profile.playlists.filter(Q(marked_as="watching") & Q(is_in_db=True))
+                playlists = request.user.playlists.filter(Q(marked_as="watching") & Q(is_in_db=True))
             elif playlists_type == "Plan to Watch":
-                playlists = request.user.profile.playlists.filter(Q(marked_as="plan-to-watch") & Q(is_in_db=True))
+                playlists = request.user.playlists.filter(Q(marked_as="plan-to-watch") & Q(is_in_db=True))
             else:
                 return redirect('/playlists/home')
 
-            if playlists.count() == 0:
+            if not playlists.exists():
                 messages.warning(request, f"No playlists in {playlists_type}")
                 return redirect('/playlists/home')
             random_playlist = random.choice(playlists)
@@ -262,22 +239,22 @@ def all_videos(request, videos_type):
     videos_type = videos_type.lower()
 
     if videos_type == "" or videos_type == "all":
-        playlists = request.user.profile.playlists.all().filter(is_in_db=True)
+        playlists = request.user.playlists.all().filter(is_in_db=True)
         videos_type_display = "All Videos"
     elif videos_type == "user-owned":  # YT playlists owned by user
-        playlists = request.user.profile.playlists.all().filter(Q(is_user_owned=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_user_owned=True) & Q(is_in_db=True))
         videos_type_display = "All Videos in your YouTube Playlists"
     elif videos_type == "imported":  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(Q(is_user_owned=False) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_user_owned=False) & Q(is_in_db=True))
         videos_type_display = "Imported YouTube Playlists Videos"
     elif videos_type == "favorites":  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
         videos_type_display = "Favorite Videos"
     elif videos_type == "watched":  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
         videos_type_display = "Watched Videos"
     elif videos_type == 'hidden-videos':  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
         videos_type_display = "Hidden Videos"
     elif videos_type.lower() == "home":  # displays cards of all playlist types
         return render(request, 'videos_home.html')
@@ -288,17 +265,18 @@ def all_videos(request, videos_type):
                                                   "videos_type": videos_type,
                                                   "videos_type_display": videos_type_display})
 
+
 @login_required
 def order_playlist_by(request, playlist_id, order_by):
-    playlist = request.user.profile.playlists.get(Q(playlist_id=playlist_id) & Q(is_in_db=True))
+    playlist = request.user.playlists.get(Q(playlist_id=playlist_id) & Q(is_in_db=True))
 
     display_text = "Nothing in this playlist! Add something!"  # what to display when requested order/filter has no videws
     videos_details = ""
 
     if order_by == "all":
-        videos = playlist.videos.order_by("video_position")
+        playlist_items = playlist.playlist_items.order_by("video_position")
     elif order_by == "favorites":
-        videos = playlist.videos.filter(is_favorite=True).order_by("video_position")
+        playlist_items = playlist.playlist_items.filter(video__is_favorite=True).order_by("video_position")
         videos_details = "Sorted by Favorites"
         display_text = "No favorites yet!"
     elif order_by == "popularity":
@@ -318,23 +296,23 @@ def order_playlist_by(request, playlist_id, order_by):
         videos_details = "Sorted by Video Duration"
         videos = playlist.videos.order_by("-duration_in_seconds")
     elif order_by == 'new-updates':
-        videos = []
+        playlist_items = []
         videos_details = "Sorted by New Updates"
         display_text = "No new updates! Note that deleted videos will not show up here."
         if playlist.has_new_updates:
-            recently_updated_videos = playlist.videos.filter(video_details_modified=True)
+            recently_updated_videos = playlist.playlist_items.filter(video__video_details_modified=True)
 
-            for video in recently_updated_videos:
-                if video.video_details_modified_at + datetime.timedelta(hours=12) < datetime.datetime.now(
+            for playlist_item in recently_updated_videos:
+                if playlist_item.video.video_details_modified_at + datetime.timedelta(hours=12) < datetime.datetime.now(
                         pytz.utc):  # expired
-                    video.video_details_modified = False
-                    video.save()
+                    playlist_item.video.video_details_modified = False
+                    playlist_item.video.save(update_fields=['video_details_modified'])
 
-            if recently_updated_videos.count() == 0:
+            if not recently_updated_videos.exists():
                 playlist.has_new_updates = False
-                playlist.save()
+                playlist.save(update_fields=['has_new_updates'])
             else:
-                videos = recently_updated_videos.order_by("video_position")
+                playlist_items = recently_updated_videos.order_by("video_position")
     elif order_by == 'unavailable-videos':
         videos = playlist.videos.filter(Q(is_unavailable_on_yt=True) & Q(was_deleted_on_yt=True))
         videos_details = "Sorted by Unavailable Videos"
@@ -343,7 +321,7 @@ def order_playlist_by(request, playlist_id, order_by):
         return redirect('home')
 
     return HttpResponse(loader.get_template("intercooler/videos.html").render({"playlist": playlist,
-                                                                               "videos": videos,
+                                                                               "playlist_items": playlist_items,
                                                                                "videos_details": videos_details,
                                                                                "display_text": display_text,
                                                                                "order_by": order_by}))
@@ -360,17 +338,17 @@ def order_playlists_by(request, playlist_type, order_by):
     watching = False
 
     if playlist_type == "" or playlist_type.lower() == "all":
-        playlists = request.user.profile.playlists.all()
+        playlists = request.user.playlists.all()
     elif playlist_type.lower() == "favorites":
-        playlists = request.user.profile.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True))
     elif playlist_type.lower() in ["watching", "plan-to-watch"]:
-        playlists = request.user.profile.playlists.filter(Q(marked_as=playlist_type.lower()) & Q(is_in_db=True))
+        playlists = request.user.playlists.filter(Q(marked_as=playlist_type.lower()) & Q(is_in_db=True))
         if playlist_type.lower() == "watching":
             watching = True
     elif playlist_type.lower() == "imported":
-        playlists = request.user.profile.playlists.filter(Q(is_user_owned=False) & Q(is_in_db=True))
+        playlists = request.user.playlists.filter(Q(is_user_owned=False) & Q(is_in_db=True))
     elif playlist_type.lower() == "user-owned":
-        playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
+        playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
     else:
         return HttpResponse("Not found.")
 
@@ -387,7 +365,7 @@ def order_playlists_by(request, playlist_type, order_by):
 
 @login_required
 def mark_playlist_as(request, playlist_id, mark_as):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     marked_as_response = '<span></span><meta http-equiv="refresh" content="0" />'
 
@@ -396,6 +374,8 @@ def mark_playlist_as(request, playlist_id, mark_as):
         playlist.save()
         icon = ""
         if mark_as == "watching":
+            playlist.last_watched = datetime.datetime.now(pytz.utc)
+            playlist.save(update_fields=['last_watched'])
             icon = '<i class="fas fa-fire-alt me-2"></i>'
         elif mark_as == "plan-to-watch":
             icon = '<i class="fas fa-flag me-2"></i>'
@@ -413,7 +393,7 @@ def mark_playlist_as(request, playlist_id, mark_as):
             playlist.save()
             return HttpResponse('<i class="fas fa-star"></i>')
     else:
-        return render('home')
+        return redirect('home')
 
     return HttpResponse(marked_as_response)
 
@@ -441,7 +421,7 @@ def delete_videos(request, playlist_id, command):
     if command == "confirm":
         print(video_ids)
 
-        if num_vids == request.user.profile.playlists.get(playlist_id=playlist_id).videos.all().count():
+        if num_vids == request.user.playlists.get(playlist_id=playlist_id).videos.all().count():
             delete_text = "ALL VIDEOS"
             extra_text = " This will not delete the playlist itself, will only make the playlist empty. "
         else:
@@ -461,7 +441,7 @@ def delete_videos(request, playlist_id, command):
     elif command == "start":
         print("Deleting", len(video_ids), "videos")
         Playlist.objects.deletePlaylistItems(request.user, playlist_id, video_ids)
-        # playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+        # playlist = request.user.playlists.get(playlist_id=playlist_id)
         # playlist.has_playlist_changed = True
         # playlist.save(update_fields=['has_playlist_changed'])
         return HttpResponse(f"""
@@ -489,35 +469,36 @@ def search_playlists(request, playlist_type):
     search_query = request.POST["search"]
     watching = False
 
+    playlists = None
     if playlist_type == "all":
         try:
-            playlists = request.user.profile.playlists.all().filter(Q(name__startswith=search_query) & Q(is_in_db=True))
+            playlists = request.user.playlists.all().filter(Q(name__startswith=search_query) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.all()
+            playlists = request.user.playlists.all()
     elif playlist_type == "user-owned":  # YT playlists owned by user
         try:
-            playlists = request.user.profile.playlists.filter(
+            playlists = request.user.playlists.filter(
                 Q(name__startswith=search_query) & Q(is_user_owned=True) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
+            playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
     elif playlist_type == "imported":  # YT playlists (public) owned by others
         try:
-            playlists = request.user.profile.playlists.filter(
+            playlists = request.user.playlists.filter(
                 Q(name__startswith=search_query) & Q(is_user_owned=False) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
+            playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
     elif playlist_type == "favorites":  # YT playlists (public) owned by others
         try:
-            playlists = request.user.profile.playlists.filter(
+            playlists = request.user.playlists.filter(
                 Q(name__startswith=search_query) & Q(is_favorite=True) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True))
+            playlists = request.user.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True))
     elif playlist_type in ["watching", "plan-to-watch"]:
         try:
-            playlists = request.user.profile.playlists.filter(
+            playlists = request.user.playlists.filter(
                 Q(name__startswith=search_query) & Q(marked_as=playlist_type) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.all().filter(Q(marked_as=playlist_type) & Q(is_in_db=True))
+            playlists = request.user.playlists.all().filter(Q(marked_as=playlist_type) & Q(is_in_db=True))
         if playlist_type == "watching":
             watching = True
 
@@ -529,7 +510,7 @@ def search_playlists(request, playlist_type):
 #### MANAGE VIDEOS #####
 @login_required
 def mark_video_favortie(request, playlist_id, video_id):
-    video = request.user.profile.playlists.get(playlist_id=playlist_id).videos.get(video_id=video_id)
+    video = request.user.videos.get(video_id=video_id)
 
     if video.is_favorite:
         video.is_favorite = False
@@ -543,7 +524,7 @@ def mark_video_favortie(request, playlist_id, video_id):
 
 @login_required
 def mark_video_watched(request, playlist_id, video_id):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
     video = playlist.videos.get(video_id=video_id)
 
     if video.is_marked_as_watched:
@@ -555,19 +536,20 @@ def mark_video_watched(request, playlist_id, video_id):
     else:
         video.is_marked_as_watched = True
         video.save(update_fields=['is_marked_as_watched'])
+        playlist.last_watched = datetime.datetime.now(pytz.utc)
+        playlist.save(update_fields=['last_watched'])
+
         return HttpResponse(
             f'<i class="fas fa-check-circle" hx-get="/playlist/{playlist_id}/get-watch-message" hx-trigger="load" hx-target="#playlist-watch-message"></i>')
 
-    generateWatchingMessage(playlist)
-
 
 ###########
 @login_required
 def search(request):
     if request.method == "GET":
-        return render(request, 'search_untube_page.html', {"playlists": request.user.profile.playlists.all()})
+        return render(request, 'search_untube_page.html', {"playlists": request.user.playlists.all()})
     else:
-        return render('home')
+        return redirect('home')
 
 
 @login_required
@@ -577,41 +559,41 @@ def search_UnTube(request):
 
     search_query = request.POST["search"]
 
-    all_playlists = request.user.profile.playlists.filter(is_in_db=True)
+    all_playlists = request.user.playlists.filter(is_in_db=True)
     if 'playlist-tags' in request.POST:
         tags = request.POST.getlist('playlist-tags')
         for tag in tags:
             all_playlists = all_playlists.filter(tags__name=tag)
         #all_playlists = all_playlists.filter(tags__name__in=tags)
 
-    videos = []
+    playlist_items = []
 
     if request.POST['search-settings'] == 'starts-with':
-        playlists = all_playlists.filter(name__istartswith=search_query) if search_query != "" else all_playlists.none()
+        playlists = all_playlists.filter(Q(name__istartswith=search_query) | Q(user_label__istartswith=search_query)) if search_query != "" else all_playlists.none()
 
         if search_query != "":
             for playlist in all_playlists:
-                pl_videos = playlist.videos.filter(name__istartswith=search_query)
+                pl_items = playlist.playlist_items.filter(Q(video__name__istartswith=search_query) | Q(video__user_label__istartswith=search_query) & Q(is_duplicate=False))
 
-                if pl_videos.count() != 0:
-                    for v in pl_videos.all():
-                        videos.append(v)
+                if pl_items.exists():
+                    for v in pl_items.all():
+                        playlist_items.append(v)
 
     else:
-        playlists = all_playlists.filter(name__icontains=search_query) if search_query != "" else all_playlists.none()
+        playlists = all_playlists.filter(Q(name__icontains=search_query) | Q(user_label__istartswith=search_query)) if search_query != "" else all_playlists.none()
 
         if search_query != "":
             for playlist in all_playlists:
-                pl_videos = playlist.videos.filter(name__icontains=search_query)
+                pl_items = playlist.playlist_items.filter(Q(video__name__icontains=search_query) | Q(video__user_label__istartswith=search_query) & Q(is_duplicate=False))
 
-                if pl_videos.count() != 0:
-                    for v in pl_videos.all():
-                        videos.append(v)
+                if pl_items.exists():
+                    for v in pl_items.all():
+                        playlist_items.append(v)
 
     return HttpResponse(loader.get_template("intercooler/search_untube_results.html")
                         .render({"playlists": playlists,
-                                 "videos": videos,
-                                 "videos_count": len(videos),
+                                 "playlist_items": playlist_items,
+                                 "videos_count": len(playlist_items),
                                  "search_query": True if search_query != "" else False,
                                  "all_playlists": all_playlists}))
 
@@ -661,18 +643,20 @@ def manage_import_playlists(request):
             if pl_id is None:
                 num_playlists_not_found += 1
                 continue
-            status = Playlist.objects.initPlaylist(request.user, pl_id)
+                
+            status = Playlist.objects.initializePlaylist(request.user, pl_id)["status"]
             if status == -1 or status == -2:
                 print("\nNo such playlist found:", pl_id)
                 num_playlists_not_found += 1
                 not_found_playlists.append(playlist_link)
-            elif status == -3:
+            elif status == -3:  # playlist already in db
                 num_playlists_already_in_db += 1
-                playlist = request.user.profile.playlists.get(playlist_id__exact=pl_id)
+                playlist = request.user.playlists.get(playlist_id__exact=pl_id)
                 old_playlists.append(playlist)
-            else:
+            else:  # only if playlist exists on YT, so import its videos
                 print(status)
-                playlist = request.user.profile.playlists.get(playlist_id__exact=pl_id)
+                Playlist.objects.getAllVideosForPlaylist(request.user, pl_id)
+                playlist = request.user.playlists.get(playlist_id__exact=pl_id)
                 new_playlists.append(playlist)
                 num_playlists_initialized_in_db += 1
             done.append(playlist_link.strip())
@@ -700,12 +684,14 @@ def manage_create_playlist(request):
 
 @login_required
 def load_more_videos(request, playlist_id, order_by, page):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
+    playlist_items = None
     if order_by == "all":
-        videos = playlist.videos.order_by("video_position")
+        playlist_items = playlist.playlist_items.order_by("video_position")
+        print(f"loading page 1: {playlist_items.count()} videos")
     elif order_by == "favorites":
-        videos = playlist.videos.filter(is_favorite=True).order_by("video_position")
+        playlist_items = playlist.playlist_items.filter(video__is_favorite=True).order_by("video_position")
     elif order_by == "popularity":
         videos = playlist.videos.order_by("-like_count")
     elif order_by == "date-published":
@@ -717,21 +703,21 @@ def load_more_videos(request, playlist_id, order_by, page):
     elif order_by == "duration":
         videos = playlist.videos.order_by("-duration_in_seconds")
     elif order_by == 'new-updates':
-        videos = []
+        playlist_items = []
         if playlist.has_new_updates:
-            recently_updated_videos = playlist.videos.filter(video_details_modified=True)
+            recently_updated_videos = playlist.playlist_items.filter(video__video_details_modified=True)
 
-            for video in recently_updated_videos:
-                if video.video_details_modified_at + datetime.timedelta(hours=12) < datetime.datetime.now(
+            for playlist_item in recently_updated_videos:
+                if playlist_item.video.video_details_modified_at + datetime.timedelta(hours=12) < datetime.datetime.now(
                         pytz.utc):  # expired
-                    video.video_details_modified = False
-                    video.save()
+                    playlist_item.video.video_details_modified = False
+                    playlist_item.video.save()
 
-            if recently_updated_videos.count() == 0:
+            if not recently_updated_videos.exists():
                 playlist.has_new_updates = False
                 playlist.save()
             else:
-                videos = recently_updated_videos.order_by("video_position")
+                playlist_items = recently_updated_videos.order_by("video_position")
     elif order_by == 'unavailable-videos':
         videos = playlist.videos.filter(Q(is_unavailable_on_yt=True) & Q(was_deleted_on_yt=True))
 
@@ -739,7 +725,7 @@ def load_more_videos(request, playlist_id, order_by, page):
         .render(
         {
             "playlist": playlist,
-            "videos": videos[50 * page:],  # only send 50 results per page
+            "playlist_items": playlist_items[50 * page:],  # only send 50 results per page
             "page": page + 1,
             "order_by": order_by}))
 
@@ -751,7 +737,7 @@ def update_playlist_settings(request, playlist_id):
     message_content = "Saved!"
 
     print(request.POST)
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
     if "user_label" in request.POST:
         playlist.user_label = request.POST["user_label"]
         playlist.save(update_fields=['user_label'])
@@ -794,7 +780,7 @@ def update_playlist_settings(request, playlist_id):
 
 @login_required
 def update_playlist(request, playlist_id, type):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     if type == "checkforupdates":
         print("Checking if playlist changed...")
@@ -870,10 +856,10 @@ def update_playlist(request, playlist_id, type):
                 </div>""")
 
     print("Attempting to update playlist")
-    status, deleted_video_ids, unavailable_videos, added_videos = Playlist.objects.updatePlaylist(request.user,
+    status, deleted_playlist_item_ids, unavailable_videos, added_videos = Playlist.objects.updatePlaylist(request.user,
                                                                                                   playlist_id)
 
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     if status == -1:
         playlist_name = playlist.name
@@ -903,16 +889,16 @@ def update_playlist(request, playlist_id, type):
             playlist_changed_text.append(f"\n{len(unavailable_videos)} went unavailable")
         for video in unavailable_videos:
             playlist_changed_text.append(f"--> {video.name}")
-    if len(deleted_video_ids) != 0:
+    if len(deleted_playlist_item_ids) != 0:
         if len(playlist_changed_text) == 0:
-            playlist_changed_text.append(f"{len(deleted_video_ids)} deleted")
+            playlist_changed_text.append(f"{len(deleted_playlist_item_ids)} deleted")
         else:
-            playlist_changed_text.append(f"\n{len(deleted_video_ids)} deleted")
+            playlist_changed_text.append(f"\n{len(deleted_playlist_item_ids)} deleted")
 
-        for video_id in deleted_video_ids:
-            video = playlist.videos.get(video_id=video_id)
-            playlist_changed_text.append(f"--> {video.name}")
-            video.delete()
+        for playlist_item_id in deleted_playlist_item_ids:
+            playlist_item = playlist.playlist_items.get(playlist_item_id=playlist_item_id)
+            playlist_changed_text.append(f"--> {playlist_item.video.name}")
+            playlist_item.delete()
 
     if len(playlist_changed_text) == 0:
         playlist_changed_text = ["Successfully refreshed playlist! No new changes found!"]
@@ -927,7 +913,7 @@ def update_playlist(request, playlist_id, type):
 @login_required
 def view_playlist_settings(request, playlist_id):
     try:
-        playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+        playlist = request.user.playlists.get(playlist_id=playlist_id)
     except apps.main.models.Playlist.DoesNotExist:
         messages.error(request, "No such playlist found!")
         return redirect('home')
@@ -937,7 +923,7 @@ def view_playlist_settings(request, playlist_id):
 
 @login_required
 def get_playlist_tags(request, playlist_id):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
     playlist_tags = playlist.tags.all()
 
     return HttpResponse(loader.get_template("intercooler/playlist_tags.html")
@@ -948,7 +934,7 @@ def get_playlist_tags(request, playlist_id):
 
 @login_required
 def get_unused_playlist_tags(request, playlist_id):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     user_created_tags = Tag.objects.filter(created_by=request.user)
     playlist_tags = playlist.tags.all()
@@ -962,7 +948,7 @@ def get_unused_playlist_tags(request, playlist_id):
 
 @login_required
 def get_watch_message(request, playlist_id):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     return HttpResponse(loader.get_template("intercooler/playlist_watch_message.html")
         .render(
@@ -977,10 +963,10 @@ def create_playlist_tag(request, playlist_id):
     if tag_name.lower() == 'Pick from existing unused tags'.lower():
         return HttpResponse("Can't use that! Try again >_<")
 
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     user_created_tags = Tag.objects.filter(created_by=request.user)
-    if user_created_tags.filter(name__iexact=tag_name).count() == 0:  # no tag found, so create it
+    if not user_created_tags.filter(name__iexact=tag_name).exists():  # no tag found, so create it
         tag = Tag(name=tag_name, created_by=request.user)
         tag.save()
 
@@ -1010,10 +996,10 @@ def add_playlist_tag(request, playlist_id):
     if tag_name == 'Pick from existing unused tags':
         return HttpResponse("Pick something! >w<")
 
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     playlist_tags = playlist.tags.all()
-    if playlist_tags.filter(name__iexact=tag_name).count() == 0:  # tag not on this playlist, so add it
+    if not playlist_tags.filter(name__iexact=tag_name).exists():  # tag not on this playlist, so add it
         tag = Tag.objects.filter(Q(created_by=request.user) & Q(name__iexact=tag_name)).first()
 
         # add it to playlist
@@ -1030,10 +1016,10 @@ def add_playlist_tag(request, playlist_id):
 @login_required
 @require_POST
 def remove_playlist_tag(request, playlist_id, tag_name):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     playlist_tags = playlist.tags.all()
-    if playlist_tags.filter(name__iexact=tag_name).count() != 0:  # tag on this playlist, remove it it
+    if playlist_tags.filter(name__iexact=tag_name).exists():  # tag on this playlist, remove it it
         tag = Tag.objects.filter(Q(created_by=request.user) & Q(name__iexact=tag_name)).first()
 
         print("Removed tag", tag_name)
@@ -1047,7 +1033,7 @@ def remove_playlist_tag(request, playlist_id, tag_name):
 
 @login_required
 def delete_playlist(request, playlist_id):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     if not playlist.is_user_owned:  # if playlist trying to delete isn't user owned
         playlist.delete()  # just delete it from untrue
@@ -1062,7 +1048,7 @@ def delete_playlist(request, playlist_id):
 
 @login_required
 def reset_watched(request, playlist_id):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.playlists.get(playlist_id=playlist_id)
 
     for video in playlist.videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=False)):
         video.is_marked_as_watched = False

+ 10 - 3
apps/users/templates/index.html

@@ -51,12 +51,19 @@
 
 
       <div class="container py-4">
-
+            {% if messages %}
+                    {% for message in messages %}
+                      <div class="alert alert-success alert-dismissible fade show" role="alert">
+                          {{ message }}
+                          <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+                      </div>
+                    {% endfor %}
+                {% endif %}
 
     <div class="p-5 mb-4 bg-light rounded-3">
       <div class="container-fluid py-5">
-        <h1 class="display-5 fw-bold">UnTube: A YouTube Playlist Manager</h1>
-        <p class="col-md-8 fs-4">UnTube is a simple Youtube playlist manager to help you modify and keep track of your YouTube playlists with ease.</p>
+        <h1 class="display-5 fw-bold">UnTube</h1>
+        <p class="col-md-8 fs-4">A simple Youtube playlist manager to help you modify and keep track of your YouTube playlists with ease.</p>
 
           <a class="btn btn-danger btn-lg" href="{% provider_login_url 'google' %}">Login with Google</a>
           <a class="btn btn-primary btn-lg" href="#more">Tell me more</a>

+ 7 - 1
apps/users/templates/intercooler/user_playlist_updates.html

@@ -32,7 +32,13 @@
                 {% endfor %}
             </ul>
         {% endif %}
-            <button class="btn btn-success" hx-get="{% url 'user_playlists_updates' 'init-update' %}" hx-trigger="click" hx-target="#user-pl-updates">Import</button>
+            <button class="btn btn-success" hx-get="{% url 'user_playlists_updates' 'init-update' %}" hx-trigger="click" hx-target="#user-pl-updates">
+                {% if deleted_playlist_names and not playlists %}
+                    OK
+                {% else %}
+                    Import
+                {% endif %}
+            </button>
             </p>
         <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 

+ 25 - 25
apps/users/templates/profile.html

@@ -40,7 +40,7 @@
               </li>
               <li class="list-group-item bg-dark d-flex justify-content-between align-items-center flex-wrap">
                 <h6 class="mb-0 text-white">Total Playlist Count</h6>
-                <span class="text-white-50">{{ user.profile.playlists.count }}</span>
+                <span class="text-white-50">{{ user.playlists.count }}</span>
               </li>
               <li class="list-group-item bg-dark d-flex justify-content-between align-items-center flex-wrap">
                 <h6 class="mb-0 text-white">Profile Views</h6>
@@ -85,33 +85,33 @@
                 </div>
               {% endif %}
             <div class="col-sm-6 mb-3">
-              <div class="card h-100 bg-dark text-white">
-                <div class="card-body">
-                  <h6 class="d-flex align-items-center mb-3"><span class="text-info me-2">{{ user.profile.playlists.count }}</span>Playlists Statistics</h6>
+                  <div class="card h-100 bg-dark text-white">
+                    <div class="card-body">
+                      <h6 class="d-flex align-items-center mb-3"><span class="text-info me-2">{{ user.playlists.count }}</span>Playlists Statistics</h6>
 
-                  <small>Public <span class="text-warning ms-1">{{ statistics.public_x }}%</span></small>
-                  <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.public_x }}%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
-                  </div>
+                      <small>Public <span class="text-warning ms-1">{{ statistics.public_x }}%</span></small>
+                      <div class="progress mb-3" style="height: 5px">
+                        <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.public_x }}%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
+                      </div>
 
-                  <small>Private <span class="text-warning ms-1">{{ statistics.private_x }}%</span></small>
-                  <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.private_x }}%" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100"></div>
-                  </div>
-                  <small>Favorites <span class="text-warning ms-1">{{ statistics.favorites_x }}%</span></small>
-                  <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.favorites_x }}%" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100"></div>
-                  </div>
-                  <small>Watching <span class="text-warning ms-1">{{ statistics.watching_x }}%</span></small>
-                  <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.watching_x }}%" aria-valuenow="55" aria-valuemin="0" aria-valuemax="100"></div>
-                  </div>
-                  <small>Imported <span class="text-warning ms-1">{{ statistics.imported_x }}%</span></small>
-                  <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.imported_x }}%" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100"></div>
+                      <small>Private <span class="text-warning ms-1">{{ statistics.private_x }}%</span></small>
+                      <div class="progress mb-3" style="height: 5px">
+                        <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.private_x }}%" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100"></div>
+                      </div>
+                      <small>Favorites <span class="text-warning ms-1">{{ statistics.favorites_x }}%</span></small>
+                      <div class="progress mb-3" style="height: 5px">
+                        <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.favorites_x }}%" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100"></div>
+                      </div>
+                      <small>Watching <span class="text-warning ms-1">{{ statistics.watching_x }}%</span></small>
+                      <div class="progress mb-3" style="height: 5px">
+                        <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.watching_x }}%" aria-valuenow="55" aria-valuemin="0" aria-valuemax="100"></div>
+                      </div>
+                      <small>Imported <span class="text-warning ms-1">{{ statistics.imported_x }}%</span></small>
+                      <div class="progress mb-3" style="height: 5px">
+                        <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.imported_x }}%" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100"></div>
+                      </div>
+                    </div>
                   </div>
-                </div>
-              </div>
             </div>
             <div class="col-sm-6 mb-3">
               <div class="card h-100 bg-dark text-white overflow-auto" style="height: 100px;">

+ 14 - 11
apps/users/views.py

@@ -31,7 +31,7 @@ def index(request):
 
 @login_required
 def profile(request):
-    user_playlists = request.user.profile.playlists.all()
+    user_playlists = request.user.playlists.all()
     watching = user_playlists.filter(marked_as="watching")
 
     total_num_playlists = user_playlists.count()
@@ -52,7 +52,9 @@ def profile(request):
         statistics["watching_x"] = round(user_playlists.filter(marked_as="watching").count() / total_num_playlists, 1) * 100
         statistics["imported_x"] = round(user_playlists.filter(is_user_owned=False).count() / total_num_playlists, 1) * 100
 
-    return render(request, 'profile.html', {"statistics": statistics,
+    return render(request, 'profile.html', {
+                                            "total_num_playlists": total_num_playlists,
+                                            "statistics": statistics,
                                             "watching": watching})
 
 
@@ -148,7 +150,7 @@ def start_import(request):
 
         request.user.save()
 
-    result = Playlist.objects.getAllPlaylistsFromYT(request.user)
+    result = Playlist.objects.initializePlaylist(request.user)
     channel_found = True
     if result["status"] == -1:
         print("User has no YT channel")
@@ -193,10 +195,10 @@ def continue_import(request):
     if request.user.profile.import_in_progress is False:
         return redirect('home')
 
-    num_of_playlists = request.user.profile.playlists.all().count()
+    num_of_playlists = request.user.playlists.all().count()
 
     try:
-        remaining_playlists = request.user.profile.playlists.filter(is_in_db=False)
+        remaining_playlists = request.user.playlists.filter(is_in_db=False)
         playlists_imported = num_of_playlists - remaining_playlists.count() + 1
         playlist = remaining_playlists.order_by("created_at")[0]
         playlist_name = playlist.name
@@ -230,10 +232,11 @@ def continue_import(request):
 @login_required
 def user_playlists_updates(request, action):
     if action == 'check-for-updates':
-        user_playlists_on_UnTube = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
+        user_playlists_on_UnTube = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
 
-        result = Playlist.objects.getAllPlaylistsFromYT(request.user)
+        result = Playlist.objects.initializePlaylist(request.user)
 
+        print(result)
         youtube_playlist_ids = result["playlist_ids"]
         untube_playlist_ids = []
         for playlist in user_playlists_on_UnTube:
@@ -244,7 +247,7 @@ def user_playlists_updates(request, action):
         for pl_id in untube_playlist_ids:
             if pl_id not in youtube_playlist_ids:  # ie this playlist was deleted on youtube
                 deleted_playlist_ids.append(pl_id)
-                pl = request.user.profile.playlists.get(playlist_id__exact=pl_id)
+                pl = request.user.playlists.get(playlist_id__exact=pl_id)
                 deleted_playlist_names.append(f"{pl.name} (had {pl.video_count} videos)")
                 pl.delete()
 
@@ -252,7 +255,7 @@ def user_playlists_updates(request, action):
             print("No new updates")
             playlists = []
         else:
-            playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False))
+            playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False))
             print(
                 f"New updates found! {playlists.count()} newly added and {len(deleted_playlist_ids)} playlists deleted!")
             print(deleted_playlist_names)
@@ -261,7 +264,7 @@ def user_playlists_updates(request, action):
             {"playlists": playlists,
              "deleted_playlist_names": deleted_playlist_names}))
     elif action == 'init-update':
-        unimported_playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False)).count()
+        unimported_playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False)).count()
 
         return HttpResponse(f"""
         <div hx-get="/updates/user-playlists/start-update" hx-trigger="load" hx-target="#user-pl-updates">
@@ -274,7 +277,7 @@ def user_playlists_updates(request, action):
         </div>
         """)
     elif action == 'start-update':
-        unimported_playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False))
+        unimported_playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False))
 
         for playlist in unimported_playlists:
             Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)

+ 11 - 11
templates/base.html

@@ -102,12 +102,9 @@
         <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/bbbootstrap/libraries@main/choices.min.css">
 
         <script src="{% static 'htmx/htmx.min.js' %}" type="application/javascript"></script>
-        <script src="{% static 'clipboard.js/clipboard.min.js' %}" type="application/javascript"></script>
         <script src="{% static 'jquery3.6.0/js/jquery-3.6.0.min.js' %}" type="application/javascript"></script>
         <script src="{% static 'bootstrap5.0.1/js/bootstrap.bundle.min.js' %}" type="application/javascript"></script>
-        <script src="https://cdn.jsdelivr.net/gh/bbbootstrap/libraries@main/choices.min.js"></script>
-        <script src="{% static 'htmx/extensions/class-tools.js' %}" type="application/javascript"></script>
-    </head>
+            </head>
     <body class="text-dark" style="font-family: 'Fredoka One', monospace; background-color: #FDF4DC;">
 
 
@@ -215,6 +212,11 @@
         <br>
 
 
+        <script src="{% static 'BackgroundCheck/BackgroundCheck.min.js' %}" type="application/javascript"></script>
+        <script src="{% static 'clipboard.js/clipboard.min.js' %}" type="application/javascript"></script>
+        <script async src="https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js" integrity="sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D" crossorigin="anonymous"></script>
+        <script src="https://cdn.jsdelivr.net/gh/bbbootstrap/libraries@main/choices.min.js"></script>
+        <script src="{% static 'htmx/extensions/class-tools.js' %}" type="application/javascript"></script>
 
         <script type="application/javascript">
 
@@ -225,8 +227,13 @@
                     removeItemButton: true,
                 });
 
+                BackgroundCheck.init({
+                  targets: '.ui',
+                    images: '.ui-img'
+                });
             });
 
+
             // copy functionality
             var clipboard = new ClipboardJS('.copy-btn');
 
@@ -270,13 +277,6 @@
 
         </script>
 
-
-        <!--
-        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
-        <script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script>
-        <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
-        -->
-
     </body>
 </html>
 {% endwith %}

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