فهرست منبع

optimized code and redesigned search

sleepytaco 4 سال پیش
والد
کامیت
ac60f61e27
38فایلهای تغییر یافته به همراه783 افزوده شده و 1678 حذف شده
  1. 2 1
      UnTube/settings.py
  2. 3 1
      UnTube/urls.py
  3. 1 1
      apps/charts/views.py
  4. 21 0
      apps/main/migrations/0043_auto_20210723_2326.py
  5. 34 9
      apps/main/models.py
  6. 0 4
      apps/main/static/BackgroundCheck/BackgroundCheck.min.js
  7. 0 427
      apps/main/static/Skeleton-2.0.4/css/normalize.css
  8. 0 418
      apps/main/static/Skeleton-2.0.4/css/skeleton.css
  9. 0 0
      apps/main/static/choices.js/choices.min.css
  10. 1 0
      apps/main/static/choices.js/choices.min.js
  11. 6 3
      apps/main/templates/all_playlists.html
  12. 37 174
      apps/main/templates/favorites.html
  13. 103 197
      apps/main/templates/home.html
  14. 0 0
      apps/main/templates/intercooler/playlist_items.html
  15. 106 70
      apps/main/templates/intercooler/playlists.html
  16. 0 119
      apps/main/templates/intercooler/search_untube_results.html
  17. 46 0
      apps/main/templates/intercooler/video_cards.html
  18. 21 5
      apps/main/templates/view_playlist.html
  19. 14 8
      apps/main/templates/view_playlist_settings.html
  20. 25 12
      apps/main/templates/view_video.html
  21. 2 6
      apps/main/urls.py
  22. 47 165
      apps/main/views.py
  23. 0 0
      apps/search/__init__.py
  24. 3 0
      apps/search/admin.py
  25. 6 0
      apps/search/apps.py
  26. 0 0
      apps/search/migrations/__init__.py
  27. 3 0
      apps/search/models.py
  28. 0 0
      apps/search/serializers.py
  29. 32 0
      apps/search/templates/intercooler/search_untube_results.html
  30. 53 20
      apps/search/templates/search_untube_page.html
  31. 3 0
      apps/search/tests.py
  32. 10 0
      apps/search/urls.py
  33. 118 0
      apps/search/views.py
  34. 18 0
      apps/users/migrations/0010_profile_enable_gradient_bg.py
  35. 12 0
      apps/users/models.py
  36. 4 2
      apps/users/templates/intercooler/progress_bar.html
  37. 47 29
      apps/users/views.py
  38. 5 7
      templates/base.html

+ 2 - 1
UnTube/settings.py

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

+ 3 - 1
UnTube/urls.py

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

+ 1 - 1
apps/charts/views.py

@@ -28,7 +28,7 @@ def overall_channels_distribution(request):
     videos = request.user.videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=False))
     videos = request.user.videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=False))
 
 
     queryset = videos.values(
     queryset = videos.values(
-        'channel_name').annotate(channel_videos_count=Count('video_id'))
+        'channel_name').annotate(channel_videos_count=Count('video_id')).order_by('-channel_videos_count')[:100]
 
 
     for entry in queryset:
     for entry in queryset:
         labels.append(entry['channel_name'])
         labels.append(entry['channel_name'])

+ 21 - 0
apps/main/migrations/0043_auto_20210723_2326.py

@@ -0,0 +1,21 @@
+# Generated by Django 3.2.3 on 2021-07-24 04:26
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0042_auto_20210722_2040'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='playlist',
+            name='is_pinned',
+        ),
+        migrations.RemoveField(
+            model_name='video',
+            name='is_pinned',
+        ),
+    ]

+ 34 - 9
apps/main/models.py

@@ -14,6 +14,10 @@ import googleapiclient.errors
 from django.db.models import Q, Sum
 from django.db.models import Q, Sum
 
 
 
 
+def get_message_from_httperror(e):
+    return e.error_details[0]['message']
+
+
 class PlaylistManager(models.Manager):
 class PlaylistManager(models.Manager):
     def getCredentials(self, user):
     def getCredentials(self, user):
         credentials = Credentials(
         credentials = Credentials(
@@ -38,8 +42,11 @@ class PlaylistManager(models.Manager):
 
 
         return credentials
         return credentials
 
 
-    def getPlaylistId(self, video_link):
-        temp = video_link.split("?")[-1].split("&")
+    def getPlaylistId(self, playlist_link):
+        if "?" not in playlist_link:
+            return playlist_link
+
+        temp = playlist_link.split("?")[-1].split("&")
 
 
         for el in temp:
         for el in temp:
             if "list=" in el:
             if "list=" in el:
@@ -83,6 +90,7 @@ class PlaylistManager(models.Manager):
         result = {"status": 0,
         result = {"status": 0,
                   "num_of_playlists": 0,
                   "num_of_playlists": 0,
                   "first_playlist_name": "N/A",
                   "first_playlist_name": "N/A",
+                  "error_message": "",
                   "playlist_ids": []}
                   "playlist_ids": []}
 
 
         credentials = self.getCredentials(user)
         credentials = self.getCredentials(user)
@@ -106,10 +114,11 @@ class PlaylistManager(models.Manager):
             # execute the above request, and store the response
             # execute the above request, and store the response
             try:
             try:
                 pl_response = pl_request.execute()
                 pl_response = pl_request.execute()
-            except googleapiclient.errors.HttpError:
+            except googleapiclient.errors.HttpError as e:
                 print("YouTube channel not found if mine=True")
                 print("YouTube channel not found if mine=True")
                 print("YouTube playlist not found if id=playlist_id")
                 print("YouTube playlist not found if id=playlist_id")
                 result["status"] = -1
                 result["status"] = -1
+                result["error_message"] = get_message_from_httperror(e)
                 return result
                 return result
 
 
             print(pl_response)
             print(pl_response)
@@ -143,7 +152,7 @@ class PlaylistManager(models.Manager):
             # check if this playlist already exists in user's untube collection
             # check if this playlist already exists in user's untube collection
             if user.playlists.filter(playlist_id=playlist_id).exists():
             if user.playlists.filter(playlist_id=playlist_id).exists():
                 playlist = user.playlists.get(playlist_id=playlist_id)
                 playlist = user.playlists.get(playlist_id=playlist_id)
-                print(f"PLAYLIST {playlist.name} ALREADY EXISTS IN DB")
+                print(f"PLAYLIST {playlist.name} ({playlist_id}) ALREADY EXISTS IN DB")
 
 
                 # POSSIBLE CASES:
                 # POSSIBLE CASES:
                 # 1. PLAYLIST HAS DUPLICATE VIDEOS, DELETED VIDS, UNAVAILABLE VIDS
                 # 1. PLAYLIST HAS DUPLICATE VIDEOS, DELETED VIDS, UNAVAILABLE VIDS
@@ -157,6 +166,8 @@ class PlaylistManager(models.Manager):
                     result["status"] = -3
                     result["status"] = -3
                     return result
                     return result
             else:  # no such playlist in database
             else:  # no such playlist in database
+                print(f"CREATING {item['snippet']['title']} ({playlist_id})")
+
                 ### MAKE THE PLAYLIST AND LINK IT TO CURRENT_USER
                 ### MAKE THE PLAYLIST AND LINK IT TO CURRENT_USER
                 playlist = Playlist(  # create the playlist and link it to current user
                 playlist = Playlist(  # create the playlist and link it to current user
                     playlist_id=playlist_id,
                     playlist_id=playlist_id,
@@ -250,7 +261,12 @@ class PlaylistManager(models.Manager):
                         video=video
                         video=video
                     )
                     )
                     playlist_item.save()
                     playlist_item.save()
+
                 else:  # video found in user's db
                 else:  # video found in user's db
+                    if playlist.playlist_items.filter(playlist_item_id=playlist_item_id).exists():
+                        print("PLAYLIST ITEM ALREADY EXISTS")
+                        continue
+
                     video = user.videos.get(video_id=video_id)
                     video = user.videos.get(video_id=video_id)
 
 
                     # if video already in playlist.videos
                     # if video already in playlist.videos
@@ -469,7 +485,11 @@ class PlaylistManager(models.Manager):
                 )
                 )
 
 
                 # execute the above request, and store the response
                 # execute the above request, and store the response
-                pl_response = pl_request.execute()
+                try:
+                    pl_response = pl_request.execute()
+                except googleapiclient.errors.HttpError as e:
+                    if e.status_code == 404:  # playlist not found
+                        return [-1, "Playlist not found!"]
 
 
                 for item in pl_response['items']:
                 for item in pl_response['items']:
                     playlist_item_id = item['id']
                     playlist_item_id = item['id']
@@ -908,12 +928,17 @@ class PlaylistManager(models.Manager):
                 # playlistNotFound  (404)
                 # playlistNotFound  (404)
                 # playlistOperationUnsupported (400)
                 # playlistOperationUnsupported (400)
                 print(e.error_details, e.status_code)
                 print(e.error_details, e.status_code)
-                return -1
+                return [-1, get_message_from_httperror(e), e.status_code]
 
 
             # playlistItem was successfully deleted if no HttpError, so delete it from db
             # playlistItem was successfully deleted if no HttpError, so delete it from db
+            video_ids = [video.video_id for video in playlist.videos.all()]
             playlist.delete()
             playlist.delete()
+            for video_id in video_ids:
+                video = user.videos.get(video_id=video_id)
+                if video.playlists.all().count() == 0:
+                    video.delete()
 
 
-        return 0
+        return [0]
 
 
     def deletePlaylistItems(self, user, playlist_id, playlist_item_ids):
     def deletePlaylistItems(self, user, playlist_id, playlist_item_ids):
         """
         """
@@ -949,11 +974,11 @@ class PlaylistManager(models.Manager):
 
 
                 if not playlist.playlist_items.filter(video__video_id=video.video_id).exists():
                 if not playlist.playlist_items.filter(video__video_id=video.video_id).exists():
                     playlist.videos.remove(video)
                     playlist.videos.remove(video)
+                    if video.playlists.all().count() == 0: # also delete the video if it is not found in other playlists
+                        video.delete()
 
 
-                # video = playlist.videos.get(playlist_item_id=playlist_item_id)
                 new_playlist_video_count -= 1
                 new_playlist_video_count -= 1
                 new_playlist_duration_in_seconds -= video.duration_in_seconds
                 new_playlist_duration_in_seconds -= video.duration_in_seconds
-                # video.delete()
 
 
         playlist.video_count = new_playlist_video_count
         playlist.video_count = new_playlist_video_count
         playlist.playlist_duration_in_seconds = new_playlist_duration_in_seconds
         playlist.playlist_duration_in_seconds = new_playlist_duration_in_seconds

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 4
apps/main/static/BackgroundCheck/BackgroundCheck.min.js


+ 0 - 427
apps/main/static/Skeleton-2.0.4/css/normalize.css

@@ -1,427 +0,0 @@
-/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
-
-/**
- * 1. Set default font family to sans-serif.
- * 2. Prevent iOS text size adjust after orientation change, without disabling
- *    user zoom.
- */
-
-html {
-  font-family: sans-serif; /* 1 */
-  -ms-text-size-adjust: 100%; /* 2 */
-  -webkit-text-size-adjust: 100%; /* 2 */
-}
-
-/**
- * Remove default margin.
- */
-
-body {
-  margin: 0;
-}
-
-/* HTML5 display definitions
-   ========================================================================== */
-
-/**
- * Correct `block` display not defined for any HTML5 element in IE 8/9.
- * Correct `block` display not defined for `details` or `summary` in IE 10/11
- * and Firefox.
- * Correct `block` display not defined for `main` in IE 11.
- */
-
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-main,
-menu,
-nav,
-section,
-summary {
-  display: block;
-}
-
-/**
- * 1. Correct `inline-block` display not defined in IE 8/9.
- * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
- */
-
-audio,
-canvas,
-progress,
-video {
-  display: inline-block; /* 1 */
-  vertical-align: baseline; /* 2 */
-}
-
-/**
- * Prevent modern browsers from displaying `audio` without controls.
- * Remove excess height in iOS 5 devices.
- */
-
-audio:not([controls]) {
-  display: none;
-  height: 0;
-}
-
-/**
- * Address `[hidden]` styling not present in IE 8/9/10.
- * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
- */
-
-[hidden],
-template {
-  display: none;
-}
-
-/* Links
-   ========================================================================== */
-
-/**
- * Remove the gray background color from active links in IE 10.
- */
-
-a {
-  background-color: transparent;
-}
-
-/**
- * Improve readability when focused and also mouse hovered in all browsers.
- */
-
-a:active,
-a:hover {
-  outline: 0;
-}
-
-/* Text-level semantics
-   ========================================================================== */
-
-/**
- * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
- */
-
-abbr[title] {
-  border-bottom: 1px dotted;
-}
-
-/**
- * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
- */
-
-b,
-strong {
-  font-weight: bold;
-}
-
-/**
- * Address styling not present in Safari and Chrome.
- */
-
-dfn {
-  font-style: italic;
-}
-
-/**
- * Address variable `h1` font-size and margin within `section` and `article`
- * contexts in Firefox 4+, Safari, and Chrome.
- */
-
-h1 {
-  font-size: 2em;
-  margin: 0.67em 0;
-}
-
-/**
- * Address styling not present in IE 8/9.
- */
-
-mark {
-  background: #ff0;
-  color: #000;
-}
-
-/**
- * Address inconsistent and variable font size in all browsers.
- */
-
-small {
-  font-size: 80%;
-}
-
-/**
- * Prevent `sub` and `sup` affecting `line-height` in all browsers.
- */
-
-sub,
-sup {
-  font-size: 75%;
-  line-height: 0;
-  position: relative;
-  vertical-align: baseline;
-}
-
-sup {
-  top: -0.5em;
-}
-
-sub {
-  bottom: -0.25em;
-}
-
-/* Embedded content
-   ========================================================================== */
-
-/**
- * Remove border when inside `a` element in IE 8/9/10.
- */
-
-img {
-  border: 0;
-}
-
-/**
- * Correct overflow not hidden in IE 9/10/11.
- */
-
-svg:not(:root) {
-  overflow: hidden;
-}
-
-/* Grouping content
-   ========================================================================== */
-
-/**
- * Address margin not present in IE 8/9 and Safari.
- */
-
-figure {
-  margin: 1em 40px;
-}
-
-/**
- * Address differences between Firefox and other browsers.
- */
-
-hr {
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-  height: 0;
-}
-
-/**
- * Contain overflow in all browsers.
- */
-
-pre {
-  overflow: auto;
-}
-
-/**
- * Address odd `em`-unit font size rendering in all browsers.
- */
-
-code,
-kbd,
-pre,
-samp {
-  font-family: monospace, monospace;
-  font-size: 1em;
-}
-
-/* Forms
-   ========================================================================== */
-
-/**
- * Known limitation: by default, Chrome and Safari on OS X allow very limited
- * styling of `select`, unless a `border` property is set.
- */
-
-/**
- * 1. Correct color not being inherited.
- *    Known issue: affects color of disabled elements.
- * 2. Correct font properties not being inherited.
- * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
- */
-
-button,
-input,
-optgroup,
-select,
-textarea {
-  color: inherit; /* 1 */
-  font: inherit; /* 2 */
-  margin: 0; /* 3 */
-}
-
-/**
- * Address `overflow` set to `hidden` in IE 8/9/10/11.
- */
-
-button {
-  overflow: visible;
-}
-
-/**
- * Address inconsistent `text-transform` inheritance for `button` and `select`.
- * All other form control elements do not inherit `text-transform` values.
- * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
- * Correct `select` style inheritance in Firefox.
- */
-
-button,
-select {
-  text-transform: none;
-}
-
-/**
- * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
- *    and `video` controls.
- * 2. Correct inability to style clickable `input` types in iOS.
- * 3. Improve usability and consistency of cursor style between image-type
- *    `input` and others.
- */
-
-button,
-html input[type="button"], /* 1 */
-input[type="reset"],
-input[type="submit"] {
-  -webkit-appearance: button; /* 2 */
-  cursor: pointer; /* 3 */
-}
-
-/**
- * Re-set default cursor for disabled elements.
- */
-
-button[disabled],
-html input[disabled] {
-  cursor: default;
-}
-
-/**
- * Remove inner padding and border in Firefox 4+.
- */
-
-button::-moz-focus-inner,
-input::-moz-focus-inner {
-  border: 0;
-  padding: 0;
-}
-
-/**
- * Address Firefox 4+ setting `line-height` on `input` using `!important` in
- * the UA stylesheet.
- */
-
-input {
-  line-height: normal;
-}
-
-/**
- * It's recommended that you don't attempt to style these elements.
- * Firefox's implementation doesn't respect box-sizing, padding, or width.
- *
- * 1. Address box sizing set to `content-box` in IE 8/9/10.
- * 2. Remove excess padding in IE 8/9/10.
- */
-
-input[type="checkbox"],
-input[type="radio"] {
-  box-sizing: border-box; /* 1 */
-  padding: 0; /* 2 */
-}
-
-/**
- * Fix the cursor style for Chrome's increment/decrement buttons. For certain
- * `font-size` values of the `input`, it causes the cursor style of the
- * decrement button to change from `default` to `text`.
- */
-
-input[type="number"]::-webkit-inner-spin-button,
-input[type="number"]::-webkit-outer-spin-button {
-  height: auto;
-}
-
-/**
- * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
- * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
- *    (include `-moz` to future-proof).
- */
-
-input[type="search"] {
-  -webkit-appearance: textfield; /* 1 */
-  -moz-box-sizing: content-box;
-  -webkit-box-sizing: content-box; /* 2 */
-  box-sizing: content-box;
-}
-
-/**
- * Remove inner padding and search cancel button in Safari and Chrome on OS X.
- * Safari (but not Chrome) clips the cancel button when the search input has
- * padding (and `textfield` appearance).
- */
-
-input[type="search"]::-webkit-search-cancel-button,
-input[type="search"]::-webkit-search-decoration {
-  -webkit-appearance: none;
-}
-
-/**
- * Define consistent border, margin, and padding.
- */
-
-fieldset {
-  border: 1px solid #c0c0c0;
-  margin: 0 2px;
-  padding: 0.35em 0.625em 0.75em;
-}
-
-/**
- * 1. Correct `color` not being inherited in IE 8/9/10/11.
- * 2. Remove padding so people aren't caught out if they zero out fieldsets.
- */
-
-legend {
-  border: 0; /* 1 */
-  padding: 0; /* 2 */
-}
-
-/**
- * Remove default vertical scrollbar in IE 8/9/10/11.
- */
-
-textarea {
-  overflow: auto;
-}
-
-/**
- * Don't inherit the `font-weight` (applied by a rule above).
- * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
- */
-
-optgroup {
-  font-weight: bold;
-}
-
-/* Tables
-   ========================================================================== */
-
-/**
- * Remove most spacing between table cells.
- */
-
-table {
-  border-collapse: collapse;
-  border-spacing: 0;
-}
-
-td,
-th {
-  padding: 0;
-}

