2
0
Эх сурвалжийг харах

optimized code and redesigned search

sleepytaco 3 жил өмнө
parent
commit
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',
     'apps.users',  # has stuff related to user management in it (login, signup, show homepage, import)
     'apps.main',  # main app, shows user their homepage
-    'apps.charts'
+    'apps.charts',
+    'apps.search',
 ]
 
 CRISPY_TEMPLATE_PACK = 'bootstrap4'

+ 3 - 1
UnTube/urls.py

@@ -20,5 +20,7 @@ urlpatterns = [
     path('admin/', admin.site.urls),
     path('', include("apps.users.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))
 
     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:
         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
 
 
+def get_message_from_httperror(e):
+    return e.error_details[0]['message']
+
+
 class PlaylistManager(models.Manager):
     def getCredentials(self, user):
         credentials = Credentials(
@@ -38,8 +42,11 @@ class PlaylistManager(models.Manager):
 
         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:
             if "list=" in el:
@@ -83,6 +90,7 @@ class PlaylistManager(models.Manager):
         result = {"status": 0,
                   "num_of_playlists": 0,
                   "first_playlist_name": "N/A",
+                  "error_message": "",
                   "playlist_ids": []}
 
         credentials = self.getCredentials(user)
@@ -106,10 +114,11 @@ class PlaylistManager(models.Manager):
             # execute the above request, and store the response
             try:
                 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 playlist not found if id=playlist_id")
                 result["status"] = -1
+                result["error_message"] = get_message_from_httperror(e)
                 return result
 
             print(pl_response)
@@ -143,7 +152,7 @@ class PlaylistManager(models.Manager):
             # check if this playlist already exists in user's untube collection
             if user.playlists.filter(playlist_id=playlist_id).exists():
                 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:
                 # 1. PLAYLIST HAS DUPLICATE VIDEOS, DELETED VIDS, UNAVAILABLE VIDS
@@ -157,6 +166,8 @@ class PlaylistManager(models.Manager):
                     result["status"] = -3
                     return result
             else:  # no such playlist in database
+                print(f"CREATING {item['snippet']['title']} ({playlist_id})")
+
                 ### MAKE THE PLAYLIST AND LINK IT TO CURRENT_USER
                 playlist = Playlist(  # create the playlist and link it to current user
                     playlist_id=playlist_id,
@@ -250,7 +261,12 @@ class PlaylistManager(models.Manager):
                         video=video
                     )
                     playlist_item.save()
+
                 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)
 
                     # if video already in playlist.videos
@@ -469,7 +485,11 @@ class PlaylistManager(models.Manager):
                 )
 
                 # 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']:
                     playlist_item_id = item['id']
@@ -908,12 +928,17 @@ class PlaylistManager(models.Manager):
                 # playlistNotFound  (404)
                 # playlistOperationUnsupported (400)
                 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
+            video_ids = [video.video_id for video in playlist.videos.all()]
             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):
         """
@@ -949,11 +974,11 @@ class PlaylistManager(models.Manager):
 
                 if not playlist.playlist_items.filter(video__video_id=video.video_id).exists():
                     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_duration_in_seconds -= video.duration_in_seconds
-                # video.delete()
 
         playlist.video_count = new_playlist_video_count
         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-trigger="keyup changed delay:700ms"
                    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>
 
-        <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>
         {% else %}
               <div class="card bg-dark text-white mb-3">

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

@@ -2,181 +2,44 @@
 {% load humanize %}
 {% 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>
-                {% 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 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>
 {% endblock %}

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

@@ -23,7 +23,7 @@
             <h1>Welcome to UnTube, {{ user.username|capfirst }}</h1>
         </div>
         <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
-            <h2>{{ user.playlists.all.count }} playlists from YouTube have been successfully imported.</h2>
+            <h2>{{ imported_playlists_count }} playlists from YouTube have been successfully imported.</h2>
         </div>
 
         <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="card bg-transparent text-dark">
                     <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">
 
-                            <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>
-
-
-
                         </div>
                     </div>
                 </div>
@@ -85,62 +76,81 @@
 
         </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 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="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 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="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 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>
 
             <!-- FULL IMAGE CARD: might be useful
             <div class="col-sm-6 col-lg-4 mb-4">
@@ -158,12 +168,21 @@
             <div class="col">
                 <div class="card bg-transparent text-dark">
                     <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">
 
-                            <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>
+
                         </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>
                         {% if user_playlists %}
                             <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
-                                {% for playlist in user_playlists|slice:"0:3" %}
-                                    <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>
-
                         {% else %}
                             <br>
                             <h5>Nothing to see here... yet.</h5>
@@ -263,7 +260,7 @@
                     <div class="row flex-row g-3 flex-nowrap">
                         {% for playlist in watching %}
                             <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">
 
                                     <div class="card-body">
@@ -291,54 +288,8 @@
                                 </div>
                             </div>
 
-                            <!--
-                    <div class="col">
-                    <div class="card">
-                    <a style="background-color: #7e89c2;" href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item list-group-item-action" aria-current="true">
-                        <div class="card-body">
-                    <h5 class="card-title">
-                       {{ playlist.name }}
-                        {% if playlist.is_private_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
-                        {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-dark">YT</span></small> {% endif %}
-                    </h5>
-                    <p class="card-text">
-                        <span class="badge bg-{% if playlist.get_watch_time_left == "0secs." %}success{% else %}primary{% endif %} text-white">{{ playlist.get_watched_videos_count }}/{{ playlist.get_watchable_videos_count }} viewed</span>
-
-                        {% if playlist.get_watch_time_left != "0secs." %}<span class="badge bg-dark text-white">{{ playlist.get_watch_time_left }} left</span>{% endif %}
-                    </p>
-                        {% if playlist.tags.all %}
-                    <small>
-                    <i class="fas fa-tags fa-sm" style="color: yellow"></i>
-                        {% for tag in playlist.tags.all %}
-                            <span class="badge rounded-pill bg-primary mb-lg-1">
-                                {{ tag.name }}
-                            </span>
                         {% endfor %}
-                    </small>
-                        {% endif %}
-                    </div>
-                    </a>
-                    </div>
-                    </div> -->
-                            <!--
-                    {% if forloop.counter == 3 %}
-                    {% if watching.count|add:"-3" != 0 %}
-                    <div class="col">
-                        <div class="card">
-                            <a style="background-color: #7e89c2;" href="{% url 'all_playlists' 'watching' %}" class="list-group-item list-group-item-action" aria-current="true">
-                                <div class="card-body">
 
-                                    <p class="card-text">
-                                        <h3>+ {{ watching.count|add:"-3" }} more</h3>
-                                    </p>
-                                 </div>
-                            </a>
-                        </div>
-                    </div>
-                    {% endif %}
-                    {% endif %}
-                    -->
-                        {% endfor %}
                     </div>
                 </div>
             {% else %}
@@ -346,23 +297,7 @@
                 <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">
-                        {% 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>
             {% 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>
                 {% if recently_added_playlists %}
                     <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
-                        {% for playlist in recently_added_playlists %}
-                            <div class="col">
-                                <div class="card overflow-auto" style="background-color: #958a44;">
-                                    <a href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
-                                        <div class="card-body">
-                                            <h5 class="card-title">
-                                                {{ playlist.name }}
-                                                {% if playlist.is_private_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
-                                                {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-dark">YT</span></small> {% endif %}
-                                            </h5>
-                                            <small>
-                                                <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
-                                                <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
-                                                <span class="badge bg-secondary rounded-pill">{{ playlist.num_of_accesses }} clicks </span>
-
-                                            </small>
-                                        </div>
-                                    </a>
-                                </div>
-                            </div>
-                        {% endfor %}
+                        {% include 'intercooler/playlists.html' with playlists=recently_added_playlists watching=False bg_color="#958a44" show_controls=False %}
                     </div>
                 {% else %}
                     <br>
@@ -410,27 +325,7 @@
 
                 {% if recently_accessed_playlists %}
                     <div class="row row-cols-1 row-cols-md-3 g-4 text-dark mt-0">
-                        {% for playlist in recently_accessed_playlists %}
-                            <div class="col">
-                                <div class="card overflow-auto" style="background-color: #357779;">
-                                    <a  href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item bg-transparent list-group-item-action" aria-current="true">
-                                        <div class="card-body">
-                                            <h5 class="card-title">
-                                                {{ playlist.name }}
-                                                {% if playlist.is_private_on_yt %}<small><span class="badge bg-light text-dark">Private</span></small> {% endif %}
-                                                {% if playlist.is_from_yt %}<small><span class="badge bg-danger text-dark">YT</span></small> {% endif %}
-                                            </h5>
-                                            <small>
-                                                <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} videos</span>
-                                                <span class="badge bg-primary rounded-pill">{{ playlist.playlist_duration }} </span>
-                                                <span class="badge bg-secondary rounded-pill">{{ playlist.num_of_accesses }} clicks </span>
-
-                                            </small>
-                                        </div>
-                                    </a>
-                                </div>
-                            </div>
-                        {% endfor %}
+                        {% include 'intercooler/playlists.html' with playlists=recently_accessed_playlists watching=False bg_color="#357779" show_controls=False %}
                     </div>
                 {% else %}
                     <br>
@@ -445,6 +340,17 @@
 
 
         <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 type="application/javascript">
@@ -496,8 +402,8 @@
                                 tooltips: {
                                     callbacks: {
                                         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: {
                                     callbacks: {
                                         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: {
                                     callbacks: {
                                         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 %}
-       <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="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>
-                </a>
             </div>
         </div>
-
-        {% endfor %}
-    </div>
+    {% endfor %}
 
 {% 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="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 %}
                             <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>
-                            {% 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>
+
+                    {% 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>
-        {% 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>
+    {% endfor %}
+    -->
 {% 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>
         {% else %}
             <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 class="alert alert-info alert-dismissible fade show" role="alert">
                             Checking playlist for updates...
@@ -269,7 +269,7 @@
                                 </a>
                             </div>
 
-                            {% if playlist.video_count != 0 %}
+                            {% if playlist.video_count > 1 %}
                             <div class="btn-group me-2 mb-2">
                                 <a href="{% url 'playlist_open_random_video' playlist.playlist_id %}" class="btn btn-danger">
                                     Open a Random Video
@@ -426,6 +426,7 @@
                 <div class="collapse border-danger" id="deleteItemsCollapse">
                     <div class="card card-body bg-dark text-white-50">
                         <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 %}
                                 <h5>Note: You have set confirm before deleting to False. Buttons below will take effect immediately when clicked.</h5>
                                 <hr>
@@ -627,13 +628,14 @@
                 {% endif %}
 
             </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>
             <span class="mt-3 ms-2">Loading more videos...</span>
         </div>
         {% endif %}
+
+        {% endif %}
     </div>
 
     <button class="scrollToTopBtn sticky-top">
@@ -741,7 +743,21 @@
 
 
         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();
             bsDeleteCollapse.hide();
         });

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

@@ -85,9 +85,12 @@
                         <div class="col-sm-3">
                           <h6 class="mb-0">Danger Zone</h6>
                         </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">
-                                <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>
@@ -100,8 +103,8 @@
                 <div class="btn-group">
                     <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>
@@ -169,9 +172,12 @@
                         <div class="col-sm-3">
                           <h6 class="mb-0">Danger Zone</h6>
                         </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">
-                                <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>
@@ -184,8 +190,8 @@
                             <div class="btn-group">
                 <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>

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

@@ -19,24 +19,29 @@
                     </div>
                     <div class="col-md-8">
                         <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>
                                     <br><small class="h4">{% if video.user_label %}a.k.a <span style="border-bottom: 3px #ffffff dashed;"> {{ video.user_label }}</span>{% endif %}</small>
                                 </h2>
-                                <h4>
+                                <h4 class="col d-flex justify-content-end">
                                     <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 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>
                                 </h4>
                             </div>
@@ -66,6 +71,7 @@
                                         </div>
                                     </div>
                                 </div>
+                                <small>
                                 {% 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 %}
                                 <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>
                                 {% 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>
+                                {% 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>
                             <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
     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"),
 
     ### 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"),
 
     ### 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'),
 
     ### STUFF RELATED TO ONE 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>/order-by/<slug:order_by>", views.order_playlist_by,
          name='order_playlist_by'),
@@ -47,10 +45,8 @@ urlpatterns = [
          name="playlist_completion_times"),
 
     ### 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>/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'),
 
     ### STUFF RELATED TO MANAGING A PLAYLIST

+ 47 - 165
apps/main/views.py

@@ -1,6 +1,7 @@
 import datetime
+import json
 import random
-
+from django.core import serializers
 import bleach
 import pytz
 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
         """
         # user_profile.show_import_page = False
-
         if user_profile.profile.access_token.strip() == "" or user_profile.profile.refresh_token.strip() == "":
             user_social_token = SocialToken.objects.get(account__user=request.user)
             user_profile.profile.access_token = user_social_token.token
             user_profile.profile.refresh_token = user_social_token.token_secret
             user_profile.profile.expires_at = user_social_token.expires_at
             user_profile.save()
-            Playlist.objects.getUserYTChannelID(user_profile)
+            Playlist.objects.getUserYTChannelID(request.user)
 
         if user_profile.profile.imported_yt_playlists:
             user_profile.profile.show_import_page = False  # after user imports all their YT playlists no need to show_import_page again
             user_profile.profile.save(update_fields=['show_import_page'])
-            return render(request, "home.html", {"import_successful": True})
+            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")
-
-        # 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)
@@ -67,27 +62,6 @@ def home(request):
     user_playlists = user_playlists.filter(num_of_accesses__gt=0).order_by(
         "-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))
 
     channels = videos.values(
@@ -98,19 +72,20 @@ def home(request):
                                          "watching": watching,
                                          "recently_accessed_playlists": recently_accessed_playlists,
                                          "recently_added_playlists": recently_added_playlists,
-                                         "statistics": statistics,
                                          "videos": videos,
                                          "channels": channels})
 
 
 @login_required
 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')
 
     return render(request, 'favorites.html', {"playlists": favorite_playlists,
                                               "videos": favorite_videos})
 
+
 @login_required
 def view_video(request, video_id):
     if request.user.videos.filter(video_id=video_id).exists():
@@ -375,7 +350,7 @@ def order_playlist_by(request, playlist_id, order_by):
     else:
         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,
                                                                                "videos_details": videos_details,
                                                                                "display_text": display_text,
@@ -533,9 +508,13 @@ def delete_videos(request, playlist_id, command):
         else:
             help_text = "Done deleting selected videos from your playlist on YouTube."
 
+        messages.success(request, help_text)
         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>
         <hr>
         """)
@@ -552,69 +531,19 @@ def delete_specific_videos(request, playlist_id, command):
     elif command == "duplicate":
         help_text = "Deleted all duplicate videos."
 
+    messages.success(request, help_text)
+
     return HttpResponse(f"""
         <h5>
-            {help_text} Refresh page!
+            Done. Refreshing...
+            <script>
+            window.location.reload();
+            </script>
         </h5>
         <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 #####
 @login_required
 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
@@ -843,7 +715,7 @@ def load_more_videos(request, playlist_id, order_by, page):
         playlist_items = playlist.playlist_items.select_related('video').filter(
             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(
         {
             "playlist": playlist,
@@ -939,17 +811,17 @@ def update_playlist(request, playlist_id, command):
                 </div>
                 """)
         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
             return HttpResponse("""
             <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":
         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>
         """)
 
     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.")
     else:
         # deletes it from YouTube first then from UnTube
         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)
 
         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"
                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-trigger="keyup changed delay:700ms"
                 hx-target="#untube-searchbar-results"
@@ -31,38 +31,48 @@
         <br>
 
         <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()"
-                     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 %}
                     <option value="{{ tag.name }}" hx-post="{% url 'search_UnTube' %}">{{ tag.name }}</option>
                     {% endfor %}
                 </select>
             </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>
         <br>
     <div class="d-flex justify-content-center">
+
         <div class="form-check me-5">
-            <input hx-post="{% url 'search_UnTube' %}"
+            <input onclick="hideShow()" hx-post="{% url 'search_UnTube' %}"
         hx-trigger="click"
         hx-target="#untube-searchbar-results"
         hx-include="[id='search-playlist-form']"
         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>
         </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-target="#untube-searchbar-results"
         hx-include="[id='search-playlist-form']"
         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>
         </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">
             <h1 class="h2">All Playlists <span class="badge bg-primary rounded-pill">{{ playlists.count }}</span></h1>
         </div>
+            <div class="row row-cols-1 row-cols-md-4 g-4">
             {% include 'intercooler/playlists.html' %}
+            </div>
         {% endif %}
     </div>
 
@@ -88,21 +100,42 @@
     <script type="application/javascript">
 
     $(document).ready(function(){
+
+                        videos.style.display = 'none';
+
         // multiple choices select search box
-        var multipleCancelButton = new Choices('#choices-multiple-remove-button', {
+        var choicesPlaylistTags = new Choices('#choices-playlist-tags', {
                     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() {
-            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 {
-                containsCB.click();
+                videosCB.click();
+
             }
         }
     </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.contrib.auth.models import User
+from django.db.models import Count, Q
 from django.db.models.signals import post_save
 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_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
 @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="d-flex justify-content-center">
-                    <h3>Importing playlist '{{ playlist_name }}' from YouTube</h3>
+                    <h3>Imported playlist '{{ playlist_name }}' from YouTube</h3>
                 </div>
                 <div class="d-flex justify-content-center">
                     <h3>({{ playlists_imported }}/{{ total_playlists }} playlists imported)</h3>
@@ -45,7 +45,9 @@
 
             <div class="w-75">
                 <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>
                 <br>
                 <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:
         # 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', {
-                                            "total_num_playlists": total_num_playlists,
-                                            "statistics": statistics,
-                                            "watching": watching})
+        "total_num_playlists": total_num_playlists,
+        "statistics": statistics,
+        "watching": watching})
 
 
 @login_required
@@ -97,6 +102,9 @@ def update_settings(request):
 
 @login_required
 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.delete()
     request.session.flush()
@@ -144,12 +152,12 @@ def import_user_yt_playlists(request):
 
 @login_required
 def start_import(request):
-    '''
+    """
     Initializes only the user's playlist data in the database. Returns the progress bar, which will
     keep calling continue_import
     :param request:
     :return:
-    '''
+    """
     user_profile = request.user.profile
 
     if user_profile.access_token.strip() == "" or user_profile.refresh_token.strip() == "":
@@ -161,36 +169,39 @@ def start_import(request):
         request.user.save()
 
     result = Playlist.objects.initializePlaylist(request.user)
-    channel_found = True
     if result["status"] == -1:
         print("User has no YT channel")
-        channel_found = False
 
         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:
-        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")
 
-        Playlist.objects.initializePlaylist(request.user, "LL")
-
         if request.user.profile.yt_channel_id == "":
             Playlist.objects.getUserYTChannelID(request.user)
 
+        Playlist.objects.initializePlaylist(request.user, "LL")
+
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
             {"total_playlists": 0,
              "playlists_imported": 0,
              "done": True,
              "progress": 100,
-             "channel_found": channel_found}))
+             "channel_found": True}))
     else:
         if request.user.profile.yt_channel_id == "":
             Playlist.objects.getUserYTChannelID(request.user)
 
-        Playlist.objects.initializePlaylist(request.user)
+        Playlist.objects.initializePlaylist(request.user, "LL")
 
         user_profile.import_in_progress = True
         user_profile.save()
@@ -200,7 +211,7 @@ def start_import(request):
              "playlist_name": result["first_playlist_name"],
              "playlists_imported": 0,
              "progress": 0,
-             "channel_found": channel_found}
+             "channel_found": True}
         ))
 
 
@@ -210,15 +221,18 @@ def continue_import(request):
         return redirect('home')
 
     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:
-        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
         playlist = remaining_playlists.order_by("created_at")[0]
         playlist_name = playlist.name
         playlist_id = playlist.playlist_id
-        Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)
+        Playlist.objects.getAllVideosForPlaylist(request.user, playlist_id)
     except:
+        print("NO REMAINING PLAYLISTS")
         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.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(
             {"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 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)
 
@@ -276,7 +292,7 @@ def user_playlists_updates(request, action):
             print("No new updates")
             playlists = []
         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(
                 f"New updates found! {playlists.count()} newly added and {len(deleted_playlist_ids)} playlists deleted!")
             print(deleted_playlist_names)
@@ -285,7 +301,7 @@ def user_playlists_updates(request, action):
             {"playlists": playlists,
              "deleted_playlist_names": deleted_playlist_names}))
     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"""
         <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>
         """)
     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:
             Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)
@@ -311,6 +327,7 @@ def user_playlists_updates(request, action):
         </div>
         """)
 
+
 @login_required
 def get_user_liked_videos_playlist(request):
     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>
     """)
 
+
 @require_POST
 def unlike_untube(request):
     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">
                 <i class="fas fa-heart"></i> {untube.page_likes} likes (p.s :/)
               </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 '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 '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>
                         </li>
 
-                        <!--
+
                         <li class="nav-item">
-                            <a class="nav-link" href="{% url 'settings' %}">Settings</a>
+                            <a class="nav-link" href="{% url 'settings' %}">About</a>
                         </li>
-                        -->
+
                     </ul>
 
 
@@ -231,11 +231,9 @@
 
         <br>
 
-
-        <script src="{% static 'BackgroundCheck/BackgroundCheck.min.js' %}" type="application/javascript"></script>
         <script src="{% static 'clipboard.js/clipboard.min.js' %}" type="application/javascript"></script>
         <script async src="https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js" integrity="sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D" crossorigin="anonymous"></script>
-        <script src="https://cdn.jsdelivr.net/gh/bbbootstrap/libraries@main/choices.min.js"></script>
+        <script src="{% static 'choices.js/choices.min.js' %}"></script>
 
         <script type="application/javascript">
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно