瀏覽代碼

Added auto-update user YouTube playlists feature

sleepytaco 3 年之前
父節點
當前提交
eb5522a021

+ 1 - 1
UnTube/secrets.py

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

+ 23 - 9
apps/main/models.py

@@ -340,6 +340,10 @@ class PlaylistManager(models.Manager):
                     playlist_yt_player_HTML=item['player']['embedHtml'],
                     user=current_user
                 )
+
+                if user.profile.yt_channel_id.strip() != playlist.channel_id.strip():
+                    playlist.is_user_owned = False
+
                 playlist.save()
 
                 playlist = current_user.playlists.get(playlist_id__exact=playlist_id)
@@ -503,7 +507,7 @@ class PlaylistManager(models.Manager):
                     playlist.has_unavailable_videos = True
 
                 playlist.is_in_db = True
-                playlist.is_user_owned = False
+                # playlist.is_user_owned = False
                 playlist.save()
 
         if pl_id is None:
@@ -521,12 +525,16 @@ class PlaylistManager(models.Manager):
         :param user:
         :return:
         '''
-        result = {"status": 0, "num_of_playlists": 0, "first_playlist_name": "N/A"}
+        result = {"status": 0,
+                  "num_of_playlists": 0,
+                  "first_playlist_name": "N/A",
+                  "playlist_ids": []}
 
         current_user = user.profile
 
         credentials = self.getCredentials(user)
 
+        playlist_ids = []
         with build('youtube', 'v3', credentials=credentials) as youtube:
             pl_request = youtube.playlists().list(
                 part='contentDetails, snippet, id, player, status',
@@ -566,7 +574,7 @@ class PlaylistManager(models.Manager):
 
         for item in playlist_items:
             playlist_id = item["id"]
-
+            playlist_ids.append(playlist_id)
             # check if this playlist already exists in database
             if current_user.playlists.filter(playlist_id=playlist_id).count() != 0:
                 playlist = current_user.playlists.get(playlist_id__exact=playlist_id)
@@ -576,9 +584,9 @@ class PlaylistManager(models.Manager):
                 # 1. PLAYLIST HAS DUPLICATE VIDEOS, DELETED VIDS, UNAVAILABLE VIDS
 
                 # check if playlist count changed on youtube
-                if playlist.video_count != item['contentDetails']['itemCount']:
-                    playlist.has_playlist_changed = True
-                    playlist.save()
+                #if playlist.video_count != item['contentDetails']['itemCount']:
+                #    playlist.has_playlist_changed = True
+                #    playlist.save()
             else:  # no such playlist in database
                 ### MAKE THE PLAYLIST AND LINK IT TO CURRENT_USER
                 playlist = Playlist(  # create the playlist and link it to current user
@@ -599,6 +607,8 @@ class PlaylistManager(models.Manager):
                 )
                 playlist.save()
 
+        result["playlist_ids"] = playlist_ids
+
         return result
 
     def getAllVideosForPlaylist(self, user, playlist_id):
@@ -794,7 +804,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:
+                print("Playist was deleted on YouTube")
+                return [-1, [], [], []]
 
             print("ESTIMATED VIDEO IDS FROM RESPONSE", len(pl_response["items"]))
             updated_playlist_video_count += len(pl_response["items"])
@@ -981,13 +995,13 @@ class PlaylistManager(models.Manager):
 
         deleted_videos = current_video_ids  # left out video ids
 
-        return [deleted_videos, unavailable_videos, added_videos]
+        return [0, deleted_videos, unavailable_videos, added_videos]
 
 
 class Playlist(models.Model):
     # playlist details
     playlist_id = models.CharField(max_length=150)
-    name = models.CharField(max_length=150, blank=True)
+    name = models.CharField(max_length=150, blank=True)  # YT PLAYLIST NAMES CAN ONLY HAVE MAX OF 150 CHARS
     thumbnail_url = models.CharField(max_length=420, blank=True)
     description = models.CharField(max_length=420, default="No description")
     video_count = models.IntegerField(default=0)

+ 25 - 18
apps/main/templates/home.html

@@ -2,13 +2,15 @@
 {% extends 'base.html' %}
 {% block content %}
 
+            {% if messages %}
+                <br>
                 {% for message in messages %}
                   <div class="alert alert-success alert-dismissible fade show" role="alert">
                       {{ message }}
                       <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                   </div>
                 {% endfor %}
-
+            {% endif %}
         {% if import_successful %}
             <br>
                 <br>
@@ -33,24 +35,17 @@
 
                 <h1 class="h2">Dashboard</h1>
 
-                <!--
-                <div class="btn-toolbar mb-2 mb-md-0">
-              <div class="btn-group me-2">
-                <button type="button" class="btn btn-sm btn-outline-secondary">Share</button>
-                <button type="button" class="btn btn-sm btn-outline-secondary">Export</button>
-              </div>
-              <button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle">
-                <span data-feather="calendar"></span>
-                This week
-              </button>
-
+                <span><small>Logged in as <b>{{ user.username }}</b></small></span>
 
             </div>
-                -->
-                <span><small>Logged in as <b>{{ user.username }}</b></small></span>
+
+            <div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
 
             </div>
 
+
+
+
             <div class="accordion" id="accordionExample">
                 <div class="accordion-item bg-dark text-white">
                     <h2 class="accordion-header" id="headingTwo">
@@ -111,8 +106,8 @@
                     </div>
                   </div>
             </div>
-            <br>
-                        {% if watching %}
+            {% if watching %}
+                <br>
                 <div class="border border-5 rounded-3 border-primary p-3">
             <h3>Continue Watching</h3>
            <div class="row row-cols-1 row-cols-md-4 g-4 text-dark mt-0">
@@ -164,7 +159,7 @@
 
             <br>
             <h3>Most viewed playlists <a href="{% url 'all_playlists' 'all' %}" class="btn btn-sm btn-info">View All</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">
@@ -194,12 +189,18 @@
                     </div>
                 </div>
                 {% endfor %}
+
             </div>
 
+            {% else %}
+            <h5>You have no playlists ;-;</h5>
+            {% endif %}
+
 
         <br>
             <h3>Recently Accessed</h3>
 
+            {% 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">
@@ -230,10 +231,13 @@
                 </div>
                 {% endfor %}
             </div>
-
+            {% else %}
+            <h5>You have no playlists ;-;</h5>
+            {% endif %}
 
         <br>
             <h3>Recently Added</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">
@@ -264,6 +268,9 @@
                 </div>
                 {% endfor %}
             </div>
+             {% else %}
+            <h5>You have no playlists ;-;</h5>
+            {% endif %}
         <br>
 
 

+ 4 - 4
apps/main/templates/intercooler/manage_playlists_import_results.html

@@ -3,7 +3,7 @@
 <div class="row row-cols-1 row-cols-md-3 g-4">
     {% for playlist in new_playlists %}
     <div class="col">
-        <div class="card h-100" style="background-color: #1A4464;">
+        <div class="card" style="background-color: #1A4464;">
             <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 text-white">
@@ -15,13 +15,13 @@
                     </h5>
                     <p class="card-text">
                         {% if playlist.description %}
-                            {{ playlist.description }}
+                            {{ playlist.description|truncatewords:"15" }}
                         {% else %}
                             No description
                         {% endif %}
                     </p>
                     <small>
-                        <span class="badge bg-primary rounded-pill">{{ playlist.video_count }} views</span>
+                        <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>
@@ -42,7 +42,7 @@
     <div class="row row-cols-1 row-cols-md-3 g-4">
         {% for playlist in old_playlists %}
         <div class="col">
-            <div class="card h-100" style="background-color: #1a643b;">
+            <div class="card" style="background-color: #1a643b;">
                 <a style="background-color: #1a643b;" href="{% url 'playlist' playlist.playlist_id %}" class="list-group-item list-group-item-action" aria-current="true">
 
                     <div class="card-body text-white">

+ 41 - 16
apps/main/templates/intercooler/updated_playlist.html

@@ -15,7 +15,14 @@
     <div class="list-group-item list-group-item-action active">
         <div class="d-flex w-100 justify-content-between">
             <span>
-                <h2 class="mb-1">{{ playlist.name }} <small><a><i class="fas fa-pen-square"></i></a></small> </h2>
+                <h2 class="mb-1">{{ playlist.name }}
+                    <small>
+
+                        <input class="form-control me-1 visually-hidden" id="pl-{{ playlist.playlist_id }}" value="{{ playlist.playlist_id }}">
+                        <button class="copy-btn btn  btn-light mb-1" data-clipboard-target="#pl-{{ playlist.playlist_id }}">
+                            <i class="far fa-copy" aria-hidden="true"></i>
+                        </button>
+                    </small> </h2>
                 <h6>by {{ playlist.channel_name }}</h6>
             </span>
             <h4>
@@ -52,10 +59,7 @@
         <div class="d-flex bd-highlight mb-1">
             <div class="me-auto bd-highlight">
                 <div class="btn-toolbar mb-2 mb-md-0">
-                    <div class="btn-group me-2">
-                        <button type="button" class="btn {% if playlist.view_in_grid_mode %}btn-info {% else %}btn-outline-info{% endif %}">Grid</button>
-                        <button type="button" class="btn {% if not playlist.view_in_grid_mode %}btn-info {% else %}btn-outline-info{% endif %}">List</button>
-                    </div>
+
                     <div class="btn-group me-2">
                         <button type="button" class="btn btn-outline-success dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
                             Sort By
@@ -72,6 +76,18 @@
                             <li><button class="dropdown-item" hx-get="{% url 'order_playlist_by' playlist.playlist_id 'new-updates' %}" hx-trigger="click" hx-target="#videos-div">Updates</button></li>
                         </ul>
                     </div>
+
+                    <div class="btn-group me-2">
+                            <button type="button" class="btn btn-outline-warning dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
+                                Mark As
+                            </button>
+                            <ul class="dropdown-menu">
+                                <li><button class="dropdown-item" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'none' %}" hx-trigger="click" hx-target="#notice-div">None</button></li>
+                                <li><button class="dropdown-item" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'watching' %}" hx-trigger="click" hx-target="#notice-div">Watching</button></li>
+                                <li><button class="dropdown-item" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'plan-to-watch' %}" hx-trigger="click" hx-target="#notice-div">Plan to Watch</button></li>
+                            </ul>
+                        </div>
+
                 </div>
             </div>
 
@@ -91,15 +107,24 @@
                         </div>
 
 
+
                         <div class="btn-group me-2">
-                            <button type="button" class="btn btn-outline-warning dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
-                                Mark As
+                            <button type="button" class="btn btn-primary">
+                                <i class="fas fa-edit"></i>
+                            </button>
+                        </div>
+
+                        <div class="btn-group me-2">
+
+                            <button class="btn btn-warning" type="button" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'favorite' %}" hx-target="#playlist-fav">
+                                <div id="playlist-fav">
+                                    {% if playlist.is_favorite %}
+                                        <i class="fas fa-star"></i>
+                                    {% else %}
+                                        <i class="far fa-star"></i>
+                                    {% endif %}
+                                </div>
                             </button>
-                            <ul class="dropdown-menu">
-                                <li><button class="dropdown-item" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'none' %}" hx-trigger="click" hx-target="#notice-div">None</button></li>
-                                <li><button class="dropdown-item" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'watching' %}" hx-trigger="click" hx-target="#notice-div">Watching</button></li>
-                                <li><button class="dropdown-item" hx-get="{% url 'mark_playlist_as' playlist.playlist_id 'plan-to-watch' %}" hx-trigger="click" hx-target="#notice-div">Plan to Watch</button></li>
-                            </ul>
                         </div>
 
                         <div class="btn-group me-2">
@@ -258,11 +283,11 @@
             <div class="ms-5">
                 <a class="btn btn-sm btn-info mb-1" type="button" href="https://www.youtube.com/watch?v={{ video.video_id }}" class="btn btn-info me-1" target="_blank"><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
                 <input class="form-control me-1 visually-hidden" id="video-{{ video.video_id }}" value="https://www.youtube.com/watch?v={{ video.video_id }}">
-                <button class="copy-btn btn btn-success mb-1" data-clipboard-target="#video-{{ video.video_id }}">
+                <button class="copy-btn btn btn-sm  btn-success mb-1" data-clipboard-target="#video-{{ video.video_id }}">
                     <i class="far fa-copy" aria-hidden="true"></i>
                 </button>
-                <button class="btn btn-sm btn-primary mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasWithBackdrop" aria-controls="offcanvasBottom" hx-get="{% url 'video_details' playlist.playlist_id video.video_id %}" hx-trigger="click" hx-target="#video-details">Details</button>
-                <button class="btn btn-sm btn-warning mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasForVideoNotes" aria-controls="offcanvasBottom" hx-get="{% url 'video_notes' playlist.playlist_id video.video_id %}" hx-trigger="click" hx-target="#video-notes">Notes</button>
+                <button class="btn btn-sm  btn-primary mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasWithBackdrop" aria-controls="offcanvasBottom" hx-get="{% url 'video_details' playlist.playlist_id video.video_id %}" hx-trigger="click" hx-target="#video-details"><i class="fas fa-info"></i></button>
+                <button class="btn btn-sm  btn-warning mb-1" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasForVideoNotes" aria-controls="offcanvasBottom" hx-get="{% url 'video_notes' playlist.playlist_id video.video_id %}" hx-trigger="click" hx-target="#video-notes">Notes</button>
                 <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist.playlist_id video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
                     <div id="video-{{ forloop.counter }}-fav">
                         {% if video.is_favorite %}
@@ -273,7 +298,7 @@
                     </div>
                 </button>
                     {% if playlist.marked_as == "watching" %}
-                                        <button class="btn btn-sm btn-light mb-1" type="button">
+                    <button class="btn btn-sm btn-light mb-1" type="button">
 
                         <i class="far fa-check-circle"></i>
                                         </button>

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

@@ -2,6 +2,9 @@
 {% extends 'base.html' %}
 {% block content %}
     <br>
+<div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
+
+</div>
 
 <div class="row row-cols-1 row-cols-md-3 g-4" >
     <div id="manage-import-playlists-btn" class="col">

+ 9 - 7
apps/main/templates/view_playlist.html

@@ -56,17 +56,19 @@
             <h5>No description</h5>
             {% endif %}
         </p>
-        <small>
-            <span class="badge bg-dark rounded-pill">{{ playlist.video_count }} videos</span>
-            <span class="badge bg-dark rounded-pill">{{ playlist.playlist_duration }} </span>
-            {% if playlist.is_private_on_yt %}<span class="badge bg-dark rounded-pill">private</span>{% endif %}
+        <h6 class="">
+            <span class="badge bg-light text-black-50">{% if playlist.is_user_owned %}OWNED{% else %}IMPORTED{% endif %}</span>
+
+            <span class="badge bg-light text-black-50">{{ playlist.video_count }} VIDEOS</span>
+            <span class="badge bg-light text-black-50">{{ playlist.playlist_duration }} </span>
+            <span class="badge bg-light text-black-50">{% if playlist.is_private_on_yt %}PRIVATE{% else %}PUBLIC{% endif %}</span>
             {% if playlist.has_unavailable_videos %}
-                    <span class="badge bg-dark rounded-pill">some videos are unavailable</span>
+                    <span class="badge bg-light text-black-50">SOME VIDEOS ARE UNAVAILABLE</span>
             {% endif %}
             {% if playlist.has_duplicate_videos %}
-                    <span class="badge bg-dark rounded-pill">duplicate videos</span>
+                    <span class="badge bg-light text-black-50">DUPLICATE VIDEOS</span>
             {% endif %}
-        </small>
+        </h6>
     </div>
 
     <br>

+ 43 - 30
apps/main/views.py

@@ -16,10 +16,10 @@ from django.template import Context, loader
 @login_required
 def home(request):
     user_profile = request.user.profile
-    user_playlists = user_profile.playlists.order_by("-num_of_accesses")
-    watching = user_profile.playlists.filter(marked_as="watching").order_by("-num_of_accesses")
-    recently_accessed_playlists = user_profile.playlists.order_by("-updated_at")[:6]
-    recently_added_playlists = user_profile.playlists.order_by("-created_at")[:6]
+    user_playlists = user_profile.playlists.filter(is_in_db=True).order_by("-num_of_accesses")
+    watching = user_profile.playlists.filter(Q(marked_as="watching") & Q(is_in_db=True)).order_by("-num_of_accesses")
+    recently_accessed_playlists = user_profile.playlists.filter(is_in_db=True).order_by("-updated_at")[:6]
+    recently_added_playlists = user_profile.playlists.filter(is_in_db=True).order_by("-created_at")[:6]
 
     #### FOR NEWLY JOINED USERS ######
     channel_found = True
@@ -99,7 +99,7 @@ def view_playlist(request, playlist_id):
     user_profile = request.user.profile
 
     # specific playlist requested
-    if user_profile.playlists.filter(playlist_id=playlist_id).count() != 0:
+    if user_profile.playlists.filter(Q(playlist_id=playlist_id) & Q(is_in_db=True)).count() != 0:
         playlist = user_profile.playlists.get(playlist_id__exact=playlist_id)
         playlist.num_of_accesses += 1
         playlist.save()
@@ -122,19 +122,19 @@ def all_playlists(request, playlist_type):
     playlist_type = playlist_type.lower()
 
     if playlist_type == "" or playlist_type == "all":
-        playlists = request.user.profile.playlists.all()
+        playlists = request.user.profile.playlists.all().filter(is_in_db=True)
         playlist_type_display = "All Playlists"
     elif playlist_type == "user-owned":  # YT playlists owned by user
-        playlists = request.user.profile.playlists.all().filter(is_user_owned=True)
+        playlists = request.user.profile.playlists.all().filter(Q(is_user_owned=True) & Q(is_in_db=True))
         playlist_type_display = "Your YouTube Playlists"
     elif playlist_type == "imported":  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(is_user_owned=False)
+        playlists = request.user.profile.playlists.all().filter(Q(is_user_owned=False) & Q(is_in_db=True))
         playlist_type_display = "Imported playlists"
     elif playlist_type == "favorites":  # YT playlists (public) owned by others
-        playlists = request.user.profile.playlists.all().filter(is_favorite=True)
+        playlists = request.user.profile.playlists.all().filter(Q(is_favorite=True) & Q(is_in_db=True))
         playlist_type_display = "Favorites"
     elif playlist_type.lower() in ["watching", "plan-to-watch"]:
-        playlists = request.user.profile.playlists.filter(marked_as=playlist_type.lower())
+        playlists = request.user.profile.playlists.filter(Q(marked_as=playlist_type.lower()) & Q(is_in_db=True))
         playlist_type_display = playlist_type.lower().replace("-", " ")
     elif playlist_type.lower() == "home":  # displays cards of all playlist types
         return render(request, 'playlists_home.html')
@@ -148,7 +148,7 @@ def all_playlists(request, playlist_type):
 
 @login_required
 def order_playlist_by(request, playlist_id, order_by):
-    playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+    playlist = request.user.profile.playlists.get(Q(playlist_id=playlist_id) & Q(is_in_db=True))
 
     display_text = "Nothing in this playlist! Add something!"  # what to display when requested order/filter has no videws
 
@@ -180,11 +180,11 @@ def order_playlist_by(request, playlist_id, order_by):
                     video.video_details_modified = False
                     video.save()
 
-            if playlist.videos.filter(video_details_modified=True).count() == 0:
+            if recently_updated_videos.count() == 0:
                 playlist.has_new_updates = False
                 playlist.save()
             else:
-                videos = playlist.videos.filter(video_details_modified=True).order_by("video_position")
+                videos = recently_updated_videos.order_by("video_position")
     else:
         return redirect('home')
 
@@ -199,10 +199,10 @@ def order_playlists_by(request, playlist_type, order_by):
         playlists = request.user.profile.playlists.all()
         playlist_type_display = "All Playlists"
     elif playlist_type.lower() == "favorites":
-        playlists = request.user.profile.playlists.filter(is_favorite=True)
+        playlists = request.user.profile.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True))
         playlist_type_display = "Favorites"
     elif playlist_type.lower() in ["watching", "plan-to-watch"]:
-        playlists = request.user.profile.playlists.filter(marked_as=playlist_type.lower())
+        playlists = request.user.profile.playlists.filter(Q(marked_as=playlist_type.lower()) & Q(is_in_db=True))
         playlist_type_display = "Watching"
     else:
         return redirect('home')
@@ -291,34 +291,34 @@ def search_playlists(request, playlist_type):
 
     if playlist_type == "all":
         try:
-            playlists = request.user.profile.playlists.all().filter(name__startswith=search_query)
+            playlists = request.user.profile.playlists.all().filter(Q(name__startswith=search_query) & Q(is_in_db=True))
         except:
             playlists = request.user.profile.playlists.all()
         playlist_type_display = "All Playlists"
     elif playlist_type == "user-owned":  # YT playlists owned by user
         try:
-            playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_user_owned=True))
+            playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_user_owned=True) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.filter(is_user_owned=True)
+            playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
         playlist_type_display = "Your YouTube Playlists"
     elif playlist_type == "imported":  # YT playlists (public) owned by others
         try:
-            playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_user_owned=False))
+            playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_user_owned=False) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.filter(is_user_owned=False)
+            playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
         playlist_type_display = "Imported Playlists"
     elif playlist_type == "favorites":  # YT playlists (public) owned by others
         try:
-            playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_favorite=True))
+            playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_favorite=True) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.filter(is_favorite=True)
+            playlists = request.user.profile.playlists.filter(Q(is_favorite=True) & Q(is_in_db=True))
         playlist_type_display = "Your Favorites"
     elif playlist_type in ["watching", "plan-to-watch"]:
         try:
             playlists = request.user.profile.playlists.filter(
-                Q(name__startswith=search_query) & Q(marked_as=playlist_type))
+                Q(name__startswith=search_query) & Q(marked_as=playlist_type) & Q(is_in_db=True))
         except:
-            playlists = request.user.profile.playlists.all().filter(marked_as=playlist_type)
+            playlists = request.user.profile.playlists.all().filter(Q(marked_as=playlist_type) & Q(is_in_db=True))
         playlist_type_display = playlist_type.replace("-", " ")
 
     return HttpResponse(loader.get_template("intercooler/playlists.html")
@@ -351,13 +351,13 @@ def search_UnTube(request):
 
     search_query = request.POST["search"]
 
-    all_playlists = request.user.profile.playlists.all()
+    all_playlists = request.user.profile.playlists.filter(is_in_db=True)
     videos = []
     starts_with = False
     contains = False
 
     if request.POST['search-settings'] == 'starts-with':
-        playlists = request.user.profile.playlists.filter(name__startswith=search_query) if search_query != "" else []
+        playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_in_db=True)) if search_query != "" else []
 
         if search_query != "":
             for playlist in all_playlists:
@@ -369,11 +369,11 @@ def search_UnTube(request):
 
         starts_with = True
     else:
-        playlists = request.user.profile.playlists.filter(name__contains=search_query) if search_query != "" else []
+        playlists = request.user.profile.playlists.filter(Q(name__contains=search_query) & Q(is_in_db=True)) if search_query != "" else []
 
         if search_query != "":
             for playlist in all_playlists:
-                pl_videos = playlist.videos.filter(name__contains=search_query)
+                pl_videos = playlist.videos.filter(Q(name__contains=search_query) & Q(is_in_db=True))
 
                 if pl_videos.count() != 0:
                     for v in pl_videos.all():
@@ -548,9 +548,22 @@ def update_playlist(request, playlist_id, type):
                 </div>""")
 
     print("Attempting to update playlist")
-    deleted_video_ids, unavailable_videos, added_videos = Playlist.objects.updatePlaylist(request.user, playlist_id)
-    print("Updated playlist")
+    status, deleted_video_ids, unavailable_videos, added_videos = Playlist.objects.updatePlaylist(request.user, playlist_id)
+
     playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+
+    if status == -1:
+        playlist_name = playlist.name
+        playlist.delete()
+        return HttpResponse(
+            f"""
+                    <div class="d-flex justify-content-center mt-4 mb-3" id="loading-sign">
+                        <h5 class="mt-2 ms-2">Looks like the playlist '{playlist_name}' was deleted on YouTube. It has been removed from UnTube as well.</h5>
+                    </div>
+            """)
+
+
+    print("Updated playlist")
     playlist_changed_text = []
 
     if len(added_videos) != 0:

+ 43 - 0
apps/users/templates/intercooler/user_playlist_updates.html

@@ -0,0 +1,43 @@
+{% load static %}
+{% if playlists or deleted_playlist_names %}
+<div id="user-pl-updates">
+    <div class="alert alert-success alert-dismissible fade show" role="alert">
+      <h4 class="alert-heading">Updates</h4>
+
+      <p>
+          {% if playlists %}
+          The following new playlists were detected on your YouTube channel and are not on UnTube:
+          <ul>
+                {% for playlist in playlists %}
+                    <li>
+                    <a href="https://www.youtube.com/playlist?list={{ playlist.playlist_id }}" class="text-decoration-none" target="_blank">
+                        {{ playlist.name }}
+                    </a>
+                    {% if playlist.video_count == 1 %}
+                        ({{ playlist.video_count }} video)
+                    {% else %}
+                        ({{ playlist.video_count }} videos)
+                    {% endif %}
+                    </li>
+                {% endfor %}
+            </ul>
+          {% endif %}
+        {% if deleted_playlist_names %}
+        The following playlists were deleted on YouTube (and have also been removed from UnTube):
+            <ul>
+                {% for playlist in deleted_playlist_names %}
+                    <li>
+                        {{ playlist }}
+                    </li>
+                {% endfor %}
+            </ul>
+        {% endif %}
+            <button class="btn btn-success" hx-get="{% url 'user_playlists_updates' 'init-update' %}" hx-trigger="click" hx-target="#user-pl-updates">Import</button>
+            </p>
+        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+
+    </div>
+</div>
+
+
+{% endif %}

+ 2 - 0
apps/users/urls.py

@@ -12,4 +12,6 @@ urlpatterns = [
 
     path("import/start", views.start_import, name='start'),
     path("import/continue", views.continue_import, name='continue'),
+
+    path("updates/user-playlists/<slug:action>", views.user_playlists_updates, name='user_playlists_updates'),
 ]

+ 75 - 1
apps/users/views.py

@@ -1,3 +1,4 @@
+from django.db.models import Q
 from django.shortcuts import render, redirect
 from django.contrib.auth import logout
 from django.views.decorators.http import require_POST
@@ -54,6 +55,7 @@ def log_out(request):
     logout(request)  # log out authenticated user
     return redirect('/')
 
+
 @login_required
 def start_import(request):
     '''