+ 0 - 418
apps/main/static/Skeleton-2.0.4/css/skeleton.css

@@ -1,418 +0,0 @@
-/*
-* Skeleton V2.0.4
-* Copyright 2014, Dave Gamache
-* www.getskeleton.com
-* Free to use under the MIT license.
-* http://www.opensource.org/licenses/mit-license.php
-* 12/29/2014
-*/
-
-
-/* Table of contents
-––––––––––––––––––––––––––––––––––––––––––––––––––
-- Grid
-- Base Styles
-- Typography
-- Links
-- Buttons
-- Forms
-- Lists
-- Code
-- Tables
-- Spacing
-- Utilities
-- Clearing
-- Media Queries
-*/
-
-
-/* Grid
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-.container {
-  position: relative;
-  width: 100%;
-  max-width: 960px;
-  margin: 0 auto;
-  padding: 0 20px;
-  box-sizing: border-box; }
-.column,
-.columns {
-  width: 100%;
-  float: left;
-  box-sizing: border-box; }
-
-/* For devices larger than 400px */
-@media (min-width: 400px) {
-  .container {
-    width: 85%;
-    padding: 0; }
-}
-
-/* For devices larger than 550px */
-@media (min-width: 550px) {
-  .container {
-    width: 80%; }
-  .column,
-  .columns {
-    margin-left: 4%; }
-  .column:first-child,
-  .columns:first-child {
-    margin-left: 0; }
-
-  .one.column,
-  .one.columns                    { width: 4.66666666667%; }
-  .two.columns                    { width: 13.3333333333%; }
-  .three.columns                  { width: 22%;            }
-  .four.columns                   { width: 30.6666666667%; }
-  .five.columns                   { width: 39.3333333333%; }
-  .six.columns                    { width: 48%;            }
-  .seven.columns                  { width: 56.6666666667%; }
-  .eight.columns                  { width: 65.3333333333%; }
-  .nine.columns                   { width: 74.0%;          }
-  .ten.columns                    { width: 82.6666666667%; }
-  .eleven.columns                 { width: 91.3333333333%; }
-  .twelve.columns                 { width: 100%; margin-left: 0; }
-
-  .one-third.column               { width: 30.6666666667%; }
-  .two-thirds.column              { width: 65.3333333333%; }
-
-  .one-half.column                { width: 48%; }
-
-  /* Offsets */
-  .offset-by-one.column,
-  .offset-by-one.columns          { margin-left: 8.66666666667%; }
-  .offset-by-two.column,
-  .offset-by-two.columns          { margin-left: 17.3333333333%; }
-  .offset-by-three.column,
-  .offset-by-three.columns        { margin-left: 26%;            }
-  .offset-by-four.column,
-  .offset-by-four.columns         { margin-left: 34.6666666667%; }
-  .offset-by-five.column,
-  .offset-by-five.columns         { margin-left: 43.3333333333%; }
-  .offset-by-six.column,
-  .offset-by-six.columns          { margin-left: 52%;            }
-  .offset-by-seven.column,
-  .offset-by-seven.columns        { margin-left: 60.6666666667%; }
-  .offset-by-eight.column,
-  .offset-by-eight.columns        { margin-left: 69.3333333333%; }
-  .offset-by-nine.column,
-  .offset-by-nine.columns         { margin-left: 78.0%;          }
-  .offset-by-ten.column,
-  .offset-by-ten.columns          { margin-left: 86.6666666667%; }
-  .offset-by-eleven.column,
-  .offset-by-eleven.columns       { margin-left: 95.3333333333%; }
-
-  .offset-by-one-third.column,
-  .offset-by-one-third.columns    { margin-left: 34.6666666667%; }
-  .offset-by-two-thirds.column,
-  .offset-by-two-thirds.columns   { margin-left: 69.3333333333%; }
-
-  .offset-by-one-half.column,
-  .offset-by-one-half.columns     { margin-left: 52%; }
-
-}
-
-
-/* Base Styles
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-/* NOTE
-html is set to 62.5% so that all the REM measurements throughout Skeleton
-are based on 10px sizing. So basically 1.5rem = 15px :) */
-html {
-  font-size: 62.5%; }
-body {
-  font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
-  line-height: 1.6;
-  font-weight: 400;
-  font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
-  color: #222; }
-
-
-/* Typography
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-h1, h2, h3, h4, h5, h6 {
-  margin-top: 0;
-  margin-bottom: 2rem;
-  font-weight: 300; }
-h1 { font-size: 4.0rem; line-height: 1.2;  letter-spacing: -.1rem;}
-h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
-h3 { font-size: 3.0rem; line-height: 1.3;  letter-spacing: -.1rem; }
-h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
-h5 { font-size: 1.8rem; line-height: 1.5;  letter-spacing: -.05rem; }
-h6 { font-size: 1.5rem; line-height: 1.6;  letter-spacing: 0; }
-
-/* Larger than phablet */
-@media (min-width: 550px) {
-  h1 { font-size: 5.0rem; }
-  h2 { font-size: 4.2rem; }
-  h3 { font-size: 3.6rem; }
-  h4 { font-size: 3.0rem; }
-  h5 { font-size: 2.4rem; }
-  h6 { font-size: 1.5rem; }
-}
-
-p {
-  margin-top: 0; }
-
-
-/* Links
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-a {
-  color: #1EAEDB; }
-a:hover {
-  color: #0FA0CE; }
-
-
-/* Buttons
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-.button,
-button,
-input[type="submit"],
-input[type="reset"],
-input[type="button"] {
-  display: inline-block;
-  height: 38px;
-  padding: 0 30px;
-  color: #555;
-  text-align: center;
-  font-size: 11px;
-  font-weight: 600;
-  line-height: 38px;
-  letter-spacing: .1rem;
-  text-transform: uppercase;
-  text-decoration: none;
-  white-space: nowrap;
-  background-color: transparent;
-  border-radius: 4px;
-  border: 1px solid #bbb;
-  cursor: pointer;
-  box-sizing: border-box; }
-.button:hover,
-button:hover,
-input[type="submit"]:hover,
-input[type="reset"]:hover,
-input[type="button"]:hover,
-.button:focus,
-button:focus,
-input[type="submit"]:focus,
-input[type="reset"]:focus,
-input[type="button"]:focus {
-  color: #333;
-  border-color: #888;
-  outline: 0; }
-.button.button-primary,
-button.button-primary,
-input[type="submit"].button-primary,
-input[type="reset"].button-primary,
-input[type="button"].button-primary {
-  color: #FFF;
-  background-color: #33C3F0;
-  border-color: #33C3F0; }
-.button.button-primary:hover,
-button.button-primary:hover,
-input[type="submit"].button-primary:hover,
-input[type="reset"].button-primary:hover,
-input[type="button"].button-primary:hover,
-.button.button-primary:focus,
-button.button-primary:focus,
-input[type="submit"].button-primary:focus,
-input[type="reset"].button-primary:focus,
-input[type="button"].button-primary:focus {
-  color: #FFF;
-  background-color: #1EAEDB;
-  border-color: #1EAEDB; }
-
-
-/* Forms
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-input[type="email"],
-input[type="number"],
-input[type="search"],
-input[type="text"],
-input[type="tel"],
-input[type="url"],
-input[type="password"],
-textarea,
-select {
-  height: 38px;
-  padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
-  background-color: #fff;
-  border: 1px solid #D1D1D1;
-  border-radius: 4px;
-  box-shadow: none;
-  box-sizing: border-box; }
-/* Removes awkward default styles on some inputs for iOS */
-input[type="email"],
-input[type="number"],
-input[type="search"],
-input[type="text"],
-input[type="tel"],
-input[type="url"],
-input[type="password"],
-textarea {
-  -webkit-appearance: none;
-     -moz-appearance: none;
-          appearance: none; }
-textarea {
-  min-height: 65px;
-  padding-top: 6px;
-  padding-bottom: 6px; }
-input[type="email"]:focus,
-input[type="number"]:focus,
-input[type="search"]:focus,
-input[type="text"]:focus,
-input[type="tel"]:focus,
-input[type="url"]:focus,
-input[type="password"]:focus,
-textarea:focus,
-select:focus {
-  border: 1px solid #33C3F0;
-  outline: 0; }
-label,
-legend {
-  display: block;
-  margin-bottom: .5rem;
-  font-weight: 600; }
-fieldset {
-  padding: 0;
-  border-width: 0; }
-input[type="checkbox"],
-input[type="radio"] {
-  display: inline; }
-label > .label-body {
-  display: inline-block;
-  margin-left: .5rem;
-  font-weight: normal; }
-
-
-/* Lists
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-ul {
-  list-style: circle inside; }
-ol {
-  list-style: decimal inside; }
-ol, ul {
-  padding-left: 0;
-  margin-top: 0; }
-ul ul,
-ul ol,
-ol ol,
-ol ul {
-  margin: 1.5rem 0 1.5rem 3rem;
-  font-size: 90%; }
-li {
-  margin-bottom: 1rem; }
-
-
-/* Code
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-code {
-  padding: .2rem .5rem;
-  margin: 0 .2rem;
-  font-size: 90%;
-  white-space: nowrap;
-  background: #F1F1F1;
-  border: 1px solid #E1E1E1;
-  border-radius: 4px; }
-pre > code {
-  display: block;
-  padding: 1rem 1.5rem;
-  white-space: pre; }
-
-
-/* Tables
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-th,
-td {
-  padding: 12px 15px;
-  text-align: left;
-  border-bottom: 1px solid #E1E1E1; }
-th:first-child,
-td:first-child {
-  padding-left: 0; }
-th:last-child,
-td:last-child {
-  padding-right: 0; }
-
-
-/* Spacing
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-button,
-.button {
-  margin-bottom: 1rem; }
-input,
-textarea,
-select,
-fieldset {
-  margin-bottom: 1.5rem; }
-pre,
-blockquote,
-dl,
-figure,
-table,
-p,
-ul,
-ol,
-form {
-  margin-bottom: 2.5rem; }
-
-
-/* Utilities
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-.u-full-width {
-  width: 100%;
-  box-sizing: border-box; }
-.u-max-full-width {
-  max-width: 100%;
-  box-sizing: border-box; }
-.u-pull-right {
-  float: right; }
-.u-pull-left {
-  float: left; }
-
-
-/* Misc
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-hr {
-  margin-top: 3rem;
-  margin-bottom: 3.5rem;
-  border-width: 0;
-  border-top: 1px solid #E1E1E1; }
-
-
-/* Clearing
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-
-/* Self Clearing Goodness */
-.container:after,
-.row:after,
-.u-cf {
-  content: "";
-  display: table;
-  clear: both; }
-
-
-/* Media Queries
-–––––––––––––––––––––––––––––––––––––––––––––––––– */
-/*
-Note: The best way to structure the use of media queries is to create the queries
-near the relevant code. For example, if you wanted to change the styles for buttons
-on small devices, paste the mobile query code up in the buttons section and style it
-there.
-*/
-
-
-/* Larger than mobile */
-@media (min-width: 400px) {}
-
-/* Larger than phablet (also point when grid becomes active) */
-@media (min-width: 550px) {}
-
-/* Larger than tablet */
-@media (min-width: 750px) {}
-
-/* Larger than desktop */
-@media (min-width: 1000px) {}
-
-/* Larger than Desktop HD */
-@media (min-width: 1200px) {}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
apps/main/static/choices.js/choices.min.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
apps/main/static/choices.js/choices.min.js


+ 6 - 3
apps/main/templates/all_playlists.html

@@ -36,11 +36,14 @@
                    hx-post="{% url 'search_playlists' playlist_type %}"
                    hx-post="{% url 'search_playlists' playlist_type %}"
                    hx-trigger="keyup changed delay:700ms"
                    hx-trigger="keyup changed delay:700ms"
                    hx-target="#search-results"
                    hx-target="#search-results"
-                   hx-indicator=".htmx-indicator">
+                   hx-indicator=".htmx-indicator"
+                    aria-describedby="searchHelp">
+            <div id="searchHelp" class="form-text">For a more extensive playlist search, click the search icon in the nav bar.</div>
           <br>
           <br>
 
 
-        <div id="search-results">
-            {% include 'intercooler/playlists.html' %}
+
+        <div id="search-results" class="row row-cols-1 row-cols-md-4 g-4">
+            {% include 'intercooler/playlists.html' with show_controls=True %}
         </div>
         </div>
         {% else %}
         {% else %}
               <div class="card bg-dark text-white mb-3">
               <div class="card bg-dark text-white mb-3">

+ 37 - 174
apps/main/templates/favorites.html

@@ -2,181 +2,44 @@
 {% load humanize %}
 {% load humanize %}
 {% block content %}
 {% block content %}
 
 
-        <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
-            <h1 class="h2">Favorite Playlists
-                <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span>
-                {% if playlists %}
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+        <h1 class="h2">Favorite Playlists
+            <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span>
+            {% if playlists %}
                 <a href="{% url 'all_playlists' 'favorites' %}" style="text-decoration: none; color: black"><i class="fas fa-search"></i></a>
                 <a href="{% url 'all_playlists' 'favorites' %}" style="text-decoration: none; color: black"><i class="fas fa-search"></i></a>
-                {% endif %}
-            </h1>
+            {% endif %}
+        </h1>
+    </div>
+
+    <div>
+        <div class="row row-cols-1 row-cols-md-4 g-4">
+            {% if playlists %}
+                {% include 'intercooler/playlists.html' with playlists=playlists %}
+            {% else %}
+                <h5 class="text-dark align-content-center">No playlists marked favorite :(</h5>
+            {% endif %}
         </div>
         </div>
-
-        <div>
-            <div class="row row-cols-1 row-cols-md-3 g-4">
-                    {% if playlists %}
-                    {% for playlist in playlists %}
-                    <div class="col">
-                        <div class="card" style="background-color: #515355;">
-                            <div style="background-color: #1A4464;" class="list-group-item list-group-item-action" aria-current="true">
-
-                                <div class="card-body">
-
-                                    <h5 class="card-title text-white">
-                                        <a style="text-decoration: none; color: white" href="{% url 'playlist' playlist.playlist_id %}">{{ playlist.name }}</a>
-                                    </h5>
-
-                                    <p class="card-text text-uppercase">
-                                        {% if playlist.is_user_owned %}<span class="badge bg-light text-black-50">OWNED</span>{% else %}<span class="badge bg-light text-black-50">IMPORTED</span>{% endif %}
-                                        {% if playlist.is_private_on_yt %}<span class="badge bg-secondary text-white">Private</span> {% endif %}
-                                        <span class="badge bg-warning text-black-50">{{ playlist.video_count }} videos</span>
-                                        <span class="badge bg-dark text-white-50">{{ playlist.playlist_duration }} </span>
-                                        {% if playlist.is_from_yt %}<span class="badge bg-danger text-black-50">YT</span> {% endif %}
-                                        {% if playlist.marked_as == "watching" %}<span class="badge bg-primary text-white">WATCHING</span>{% endif %}
-
-                                    </p>
-
-                                    {% if playlist.tags.all %}
-                                        <p class="card-text">
-                                        <span class="d-flex justify-content-start flex-wrap">
-                                        <small>
-                                        <span style="color: #eed868;" class="me-lg-1 mb-lg-1">
-                                            <i class="fas fa-tags"></i>
-                                        </span>
-                                        </small>
-                                        {% for tag in playlist.tags.all %}
-                                            <span class="badge rounded-pill bg-info mb-lg-1 me-lg-1 text-black-50">
-                                                {{ tag.name }}
-                                            </span>
-                                        {% endfor %}
-                                        </span>
-                                        </p>
-                                    {% endif %}
-
-                                    <span class="d-flex justify-content-center">
-        <a href="https://www.youtube.com/playlist?list={{ playlist.playlist_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>
-
-                                    <button class="btn btn-dark" type="button" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'favorite' %}" hx-target="#playlist-{{ forloop.counter }}-fav">
-                                    <div id="playlist-{{ forloop.counter }}-fav">
-                                        {% if playlist.is_favorite %}
-                                            <i class="fas fa-star" style="color: #fafa06"></i>
-                                        {% else %}
-                                            <i class="far fa-star"></i>
-                                        {% endif %}
-                                    </div>
-                                </button>
-    </span>
-
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                    {% endfor %}
-
-                    {% else %}
-                    <h5 class="text-dark align-content-center">No playlists marked favorite :(</h5>
-                    {% endif %}
-                </div>
-        </div>
-
-        <br>
-        <br>
-
-        <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
-            <h1 class="h2">Favorite Videos
-                <span class="badge bg-primary rounded-pill">{{ videos.count }}</span>
-                {% if videos %}
-                <a href="{% url 'all_playlists' 'favorites' %}" style="text-decoration: none; color: black"><i class="fas fa-search"></i></a>
-                {% endif %}
-            </h1>
-        </div>
-
-                <div>
-            <div class="row row-cols-1 row-cols-md-3 g-4">
-                {% if videos %}
-                {% for video in videos %}
-                <div class="col">
-
-                    <div class="card" style="max-width: 540px; background-color: #1A4464;">
-                          <div class="row g-0">
-                            <div class="col-md-4">
-                              <img src="{{ video.thumbnail_url }}" class="img-fluid rounded-3" style="    width: 100%;     height: 15vw;     object-fit: cover;">
-                            </div>
-                            <div class="col-md-8">
-                              <div class="card-body">
-                                <h5 class="card-title"><a href="{% url 'video' video.video_id %}" style="text-decoration: none; color: white"> {{ video.name|truncatewords:"15" }}</a></h5>
-                                <h5 class="card-text">
-                                    <small>
-                                        <span class="badge bg-dark text-white-50">{{ video.duration }}</span>
-                                        {% if video.is_unavailable_on_yt %}<span class="badge bg-light text-dark">Private</span>{% endif %}
-                                        {% if video.has_cc %}<span class="badge bg-danger text-dark">CC</span>{% endif %}
-                                        <span class="badge bg-info text-black-50"><i class="fas fa-eye"></i> {% if video.view_count == -1 %}HIDDEN{% else %}{{ video.view_count|intword|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|intword|intcomma }}{% endif %}</span>
-
-                                    </small>
-                                </h5>
-
-                                <span class="card-text d-flex justify-content-start">
-                                <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 }}">
-                                <i class="far fa-copy" aria-hidden="true"></i>
-                                </button>
-                                <button class="btn btn-dark" type="button" hx-get="{% url 'mark_video_favorite' video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
-                                <div id="video-{{ forloop.counter }}-fav">
-                                {% if video.is_favorite %}
-                                <i class="fas fa-heart" style="color: #fafa06"></i>
-                                {% else %}
-                                <i class="far fa-heart"></i>
-                                {% endif %}
-                                </div>
-                                </button>
-                                </span>
-
-                              </div>
-                            </div>
-                          </div>
-                        </div>
-                <!--
-                    <div class="card" style="background-color: #1A4464;">
-
-                    <div class="card-body">
-                        <h5 class="card-title text-light">
-                            <a href="{% url 'video' video.video_id %}" style="text-decoration: none; color: white"> {{ video.name|truncatewords:"15" }}</a><br>
-
-                            <small>
-
-                                <span class="badge bg-dark text-white-50">{{ 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 %}
-                        </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 }}">
-                                <i class="far fa-copy" aria-hidden="true"></i>
-                            </button>
-                            <button class="btn btn-dark" type="button" hx-get="{% url 'mark_video_favorite' video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
-                                <div id="video-{{ forloop.counter }}-fav">
-                                    {% if video.is_favorite %}
-                                        <i class="fas fa-heart" style="color: #fafa06"></i>
-                                    {% else %}
-                                        <i class="far fa-heart"></i>
-                                    {% endif %}
-                                </div>
-                            </button>
-                        </span>
-                    </div>
-                    </div>
-                    -->
-                </div>
-                {% endfor %}
-
-                {% else %}
-                <h5 class="text-dark align-content-center">Nothing favorites :(</h5>
-                {% endif %}
-            </div>
+    </div>
+
+    <br>
+    <br>
+
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+        <h1 class="h2">Favorite Videos
+            <span class="badge bg-primary rounded-pill">{{ videos.count }}</span>
+            {% if videos %}
+                <a href="{% url 'search_UnTube' %}" style="text-decoration: none; color: black"><i class="fas fa-search"></i></a>
+            {% endif %}
+        </h1>
+    </div>
+
+    <div>
+        <div class="row row-cols-1 row-cols-md-3 g-4">
+            {% if videos %}
+                {% include 'intercooler/video_cards.html' with videos=videos %}
+            {% else %}
+                <h5 class="text-dark align-content-center">No videos marked favorite :(</h5>
+            {% endif %}
         </div>
         </div>
+    </div>
 {% endblock %}
 {% endblock %}

+ 103 - 197
apps/main/templates/home.html

@@ -23,7 +23,7 @@
             <h1>Welcome to UnTube, {{ user.username|capfirst }}</h1>
             <h1>Welcome to UnTube, {{ user.username|capfirst }}</h1>
         </div>
         </div>
         <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
         <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>
+            <h2>{{ imported_playlists_count }} playlists from YouTube have been successfully imported.</h2>
         </div>
         </div>
 
 
         <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
         <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
@@ -62,22 +62,13 @@
             <div class="col-6 mb-4">
             <div class="col-6 mb-4">
                 <div class="card bg-transparent text-dark">
                 <div class="card bg-transparent text-dark">
                     <div class="card-body">
                     <div class="card-body">
-                        <h6 class="d-flex align-items-center mb-3"><span class="text-warning me-2">{{ watching.count }}</span>
-                            {% if watching.count > 0 %}
-                                Playlist{% if watching.count > 1 %}s{% endif %} Watching: Percent Complete Chart
-                                <small> <a class="btn btn-sm btn-success ms-2" href="#continue-watching">View</a></small>
-                            {% else %}
-                                Watching: Mark playlists as watching to view their completeness % here!
-                            {% endif %}
-                        </h6>
+                        <h6 class="d-flex align-items-center mb-3">A total of <span class="text-warning me-1 ms-1" id="num-channels">{{ channels.count|intword|intcomma }} channels</span> and <span class="text-warning ms-1 me-1" id="num-channels"> {{ videos.count|intword|intcomma }} videos</span> found in your UnTube collection </h6>
+                        {% if channels.count > 100 %}<h6 class="d-flex justify-content-center">(Only top 100 channels shown below)</h6>{% endif %}
                         <div class="d-flex align-items-center mb-3">
                         <div class="d-flex align-items-center mb-3">
 
 
-                            <canvas id="watching-playlists-percent-distribution" data-url="{% url 'watching_playlists_percent_distribution' %}">
+                            <canvas id="overall-channels-distribution" data-url="{% url 'overall_channels_distribution' %}">
 
 
                             </canvas>
                             </canvas>
-
-
-
                         </div>
                         </div>
                     </div>
                     </div>
                 </div>
                 </div>
@@ -85,62 +76,81 @@
 
 
         </div>
         </div>
 
 
-        <div class="row" ><!--data-masonry='{"percentPosition": true }'-->
-                <div class="col-sm-6 col-lg-4 mb-4">
-                    <div class="card card-cover h-100 overflow-hidden text-white {% if not user.profile.enable_gradient_bg  %}gradient-bg-3{% else %}bg-dark{% endif %} 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 'all_playlists' 'all' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
-                                    All Playlists</a>
-                            </h2>
-                            <ul class="d-flex list-unstyled mt-auto">
-                                <li class="me-auto">
-                                    <h3>
-                                        <i class="fas fa-mountain fa-lg" style="color: #e26f94"></i>
-                                    </h3>
-                                </li>
-                            </ul>
-                        </div>
+        <div class="row row-cols-1 row-cols-md-4 g-4"><!--data-masonry='{"percentPosition": true }'-->
+            <div class="col mb-4">
+                <div class="card card-cover h-100 overflow-hidden text-white {% if not user.profile.enable_gradient_bg  %}gradient-bg-3{% else %}bg-dark{% endif %} 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 'all_playlists' 'all' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                                All Playlists</a>
+                        </h2>
+                        <ul class="d-flex list-unstyled mt-auto">
+                            <li class="me-auto">
+                                <h3>
+                                    <i class="fas fa-mountain fa-lg" style="color: #e26f94"></i>
+                                </h3>
+                            </li>
+                        </ul>
                     </div>
                     </div>
                 </div>
                 </div>
+            </div>
 
 
-                <div class="col-sm-6 col-lg-4 mb-4">
+            <div class="col mb-4">
                 <div class="card card-cover h-100 overflow-hidden text-white {% if not user.profile.enable_gradient_bg  %}gradient-bg-3{% else %}bg-dark{% endif %} rounded-5 shadow-lg" style="">
                 <div class="card card-cover h-100 overflow-hidden text-white {% if not user.profile.enable_gradient_bg  %}gradient-bg-3{% else %}bg-dark{% endif %} 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' 'LL' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
-                    Liked Videos
-                </a>
-                </h2>
-                <ul class="d-flex list-unstyled mt-auto">
-                <li class="me-auto">
-                    <h3>
-                        <i class="fas fa-thumbs-up fa-lg" style="color: #0090ff"></i>
-                    </h3>
-                </li>
-                </ul>
-                </div>
-                </div>
+                    <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' 'LL' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                                Liked Videos
+                            </a>
+                        </h2>
+                        <ul class="d-flex list-unstyled mt-auto">
+                            <li class="me-auto">
+                                <h3>
+                                    <i class="fas fa-thumbs-up fa-lg" style="color: #0090ff"></i>
+                                </h3>
+                            </li>
+                        </ul>
+                    </div>
                 </div>
                 </div>
+            </div>
 
 
-                <div class="col-sm-6 col-lg-4 mb-4">
+            <div class="col mb-4">
                 <div class="card card-cover h-100 overflow-hidden text-white {% if not user.profile.enable_gradient_bg  %}gradient-bg-3{% else %}bg-dark{% endif %} rounded-5 shadow-lg" style="">
                 <div class="card card-cover h-100 overflow-hidden text-white {% if not user.profile.enable_gradient_bg  %}gradient-bg-3{% else %}bg-dark{% endif %} 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 'favorites' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
-                    Your Favorites
-                </a>
-                </h2>
-                <ul class="d-flex list-unstyled mt-auto">
-                <li class="me-auto">
-                    <h3>
-                        <i class="fas fa-star fa-lg" style="color: #dbcc47"></i>
-                    </h3>
-                </li>
-                </ul>
-                </div>
+                    <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 'favorites' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                                Your Favorites
+                            </a>
+                        </h2>
+                        <ul class="d-flex list-unstyled mt-auto">
+                            <li class="me-auto">
+                                <h3>
+                                    <i class="fas fa-star fa-lg" style="color: #dbcc47"></i>
+                                </h3>
+                            </li>
+                        </ul>
+                    </div>
                 </div>
                 </div>
+            </div>
+
+            <div class="col mb-4">
+                <div class="card card-cover h-100 overflow-hidden text-white {% if not user.profile.enable_gradient_bg  %}gradient-bg-3{% else %}bg-dark{% endif %} 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="https://www.youtube.com/" target="_blank" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                                Open YouTube
+                            </a>
+                        </h2>
+                        <ul class="d-flex list-unstyled mt-auto">
+                            <li class="me-auto">
+                                <h3>
+                                    <i class="fab fa-youtube fa-lg" style="color: #db477b"></i>
+                                </h3>
+                            </li>
+                        </ul>
+                    </div>
                 </div>
                 </div>
+            </div>
 
 
             <!-- FULL IMAGE CARD: might be useful
             <!-- FULL IMAGE CARD: might be useful
             <div class="col-sm-6 col-lg-4 mb-4">
             <div class="col-sm-6 col-lg-4 mb-4">
@@ -158,12 +168,21 @@
             <div class="col">
             <div class="col">
                 <div class="card bg-transparent text-dark">
                 <div class="card bg-transparent text-dark">
                     <div class="card-body">
                     <div class="card-body">
-                        <h6 class="d-flex align-items-center mb-3">A total of <span class="text-warning me-1 ms-1" id="num-channels">{{ channels.count|intword|intcomma }} channels</span> and <span class="text-warning ms-1 me-1" id="num-channels"> {{ videos.count|intword|intcomma }} videos</span> found in your UnTube collection</h6>
+
+                        <h6 class="d-flex align-items-center mb-3"><span class="text-warning me-2">{{ watching.count }}</span>
+                            {% if watching.count > 0 %}
+                                Playlist{% if watching.count > 1 %}s{% endif %} Watching: Percent Complete Chart
+
+                            {% else %}
+                                Watching: Mark playlists as watching to view their completeness % here!
+                            {% endif %}
+                        </h6>
                         <div class="d-flex align-items-center mb-3">
                         <div class="d-flex align-items-center mb-3">
 
 
-                            <canvas id="overall-channels-distribution" data-url="{% url 'overall_channels_distribution' %}">
+                            <canvas id="watching-playlists-percent-distribution" data-url="{% url 'watching_playlists_percent_distribution' %}">
 
 
                             </canvas>
                             </canvas>
+
                         </div>
                         </div>
                     </div>
                     </div>
                 </div>
                 </div>
@@ -201,30 +220,8 @@
                         <h3><span style="border-bottom: 3px #a35a5a 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 #a35a5a 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 %}
                         {% if user_playlists %}
                             <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
                             <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
-                                {% for playlist in user_playlists|slice:"0:3" %}
-                                    <div class="col">
-                                        <div class="card overflow-auto" style="background-color: #4790c7;">
-                                            <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">
-                                                        #{{ forloop.counter }} <br><br>{{ 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>
-                                    </div>
-                                {% endfor %}
-
+                                {% include 'intercooler/playlists.html' with playlists=user_playlists|slice:"0:3" watching=False %}
                             </div>
                             </div>
-
                         {% else %}
                         {% else %}
                             <br>
                             <br>
                             <h5>Nothing to see here... yet.</h5>
                             <h5>Nothing to see here... yet.</h5>
@@ -263,7 +260,7 @@
                     <div class="row flex-row g-3 flex-nowrap">
                     <div class="row flex-row g-3 flex-nowrap">
                         {% for playlist in watching %}
                         {% for playlist in watching %}
                             <div class="col">
                             <div class="col">
-                                <div class="card" style="background-color: #EFEFEF; width: 275px; height: auto">
+                                <div class="card overflow-auto" style="background-color: #c2c68f; width: 275px; height: auto">
                                     <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">
                                     <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">
                                     <div class="card-body">
@@ -291,54 +288,8 @@
                                 </div>
                                 </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 %}
                         {% 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>
                 </div>
                 </div>
             {% else %}
             {% else %}
@@ -346,23 +297,7 @@
                 <div class="container-fluid overflow-auto border border-5 rounded-3 border-primary pb-4">
                 <div class="container-fluid overflow-auto border border-5 rounded-3 border-primary pb-4">
 
 
                     <div class="row row-cols-1 row-cols-md-4 g-4 text-dark mt-0">
                     <div class="row row-cols-1 row-cols-md-4 g-4 text-dark mt-0">
-                        {% for playlist in watching %}
-                            <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"><small class="text-muted">Last watched {{ playlist.last_watched|naturaltime }}</small></p>
-                                    </div>
-                                </div>
-                            </div>
-                        {% endfor %}
+                        {% include 'intercooler/playlists.html' with playlists=watching watching=True %}
                     </div>
                     </div>
                 </div>
                 </div>
             {% endif %}
             {% endif %}
@@ -377,27 +312,7 @@
                 <h3><span style="border-bottom: 3px #e24949 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 %}
                 {% if recently_added_playlists %}
                     <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
                     <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>
-                                    </a>
-                                </div>
-                            </div>
-                        {% endfor %}
+                        {% include 'intercooler/playlists.html' with playlists=recently_added_playlists watching=False bg_color="#958a44" show_controls=False %}
                     </div>
                     </div>
                 {% else %}
                 {% else %}
                     <br>
                     <br>
@@ -410,27 +325,7 @@
 
 
                 {% if recently_accessed_playlists %}
                 {% if recently_accessed_playlists %}
                     <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
                     <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>
-                                    </a>
-                                </div>
-                            </div>
-                        {% endfor %}
+                        {% include 'intercooler/playlists.html' with playlists=recently_accessed_playlists watching=False bg_color="#357779" show_controls=False %}
                     </div>
                     </div>
                 {% else %}
                 {% else %}
                     <br>
                     <br>
@@ -445,6 +340,17 @@
 
 
 
 
         <br>
         <br>
+
+
+        <footer class="footer mt-auto py-3 bg-transparent">
+            <div class="container d-flex justify-content-center">
+                <span class="text-dark">Loved what I made?
+                    <a href="https://www.buymeacoffee.com/mohammedabkhan" style="text-decoration: none" target="_blank">
+                        <span style="border-bottom: 3px #d56b6b dashed;">You can support me by buying me some coffee </span><i class="far fa-smile" style="color: black"></i>
+                    </a></span>
+            </div>
+        </footer>
+
         <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
         <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
 
 
         <script type="application/javascript">
         <script type="application/javascript">
@@ -496,8 +402,8 @@
                                 tooltips: {
                                 tooltips: {
                                     callbacks: {
                                     callbacks: {
                                         label: function(tooltipItem, object) {
                                         label: function(tooltipItem, object) {
-                                              return object['labels'][tooltipItem['index']] + ": " + object['datasets'][0]['data'][tooltipItem['index']] + ' playlists';
-                                            }
+                                            return object['labels'][tooltipItem['index']] + ": " + object['datasets'][0]['data'][tooltipItem['index']] + ' playlists';
+                                        }
                                     }
                                     }
                                 }
                                 }
 
 
@@ -558,8 +464,8 @@
                                 tooltips: {
                                 tooltips: {
                                     callbacks: {
                                     callbacks: {
                                         label: function(tooltipItem, object) {
                                         label: function(tooltipItem, object) {
-                                              return object['labels'][tooltipItem['index']] + ": " + object['datasets'][0]['data'][tooltipItem['index']] + '% complete';
-                                            }
+                                            return object['labels'][tooltipItem['index']] + ": " + object['datasets'][0]['data'][tooltipItem['index']] + '% complete';
+                                        }
                                     }
                                     }
                                 }
                                 }
                             }
                             }
