2
0
sleepytaco 3 жил өмнө
parent
commit
3f78b59c7c

+ 4 - 0
apps/charts/urls.py

@@ -3,4 +3,8 @@ from apps.charts import views
 
 urlpatterns = [
     path('channel-videos-distribution/<slug:playlist_id>', views.channel_videos_distribution, name='channel_videos_distribution'),
+
+    path('overall-playlists-distribution/', views.overall_playlists_distribution,
+         name='overall_playlists_distribution'),
+
 ]

+ 39 - 1
apps/charts/views.py

@@ -8,7 +8,8 @@ def channel_videos_distribution(request, playlist_id):
 
     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(
+    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'])
@@ -18,3 +19,40 @@ def channel_videos_distribution(request, playlist_id):
         'labels': labels,
         'data': data,
     })
+
+
+def overall_playlists_distribution(request):
+    labels = []
+    data = []
+
+    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": 0,
+        "private": 0,
+        "favorites": 0,
+        "watching": 0,
+        "imported": 0,
+        "youtube mixes": 0
+    }
+
+    if total_num_playlists != 0:
+        statistics["public"] = user_playlists.filter(is_private_on_yt=False).count()
+        statistics["private"] = user_playlists.filter(is_private_on_yt=True).count()
+        statistics["favorites"] = user_playlists.filter(is_favorite=True).count()
+        statistics["watching"] = user_playlists.filter(marked_as="watching").count()
+        statistics["imported"] = user_playlists.filter(is_user_owned=False).count()
+        statistics["youtube mixes"] = user_playlists.filter(is_yt_mix=True).count()
+
+
+    for key, value in statistics.items():
+        labels.append(key)
+        data.append(value)
+
+    return JsonResponse(data={
+        'labels': labels,
+        'data': data,
+    })

+ 9 - 5
apps/main/models.py

@@ -317,6 +317,8 @@ class PlaylistManager(models.Manager):
                                 'statistics'] else -1
                             vid.dislike_count = item['statistics']['dislikeCount'] if 'dislikeCount' in item[
                                 'statistics'] else -1
+                            vid.comment_count = item['statistics']['commentCount'] if 'commentCount' in item[
+                                'statistics'] else -1
                             vid.yt_player_HTML = item['player']['embedHtml'] if 'embedHtml' in item['player'] else ''
                             vid.save()
 
@@ -688,6 +690,8 @@ class PlaylistManager(models.Manager):
                         'statistics'] else -1
                     vid.dislike_count = item['statistics']['dislikeCount'] if 'dislikeCount' in item[
                         'statistics'] else -1
+                    vid.comment_count = item['statistics']['commentCount'] if 'commentCount' in item[
+                        'statistics'] else -1
                     vid.yt_player_HTML = item['player']['embedHtml'] if 'embedHtml' in item['player'] else ''
                     vid.save()
 
@@ -721,7 +725,7 @@ class PlaylistManager(models.Manager):
 
         # if its been a week since the last full scan, do a full playlist scan
         # basically checks all the playlist video for any updates
-        if playlist.last_full_scan_at + datetime.timedelta(minutes=10) < datetime.datetime.now(pytz.utc):
+        if playlist.last_full_scan_at + datetime.timedelta(minutes=2) < datetime.datetime.now(pytz.utc):
             print("DOING A FULL SCAN")
             current_video_ids = [playlist_item.video_id for playlist_item in playlist.playlist_items.all()]
             current_playlist_item_ids = [playlist_item.playlist_item_id for playlist_item in
@@ -741,8 +745,6 @@ class PlaylistManager(models.Manager):
                 # execute the above request, and store the response
                 pl_response = pl_request.execute()
 
-                print("PL ITEM", pl_response)
-
                 for item in pl_response['items']:
                     playlist_item_id = item['id']
                     video_id = item['contentDetails']['videoId']
@@ -770,8 +772,6 @@ class PlaylistManager(models.Manager):
                         pl_request = youtube.playlistItems().list_next(pl_request, pl_response)
                         pl_response = pl_request.execute()
 
-                        print("PL ITEM", pl_response)
-
                         for item in pl_response['items']:
                             playlist_item_id = item['id']
                             video_id = item['contentDetails']['videoId']
@@ -971,6 +971,7 @@ class PlaylistManager(models.Manager):
 
                     playlist_item = playlist.playlist_items.get(playlist_item_id=playlist_item_id)
                     playlist_item.video_position = item['snippet']['position']
+                    playlist_item.save(update_fields=['video_position'])
 
                     # check if the video became unavailable on youtube
                     if not playlist_item.video.is_unavailable_on_yt and not playlist_item.video.was_deleted_on_yt:
@@ -1074,6 +1075,7 @@ class PlaylistManager(models.Manager):
                             current_playlist_item_ids.remove(playlist_item_id)
                             playlist_item = playlist.playlist_items.get(playlist_item_id=playlist_item_id)
                             playlist_item.video_position = item['snippet']['position']
+                            playlist_item.save(update_fields=['video_position'])
 
                             # check if the video became unavailable on youtube
                             if not playlist_item.video.is_unavailable_on_yt and not playlist_item.video.was_deleted_on_yt:
@@ -1140,6 +1142,8 @@ class PlaylistManager(models.Manager):
                         'statistics'] else -1
                     vid.dislike_count = item['statistics']['dislikeCount'] if 'dislikeCount' in item[
                         'statistics'] else -1