@@ -64,6 +66,9 @@ def start_import(request):
     '''
     user_profile = request.user.profile
 
+    if user_profile.yt_channel_id == "":
+        Playlist.objects.getUserYTChannelID(request.user)
+
     if user_profile.access_token.strip() == "" or user_profile.refresh_token.strip() == "":
         user_social_token = SocialToken.objects.get(account__user=request.user)
         user_profile.access_token = user_social_token.token
@@ -82,6 +87,9 @@ def start_import(request):
             {"channel_found": channel_found}
         ))
     elif result["status"] == -2:
+        request.user.profile.import_in_progress = False
+        request.user.save()
+
         print("User has no playlists on YT")
 
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
@@ -99,16 +107,22 @@ def start_import(request):
              "channel_found": channel_found}
         ))
 
+
 @login_required
 def settings(request):
+
+    if request.user.profile.yt_channel_id == "":
+        Playlist.objects.getUserYTChannelID(request.user)
+
     return render(request, 'settings.html')
 
+
 @login_required
 def continue_import(request):
     if request.user.profile.import_in_progress is False:
         return redirect('home')
 
-    num_of_playlists = request.user.profile.playlists.count()
+    num_of_playlists = request.user.profile.playlists.all().count()
 
     try:
         remaining_playlists = request.user.profile.playlists.filter(is_in_db=False)
@@ -138,3 +152,63 @@ def continue_import(request):
              "done": True,
              "progress": 100,
              "channel_found": True}))
+
+
+@login_required
+def user_playlists_updates(request, action):
+    if action == 'check-for-updates':
+        user_playlists_on_UnTube = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
+
+        result = Playlist.objects.getAllPlaylistsFromYT(request.user)
+
+        youtube_playlist_ids = result["playlist_ids"]
+        untube_playlist_ids = []
+        for playlist in user_playlists_on_UnTube:
+            untube_playlist_ids.append(playlist.playlist_id)
+
+        deleted_playlist_ids = []
+        deleted_playlist_names = []
+        for pl_id in untube_playlist_ids:
+            if pl_id not in youtube_playlist_ids:  # ie this playlist was deleted on youtube
+                deleted_playlist_ids.append(pl_id)
+                pl = request.user.profile.playlists.get(playlist_id__exact=pl_id)
+                deleted_playlist_names.append(f"{pl.name} (had {pl.video_count} videos)")
+                pl.delete()
+
+        if result["num_of_playlists"] == user_playlists_on_UnTube.count() and len(deleted_playlist_ids) == 0:
+            print("No new updates")
+            playlists = []
+        else:
+            playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False))
+            print(f"New updates found! {playlists.count()} newly added and {len(deleted_playlist_ids)} playlists deleted!")
+            print(deleted_playlist_names)
+
+        return HttpResponse(loader.get_template('intercooler/user_playlist_updates.html').render(
+            {"playlists": playlists,
+             "deleted_playlist_names": deleted_playlist_names}))
+    elif action == 'init-update':
+        unimported_playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False)).count()
+
+        return HttpResponse(f"""
+        <div hx-get="/updates/user-playlists/start-update" hx-trigger="load" hx-target="#user-pl-updates">
+            <div class="alert alert-dismissible fade show" role="alert" style="background-color: cadetblue">
+                <div class="d-flex justify-content-center mt-4 mb-3 ms-2" id="loading-sign" >
+                    <img src="/static/svg-loaders/spinning-circles.svg" width="40" height="40">
+                    <h5 class="mt-2 ms-2 text-black">Importing {unimported_playlists} new playlists into UnTube, please wait!</h5>
+                </div>
+            </div>
+        </div>
+        """)
+    elif action == 'start-update':
+        unimported_playlists = request.user.profile.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False))
+
+        for playlist in unimported_playlists:
+            Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)
+
+        return HttpResponse("""
+        <div class="alert alert-success alert-dismissible fade show d-flex justify-content-center" role="alert">
+          <h4 class="">Successfully imported new playlists into UnTube! Refresh :)</h4>
+
+            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-la bel="Close"></button>
+        </div>
+        """)