@@ -613,8 +519,8 @@
                                 tooltips: {
                                 tooltips: {
                                     callbacks: {
                                     callbacks: {
                                         label: function(tooltipItem, object) {
                                         label: function(tooltipItem, object) {
-                                              return object['labels'][tooltipItem['index']] + ": " + object['datasets'][0]['data'][tooltipItem['index']] + ' videos';
-                                            }
+                                            return object['labels'][tooltipItem['index']] + ": " + object['datasets'][0]['data'][tooltipItem['index']] + ' videos';
+                                        }
                                     }
                                     }
                                 }
                                 }
 
 

+ 0 - 0
apps/main/templates/intercooler/videos.html → apps/main/templates/intercooler/playlist_items.html


+ 106 - 70
apps/main/templates/intercooler/playlists.html

@@ -1,91 +1,127 @@
-
+{% load humanize %}
 {% if watching %}
 {% if watching %}
-       <div class="row row-cols-1 row-cols-md-3 g-4 text-dark">
-        {% for playlist in playlists %}
+    {% for playlist in playlists %}
         <div class="col">
         <div class="col">
-            <div class="card">
-                <a style="background-color: #1A4464;" 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 text-white">
-                   {{ 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>
-                    <span class="badge bg-light text-black-50">{{ playlist.playlist_duration }} </span>
 
 
-                    {% if playlist.get_watch_time_left != "0secs." %}<span class="badge bg-dark text-white-50">{{ 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 class="card overflow-auto" style="background-color: {{ bg_color|default:"#c2c68f" }};">
+                <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"><small class="text-muted">Last watched {{ playlist.last_watched|naturaltime }}</small></p>
                 </div>
                 </div>
-                </a>
             </div>
             </div>
         </div>
         </div>
-
-        {% endfor %}
-    </div>
+    {% endfor %}
 
 
 {% else %}
 {% else %}
-<div class="row row-cols-1 row-cols-md-3 g-4">
-        {% if playlists %}
-        {% for playlist in playlists %}
+    {% for playlist in playlists %}
         <div class="col">
         <div class="col">
-            <div class="card" style="background-color: #515355;">
-                <a style="background-color: #1A4464;" 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 text-white">
-                            {{ playlist.name }}
-
-                        </h5>
+            <div class="card overflow-auto" style="background-color: {{ bg_color|default:"#4790c7" }};">
+                <a href="{% url 'playlist' playlist.playlist_id %}" style="text-decoration: none; color: black">
+                    <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">
+                </a>
+                <div class="card-body">
+                    <h5 class="card-title"><a href="{% url 'playlist' playlist.playlist_id %}" style="text-decoration: none; color: black">{{ playlist.name }}</a></h5>
 
 
-                        <p class="card-text">
-                            {% if playlist.is_user_owned %}<small><span class="badge bg-light text-black-50">OWNED</span></small>{% else %}<small><span class="badge bg-light text-black-50">IMPORTED</span></small>{% endif %}
-                            {% if playlist.is_private_on_yt %}<small><span class="badge bg-secondary text-white">Private</span></small> {% endif %}
-                            {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-black-50">YT</span></small> {% endif %}
-                            {% if playlist.marked_as == "watching" %}<small><span class="badge bg-primary text-white">WATCHING</span></small>{% endif %}
-                        </p>
+                    <p class="card-text text-uppercase">
+                        {% if playlist.is_user_owned %}<span class="badge bg-light text-black-50">OWNED</span>{% else %}<span class="badge bg-light text-black-50">IMPORTED</span>{% endif %}
+                        {% if playlist.is_private_on_yt %}<span class="badge bg-secondary text-white">Private</span> {% endif %}
+                        <span class="badge bg-warning text-black-50">{{ playlist.video_count }} videos</span>
+                        <span class="badge bg-dark text-white">{{ playlist.playlist_duration }} </span>
+                        {% if playlist.is_from_yt %}<span class="badge bg-danger text-black-50">YT</span> {% endif %}
+                        {% if playlist.marked_as == "watching" %}<span class="badge bg-primary text-white">WATCHING</span>{% endif %}
 
 
+                    </p>
+                    {% if show_tags|default:True %}
                         {% if playlist.tags.all %}
                         {% if playlist.tags.all %}
                             <p class="card-text">
                             <p class="card-text">
-                            <span class="d-flex justify-content-start flex-wrap">
-                            <small>
-                            <span style="color: #eed868;" class="me-lg-1 mb-lg-1">
-                                <i class="fas fa-tags"></i>
-                            </span>
-                            </small>
-                            {% for tag in playlist.tags.all %}
-                                <span class="badge rounded-pill bg-info mb-lg-1 me-lg-1 text-black-50">
-                                    {{ tag.name }}
+                                <span class="d-flex justify-content-start flex-wrap">
+                                    <small>
+                                        <span style="color: #eed868;" class="me-lg-1 mb-lg-1">
+                                            <i class="fas fa-tags"></i>
+                                        </span>
+                                    </small>
+                                    {% for tag in playlist.tags.all %}
+                                        <span class="badge rounded-pill bg-info mb-lg-1 me-lg-1 text-black-50">
+                                            {{ tag.name }}
+                                        </span>
+                                    {% endfor %}
                                 </span>
                                 </span>
-                            {% endfor %}
-                            </span>
                             </p>
                             </p>
                         {% endif %}
                         {% endif %}
-                        <small>
-                            <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
-                            <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
-                        </small>
-                    </div>
-                </a>
+
+                    {% endif %}
+
+                    {% if show_controls %}
+    <span class="d-flex justify-content-center">
+        <a href="https://www.youtube.com/playlist?list={{ playlist.playlist_id }}" class="btn btn-light me-1" target="_blank" id="share_link" style=""><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
+
+        <button class="btn btn-dark" type="button" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'favorite' %}" hx-target="#playlist-{{ forloop.counter }}-fav">
+            <div id="playlist-{{ forloop.counter }}-fav">
+                {% if playlist.is_favorite %}
+                    <i class="fas fa-star" style="color: #fafa06"></i>
+                {% else %}
+                    <i class="far fa-star"></i>
+                {% endif %}
+            </div>
+        </button>
+    </span>
+    {% endif %}
+
+                </div>
             </div>
             </div>
         </div>
         </div>
-        {% endfor %}
+    {% endfor %}
+
+    <!-- minified version
+    {% for playlist in playlists %}
+    <div class="col">
+        <div class="card" style="background-color: #515355;">
+            <a style="background-color: #1A4464;" href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item list-group-item-action" aria-current="true">
 
 
-        {% else %}
-        <h5 class="text-dark align-content-center">No playlists found :(</h5>
-        {% endif %}
+                <div class="card-body">
+
+                    <h5 class="card-title text-white">
+                        {{ playlist.name }}
+
+                    </h5>
+
+                    <p class="card-text">
+                        {% if playlist.is_user_owned %}<small><span class="badge bg-light text-black-50">OWNED</span></small>{% else %}<small><span class="badge bg-light text-black-50">IMPORTED</span></small>{% endif %}
+                        {% if playlist.is_private_on_yt %}<small><span class="badge bg-secondary text-white">Private</span></small> {% endif %}
+                        {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-black-50">YT</span></small> {% endif %}
+                        {% if playlist.marked_as == "watching" %}<small><span class="badge bg-primary text-white">WATCHING</span></small>{% endif %}
+                    </p>
+
+                    {% if playlist.tags.all %}
+                        <p class="card-text">
+                        <span class="d-flex justify-content-start flex-wrap">
+                        <small>
+                        <span style="color: #eed868;" class="me-lg-1 mb-lg-1">
+                            <i class="fas fa-tags"></i>
+                        </span>
+                        </small>
+                        {% for tag in playlist.tags.all %}
+                            <span class="badge rounded-pill bg-info mb-lg-1 me-lg-1 text-black-50">
+                                {{ tag.name }}
+                            </span>
+                        {% endfor %}
+                        </span>
+                        </p>
+                    {% endif %}
+                    <small>
+                        <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
+                        <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
+                    </small>
+                </div>
+            </a>
+        </div>
     </div>
     </div>
+    {% endfor %}
+    -->
 {% endif %}
 {% endif %}
-<br>

+ 0 - 119
apps/main/templates/intercooler/search_untube_results.html

@@ -1,119 +0,0 @@
-{% load humanize %}
-
-{% if search_query %}
-<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
-    <h1 class="h2">Playlists <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span></h1>
-</div>
-
-<div>
-    {% include 'intercooler/playlists.html' %}
-</div>
-
-<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
-    <h1 class="h2">Videos <span class="badge bg-primary rounded-pill">{{ videos_count }}</span></h1>
-</div>
-
-        <div>
-    <div class="row row-cols-1 row-cols-md-3 g-4">
-        {% 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">
-                    {{ playlist_item.video.name|truncatewords:"15" }}<br>
-
-                    <small>
-                        <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">{{ playlist_item.video.duration }}</span>
-                    </small>
-                    {% 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={{ 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-{{ playlist_item.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' playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
-                        <div id="video-{{ forloop.counter }}-fav">
-                            {% 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>
-                </span>
-            </div>
-            </div>
-        </div>
-        {% endfor %}
-
-        {% else %}
-        <h5 class="text-dark align-content-center">Nothing found :(</h5>
-        {% endif %}
-    </div>
-</div>
-{% else %}
-{% if all_playlists %}
-<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
-    <h1 class="h2">All Playlists <span class="badge bg-primary rounded-pill">{{ all_playlists.count }}</span></h1>
-</div>
-
-<div>
-    <div class="row row-cols-1 row-cols-md-3 g-4">
-        {% for playlist in all_playlists %}
-        <div class="col">
-            <div class="card" style="background-color: #515355;">
-                <a style="background-color: #1A4464;" 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 text-white">
-                            {{ playlist.name }}
-
-                        </h5>
-
-                        <p class="card-text">
-                            {% if playlist.is_user_owned %}<small><span class="badge bg-light text-black-50">OWNED</span></small>{% else %}<small><span class="badge bg-light text-black-50">IMPORTED</span></small>{% endif %}
-                            {% if playlist.is_private_on_yt %}<small><span class="badge bg-secondary text-white">Private</span></small> {% endif %}
-                            {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-black-50">YT</span></small> {% endif %}
-                            {% if playlist.marked_as == "watching" %}<small><span class="badge bg-primary text-white">WATCHING</span></small>{% endif %}
-                        </p>
-
-                        {% if playlist.tags.all %}
-                            <p class="card-text">
-                            <span class="d-flex justify-content-start flex-wrap">
-                            <small>
-                            <span style="color: #eed868;" class="me-lg-1 mb-lg-1">
-                                <i class="fas fa-tags"></i>
-                            </span>
-                            </small>
-                            {% for tag in playlist.tags.all %}
-                                <span class="badge rounded-pill bg-info mb-lg-1 me-lg-1 text-black-50">
-                                    {{ tag.name }}
-                                </span>
-                            {% endfor %}
-                            </span>
-                            </p>
-                        {% endif %}
-                        <small>
-                            <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
-                            <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
-                        </small>
-                    </div>
-                </a>
-            </div>
-        </div>
-        {% endfor %}
-    </div>
-</div>
-{% endif %}
-{% endif %}
-<br>

+ 46 - 0
apps/main/templates/intercooler/video_cards.html

@@ -0,0 +1,46 @@
+{% load humanize %}
+{% for video in videos %}
+    <div class="col">
+
+        <div class="card" style="max-width: 540px; background-color: #1A4464;">
+            <div class="row g-0">
+                <div class="col-md-4">
+                    <img src="{{ video.thumbnail_url }}" class="img-fluid" style="width: 100%; height: 15vw; object-fit: cover;">
+                </div>
+                <div class="col-md-8">
+                    <div class="card-body">
+                        <h5 class="card-title"><a href="{% url 'video' video.video_id %}" style="text-decoration: none; color: white"> {{ video.name|truncatewords:"15" }}</a></h5>
+                        <h5 class="card-text">
+                            <small>
+                                <span class="badge bg-dark text-white-50">{{ video.duration }}</span>
+                                {% if video.is_unavailable_on_yt %}<span class="badge bg-light text-dark">Private</span>{% endif %}
+                                {% if video.has_cc %}<span class="badge bg-danger text-dark">CC</span>{% endif %}
+                                <span class="badge bg-info text-black-50"><i class="fas fa-eye"></i> {% if video.view_count == -1 %}HIDDEN{% else %}{{ video.view_count|intword|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|intword|intcomma }}{% endif %}</span>
+
+                            </small>
+                        </h5>
+
+                        <span class="card-text d-flex justify-content-start">
+                            <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 }}">
+                                <i class="far fa-copy" aria-hidden="true"></i>
+                            </button>
+                            <button class="btn btn-dark" type="button" hx-get="{% url 'mark_video_favorite' video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
+                                <div id="video-{{ forloop.counter }}-fav">
+                                    {% if video.is_favorite %}
+                                        <i class="fas fa-heart" style="color: #fafa06"></i>
+                                    {% else %}
+                                        <i class="far fa-heart"></i>
+                                    {% endif %}
+                                </div>
+                            </button>
+                        </span>
+
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endfor %}

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

@@ -33,7 +33,7 @@
             </div>
             </div>
         {% else %}
         {% else %}
             <div class="sticky-top mb-3" style="top: 0.5rem;">
             <div class="sticky-top mb-3" style="top: 0.5rem;">
-                {% if not playlist.is_yt_mix %}
+                {% if not playlist.is_yt_mix and not playlist.playlist_id == "LL" %}
                     <div hx-get="{% url 'update_playlist' playlist.playlist_id 'checkforupdates' %}" hx-trigger="load" hx-swap="outerHTML" id="checkforupdates">
                     <div hx-get="{% url 'update_playlist' playlist.playlist_id 'checkforupdates' %}" hx-trigger="load" hx-swap="outerHTML" id="checkforupdates">
                         <div class="alert alert-info alert-dismissible fade show" role="alert">
                         <div class="alert alert-info alert-dismissible fade show" role="alert">
                             Checking playlist for updates...
                             Checking playlist for updates...
@@ -269,7 +269,7 @@
                                 </a>
                                 </a>
                             </div>
                             </div>
 
 
-                            {% if playlist.video_count != 0 %}
+                            {% if playlist.video_count > 1 %}
                             <div class="btn-group me-2 mb-2">
                             <div class="btn-group me-2 mb-2">
                                 <a href="{% url 'playlist_open_random_video' playlist.playlist_id %}" class="btn btn-danger">
                                 <a href="{% url 'playlist_open_random_video' playlist.playlist_id %}" class="btn btn-danger">
                                     Open a Random Video
                                     Open a Random Video
@@ -426,6 +426,7 @@
                 <div class="collapse border-danger" id="deleteItemsCollapse">
                 <div class="collapse border-danger" id="deleteItemsCollapse">
                     <div class="card card-body bg-dark text-white-50">
                     <div class="card card-body bg-dark text-white-50">
                         <div id="delete-videos-confirm-box">
                         <div id="delete-videos-confirm-box">
+                            <h5>You can select videos by clicking any where on the video box. Selected videos will be highlighted red.</h5>
                             {% if not playlist.confirm_before_deleting %}
                             {% if not playlist.confirm_before_deleting %}
                                 <h5>Note: You have set confirm before deleting to False. Buttons below will take effect immediately when clicked.</h5>
                                 <h5>Note: You have set confirm before deleting to False. Buttons below will take effect immediately when clicked.</h5>
                                 <hr>
                                 <hr>
@@ -627,13 +628,14 @@
                 {% endif %}
                 {% endif %}
 
 
             </div>
             </div>
-        {% endif %}
-        <div id="load-more-videos-spinner" class="d-flex htmx-indicator justify-content-center align-items-center">
+            <div id="load-more-videos-spinner" class="d-flex htmx-indicator justify-content-center align-items-center">
             <div class="spinner-border mt-3" role="status">
             <div class="spinner-border mt-3" role="status">
             </div>
             </div>
             <span class="mt-3 ms-2">Loading more videos...</span>
             <span class="mt-3 ms-2">Loading more videos...</span>
         </div>
         </div>
         {% endif %}
         {% endif %}
+
+        {% endif %}
     </div>
     </div>
 
 
     <button class="scrollToTopBtn sticky-top">
     <button class="scrollToTopBtn sticky-top">
@@ -741,7 +743,21 @@
 
 
 
 
         document.getElementById('manageBtn').addEventListener('click', function () {                document.getElementById("delete-videos-confirm-box").innerHTML = "";
         document.getElementById('manageBtn').addEventListener('click', function () {                document.getElementById("delete-videos-confirm-box").innerHTML = "";
-            document.getElementById("delete-videos-confirm-box").innerHTML = "";
+            document.getElementById("delete-videos-confirm-box").innerHTML = `
+                <h5>You can select videos by clicking any where on the video box. Selected videos will be highlighted red.</h5>
+                {% if not playlist.confirm_before_deleting %}
+                    <h5>Note: You have set confirm before deleting to False. Buttons below will take effect immediately when clicked.</h5>
+                    <hr>
+                {% else %}
+                    {% if playlist.has_unavailable_videos %}
+                        <h5>Note: Clicking on delete unavailable videos button will take immediate effect. All videos labelled unavailable in this playlist will be deleted from your YouTube playlist.</h5>
+                    {% endif %}
+                    {% if playlist.has_duplicate_videos %}
+                        <h5>Note: Clicking on delete duplicate videos button will take immediate effect. All videos labelled duplicate in this playlist will be deleted from your YouTube playlist.</h5>
+                    {% endif %}
+                    <hr>
+                {% endif %}
+            `;
             bsMoveCopyCollapse.hide();
             bsMoveCopyCollapse.hide();
             bsDeleteCollapse.hide();
             bsDeleteCollapse.hide();
         });
         });

+ 14 - 8
apps/main/templates/view_playlist_settings.html

@@ -85,9 +85,12 @@
                         <div class="col-sm-3">
                         <div class="col-sm-3">
                           <h6 class="mb-0">Danger Zone</h6>
                           <h6 class="mb-0">Danger Zone</h6>
                         </div>
                         </div>
-                        <div class="col-sm-9 text-white-50">
+                        <div class="col-sm-9 text-white-50 d-flex justify-content-start">
                             <div id="delete-box">
                             <div id="delete-box">
-                                <a hx-get="{% url 'delete_playlist' playlist.playlist_id %}" hx-target="#delete-box" hx-vals='{"confirmed": "no"}' class="btn btn-outline-danger">Delete Playlist From YouTube</a>
+                                <a hx-get="{% url 'delete_playlist' playlist.playlist_id %}" hx-target="#delete-box" hx-vals='{"confirmed": "no"}' class="btn btn-outline-danger" hx-indicator="#delete-pl-loader">Delete Playlist From YouTube</a>
+                            </div>
+                            <div id="delete-pl-loader">
+                            <img src="{% static 'svg-loaders/rings.svg' %}" height="40" width="40" class="htmx-indicator">
                             </div>
                             </div>
                         </div>
                         </div>
                       </div>
                       </div>
@@ -100,8 +103,8 @@
                 <div class="btn-group">
                 <div class="btn-group">
                     <a href="{% url 'playlist' playlist.playlist_id %}" class="btn btn-secondary me-2">Back</a>
                     <a href="{% url 'playlist' playlist.playlist_id %}" class="btn btn-secondary me-2">Back</a>
 
 
-                    <button href="#navbar" hx-post="{% url 'update_playlist_settings' playlist.playlist_id %}" hx-include="[id='settings-form']" hx-target="#settings-status-div"
-                            type="button" class="btn btn-success me-2">Save</button>
+                    <a href="#navbar" hx-post="{% url 'update_playlist_settings' playlist.playlist_id %}" hx-include="[id='settings-form']" hx-target="#settings-status-div"
+                            type="button" class="btn btn-success me-2">Save</a>
                 </div>
                 </div>
               </div>
               </div>
         </div>
         </div>
@@ -169,9 +172,12 @@
                         <div class="col-sm-3">
                         <div class="col-sm-3">
                           <h6 class="mb-0">Danger Zone</h6>
                           <h6 class="mb-0">Danger Zone</h6>
                         </div>
                         </div>
-                        <div class="col-sm-9 text-white-50">
+                        <div class="col-sm-9 text-white-50 d-flex justify-content-start">
                             <div id="delete-box">
                             <div id="delete-box">
-                                <a hx-get="{% url 'delete_playlist' playlist.playlist_id %}" hx-target="#delete-box" hx-vals='{"confirmed": "no"}' class="btn btn-outline-danger">Remove Playlist From UnTube</a>
+                                <a hx-get="{% url 'delete_playlist' playlist.playlist_id %}" hx-target="#delete-box" hx-vals='{"confirmed": "no"}' class="btn btn-outline-danger" hx-indicator="#delete-pl-loader">Remove Playlist From UnTube</a>
+                            </div>
+                            <div id="delete-pl-loader">
+                            <img src="{% static 'svg-loaders/rings.svg' %}" height="40" width="40" class="htmx-indicator">
                             </div>
                             </div>
                         </div>
                         </div>
                       </div>
                       </div>
@@ -184,8 +190,8 @@
                             <div class="btn-group">
                             <div class="btn-group">
                 <a href="{% url 'playlist' playlist.playlist_id %}" class="btn btn-secondary me-2">Back</a>
                 <a href="{% url 'playlist' playlist.playlist_id %}" class="btn btn-secondary me-2">Back</a>
 
 
-                <button href="#navbar" hx-post="{% url 'update_playlist_settings' playlist.playlist_id %}" hx-include="[id='settings-form']" hx-target="#settings-status-div"
-                            type="button" class="btn btn-success me-2">Save</button>
+                <a href="#navbar" hx-post="{% url 'update_playlist_settings' playlist.playlist_id %}" hx-include="[id='settings-form']" hx-target="#settings-status-div"
+                            type="button" class="btn btn-success me-2">Save</a>
                                 </div>
                                 </div>
               </div>
               </div>
         </div>
         </div>

+ 25 - 12
apps/main/templates/view_video.html

@@ -19,24 +19,29 @@
                     </div>
                     </div>
                     <div class="col-md-8">
                     <div class="col-md-8">
                         <div class="card-body">
                         <div class="card-body">
-                            <div class="d-flex justify-content-between">
-                                <h2 class="card-title text-white">
+                            <div class="row d-flex justify-content-between">
+                                <h2 class="card-title text-white col-10">
                                     <a href="https://www.youtube.com/watch?v={{ video.video_id }}" target="_blank" style="color: white; text-decoration: none">{{ video.name }}</a>
                                     <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>
                                     <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>
                                 </h2>
-                                <h4>
+                                <h4 class="col d-flex justify-content-end">
                                     <span id="notice-div">
                                     <span id="notice-div">
-                                        {% if video.is_marked_as_watched %}
-                                            <span class="badge bg-success text-white" >
-                                            
-                                                <i class="fas fa-flag-checkered me-1"></i> marked watched
-                                            </span>
-                                        {% endif %}
+
                                     </span>
                                     </span>
                                     <span id="">
                                     <span id="">
-                                        <span class="badge bg-dark">
-                                            <i class="fas fa-map-pin"></i>
-                                        </span>
+                                        <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 }}">
+                                            <i class="far fa-copy" aria-hidden="true"></i>
+                                        </button>
+                                        <button class="btn btn-dark" type="button" hx-get="{% url 'mark_video_favorite' video.video_id %}" hx-target="#video-fav">
+                                            <div id="video-fav">
+                                                {% if video.is_favorite %}
+                                                    <i class="fas fa-heart" style="color: #fafa06"></i>
+                                                {% else %}
+                                                    <i class="far fa-heart"></i>
+                                                {% endif %}
+                                            </div>
+                                        </button>
                                     </span>
                                     </span>
                                 </h4>
                                 </h4>
                             </div>
                             </div>
@@ -66,6 +71,7 @@
                                         </div>
                                         </div>
                                     </div>
                                     </div>
                                 </div>
                                 </div>
+                                <small>
                                 {% if video.has_cc %}<span class="badge bg-danger mb-1">CC</span>{% endif %}
                                 {% if video.has_cc %}<span class="badge bg-danger mb-1">CC</span>{% endif %}
                                 {% if video.published_at %}<span class="badge bg-secondary mb-1">Video uploaded on {{ video.published_at }}</span>{% endif %}
                                 {% if video.published_at %}<span class="badge bg-secondary mb-1">Video uploaded on {{ video.published_at }}</span>{% endif %}
                                 <span class="badge bg-primary text-white mb-1"><i class="fas fa-eye"></i> {% if video.view_count == -1 %}HIDDEN{% else %}{{ video.view_count|intcomma }}{% endif %}</span>
                                 <span class="badge bg-primary text-white mb-1"><i class="fas fa-eye"></i> {% if video.view_count == -1 %}HIDDEN{% else %}{{ video.view_count|intcomma }}{% endif %}</span>
@@ -74,7 +80,14 @@
                                 <span class="badge bg-info text-black-50 mb-1"><i class="fas fa-comments"></i> {% if video.comment_count == -1 %}HIDDEN{% else %}{{ video.comment_count|intcomma }}{% endif %} </span>
                                 <span class="badge bg-info text-black-50 mb-1"><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 mb-1">UNAVAILABLE</span>{% endif %}
                                 {% if video.is_unavailable_on_yt or video.was_deleted_on_yt %}<span class="badge bg-light text-black-50 mb-1">UNAVAILABLE</span>{% endif %}
                                 <span class="badge bg-light text-black-50 mb-1"><a href="#found-in" style="text-decoration: none; color: grey"> Found in {{ video.playlists.all.count }} playlist{% if video.playlists.all.count > 1 %}s{% endif %}</a></span>
                                 <span class="badge bg-light text-black-50 mb-1"><a href="#found-in" style="text-decoration: none; color: grey"> Found in {{ video.playlists.all.count }} playlist{% if video.playlists.all.count > 1 %}s{% endif %}</a></span>
+                                {% if video.is_marked_as_watched %}
+                                    <span class="badge bg-dark text-white" >
+
+                                        <i class="fas fa-flag-checkered me-1"></i> marked watched
+                                    </span>
+                                {% endif %}
 
 
+                                </small>
 
 
                             </h6>
                             </h6>
                             <p class="card-text text-white-50"><small>Last updated {{ video.video_details_modified_at|naturalday }}</small> &bullet; <small>{{ video.num_of_accesses }} clicks </small></p>                        </div>
                             <p class="card-text text-white-50"><small>Last updated {{ video.video_details_modified_at|naturalday }}</small> &bullet; <small>{{ video.num_of_accesses }} clicks </small></p>                        </div>

+ 2 - 6
apps/main/urls.py

@@ -6,8 +6,6 @@ urlpatterns = [
 
 
     ### STUFF RELATED TO WHOLE SITE
     ### STUFF RELATED TO WHOLE SITE
     path("home/", views.home, name='home'),
     path("home/", views.home, name='home'),
-    path("search", views.search, name="search"),
-    path("search/UnTube/", views.search_UnTube, name="search_UnTube"),
     path("favorites", views.favorites, name="favorites"),
     path("favorites", views.favorites, name="favorites"),
 
 
     ### STUFF RELATED TO INDIVIDUAL VIDEOS
     ### STUFF RELATED TO INDIVIDUAL VIDEOS
@@ -17,12 +15,12 @@ urlpatterns = [
     path("video/<slug:video_id>/get-video-completion-times", views.video_completion_times, name="video_completion_times"),
     path("video/<slug:video_id>/get-video-completion-times", views.video_completion_times, name="video_completion_times"),
 
 
     ### STUFF RELATED TO VIDEO(S) INSIDE PLAYLISTS
     ### 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/watched', views.mark_video_watched, name='mark_video_watched'),
     path("videos/<slug:videos_type>", views.all_videos, name='all_videos'),
     path("videos/<slug:videos_type>", views.all_videos, name='all_videos'),
 
 
     ### STUFF RELATED TO ONE PLAYLIST
     ### STUFF RELATED TO ONE PLAYLIST
     path("playlist/<slug:playlist_id>", views.view_playlist, name='playlist'),
     path("playlist/<slug:playlist_id>", views.view_playlist, name='playlist'),
+    path('playlist/<slug:playlist_id>/<slug:video_id>/video-details/watched', views.mark_video_watched,
+         name='mark_video_watched'),
     path("playlist/<slug:playlist_id>/settings", views.view_playlist_settings, name="view_playlist_settings"),
     path("playlist/<slug:playlist_id>/settings", views.view_playlist_settings, name="view_playlist_settings"),
     path("playlist/<slug:playlist_id>/order-by/<slug:order_by>", views.order_playlist_by,
     path("playlist/<slug:playlist_id>/order-by/<slug:order_by>", views.order_playlist_by,
          name='order_playlist_by'),
          name='order_playlist_by'),
@@ -47,10 +45,8 @@ urlpatterns = [
          name="playlist_completion_times"),
          name="playlist_completion_times"),
 
 
     ### STUFF RELATED TO PLAYLISTS IN BULK
     ### STUFF RELATED TO PLAYLISTS IN BULK
-    path("search/playlists/<slug:playlist_type>", views.search_playlists, name="search_playlists"),
     path("playlists/<slug:playlist_type>", views.all_playlists, name='all_playlists'),
     path("playlists/<slug:playlist_type>", views.all_playlists, name='all_playlists'),
     path("playlists/<slug:playlist_type>/order-by/<slug:order_by>", views.order_playlists_by, name='order_playlists_by'),
     path("playlists/<slug:playlist_type>/order-by/<slug:order_by>", views.order_playlists_by, name='order_playlists_by'),
-    path("search/tagged-playlists/<str:tag>", views.search_tagged_playlists, name="search_tagged_playlists"),
     path("playlists/tag/<str:tag>", views.tagged_playlists, name='tagged_playlists'),
     path("playlists/tag/<str:tag>", views.tagged_playlists, name='tagged_playlists'),
 
 
     ### STUFF RELATED TO MANAGING A PLAYLIST
     ### STUFF RELATED TO MANAGING A PLAYLIST

+ 47 - 165
apps/main/views.py

@@ -1,6 +1,7 @@
 import datetime
 import datetime
+import json
 import random
 import random
-
+from django.core import serializers
 import bleach
 import bleach
 import pytz
 import pytz
 from django.db.models import Q, Count
 from django.db.models import Q, Count
@@ -39,27 +40,21 @@ def home(request):
         show_import_page is only set false in the import_in_progress.html page, i.e when user cancels YT import
         show_import_page is only set false in the import_in_progress.html page, i.e when user cancels YT import
         """
         """
         # user_profile.show_import_page = False
         # user_profile.show_import_page = False
-
         if user_profile.profile.access_token.strip() == "" or user_profile.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_social_token = SocialToken.objects.get(account__user=request.user)
             user_profile.profile.access_token = user_social_token.token
             user_profile.profile.access_token = user_social_token.token
             user_profile.profile.refresh_token = user_social_token.token_secret
             user_profile.profile.refresh_token = user_social_token.token_secret
             user_profile.profile.expires_at = user_social_token.expires_at
             user_profile.profile.expires_at = user_social_token.expires_at
             user_profile.save()
             user_profile.save()
-            Playlist.objects.getUserYTChannelID(user_profile)
+            Playlist.objects.getUserYTChannelID(request.user)
 
 
         if user_profile.profile.imported_yt_playlists:
         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.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'])
             user_profile.profile.save(update_fields=['show_import_page'])
-            return render(request, "home.html", {"import_successful": True})
+            imported_playlists_count = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True)).exclude(playlist_id="LL").count()
+            return render(request, "home.html", {"import_successful": True, "imported_playlists_count": imported_playlists_count})
 
 
         return render(request, "import_in_progress.html")
         return render(request, "import_in_progress.html")
-
-        # if Playlist.objects.getUserYTChannelID(request.user) == -1:  # user channel not found
-        #    channel_found = False
-        # else:
-        #   Playlist.objects.initPlaylist(request.user, None)  # get all playlists from user's YT channel
-        #  return render(request, "home.html", {"import_successful": True})
     ##################################
     ##################################
 
 
     user_playlists = request.user.playlists.filter(is_in_db=True)
     user_playlists = request.user.playlists.filter(is_in_db=True)
@@ -67,27 +62,6 @@ def home(request):
     user_playlists = user_playlists.filter(num_of_accesses__gt=0).order_by(
     user_playlists = user_playlists.filter(num_of_accesses__gt=0).order_by(
         "-num_of_accesses")
         "-num_of_accesses")
 
 
-    statistics = {
-        "public_x": 0,
-        "private_x": 0,
-        "favorites_x": 0,
-        "watching_x": 0,
-        "imported_x": 0
-    }
-
-    if total_num_playlists != 0:
-        # x means  percentage
-        statistics["public_x"] = round(user_playlists.filter(is_private_on_yt=False).count() / total_num_playlists,
-                                       1) * 100
-        statistics["private_x"] = round(user_playlists.filter(is_private_on_yt=True).count() / total_num_playlists,
-                                        1) * 100
-        statistics["favorites_x"] = round(user_playlists.filter(is_favorite=True).count() / total_num_playlists,
-                                          1) * 100
-        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
-
     videos = request.user.videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=False))
     videos = request.user.videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=False))
 
 
     channels = videos.values(
     channels = videos.values(
@@ -98,19 +72,20 @@ def home(request):
                                          "watching": watching,
                                          "watching": watching,
                                          "recently_accessed_playlists": recently_accessed_playlists,
                                          "recently_accessed_playlists": recently_accessed_playlists,
                                          "recently_added_playlists": recently_added_playlists,
                                          "recently_added_playlists": recently_added_playlists,
-                                         "statistics": statistics,
                                          "videos": videos,
                                          "videos": videos,
                                          "channels": channels})
                                          "channels": channels})
 
 
 
 
 @login_required
 @login_required
 def favorites(request):
 def favorites(request):
-    favorite_playlists = request.user.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True)).order_by('-last_accessed_on')
+    favorite_playlists = request.user.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True)).order_by(
+        '-last_accessed_on')
     favorite_videos = request.user.videos.filter(is_favorite=True).order_by('updated_at')
     favorite_videos = request.user.videos.filter(is_favorite=True).order_by('updated_at')
 
 
     return render(request, 'favorites.html', {"playlists": favorite_playlists,
     return render(request, 'favorites.html', {"playlists": favorite_playlists,
                                               "videos": favorite_videos})
                                               "videos": favorite_videos})
 
 