+                    vid.comment_count = item['statistics']['commentCount'] if 'commentCount' in item[
+                        'statistics'] else -1
                     vid.yt_player_HTML = item['player']['embedHtml'] if 'embedHtml' in item['player'] else ''
                     vid.save()
 

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

@@ -4,7 +4,7 @@
 
 
         <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 mb-3">
-            <h1 class="h2"><span style="border-bottom: 3px #ffffff dashed;">{{ playlist_type_display|title }}</span> <span class="badge bg-primary rounded-pill">{{ playlists.count }}</span></h1>
+            <h1 class="h2"><span style="border-bottom: 3px #e24949 dashed;">{{ playlist_type_display|title }}</span> <span class="badge bg-primary rounded-pill">{{ playlists.count }}</span></h1>
 
             {% if playlists %}
             <div class="btn-toolbar mb-2 mb-md-0">

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

@@ -5,7 +5,7 @@
     <div id="search-results">
 
         <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 mb-3">
-            <h1 class="h2"> <span style="border-bottom: 3px #ffffff dashed;" class="pt-3">Playlists Tagged as</span> <kbd>{{ tag.name }}</kbd> <span class="badge bg-warning rounded-pill">{{ playlists.count }}</span></h1>
+            <h1 class="h2"> <span style="border-bottom: 3px #e24949 dashed;" class="pt-3">Playlists Tagged as</span> <kbd>{{ tag.name }}</kbd> <span class="badge bg-warning rounded-pill">{{ playlists.count }}</span></h1>
 
         </div>
 

+ 91 - 42
apps/main/templates/home.html

@@ -2,7 +2,6 @@
 {% 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>
@@ -45,7 +44,7 @@
 
         {% 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>
+                <h3><span style="border-bottom: 3px #e24949 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">
@@ -130,7 +129,7 @@
         {% endif %}
 
 
-        <div class="row" data-masonry='{"percentPosition": true }'>
+        <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="">
@@ -164,18 +163,22 @@
                     </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 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>
+                        <div class="d-flex align-items-center mb-3">
+
+                            <canvas id="overall-playlists-distribution" data-url="{% url 'overall_playlists_distribution' %}">
+
+                            </canvas>
+
+                        </div>
+                    </div>
                 </div>
             </div>
+
             <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>
@@ -187,35 +190,20 @@
                     </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 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>
+
             <div class="col-sm-6 col-lg-4 mb-4">
                 <div class="card text-center">
                     <div class="card-body">
@@ -358,7 +346,7 @@
                 <!--
                   <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>
+                              <h3><span style="border-bottom: 3px #e24949 dashed;">Most viewed playlists</span> <a href="{% url 'all_playlists' 'all' %}" class="pt-1"><i class="fas fa-binoculars"></i> </a></h3>
                                 {% if user_playlists %}
                                 <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
                                     {% for playlist in user_playlists|slice:"0:3" %}
@@ -403,7 +391,7 @@
         <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>
+                <h3><span style="border-bottom: 3px #e24949 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 %}
@@ -435,7 +423,7 @@
             </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>
+                <h3><span style="border-bottom: 3px #e24949 dashed;">Recently Accessed</span> <i class="fas fa-redo fa-sm" style="color: #3c3fd2"></i></h3>
 
                 {% if recently_accessed_playlists %}
                     <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
@@ -472,7 +460,68 @@
 
         <br>
         <br>
+        <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
+
+        <script type="application/javascript">
+
+                    $(function () {
+
+                  var $populationChart = $("#overall-playlists-distribution");
+                  $.ajax({
+                    url: $populationChart.data("url"),
+                    success: function (data) {
+
+                      var ctx = $populationChart[0].getContext("2d");
+                       var coloR = [];
+
+                     var dynamicColors = function() { // generate random color
+                            var r = Math.floor(Math.random() * 255);
+                            var g = Math.floor(Math.random() * 255);
+                            var b = Math.floor(Math.random() * 255);
+                            return "rgb(" + r + "," + g + "," + b + ")";
+                         };
+
+                      for (var i in data.labels) {
+                          if (data.labels)
+                          coloR.push(dynamicColors());
+                     }
+
+                      new Chart(ctx, {
+                        type: 'pie',
+                        data: {
+                          labels: data.labels,
+                          datasets: [{
+                            label: 'Playlist Types',
+                            backgroundColor: coloR,
+                            data: data.data
+                          }]
+                        },
+                        options: {
+                          responsive: true,
+                          legend: {
+                            position: 'right',
+                              display: true
+                          },
+                          title: {
+                            display: false,
+                              text: 'Video Count Distribution per Channel',
+                              fontSize: 20,
+                              fontColor: '#fff',
+                          },
+
+                        }
+                      });
+
+                    }
+                  });
+
+                });
+
+
+        </script>
     {% endif %}
 
 
+
+
 {% endblock %}

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

@@ -19,8 +19,9 @@
                           <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={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" target="_blank">
-                                {{ playlist_item.video_position }}. {{ playlist_item.video.name }}
+                            {{ playlist_item.video_position }}.
+                            <a class="link-dark" href="{% url 'video' playlist_item.video.video_id %}">
+                                 {{ playlist_item.video.name }}
                             </a>
                             <br><br>
                         </div>
@@ -38,7 +39,7 @@
 
                         {% 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">
+                            <a class="link-dark" href="{% url 'video' playlist_item.video.video_id %}">
                                 {{ playlist_item.video.name|truncatewords:"16" }}
                             </a>
                             <br>
@@ -47,15 +48,15 @@
                             <br><br>
                         {% else %}
                             {{ 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">
+                            <a class="link-dark" href="{% url 'video' playlist_item.video.video_id %}">
                                 {{ 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 playlist_item.video.view_count %}<span class="badge bg-info">{{ playlist_item.video.view_count|intword|intcomma }} views</span>{% endif %}
                             {% if playlist_item.video.is_duplicate %}<span class="badge bg-primary">duplicate</span>{% endif %}
+                            {% if playlist_item.video.playlists.count|add:"-1" != 0 %}<span class="badge bg-dark"><a href="{% url 'video' playlist_item.video.video_id %}#found-in" style="text-decoration: none; color: white">found in {{ playlist_item.video.playlists.count|add:"-1" }} other playlists</a></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 %}
@@ -66,7 +67,7 @@
                 </div>
                 <div class="ms-5">
                 {% 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>
+                <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
                 <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 playlist_item.video.is_favorite %}
@@ -78,13 +79,12 @@
                 </button>
                 {% else %}
 
-                <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>
+                <a class="btn btn-sm btn-info mb-1" type="button" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_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 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>
+                <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
                 <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 playlist_item.video.is_favorite %}

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

@@ -1,7 +1,6 @@
 
 {% extends 'base.html' %}
 {% block content %}
-    <br>
     {% if user.profile.imported_yt_playlists %}
     <div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
 

+ 1 - 2
apps/main/templates/manage_playlists_create.html

@@ -1,10 +1,9 @@
 
 {% extends 'base.html' %}
 {% block content %}
-<br>
 
 <div class="container-fluid">
-    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom border-2 border-light">
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom border-2 border-dark">
         <h1 class="h2">Create a new YouTube Playlist</h1>
     </div>
     <br>

+ 0 - 1
apps/main/templates/playlists_home.html

@@ -1,6 +1,5 @@
 {% extends 'base.html' %}
 {% block content %}
-    <br>
 
 <div class="row row-cols-1 row-cols-md-3 g-4" >
     <div class="col">

+ 15 - 21
apps/main/templates/view_playlist.html

@@ -9,7 +9,7 @@
         {% if playlist.has_playlist_changed %}
             <div hx-get="{% url 'update_playlist' playlist.playlist_id 'auto' %}" hx-trigger="load" hx-swap="outerHTML">
                 <div class="d-flex justify-content-center mt-4 mb-3" id="loading-sign">
-                    <img src="{% static 'svg-loaders/circles.svg' %}" width="40" height="40">
+                    <img src="{% static 'svg-loaders/circles.svg' %}" width="40" height="40" style="color: black">
                     <h5 class="mt-2 ms-2">Updating playlist '{{ playlist.name }}', please wait!</h5>
                 </div>
             </div>
@@ -59,7 +59,7 @@
                                     </span>
                                 </h4>
                             </div>
-                            <h6>by {{ playlist.channel_name }}{{ playlist.channel_id }}</h6>
+                            <h6>by {{ playlist.channel_name }}</h6>
                             <p class="card-text">
                                 {% if playlist.description %}
                                     <h5 class="overflow-auto" {% if playlist.description|length > 750 %} style="height: 150px;"{% endif %}>
@@ -78,6 +78,8 @@
                                 <span class="badge bg-light text-black-50">{{ playlist.video_count }} VIDEOS</span>
                                 <span class="badge bg-light text-black-50">{{ playlist.playlist_duration }} </span>
                                 <span class="badge bg-light text-black-50">{% if playlist.is_private_on_yt %}PRIVATE{% else %}PUBLIC{% endif %}</span>
+                                {% if playlist.is_yt_mix %}<span class="badge bg-light text-black-50">YT MIX</span>{% endif %}
+
                                 {% if playlist.has_unavailable_videos %}
                                     <span class="badge bg-light text-black-50">
                                         {% if playlist.get_watchable_videos_count == 0 %}
@@ -119,7 +121,7 @@
                                         {% endfor %}
                                     </span>
                                     <a data-bs-toggle="collapse" href="#addTagsCollapse" role="button" aria-expanded="false" aria-controls="addTagsCollapse">
-                                        <span class="badge rounded-pill bg-warning mb-lg-2"><i class="fas fa-plus"></i>{% if playlist_tags.count == 0 %} add a tag{% endif %}</span>
+                                        <span class="badge rounded-pill bg-warning mb-lg-2"><i class="fas fa-plus"></i></span>
                                     </a>
                                 </span>
                                 <div class="collapse" id="addTagsCollapse">
@@ -158,7 +160,7 @@
                                 </div>
                             </h6>
 
-                            <p class="card-text"><small class="text-white-50">Last updated {{ playlist.created_at|naturalday }}</small></p>
+                            <p class="card-text"><small class="text-white-50">Last updated {{ playlist.updated_at|naturalday }}</small></p>
                         </div>
                     </div>
                 </div>
@@ -427,7 +429,7 @@
                                             <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={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" target="_blank">
+                                            <a class="link-dark" href="{% url 'video' playlist_item.video.video_id %}">
                                                 {{ playlist_item.video_position|add:"1" }}. {{ playlist_item.video.name }}
                                             </a>
                                             <br><br>
@@ -448,7 +450,7 @@
 
                                             {% if playlist_item.video.is_unavailable_on_yt or playlist_item.video.was_deleted_on_yt %}
                                                 {{ playlist_item.video_position|add:"1" }}.
-                                                <a class="link-dark" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" target="_blank">
+                                                <a class="link-dark" href="{% url 'video' playlist_item.video.video_id %}">
                                                     {{ playlist_item.video.name|truncatewords:"16" }}
                                                 </a>
                                                 <br>
@@ -457,25 +459,18 @@
                                                 <br><br>
                                             {% else %}
                                                 {{ playlist_item.video_position|add:"1" }}.
-                                                <a class="link-dark" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" target="_blank">
+                                                <a class="link-dark" href="{% url 'video' playlist_item.video.video_id %}">
                                                     {{ 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 playlist_item.video.published_at %}<span class="badge bg-secondary">added to playlist on {{ playlist_item.published_at }}</span>{% endif %}
+                                                <span class="badge bg-info"><i class="fas fa-eye"></i> {% if playlist_item.video.view_count == -1 %}HIDDEN{% else %}{{ playlist_item.video.view_count|intword|intcomma }}{% endif %}</span>
 
                                                 {% if playlist_item.is_duplicate %}<span class="badge bg-primary">duplicate</span>{% endif %}
+                                                {% if playlist_item.video.playlists.count|add:"-1" != 0 %}<span class="badge bg-dark"><a href="{% url 'video' playlist_item.video.video_id %}#found-in" style="text-decoration: none; color: white">found in {{ playlist_item.video.playlists.count|add:"-1" }} other playlists</a></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>
-                                                <span>Found in {{ playlist_item.video.playlists.count|add:"-1" }} other playlists</span>
-                                                <ul>
-                                                    {% for pl in playlist_item.video.playlists.all %}
-                                                        {% if pl.playlist_id != playlist.playlist_id %}
-                                                        <li>{{ pl.name }}</li>
-                                                        {% endif %}
-                                                    {% endfor %}
-                                                </ul>
                                             {% endif %}
 
 
@@ -483,7 +478,7 @@
                                     </div>
                                     <div class="ms-5">
                                         {% 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>
+                                            <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
                                             <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 playlist_item.video.is_favorite %}
@@ -495,13 +490,12 @@
                                             </button>
                                         {% else %}
 
-                                            <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>
+                                            <a class="btn btn-sm btn-info mb-1" type="button" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_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 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> -->
+                                            <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
                                             <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 playlist_item.video.is_favorite %}

+ 215 - 0
apps/main/templates/view_video.html

@@ -0,0 +1,215 @@
+
+{% extends 'base.html' %}
+{% load humanize %}
+{% load static %}
+
+{% block content %}
+    <script src="{% static 'htmx/extensions/class-tools.js' %}" type="application/javascript"></script>
+    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
+
+    <div id="view_video">
+        {% if playlist.has_playlist_changed %}
+
+        {% else %}
+
+
+            <div class="card text-white bg-dark" style="max-width: 100%;">
+                <div class="row g-0">
+                    <div class="col-md-4 p-3">
+                        <img  class="img-fluid rounded-3" src="{{ video.thumbnail_url }}" style="max-width:100%; height: auto;   object-fit: cover;">
+                    </div>
+                    <div class="col-md-8">
+                        <div class="card-body">
+                            <div class="d-flex justify-content-between">
+                                <h2 class="card-title text-white">
+                                    <a href="https://www.youtube.com/watch?v={{ video.video_id }}" target="_blank" style="color: white; text-decoration: none">{{ video.name }}</a>
+                                    <br><small class="h4">{% if video.user_label %}a.k.a <span style="border-bottom: 3px #ffffff dashed;"> {{ video.user_label }}</span>{% endif %}</small>
+                                </h2>
+                                <h4>
+                                    <span id="notice-div">
+                                        {% if video.is_marked_as_watched %}
+                                            <span class="badge bg-success text-white" >
+                                            
+                                                <i class="fas fa-fire-alt me-1"></i> marked watched
+                                            </span>
+                                        {% endif %}
+                                    </span>
+                                    <span id="">
+                                        <span class="badge bg-dark">
+                                            <i class="fas fa-map-pin"></i>
+                                        </span>
+                                    </span>
+                                </h4>
+                            </div>
+                            <h6>by {{ video.channel_name }}</h6>
+                            <p class="card-text">
+                                {% if video.description %}
+                                    <h5 class="overflow-auto" {% if video.description|length > 750 %} style="height: 250px;"{% endif %}>
+                                        {{ video.description|linebreaksbr|urlize  }}
+                                    </h5>
+
+                                {% else %}
+                                    <h5>No description</h5>
+                                {% endif %}
+                            </p>
+
+
+                            <h6 class="h6 text-uppercase overflow-auto text-black-50">
+                                <span class="badge bg-secondary">{{ video.duration }}</span>
+                                {% if video.has_cc %}<span class="badge bg-danger">CC</span>{% endif %}
+                                {% if video.published_at %}<span class="badge bg-secondary">Video uploaded on {{ video.published_at }}</span>{% endif %}
+                                <span class="badge bg-primary text-white"><i class="fas fa-eye"></i> {% if video.view_count == -1 %}HIDDEN{% else %}{{ video.view_count|intcomma }}{% endif %}</span>
+                                <span class="badge bg-warning text-black-50"><i class="fas fa-thumbs-up"></i> {% if video.like_count == -1 %}HIDDEN{% else %}{{ video.like_count|intcomma }}{% endif %}</span>
+                                <span class="badge bg-warning text-black-50"><i class="fas fa-thumbs-down"></i> {% if video.dislike_count == -1 %}HIDDEN{% else %}{{ video.dislike_count|intcomma }}{% endif %}</span>
+                                <span class="badge bg-info text-black-50"><i class="fas fa-comments"></i> {% if video.comment_count == -1 %}HIDDEN{% else %}{{ video.comment_count|intcomma }}{% endif %} </span>
+                                {% if video.is_unavailable_on_yt or video.was_deleted_on_yt %}<span class="badge bg-light text-black-50">UNAVAILABLE</span>{% endif %}
+                                <span class="badge bg-light text-black-50"><a href="#found-in" style="text-decoration: none; color: grey"> Found in {{ video.playlistitem_set.count }} playlists</a></span>
+
+
+                            </h6>
+                            <p class="card-text"><small class="text-white-50">Last updated {{ video.updated_at|naturaltime }}</small></p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+
+            <br>
+
+        {% endif %}
+
+
+    </div>
+
+    <div class="row">
+        <div class="col-6">
+         <iframe width="100%" height="100%" src="//www.youtube.com/embed/{{ video.video_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+        </div>
+        <div class="col-6">
+            <div class="row">
+                <div class="col-6">
+                        <h4>Your notes for this video
+
+                    </h4>
+                </div>
+                <div class="col d-flex justify-content-end pt-2">
+
+                    <span id="notes-save-status" class="text-success">
+                    </span>
+                </div>
+            </div>
+
+            <div >
+                <textarea name="video-notes-text-area"
+                          hx-post="{% url 'video_notes' video.video_id %}"
+                          hx-trigger="keyup changed delay:2s"
+                          hx-target="#notes-save-status"
+                          class="form-control"
+                          id="video-notes-text-area"
+                          placeholder="Enter here"
+                          rows="13">
+                    {{ video.user_notes }}
+                </textarea>
+
+                <div>
+
+
+                </div>
+            </div>
+        </div>
+    </div>
+
+      <br>
+
+    <div class="">
+        <h3><span style="border-bottom: 3px #497ce2 dashed;">Video found in the following playlist(s)</span><i class="fas fa-binoculars ms-2" style="color: #4669d2"></i></h3>
+        <div id="found-in" class="row row-cols-1 row-cols-md-4 g-4 text-dark mt-0" data-masonry='{"percentPosition": true }'>
+            {% for playlist in video.playlists.all %}
+                <div class="col">
+
+                    <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>
+
+                <!--
+        <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">
+                    <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>
+                <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>
+                    {% if playlist.tags.all %}
+                <small>
+                <i class="fas fa-tags fa-sm" style="color: yellow"></i>
+                    {% for tag in playlist.tags.all %}
+                        <span class="badge rounded-pill bg-primary mb-lg-1">
+                            {{ tag.name }}
+                        </span>
+                    {% endfor %}
+                </small>
+                    {% endif %}
+                </div>
+                </a>
+            </div>
+        </div> -->
+                <!--
+            {% if forloop.counter == 3 %}
+                {% if watching.count|add:"-3" != 0 %}
+                <div class="col">
+                    <div class="card">
+                        <a style="background-color: #7e89c2;" href="{% url 'all_playlists' 'watching' %}" class="list-group-item list-group-item-action" aria-current="true">
+                            <div class="card-body">
+
+                                <p class="card-text">
+                                    <h3>+ {{ watching.count|add:"-3" }} more</h3>
+                                </p>
+                             </div>
+                        </a>
+                    </div>
+                </div>
+                {% endif %}
+            {% endif %}
+            -->
+            {% endfor %}
+        </div>
+    </div>
+
+    <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
+
+    <!-- Initialize Quill editor -->
+    <script>
+      var quill = new Quill('#editor', {
+        theme: 'snow'
+      });
+    </script>
+{% endblock %}

+ 5 - 2
apps/main/urls.py

@@ -7,13 +7,16 @@ urlpatterns = [
     # path("", views.index, name='index'),
     # path("login/", views.log_in, name='log_in'),
 
+    ### STUFF RELATED TO INDIVIDUAL VIDEOS
+    path("video/<slug:video_id>", views.view_video, name='video'),
+    path("video/<slug:video_id>/notes", views.video_notes, name='video_notes'),
+
     ### STUFF RELATED TO WHOLE SITE
     path("search", views.search, name="search"),
     path("search/UnTube/", views.search_UnTube, name="search_UnTube"),
 
-    ### STUFF RELATED TO VIDEO(S)
+    ### STUFF RELATED TO VIDEO(S) INSIDE PLAYLISTS
     path("<slug:playlist_id>/<slug:video_id>/video-details", views.view_video, name='video_details'),
-    path("<slug:playlist_id>/<slug:video_id>/video-details/notes", views.video_notes, name='video_notes'),
     path("<slug:playlist_id>/<slug:video_id>/video-details/favorite", views.mark_video_favortie, name='mark_video_favorite'),
     path('<slug:playlist_id>/<slug:video_id>/video-details/watched', views.mark_video_watched, name='mark_video_watched'),
     path("from/<slug:playlist_id>/delete-videos/<slug:command>", views.delete_videos, name='delete_videos'),

+ 45 - 28
apps/main/views.py

@@ -1,9 +1,13 @@
 import datetime
 import random
+
+import bleach
 import pytz
 from django.db.models import Q
 from django.http import HttpResponse
 from django.shortcuts import render, redirect, get_object_or_404
+from django.utils.html import strip_tags
+
 import apps
 from apps.main.models import Playlist, Tag
 from django.contrib.auth.decorators import login_required  # redirects user to settings.LOGIN_URL
@@ -88,27 +92,39 @@ def home(request):
 
 
 @login_required
-def view_video(request, playlist_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}))
+def view_video(request, video_id):
+    if request.user.videos.filter(video_id=video_id).exists():
+        video = request.user.videos.get(video_id=video_id)
+        if video.is_unavailable_on_yt or video.was_deleted_on_yt:
+            messages.error(request, "Video went private/deleted on YouTube!")
+            return redirect('home')
+
+        return render(request, 'view_video.html', {"video": video})
+    else:
+        messages.error(request, "No such video in your UnTube collection!")
+        return redirect('home')
+
 
 
 @login_required
-def video_notes(request, playlist_id, video_id):
-    video = request.user.playlists.get(playlist_id=playlist_id).videos.get(video_id=video_id)
+@require_POST
+def video_notes(request, video_id):
+    print(request.POST)
+    if request.user.videos.filter(video_id=video_id).exists():
+        video = request.user.videos.get(video_id=video_id)
 
-    if request.method == "POST":
         if 'video-notes-text-area' in request.POST:
-            video.user_notes = request.POST['video-notes-text-area']
-            video.save()
-            return HttpResponse(loader.get_template("intercooler/messages.html").render(
-                {"message_type": "success", "message_content": "Saved!"}))
-    else:
-        print("GET VIDEO NOTES")
+            video.user_notes = bleach.clean(request.POST['video-notes-text-area'], tags=['br'])
+            video.save(update_fields=['user_notes', 'user_label'])
+            # messages.success(request, 'Saved!')
 
-    return HttpResponse(loader.get_template("intercooler/video_notes.html").render({"video": video,
-                                                                                    "playlist_id": playlist_id}))
+        return HttpResponse("""
+            <div hx-ext="class-tools">
+                <div classes="add visually-hidden:2s">Saved!</div>
+            </div>
+        """)
+    else:
+        return HttpResponse('No such video in your UnTube collection!')
 
 
 @login_required
@@ -142,7 +158,7 @@ def view_playlist(request, playlist_id):
             playlist.has_new_updates = False
             playlist.save()
 
-    playlist_items = playlist.playlist_items.order_by("video_position")
+    playlist_items = playlist.playlist_items.select_related('video').order_by("video_position")
 
     user_created_tags = Tag.objects.filter(created_by=request.user)
     playlist_tags = playlist.tags.all()
@@ -212,7 +228,7 @@ def all_playlists(request, playlist_type):
                 return redirect('/playlists/home')
 
             if not playlists.exists():
-                messages.warning(request, f"No playlists in {playlists_type}")
+                messages.info(request, f"No playlists in {playlists_type}")
                 return redirect('/playlists/home')
             random_playlist = random.choice(playlists)
             return redirect(f'/playlist/{random_playlist.playlist_id}')
@@ -274,9 +290,9 @@ def order_playlist_by(request, playlist_id, order_by):
     videos_details = ""
 
     if order_by == "all":
-        playlist_items = playlist.playlist_items.order_by("video_position")
+        playlist_items = playlist.playlist_items.select_related('video').order_by("video_position")
     elif order_by == "favorites":
-        playlist_items = playlist.playlist_items.filter(video__is_favorite=True).order_by("video_position")
+        playlist_items = playlist.playlist_items.select_related('video').filter(video__is_favorite=True).order_by("video_position")
         videos_details = "Sorted by Favorites"
         display_text = "No favorites yet!"
     elif order_by == "popularity":
@@ -300,7 +316,7 @@ def order_playlist_by(request, playlist_id, order_by):
         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.playlist_items.filter(video__video_details_modified=True)
+            recently_updated_videos = playlist.playlist_items.select_related('video').filter(video__video_details_modified=True)
 
             for playlist_item in recently_updated_videos:
                 if playlist_item.video.video_details_modified_at + datetime.timedelta(hours=12) < datetime.datetime.now(
@@ -434,9 +450,10 @@ def delete_videos(request, playlist_id, command):
             """)
     elif command == "confirmed":
         print(video_ids)
+        url = f"/from/{playlist_id}/delete-videos/start"
         return HttpResponse(
-            """
-            <div class="spinner-border text-light" role="status" hx-post="/from/""" + playlist_id + """/delete-videos/start" hx-trigger="load" hx-include="[id='video-checkboxes']" hx-target="#delete-videos-confirm-box" hx-vals="{'confirm before deleting': '""" + request.POST['confirm before deleting'] + """'}"></div><hr>
+            f"""
+            <div class="spinner-border text-light" role="status" hx-post="{url}" hx-trigger="load" hx-include="[id='video-checkboxes']" hx-target="#delete-videos-confirm-box"></div><hr>
             """)
     elif command == "start":
         print("Deleting", len(video_ids), "videos")
@@ -573,7 +590,7 @@ def search_UnTube(request):
 
         if search_query != "":
             for playlist in all_playlists:
-                pl_items = playlist.playlist_items.filter(Q(video__name__istartswith=search_query) | Q(video__user_label__istartswith=search_query) & Q(is_duplicate=False))
+                pl_items = playlist.playlist_items.select_related('video').filter(Q(video__name__istartswith=search_query) | Q(video__user_label__istartswith=search_query) & Q(is_duplicate=False))
 
                 if pl_items.exists():
                     for v in pl_items.all():
@@ -584,7 +601,7 @@ def search_UnTube(request):
 
         if search_query != "":
             for playlist in all_playlists:
-                pl_items = playlist.playlist_items.filter(Q(video__name__icontains=search_query) | Q(video__user_label__istartswith=search_query) & Q(is_duplicate=False))
+                pl_items = playlist.playlist_items.select_related('video').filter(Q(video__name__icontains=search_query) | Q(video__user_label__istartswith=search_query) & Q(is_duplicate=False))
 
                 if pl_items.exists():
                     for v in pl_items.all():
@@ -688,10 +705,10 @@ def load_more_videos(request, playlist_id, order_by, page):
 
     playlist_items = None
     if order_by == "all":
-        playlist_items = playlist.playlist_items.order_by("video_position")
+        playlist_items = playlist.playlist_items.select_related('video').order_by("video_position")
         print(f"loading page 1: {playlist_items.count()} videos")
     elif order_by == "favorites":
-        playlist_items = playlist.playlist_items.filter(video__is_favorite=True).order_by("video_position")
+        playlist_items = playlist.playlist_items.select_related('video').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":
@@ -705,7 +722,7 @@ def load_more_videos(request, playlist_id, order_by, page):
     elif order_by == 'new-updates':
         playlist_items = []
         if playlist.has_new_updates:
-            recently_updated_videos = playlist.playlist_items.filter(video__video_details_modified=True)
+            recently_updated_videos = playlist.playlist_items.select_related('video').filter(video__video_details_modified=True)
 
             for playlist_item in recently_updated_videos:
                 if playlist_item.video.video_details_modified_at + datetime.timedelta(hours=12) < datetime.datetime.now(
@@ -896,7 +913,7 @@ def update_playlist(request, playlist_id, type):
             playlist_changed_text.append(f"\n{len(deleted_playlist_item_ids)} deleted")
 
         for playlist_item_id in deleted_playlist_item_ids:
-            playlist_item = playlist.playlist_items.get(playlist_item_id=playlist_item_id)
+            playlist_item = playlist.playlist_items.select_related('video').get(playlist_item_id=playlist_item_id)
             playlist_changed_text.append(f"--> {playlist_item.video.name}")
             playlist_item.delete()
 

+ 1 - 2
templates/base.html

@@ -195,7 +195,7 @@
                 <main class="ms-lg-auto px-lg-5">
                 {% if messages %}
                     {% for message in messages %}
-                      <div class="alert alert-success alert-dismissible fade show" role="alert">
+                      <div class="alert alert-{% if message.tags == "error" %}danger{% else %}{{ message.tags }}{% endif %} alert-dismissible fade show" role="alert">
                           {{ message }}
                           <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                       </div>
@@ -216,7 +216,6 @@
         <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">