+
 @login_required
 @login_required
 def view_video(request, video_id):
 def view_video(request, video_id):
     if request.user.videos.filter(video_id=video_id).exists():
     if request.user.videos.filter(video_id=video_id).exists():
@@ -375,7 +350,7 @@ def order_playlist_by(request, playlist_id, order_by):
     else:
     else:
         return HttpResponse("Something went wrong :(")
         return HttpResponse("Something went wrong :(")
 
 
-    return HttpResponse(loader.get_template("intercooler/videos.html").render({"playlist": playlist,
+    return HttpResponse(loader.get_template("intercooler/playlist_items.html").render({"playlist": playlist,
                                                                                "playlist_items": playlist_items,
                                                                                "playlist_items": playlist_items,
                                                                                "videos_details": videos_details,
                                                                                "videos_details": videos_details,
                                                                                "display_text": display_text,
                                                                                "display_text": display_text,
@@ -533,9 +508,13 @@ def delete_videos(request, playlist_id, command):
         else:
         else:
             help_text = "Done deleting selected videos from your playlist on YouTube."
             help_text = "Done deleting selected videos from your playlist on YouTube."
 
 
+        messages.success(request, help_text)
         return HttpResponse(f"""
         return HttpResponse(f"""
-        <h5 hx-get="/playlist/{playlist_id}/update/checkforupdates" hx-trigger="load delay:2s" hx-target="#checkforupdates">
-            {help_text} Refresh page!
+        <h5>
+            Done! Refreshing...
+            <script>
+        window.location.reload();
+        </script>
         </h5>
         </h5>
         <hr>
         <hr>
         """)
         """)
@@ -552,69 +531,19 @@ def delete_specific_videos(request, playlist_id, command):
     elif command == "duplicate":
     elif command == "duplicate":
         help_text = "Deleted all duplicate videos."
         help_text = "Deleted all duplicate videos."
 
 
+    messages.success(request, help_text)
+
     return HttpResponse(f"""
     return HttpResponse(f"""
         <h5>
         <h5>
-            {help_text} Refresh page!
+            Done. Refreshing...
+            <script>
+            window.location.reload();
+            </script>
         </h5>
         </h5>
         <hr>
         <hr>
         """)
         """)
 
 
 
 
-@login_required
-@require_POST
-def search_tagged_playlists(request, tag):
-    tag = get_object_or_404(Tag, created_by=request.user, name=tag)
-    playlists = tag.playlists.all()
-
-    return HttpResponse("yay")
-
-
-@login_required
-@require_POST
-def search_playlists(request, playlist_type):
-    # print(request.POST)  # prints <QueryDict: {'search': ['aa']}>
-
-    search_query = request.POST["search"]
-    watching = False
-
-    playlists = None
-    if playlist_type == "all":
-        try:
-            playlists = request.user.playlists.all().filter(Q(name__startswith=search_query) & Q(is_in_db=True))
-        except:
-            playlists = request.user.playlists.all()
-    elif playlist_type == "user-owned":  # YT playlists owned by user
-        try:
-            playlists = request.user.playlists.filter(
-                Q(name__startswith=search_query) & Q(is_user_owned=True) & Q(is_in_db=True))
-        except:
-            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.playlists.filter(
-                Q(name__startswith=search_query) & Q(is_user_owned=False) & Q(is_in_db=True))
-        except:
-            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.playlists.filter(
-                Q(name__startswith=search_query) & Q(is_favorite=True) & Q(is_in_db=True))
-        except:
-            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.playlists.filter(
-                Q(name__startswith=search_query) & Q(marked_as=playlist_type) & Q(is_in_db=True))
-        except:
-            playlists = request.user.playlists.all().filter(Q(marked_as=playlist_type) & Q(is_in_db=True))
-        if playlist_type == "watching":
-            watching = True
-
-    return HttpResponse(loader.get_template("intercooler/playlists.html")
-                        .render({"playlists": playlists,
-                                 "watching": watching}))
-
-
 #### MANAGE VIDEOS #####
 #### MANAGE VIDEOS #####
 @login_required
 @login_required
 def mark_video_favortie(request, video_id):
 def mark_video_favortie(request, video_id):
@@ -652,64 +581,7 @@ def mark_video_watched(request, playlist_id, video_id):
 
 
 
 
 ###########
 ###########
-@login_required
-def search(request):
-    if request.method == "GET":
-        return render(request, 'search_untube_page.html', {"playlists": request.user.playlists.all()})
-    else:
-        return redirect('home')
-
-
-@login_required
-@require_POST
-def search_UnTube(request):
-    print(request.POST)
-
-    search_query = request.POST["search"]
-
-    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)
-
-    playlist_items = []
-
-    if request.POST['search-settings'] == 'starts-with':
-        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_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():
-                        playlist_items.append(v)
-
-    else:
-        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_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():
-                        playlist_items.append(v)
-
-    return HttpResponse(loader.get_template("intercooler/search_untube_results.html")
-                        .render({"playlists": playlists,
-                                 "playlist_items": playlist_items,
-                                 "videos_count": len(playlist_items),
-                                 "search_query": True if search_query != "" else False,
-                                 "all_playlists": all_playlists}))
 
 
 
 
 @login_required
 @login_required
@@ -843,7 +715,7 @@ def load_more_videos(request, playlist_id, order_by, page):
         playlist_items = playlist.playlist_items.select_related('video').filter(
         playlist_items = playlist.playlist_items.select_related('video').filter(
             video__channel_name=channel_name).order_by("video_position")
             video__channel_name=channel_name).order_by("video_position")
 
 
-    return HttpResponse(loader.get_template("intercooler/videos.html")
+    return HttpResponse(loader.get_template("intercooler/playlist_items.html")
         .render(
         .render(
         {
         {
             "playlist": playlist,
             "playlist": playlist,
@@ -939,17 +811,17 @@ def update_playlist(request, playlist_id, command):
                 </div>
                 </div>
                 """)
                 """)
         elif result[0] == -1:  # playlist changed
         elif result[0] == -1:  # playlist changed
-            print("!!!Playlist changed")
-
-            # current_playlist_vid_count = playlist.video_count
-            # new_playlist_vid_count = result[1]
-
-            # print(current_playlist_vid_count)
-            # print(new_playlist_vid_count)
-
-            # playlist.has_playlist_changed = True
-            # playlist.save()
-            # print(playlist.playlist_changed_text)
+            print("Playlist was deleted from YouTube")
+            playlist.videos.all().delete()
+            playlist.delete()
+            return HttpResponse("""
+                        <div id="checkforupdates" class="sticky-top" style="top: 0.5em;">
+                            <div class="alert alert-danger alert-dismissible fade show sticky-top visually-hidden" role="alert" style="top: 0.5em;">
+                                The playlist owner deleted this playlist on YouTube. It will be deleted for you as well :(
+                                <meta http-equiv="refresh" content="1" />
+                            </div>
+                        </div>
+                        """)
         else:  # no updates found
         else:  # no updates found
             return HttpResponse("""
             return HttpResponse("""
             <div id="checkforupdates" class="sticky-top" style="top: 0.5em;">
             <div id="checkforupdates" class="sticky-top" style="top: 0.5em;">
@@ -1172,18 +1044,28 @@ def delete_playlist(request, playlist_id):
 
 
     if request.GET["confirmed"] == "no":
     if request.GET["confirmed"] == "no":
         return HttpResponse(f"""
         return HttpResponse(f"""
-            <a href="/playlist/{playlist_id}/delete-playlist?confirmed=yes" class="btn btn-danger">Confirm Delete</a>
+            <a href="/playlist/{playlist_id}/delete-playlist?confirmed=yes" hx-indicator="#delete-pl-loader" class="btn btn-danger">Confirm Delete</a>
             <a href="/playlist/{playlist_id}" class="btn btn-secondary ms-1">Cancel</a>
             <a href="/playlist/{playlist_id}" class="btn btn-secondary ms-1">Cancel</a>
         """)
         """)
 
 
     if not playlist.is_user_owned:  # if playlist trying to delete isn't user owned
     if not playlist.is_user_owned:  # if playlist trying to delete isn't user owned
-        playlist.delete()  # just delete it from untrue
+        video_ids = [video.video_id for video in playlist.videos.all()]
+        playlist.delete()
+        for video_id in video_ids:
+            video = request.user.videos.get(video_id=video_id)
+            if video.playlists.all().count() == 0:
+                video.delete()
+
         messages.success(request, "Successfully deleted playlist from UnTube.")
         messages.success(request, "Successfully deleted playlist from UnTube.")
     else:
     else:
         # deletes it from YouTube first then from UnTube
         # deletes it from YouTube first then from UnTube
         status = Playlist.objects.deletePlaylistFromYouTube(request.user, playlist_id)
         status = Playlist.objects.deletePlaylistFromYouTube(request.user, playlist_id)
-        if status == -1:  # failed to delete playlist from youtube
-            messages.error(request, "Failed to delete playlist from YouTube :(")
+        if status[0] == -1:  # failed to delete playlist from youtube
+            # if status[2] == 404:
+            #    playlist.delete()
+            #    messages.success(request, 'Looks like the playlist was already deleted on YouTube. Removed it from UnTube as well.')
+            #    return redirect('home')
+            messages.error(request, f"[{status[1]}] Failed to delete playlist from YouTube :(")
             return redirect('view_playlist_settings', playlist_id=playlist_id)
             return redirect('view_playlist_settings', playlist_id=playlist_id)
 
 
         messages.success(request, "Successfully deleted playlist from YouTube and removed it from UnTube as well.")
         messages.success(request, "Successfully deleted playlist from YouTube and removed it from UnTube as well.")

+ 0 - 0
apps/search/__init__.py


+ 3 - 0
apps/search/admin.py

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

+ 6 - 0
apps/search/apps.py

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

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


+ 3 - 0
apps/search/models.py

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

+ 0 - 0
apps/search/serializers.py


+ 32 - 0
apps/search/templates/intercooler/search_untube_results.html

@@ -0,0 +1,32 @@
+{% load humanize %}
+
+{% if playlists %}
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+        <h1 class="h2">{% if search_query == "" %}All{% endif %} Playlists <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span></h1>
+    </div>
+
+    <div class="row row-cols-1 row-cols-md-4 g-4">
+        {% if playlists %}
+            {% include 'intercooler/playlists.html' with playlists=playlists show_controls=True %}
+        {% else %}
+            <h5 class="text-dark align-content-center">No playlists found :(</h5>
+        {% endif %}
+    </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">
+        <h1 class="h2">Videos <span class="badge bg-primary rounded-pill">{{ videos.count }}</span>{% if videos.count > 250 %}(Only top 250 results shown){% endif %}</h1>
+    </div>
+
+    <div>
+        <div class="row row-cols-1 row-cols-md-4 g-4">
+            {% if videos %}
+                {% include 'intercooler/video_cards.html' with videos=videos %}
+            {% else %}
+                <h5 class="text-dark align-content-center">Nothing found :(</h5>
+            {% endif %}
+        </div>
+    </div>
+
+{% endif %}
+<br>

+ 53 - 20
apps/main/templates/search_untube_page.html → apps/search/templates/search_untube_page.html

@@ -21,7 +21,7 @@
 
 
         <input class="form-control me-lg-2" type="text"
         <input class="form-control me-lg-2" type="text"
                id="untubeSearchBar"
                id="untubeSearchBar"
-                name="search" placeholder="Search UnTube"
+                name="search" placeholder="Search UnTube (leave this empty to just filter playlists by tags or filter videos by channels)"
                 hx-post="{% url 'search_UnTube' %}"
                 hx-post="{% url 'search_UnTube' %}"
                 hx-trigger="keyup changed delay:700ms"
                 hx-trigger="keyup changed delay:700ms"
                 hx-target="#untube-searchbar-results"
                 hx-target="#untube-searchbar-results"
@@ -31,38 +31,48 @@
         <br>
         <br>
 
 
         <div class="row d-flex justify-content-center">
         <div class="row d-flex justify-content-center">
-            <div class="col-md-6">
-
+            <div class="col-md-6" id="playlists">
+                Filter by playlist tags:
                 <select class="visually-hidden" onchange="triggerSubmit()"
                 <select class="visually-hidden" onchange="triggerSubmit()"
-                     id="choices-multiple-remove-button" name="playlist-tags" placeholder="Add playlist tags to search within" multiple>
+                     id="choices-playlist-tags" name="playlist-tags" placeholder="Add playlist tags to search within" multiple>
                     {% for tag in user.playlist_tags.all %}
                     {% for tag in user.playlist_tags.all %}
                     <option value="{{ tag.name }}" hx-post="{% url 'search_UnTube' %}">{{ tag.name }}</option>
                     <option value="{{ tag.name }}" hx-post="{% url 'search_UnTube' %}">{{ tag.name }}</option>
                     {% endfor %}
                     {% endfor %}
                 </select>
                 </select>
             </div>
             </div>
+            <div class="col-md-6" id="videos">
+                Filter by video channels:
+                <select class="visually-hidden" onchange="triggerSubmit()"
+                     id="choices-channels" name="channel-names" placeholder="Select channels to search within" multiple>
+                    {% for channel in user.profile.get_channels_list %}
+                    <option value="{{ channel }}" hx-post="{% url 'search_UnTube' %}">{{ channel }}</option>
+                    {% endfor %}
+                </select>
+            </div>
         </div>
         </div>
         <br>
         <br>
     <div class="d-flex justify-content-center">
     <div class="d-flex justify-content-center">
+
         <div class="form-check me-5">
         <div class="form-check me-5">
-            <input hx-post="{% url 'search_UnTube' %}"
+            <input onclick="hideShow()" hx-post="{% url 'search_UnTube' %}"
         hx-trigger="click"
         hx-trigger="click"
         hx-target="#untube-searchbar-results"
         hx-target="#untube-searchbar-results"
         hx-include="[id='search-playlist-form']"
         hx-include="[id='search-playlist-form']"
         hx-indicator="#spinner"
         hx-indicator="#spinner"
-                           class="form-check-input" type="radio" name="search-settings" value="starts-with" id="starts-with-cb" checked>
-            <label class="form-check-label" for="starts-with-cb">
-                    Starts with
+                           class="form-check-input" type="radio" name="search-settings" value="playlists" id="playlists-cb" checked>
+            <label class="form-check-label " for="playlists-cb">
+                    Playlists
             </label>
             </label>
         </div>
         </div>
-         <div class="form-check">
-            <input hx-post="{% url 'search_UnTube' %}"
+         <div class="form-check" >
+            <input onclick="hideShow();" hx-post="{% url 'search_UnTube' %}"
         hx-trigger="click"
         hx-trigger="click"
         hx-target="#untube-searchbar-results"
         hx-target="#untube-searchbar-results"
         hx-include="[id='search-playlist-form']"
         hx-include="[id='search-playlist-form']"
         hx-indicator="#spinner"
         hx-indicator="#spinner"
-                    class="form-check-input" type="radio" name="search-settings" value="contains" id="contains-cb">
-                  <label class="form-check-label" for="contains-cb">
-                    Contains
+                    class="form-check-input" type="radio" name="search-settings" value="videos" id="videos-cb">
+                  <label class="form-check-label" for="videos-cb">
+                    Videos
                   </label>
                   </label>
         </div>
         </div>
     </div>
     </div>
@@ -78,7 +88,9 @@
         <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-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
             <h1 class="h2">All Playlists <span class="badge bg-primary rounded-pill">{{ playlists.count }}</span></h1>
             <h1 class="h2">All Playlists <span class="badge bg-primary rounded-pill">{{ playlists.count }}</span></h1>
         </div>
         </div>
+            <div class="row row-cols-1 row-cols-md-4 g-4">
             {% include 'intercooler/playlists.html' %}
             {% include 'intercooler/playlists.html' %}
+            </div>
         {% endif %}
         {% endif %}
     </div>
     </div>
 
 
@@ -88,21 +100,42 @@
     <script type="application/javascript">
     <script type="application/javascript">
 
 
     $(document).ready(function(){
     $(document).ready(function(){
+
+                        videos.style.display = 'none';
+
         // multiple choices select search box
         // multiple choices select search box
-        var multipleCancelButton = new Choices('#choices-multiple-remove-button', {
+        var choicesPlaylistTags = new Choices('#choices-playlist-tags', {
                     removeItemButton: true,
                     removeItemButton: true,
                 });
                 });
 
 
-            });
+        var choicesChannels = new Choices('#choices-channels', {
+                    removeItemButton: true,
+                });
+    });
+
+
+        function hideShow() {
+            var playlistsCB = document.getElementById("playlists-cb");
 
 
+            var videos = document.getElementById("videos");
+            var playlists = document.getElementById("playlists");
+            if (playlistsCB.checked) {
+                videos.style.display = 'none';
+                playlists.style.display = 'block';
+            } else {
+                videos.style.display = 'block';
+                playlists.style.display = 'none';
+            }
+        }
         function triggerSubmit() {
         function triggerSubmit() {
-            var startsWithCB = document.getElementById("starts-with-cb");
-            var containsCB = document.getElementById("contains-cb");
+            var playlistsCB = document.getElementById("playlists-cb");
+            var videosCB = document.getElementById("videos-cb");
 
 
-            if (startsWithCB.checked) {
-                startsWithCB.click();
+            if (playlistsCB.checked) {
+                playlistsCB.click();
             } else {
             } else {
-                containsCB.click();
+                videosCB.click();
+
             }
             }
         }
         }
     </script>
     </script>

+ 3 - 0
apps/search/tests.py

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

+ 10 - 0
apps/search/urls.py

@@ -0,0 +1,10 @@
+from django.urls import path
+from . import views
+
+urlpatterns = [
+    path("", views.search, name="search"),
+    path("untube/", views.search_UnTube, name="search_UnTube"),
+    path("playlists/<slug:playlist_type>", views.search_playlists, name="search_playlists"),
+    path("videos/<slug:playlist_type>", views.search_playlists, name="search_playlists"),
+    path("tagged-playlists/<str:tag>", views.search_tagged_playlists, name="search_tagged_playlists"),
+]

+ 118 - 0
apps/search/views.py

@@ -0,0 +1,118 @@
+from django.contrib.auth.decorators import login_required
+from django.db.models import Q
+from django.http import HttpResponse
+from django.shortcuts import render, redirect, get_object_or_404
+from django.template import loader
+from django.views.decorators.http import require_POST
+
+from apps.main.models import Video, Tag
+
+
+@login_required
+def search(request):
+    if request.method == "GET":
+        return render(request, 'search_untube_page.html', {"playlists": request.user.playlists.all()})
+    else:
+        return redirect('home')
+
+
+@login_required
+@require_POST
+def search_UnTube(request):
+    print(request.POST)
+
+    search_query = request.POST["search"]
+
+    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)
+
+    channels = []
+    if 'channel-names' in request.POST:
+        channels = request.POST.getlist('channel-names')
+
+    if request.POST['search-settings'] == 'playlists':
+        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 == "":
+            playlists = all_playlists
+
+        return HttpResponse(loader.get_template("intercooler/search_untube_results.html")
+                            .render({"playlists": playlists,
+                                     "search_query": search_query}))
+    else:
+        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 == "":
+            playlists = all_playlists
+
+        videos = Video.objects.none()
+        for playlist in playlists:
+            pl_videos = playlist.videos.filter(is_unavailable_on_yt=False)
+            videos = videos | pl_videos
+
+        videos = videos.filter(
+            Q(name__icontains=search_query) | Q(user_label__istartswith=search_query)).distinct()
+
+        return HttpResponse(loader.get_template("intercooler/search_untube_results.html")
+                            .render({"videos": videos[:250],
+                                     "show_all_videos": len(channels) > 0}))
+
+
+@login_required
+@require_POST
+def search_playlists(request, playlist_type):
+    # print(request.POST)  # prints <QueryDict: {'search': ['aa']}>
+
+    search_query = request.POST["search"]
+    watching = False
+
+    playlists = None
+    if playlist_type == "all":
+        try:
+            playlists = request.user.playlists.all().filter(Q(name__startswith=search_query) & Q(is_in_db=True))
+        except:
+            playlists = request.user.playlists.all()
+    elif playlist_type == "user-owned":  # YT playlists owned by user
+        try:
+            playlists = request.user.playlists.filter(
+                Q(name__startswith=search_query) & Q(is_user_owned=True) & Q(is_in_db=True))
+        except:
+            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.playlists.filter(
+                Q(name__startswith=search_query) & Q(is_user_owned=False) & Q(is_in_db=True))
+        except:
+            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.playlists.filter(
+                Q(name__startswith=search_query) & Q(is_favorite=True) & Q(is_in_db=True))
+        except:
+            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.playlists.filter(
+                Q(name__startswith=search_query) & Q(marked_as=playlist_type) & Q(is_in_db=True))
+        except:
+            playlists = request.user.playlists.all().filter(Q(marked_as=playlist_type) & Q(is_in_db=True))
+        if playlist_type == "watching":
+            watching = True
+
+    return HttpResponse(loader.get_template("intercooler/playlists.html")
+                        .render({"playlists": playlists,
+                                 "watching": watching}))
+
+
+@login_required
+@require_POST
+def search_tagged_playlists(request, tag):
+    tag = get_object_or_404(Tag, created_by=request.user, name=tag)
+    playlists = tag.playlists.all()
+
+    return HttpResponse("yay")

+ 18 - 0
apps/users/migrations/0010_profile_enable_gradient_bg.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2021-07-23 20:57
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0009_untube'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='profile',
+            name='enable_gradient_bg',
+            field=models.BooleanField(default=False),
+        ),
+    ]

+ 12 - 0
apps/users/models.py

@@ -3,6 +3,7 @@ from django.contrib.auth.hashers import make_password, check_password
 
 
 from django.db import models
 from django.db import models
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
+from django.db.models import Count, Q
 from django.db.models.signals import post_save
 from django.db.models.signals import post_save
 from django.dispatch import receiver
 from django.dispatch import receiver
 
 
@@ -60,6 +61,17 @@ class Profile(models.Model):
     create_playlist_add_vids_from_collection = models.CharField(max_length=50, default="")
     create_playlist_add_vids_from_collection = models.CharField(max_length=50, default="")
     create_playlist_add_vids_from_links = models.CharField(max_length=50, default="")
     create_playlist_add_vids_from_links = models.CharField(max_length=50, default="")
 
 
+    def get_channels_list(self):
+        channels_list = []
+        videos = self.user.videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=False))
+
+        queryset = videos.values(
+            'channel_name').annotate(channel_videos_count=Count('video_id')).order_by('-channel_videos_count')
+
+        for entry in queryset:
+            channels_list.append(entry['channel_name'])
+
+        return channels_list
 
 
 # as soon as one User object is created, create an associated profile object
 # as soon as one User object is created, create an associated profile object
 @receiver(post_save, sender=User)
 @receiver(post_save, sender=User)

+ 4 - 2
apps/main/templates/intercooler/progress_bar.html → apps/users/templates/intercooler/progress_bar.html

@@ -8,7 +8,7 @@
 
 
             <div class="w-50">
             <div class="w-50">
                 <div class="d-flex justify-content-center">
                 <div class="d-flex justify-content-center">
-                    <h3>Importing playlist '{{ playlist_name }}' from YouTube</h3>
+                    <h3>Imported playlist '{{ playlist_name }}' from YouTube</h3>
                 </div>
                 </div>
                 <div class="d-flex justify-content-center">
                 <div class="d-flex justify-content-center">
                     <h3>({{ playlists_imported }}/{{ total_playlists }} playlists imported)</h3>
                     <h3>({{ playlists_imported }}/{{ total_playlists }} playlists imported)</h3>
@@ -45,7 +45,9 @@
 
 
             <div class="w-75">
             <div class="w-75">
                 <div class="d-flex justify-content-center">
                 <div class="d-flex justify-content-center">
-                    <h3>Uh-oh, we were not able to find any YouTube channel linked to this Google account. Please create a YouTube channel to be able to import/export playlists back and forth between YouTube and UnTube.</h3>
+                    <h3>
+                        [{{ error_message }}] Uh-oh, ran into an error while trying to import your playlists.
+                        </h3>
                 </div>
                 </div>
                 <br>
                 <br>
                 <div class="d-flex justify-content-center">
                 <div class="d-flex justify-content-center">

+ 47 - 29
apps/users/views.py

@@ -46,16 +46,21 @@ def profile(request):
 
 
     if total_num_playlists != 0:
     if total_num_playlists != 0:
         # x means  percentage
         # x means  percentage
-        statistics["public_x"] = round(user_playlists.filter(is_private_on_yt=False).count() / total_num_playlists, 1) * 100
-        statistics["private_x"] = round(user_playlists.filter(is_private_on_yt=True).count() / total_num_playlists, 1) * 100
-        statistics["favorites_x"] = round(user_playlists.filter(is_favorite=True).count() / total_num_playlists, 1) * 100
-        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
+        statistics["public_x"] = round(user_playlists.filter(is_private_on_yt=False).count() / total_num_playlists,
+                                       1) * 100
+        statistics["private_x"] = round(user_playlists.filter(is_private_on_yt=True).count() / total_num_playlists,
+                                        1) * 100
+        statistics["favorites_x"] = round(user_playlists.filter(is_favorite=True).count() / total_num_playlists,
+                                          1) * 100
+        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', {
     return render(request, 'profile.html', {
-                                            "total_num_playlists": total_num_playlists,
-                                            "statistics": statistics,
-                                            "watching": watching})
+        "total_num_playlists": total_num_playlists,
+        "statistics": statistics,
+        "watching": watching})
 
 
 
 
 @login_required
 @login_required
@@ -97,6 +102,9 @@ def update_settings(request):
 
 
 @login_required
 @login_required
 def delete_account(request):
 def delete_account(request):
+    request.user.playlists.all().delete()
+    request.user.videos.all().delete()
+    request.user.playlist_tags.all().delete()
     request.user.profile.delete()
     request.user.profile.delete()
     request.user.delete()
     request.user.delete()
     request.session.flush()
     request.session.flush()
@@ -144,12 +152,12 @@ def import_user_yt_playlists(request):
 
 
 @login_required
 @login_required
 def start_import(request):
 def start_import(request):
-    '''
+    """
     Initializes only the user's playlist data in the database. Returns the progress bar, which will
     Initializes only the user's playlist data in the database. Returns the progress bar, which will
     keep calling continue_import
     keep calling continue_import
     :param request:
     :param request:
     :return:
     :return:
-    '''
+    """
     user_profile = request.user.profile
     user_profile = request.user.profile
 
 
     if user_profile.access_token.strip() == "" or user_profile.refresh_token.strip() == "":
     if user_profile.access_token.strip() == "" or user_profile.refresh_token.strip() == "":
@@ -161,36 +169,39 @@ def start_import(request):
         request.user.save()
         request.user.save()
 
 
     result = Playlist.objects.initializePlaylist(request.user)
     result = Playlist.objects.initializePlaylist(request.user)
-    channel_found = True
     if result["status"] == -1:
     if result["status"] == -1:
         print("User has no YT channel")
         print("User has no YT channel")
-        channel_found = False
 
 
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
-            {"channel_found": channel_found}
+            {
+                "channel_found": False,
+                "error_message": result["error_message"]
+            }
         ))
         ))
     elif result["status"] == -2:
     elif result["status"] == -2:
-        request.user.profile.import_in_progress = False
-        request.user.save()
+        user_profile.import_in_progress = False
+        user_profile.imported_yt_playlists = True
+        user_profile.show_import_page = True
+        user_profile.save()
 
 
         print("User has no playlists on YT")
         print("User has no playlists on YT")
 
 
-        Playlist.objects.initializePlaylist(request.user, "LL")
-
         if request.user.profile.yt_channel_id == "":
         if request.user.profile.yt_channel_id == "":
             Playlist.objects.getUserYTChannelID(request.user)
             Playlist.objects.getUserYTChannelID(request.user)
 
 
+        Playlist.objects.initializePlaylist(request.user, "LL")
+
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
             {"total_playlists": 0,
             {"total_playlists": 0,
              "playlists_imported": 0,
              "playlists_imported": 0,
              "done": True,
              "done": True,
              "progress": 100,
              "progress": 100,
-             "channel_found": channel_found}))
+             "channel_found": True}))
     else:
     else:
         if request.user.profile.yt_channel_id == "":
         if request.user.profile.yt_channel_id == "":
             Playlist.objects.getUserYTChannelID(request.user)
             Playlist.objects.getUserYTChannelID(request.user)
 
 
-        Playlist.objects.initializePlaylist(request.user)
+        Playlist.objects.initializePlaylist(request.user, "LL")
 
 
         user_profile.import_in_progress = True
         user_profile.import_in_progress = True
         user_profile.save()
         user_profile.save()
@@ -200,7 +211,7 @@ def start_import(request):
              "playlist_name": result["first_playlist_name"],
              "playlist_name": result["first_playlist_name"],
              "playlists_imported": 0,
              "playlists_imported": 0,
              "progress": 0,
              "progress": 0,
-             "channel_found": channel_found}
+             "channel_found": True}
         ))
         ))
 
 
 
 
@@ -210,15 +221,18 @@ def continue_import(request):
         return redirect('home')
         return redirect('home')
 
 
     num_of_playlists = request.user.playlists.filter(Q(is_user_owned=True)).exclude(playlist_id="LL").count()
     num_of_playlists = request.user.playlists.filter(Q(is_user_owned=True)).exclude(playlist_id="LL").count()
-
+    print("NUM OF PLAYLISTS", num_of_playlists)
     try:
     try:
-        remaining_playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False)).exclude(playlist_id="LL")
+        remaining_playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False)).exclude(
+            playlist_id="LL")
+        print(remaining_playlists.count(), "REMAINING PLAYLISTS")
         playlists_imported = num_of_playlists - remaining_playlists.count() + 1
         playlists_imported = num_of_playlists - remaining_playlists.count() + 1
         playlist = remaining_playlists.order_by("created_at")[0]
         playlist = remaining_playlists.order_by("created_at")[0]
         playlist_name = playlist.name
         playlist_name = playlist.name
         playlist_id = playlist.playlist_id
         playlist_id = playlist.playlist_id
-        Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)
+        Playlist.objects.getAllVideosForPlaylist(request.user, playlist_id)
     except:
     except:
+        print("NO REMAINING PLAYLISTS")
         playlist_id = -1
         playlist_id = -1
 
 
     if playlist_id != -1:
     if playlist_id != -1:
@@ -235,7 +249,8 @@ def continue_import(request):
         request.user.profile.show_import_page = True  # set back to true again so as to show users the welcome screen on 'home'
         request.user.profile.show_import_page = True  # set back to true again so as to show users the welcome screen on 'home'
         request.user.save()
         request.user.save()
 
 
-        user_pl_count = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True)).exclude(playlist_id="LL").count()
+        user_pl_count = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True)).exclude(
+            playlist_id="LL").count()
 
 
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
             {"total_playlists": user_pl_count,
             {"total_playlists": user_pl_count,
@@ -253,7 +268,8 @@ def user_playlists_updates(request, action):
     If any new playlist id, imports it to UnTube
     If any new playlist id, imports it to UnTube
     """
     """
     if action == 'check-for-updates':
     if action == 'check-for-updates':
-        user_playlists_on_UnTube = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True)).exclude(playlist_id="LL")
+        user_playlists_on_UnTube = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True)).exclude(
+            playlist_id="LL")
 
 
         result = Playlist.objects.initializePlaylist(request.user)
         result = Playlist.objects.initializePlaylist(request.user)
 
 
@@ -276,7 +292,7 @@ def user_playlists_updates(request, action):
             print("No new updates")
             print("No new updates")
             playlists = []
             playlists = []
         else:
         else:
-            playlists = request.user.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)).exclude(playlist_id="LL")
             print(
             print(
                 f"New updates found! {playlists.count()} newly added and {len(deleted_playlist_ids)} playlists deleted!")
                 f"New updates found! {playlists.count()} newly added and {len(deleted_playlist_ids)} playlists deleted!")
             print(deleted_playlist_names)
             print(deleted_playlist_names)
@@ -285,7 +301,7 @@ def user_playlists_updates(request, action):
             {"playlists": playlists,
             {"playlists": playlists,
              "deleted_playlist_names": deleted_playlist_names}))
              "deleted_playlist_names": deleted_playlist_names}))
     elif action == 'init-update':
     elif action == 'init-update':
-        unimported_playlists = request.user.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)).exclude(playlist_id="LL").count()
 
 
         return HttpResponse(f"""
         return HttpResponse(f"""
         <div hx-get="/updates/user-playlists/start-update" hx-trigger="load" hx-target="#user-pl-updates">
         <div hx-get="/updates/user-playlists/start-update" hx-trigger="load" hx-target="#user-pl-updates">
@@ -298,7 +314,7 @@ def user_playlists_updates(request, action):
         </div>
         </div>
         """)
         """)
     elif action == 'start-update':
     elif action == 'start-update':
-        unimported_playlists = request.user.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)).exclude(playlist_id="LL")
 
 
         for playlist in unimported_playlists:
         for playlist in unimported_playlists:
             Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)
             Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)
@@ -311,6 +327,7 @@ def user_playlists_updates(request, action):
         </div>
         </div>
         """)
         """)
 
 
+
 @login_required
 @login_required
 def get_user_liked_videos_playlist(request):
 def get_user_liked_videos_playlist(request):
     if not request.user.playlists.filter(Q(playlist_id="LL") & Q(is_in_db=True)).exists():
     if not request.user.playlists.filter(Q(playlist_id="LL") & Q(is_in_db=True)).exists():
@@ -341,6 +358,7 @@ def like_untube(request):
           </a>
           </a>
     """)
     """)
 
 
+
 @require_POST
 @require_POST
 def unlike_untube(request):
 def unlike_untube(request):
     untube = Untube.objects.all().first()
     untube = Untube.objects.all().first()
@@ -354,4 +372,4 @@ def unlike_untube(request):
                 <a hx-post="/like-untube/" hx-swap="outerHTML" style="text-decoration: none; color: black">
                 <a hx-post="/like-untube/" hx-swap="outerHTML" style="text-decoration: none; color: black">
                 <i class="fas fa-heart"></i> {untube.page_likes} likes (p.s :/)
                 <i class="fas fa-heart"></i> {untube.page_likes} likes (p.s :/)
               </a>
               </a>
-        """)
+        """)

+ 5 - 7
templates/base.html

@@ -119,7 +119,7 @@
 
 
         <link href="{% static 'fontawesome-free-5.15.3-web/css/all.min.css' %}" rel="stylesheet">
         <link href="{% static 'fontawesome-free-5.15.3-web/css/all.min.css' %}" rel="stylesheet">
         <link href="{% static 'bootstrap5.0.1/css/bootstrap.min.css' %}" rel="stylesheet">
         <link href="{% static 'bootstrap5.0.1/css/bootstrap.min.css' %}" rel="stylesheet">
-        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/bbbootstrap/libraries@main/choices.min.css">
+        <link rel="stylesheet" href="{% static 'choices.js/choices.min.css' %}">
 
 
         <script src="{% static 'htmx/htmx.min.js' %}" type="application/javascript"></script>
         <script src="{% static 'htmx/htmx.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 'jquery3.6.0/js/jquery-3.6.0.min.js' %}" type="application/javascript"></script>
@@ -171,11 +171,11 @@
                             <a class="nav-link" href="{% url 'manage_playlists' %}">Manage</a>
                             <a class="nav-link" href="{% url 'manage_playlists' %}">Manage</a>
                         </li>
                         </li>
 
 
-                        <!--
+
                         <li class="nav-item">
                         <li class="nav-item">
-                            <a class="nav-link" href="{% url 'settings' %}">Settings</a>
+                            <a class="nav-link" href="{% url 'settings' %}">About</a>
                         </li>
                         </li>
-                        -->
+
                     </ul>
                     </ul>
 
 
 
 
@@ -231,11 +231,9 @@
 
 
         <br>
         <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 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 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 'choices.js/choices.min.js' %}"></script>
 
 
         <script type="application/javascript">
         <script type="application/javascript">
 
 

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است