Bläddra i källkod

Wrote down playlist settings form logic

sleepytaco 3 år sedan
förälder
incheckning
10af8b400d

+ 40 - 2
apps/main/models.py

@@ -522,7 +522,7 @@ class PlaylistManager(models.Manager):
                 playlist.save()
 
         if pl_id is None:
-            user.profile.just_joined = False
+            user.profile.show_import_page = False
             user.profile.import_in_progress = False
             user.save()
 
@@ -1035,8 +1035,46 @@ class PlaylistManager(models.Manager):
                     pass
 
                 # playlistItem was successfully deleted if no HttpError, so delete it from db
-                playlist.videos.get(playlist_item_id=playlist_item_id).delete()
+                # playlist.videos.get(playlist_item_id=playlist_item_id).delete()  # updatePlaylist will be called so unecessary for now
 
+    def updatePlaylistDetails(self, user, playlist_id, details):
+        """
+        Takes in playlist itemids for the videos in a particular playlist
+        """
+        credentials = self.getCredentials(user)
+        playlist = user.profile.playlists.get(playlist_id=playlist_id)
+
+        with build('youtube', 'v3', credentials=credentials) as youtube:
+            pl_request = youtube.playlists().update(
+                part="id,snippet,status",
+                body={
+                    "id": playlist_id,
+                    "snippet": {
+                        "title": details["title"],
+                        "description": details["description"],
+                    },
+                    "status": {
+                        "privacyStatus": "private" if details["privacyStatus"] else "public"
+                    }
+                },
+            )
+
+            try:
+                pl_response = pl_request.execute()
+            except googleapiclient.errors.HttpError:  # failed to update playlist details
+                # possible causes:
+                # playlistItemsNotAccessible (403)
+                # playlistItemNotFound (404)
+                # playlistOperationUnsupported (400)
+                return -1
+
+            print(pl_response)
+            playlist.name = pl_response['snippet']['title']
+            playlist.description = pl_response['snippet']['description']
+            playlist.is_private_on_yt = True if pl_response['status']['privacyStatus'] == "private" else False
+            playlist.save(update_fields=['name', 'description', 'is_private_on_yt'])
+
+            return 0
 
 
 class Playlist(models.Model):

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

@@ -1,6 +1,8 @@
 
 {% extends 'base.html' %}
 {% block content %}
+
+        {% if playlists %}
     <div id="search-results">
 
         <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
@@ -28,8 +30,6 @@
 
         </div>
 
-        {% if playlists %}
-
         <div class="">
             <input   class="form-control border border-secondary" type="text"
        name="search" placeholder="Begin to search playlists..."
@@ -69,13 +69,41 @@
                 </div>
                 {% endfor %}
 
-                {% else %}
-                <h5 class="text-white align-content-center">Nothing to see here. Add something!</h5>
-            </div>
-        {% endif %}
+
+        </div>
 
         <br>
 
     </div>
+        {% else %}
+            <div class="container-fluid">
+            <div class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom">
+            <h1 class="h2">{{ playlist_type_display|title }} <span class="badge bg-primary rounded-pill">{{ playlists.count }}</span></h1>
+
+            <div class="btn-toolbar mb-2 mb-md-0">
+                <!--
+                <div class="btn-group me-2">
+                    <button type="button" class="btn btn-outline-info">Grid</button>
+                    <button type="button" class="btn btn-outline-info">List</button>
+                </div>
+                -->
+                <div class="btn-group">
+                  <button type="button" class="btn btn-outline-success dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
+                    Sort By
+                  </button>
+                  <ul class="dropdown-menu">
+                    <li><a class="dropdown-item" hx-get="{% url 'order_playlists_by' playlist_type 'playlist-duration-in-seconds' %}" hx-trigger="click" hx-target="#search-results">Duration</a></li>
+                    <li><a class="dropdown-item" hx-get="{% url 'order_playlists_by' playlist_type 'video-count' %}" hx-trigger="click" hx-target="#search-results"># Videos</a></li>
+                    <li><a class="dropdown-item" hx-get="{% url 'order_playlists_by' playlist_type 'recently-accessed' %}" hx-trigger="click" hx-target="#search-results">Recently Accessed</a></li>
+                  </ul>
+                </div>
+
+            </div>
+
+        </div>
+            <h5 class="text-white align-content-center">Nothing to see here. Add something!</h5>
+            </div>
+        {% endif %}
+
 
 {% endblock %}

+ 23 - 5
apps/main/templates/home.html

@@ -3,14 +3,28 @@
 {% 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 %}
+            {% else %}
+                <br>
             {% endif %}
+        {% if user.profile.playlists.all.count == 0 %}
+            <div class="alert alert-success" role="alert">
+              <h4 class="alert-heading">It's empty in here</h4>
+              <p>
+                  There's no playlists in your UnTube right now. You can change that by heading over to <a href="{% url 'manage_playlists' %}" class="btn btn-sm btn-primary">Manage</a> to import some public playlists into your UnTube.
+                  {% if not user.profile.imported_yt_playlists %}
+                      Or you could always head over to your <a href="{% url 'profile' %}" class="btn btn-sm btn-primary">Profile</a> to import all of your public/private YouTube playlists.
+                  {% else %}
+                      Keep in mind that your own YouTube playlists will automatically be imported into UnTube.
+                  {% endif %}
+              </p>
+            </div>
+        {% endif %}
         {% if import_successful %}
             <br>
                 <br>
@@ -22,7 +36,7 @@
                   </div>
 
                   <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
-                        <h3>You'll now be able to import/export playlists from YouTube and UnTube :)</h3>
+                        <h3>You'll now be notified on the Dashboard whenever there's any new un-exported playlists on YouTube :)</h3>
                   </div>
 
 
@@ -42,10 +56,11 @@
             </div>
             -->
 
+            {% if user.profile.imported_yt_playlists %}
             <div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
 
             </div>
-
+            {% endif %}
 
 
 
@@ -101,7 +116,7 @@
                 {% endif %}
 
             <br>
-            <h3><span style="border-bottom: 3px #ffffff dashed;">Most viewed playlists</span> <a href="{% url 'all_playlists' 'all' %}" class="btn btn-sm btn-info">View All</a></h3>
+            <h3><span style="border-bottom: 3px #ffffff dashed;">Most viewed playlists</span> {% if user_playlists.count > 3 %}<a href="{% url 'all_playlists' 'all' %}" class="btn btn-sm btn-info">View All</a>{% endif %}</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" %}
@@ -136,7 +151,8 @@
             </div>
 
             {% else %}
-            <h5>You have no playlists ;-;</h5>
+                <br>
+            <h5>Nothing to see here... yet.</h5>
             {% endif %}
 
 
@@ -175,6 +191,7 @@
                 {% endfor %}
             </div>
             {% else %}
+            <br>
             <h5>You have no playlists ;-;</h5>
             {% endif %}
 
@@ -212,6 +229,7 @@
                 {% endfor %}
             </div>
              {% else %}
+            <br>
             <h5>You have no playlists ;-;</h5>
             {% endif %}
         <br>

+ 29 - 3
apps/main/templates/import_in_progress.html

@@ -10,6 +10,26 @@
         <title>Import in progress</title>
 
         <style type="text/css">
+                    body {
+                //background: linear-gradient(-45deg, #B2A3FF, #84bcf3, #AE876B, #B0E7AE);
+                //background: linear-gradient(-45deg, #0645a4, #2480cd, #84bcf3, #b7d6f7);
+                background: linear-gradient(-45deg, #AE876B, #ABA27B, #A7BC8A, #A3D69A);
+                background-size: 400% 400%;
+                animation: gradient 5s ease infinite;
+                }
+
+            @keyframes gradient {
+                0% {
+                    background-position: 0% 50%;
+                }
+                50% {
+                    background-position: 100% 50%;
+                }
+                100% {
+                    background-position: 0% 50%;
+                }
+            }
+
             .progress {
                 height: 20px;
                 margin-bottom: 20px;
@@ -46,13 +66,13 @@
         <!-- Bootstrap core CSS -->
         <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
     </head>
-    <body class="bg-dark text-white"  style="font-family: 'Fredoka One'">
+    <body class="bg-dark text-dark"  style="font-family: 'Fredoka One',monospace">
 
         <div class="container-fluid">
 
 
             <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
-                    <h1>UnTube</h1>
+                <h1><kbd>UnTube</kbd></h1>
 
             </div>
             <br>
@@ -73,9 +93,15 @@
                 <div hx-target="this" hx-swap="outerHTML">
                     <div class="d-flex justify-content-center pt-3 pb-2 mb-3">
 
-                      <button class="btn btn-lg btn-success" hx-post="{% url 'start' %}">
+                      <button class="btn btn-lg btn-success me-2" hx-post="{% url 'start' %}">
                                 Import
                       </button>
+
+                    </div>
+                    <div class="d-flex justify-content-center">
+                    <a class="btn btn-lg btn-secondary" href="{% url 'cancel' %}">
+                          Nevermind, I'll do it later :(
+                      </a>
                     </div>
                 </div>
 

+ 16 - 27
apps/main/templates/manage_playlists.html

@@ -2,14 +2,14 @@
 {% extends 'base.html' %}
 {% block content %}
     <br>
-<div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
-
-</div>
+    {% if user.profile.imported_yt_playlists %}
+    <div hx-get="{% url 'user_playlists_updates' 'check-for-updates' %}" hx-trigger="load" hx-swap="outerHTML">
 
+    </div>
+    {% endif %}
 <div class="row row-cols-1 row-cols-md-3 g-4" >
     <div id="manage-import-playlists-btn" class="col">
-
-        <a  hx-get="{% url 'manage_view_page' 'import' %}" hx-trigger="click" hx-target="#manage-pl-div" class="text-decoration-none text-white">
+        <a  href="{% url 'manage_view_page' 'import' %}" class="text-decoration-none text-white">
         <div class="card" style="background-color: #25641a;">
             <div class="card-body">
                 <h4 class="card-title">Import Playlists</h4>
@@ -19,7 +19,7 @@
         </a>
     </div>
     <div id="manage-create-playlists-btn" class="col">
-        <a  hx-get="{% url 'manage_view_page' 'create' %}" hx-trigger="click" hx-target="#manage-pl-div" class="text-decoration-none text-white">
+        <a  href="{% url 'manage_view_page' 'create' %}" class="text-decoration-none text-white">
         <div class="card" style="background-color: #64631a;">
             <div class="card-body">
                 <h4 class="card-title">Create a New Playlist</h4>
@@ -38,27 +38,16 @@
         </div>
         </a>
     </div>
-</div>
-<script>
-    document.getElementById('manage-import-playlists-btn').onclick = function() {
-        document.getElementById('manage-import-playlists-btn').className = "col border border-5 rounded-3 border-primary p-3";
-        document.getElementById('manage-create-playlists-btn').className = "col";
-        document.getElementById('manage-untube-playlists-btn').className = "col";
-    }
-
-    document.getElementById('manage-create-playlists-btn').onclick = function() {
-        document.getElementById('manage-import-playlists-btn').className = "col";
-        document.getElementById('manage-create-playlists-btn').className = "col border border-5 rounded-3 border-primary p-3";
-        document.getElementById('manage-untube-playlists-btn').className = "col";
-    }
-
-    document.getElementById('manage-untube-playlists-btn').onclick = function() {
-        document.getElementById('manage-import-playlists-btn').className = "col";
-        document.getElementById('manage-create-playlists-btn').className = "col";
-        document.getElementById('manage-untube-playlists-btn').className = "col border border-5 rounded-3 border-primary p-3";
-    }
-</script>
-<div id="manage-pl-div">
+    <div id="manage-delete-playlists-btn" class="col">
+        <a  hx-get="{% url 'manage_view_page' 'nuke-playlists' %}" hx-trigger="click" hx-target="#manage-pl-div" class="text-decoration-none text-white">
+        <div class="card" style="background-color: #a72744;">
+            <div class="card-body">
+                <h4 class="card-title">Nuke Playlists</h4>
+                <p class="card-text">Bulk delete multiple YouTube playlists, or just remove them from UnTube.</p>
+            </div>
+        </div>
+        </a>
+    </div>
 
 </div>
 {% endblock %}

+ 15 - 10
apps/main/templates/intercooler/manage_playlists_create.html → apps/main/templates/manage_playlists_create.html

@@ -1,21 +1,24 @@
-<br>
-<hr>
+
+{% extends 'base.html' %}
+{% block content %}
 <br>
 
 <div class="container-fluid">
-<h2>Create a new YouTube Playlist</h2>
-<br>
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom border-2 border-light">
+        <h1 class="h2">Create a new YouTube Playlist</h1>
+    </div>
+    <br>
     <div id="import-playlists-from">
       <div class="mb-3">
         <h4 for="playlistName" class="form-label">Playlist Name</h4>
-        <input type="email" class="form-control bg-dark text-white" placeholder="Enter playlist name" id="playlistName" name="playlistName" describedby="playlistNameHelp">
+        <input type="email" class="form-control" placeholder="Enter playlist name" id="playlistName" name="playlistName" describedby="playlistNameHelp">
         <div id="playlistNameHelp" class="form-text">Make sure the playlist name is unique!</div>
       </div>
 
 
         <div class="mb-3">
           <h4 for="playlist-desc-text-area" class="form-label">Playlist Description</h4>
-          <textarea name="playlistDescription" class="form-control bg-dark text-white" id="playlist-desc-text-area" placeholder="Enter playlist description here" rows="5"
+          <textarea name="playlistDescription" class="form-control" id="playlist-desc-text-area" placeholder="Enter playlist description here" rows="5"
             hx-post="{% url 'manage_save' 'manage_playlists_import_textarea' %}"
             hx-trigger="keyup changed delay:500ms"
             hx-indicator="#spinner"></textarea>
@@ -42,13 +45,13 @@
 
         <div class="d-flex justify-content-evenly mb-3">
             <div class="container-fluid">
-                <input class="form-control bg-dark text-black-50 mb-2" type="text"
+                <input class="form-control text-black-50 mb-2" type="text"
                    name="searchForVideosToAddBar" placeholder="Begin to search your video collection..."
                    hx-post="#"
                    hx-trigger="keyup changed delay:500ms"
                    hx-target="#search-results"
                    hx-indicator=".htmx-indicator">
-                <select class="form-select bg-dark text-white" name="videoIdsFromSearch" multiple aria-label="multiple select example" size="10">
+                <select class="form-select" name="videoIdsFromSearch" multiple aria-label="multiple select example" size="10">
                   <option selected>Open this select menu</option>
                   <option value="1">One</option>
                   <option value="2">Two</option>
@@ -64,7 +67,7 @@
                 <h5 class="d-flex justify-content-start">
                     All these will be added to the playlist
                 </h5>
-                <select class="form-select bg-dark text-white" name="videoIdsToBeAdded" multiple aria-label="multiple select example" size="10">
+                <select class="form-select" name="videoIdsToBeAdded" multiple aria-label="multiple select example" size="10">
                   <div id="videosToBeAddedBox">
                     <option value="1" selected>One</option>
                   <option value="2" selected>Two</option>
@@ -78,7 +81,7 @@
 
         <div class="mb-3">
           <h4 for="playlist-video-links-text-area" class="form-label">Or add videos via video links</h4>
-          <textarea name="externalVideoLinks" class="form-control bg-dark text-white" id="playlist-video-links-text-area" placeholder="Enter video links line by line" rows="5"
+          <textarea name="externalVideoLinks" class="form-control" id="playlist-video-links-text-area" placeholder="Enter video links line by line" rows="5"
             hx-post="{% url 'manage_save' 'manage_playlists_import_textarea' %}"
             hx-trigger="keyup changed delay:500ms"
             hx-indicator="#spinner">{{ manage_playlists_import_textarea }}</textarea>
@@ -111,3 +114,5 @@
 
 
   </div>
+
+{% endblock %}

+ 11 - 6
apps/main/templates/intercooler/manage_playlists_import.html → apps/main/templates/manage_playlists_import.html

@@ -1,12 +1,15 @@
-<br>
-<hr>
-<br>
+{% extends 'base.html' %}
+{% block content %}
 
 <div class="container-fluid">
-<h2>Enter a Playlist link or list multiple Playlist links line by line</h2>
-<br>
+    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom border-2 border-light">
+        <h1 class="h2">Import Public YouTube playlists</h1>
+    </div>
+    <br>
+
+<h4>Enter a Playlist link or list multiple Playlist links line by line</h4>
     <div id="import-playlists-from">
-      <textarea name="import-playlist-textarea" class="form-control bg-dark text-white" id="video-notes-text-area" placeholder="Enter here" rows="5"
+      <textarea name="import-playlist-textarea" class="form-control" id="video-notes-text-area" placeholder="Enter here" rows="5"
                 hx-post="{% url 'manage_save' 'manage_playlists_import_textarea' %}"
                 hx-trigger="keyup changed delay:500ms"
                 hx-indicator="#spinner">{{ manage_playlists_import_textarea }}</textarea>
@@ -36,3 +39,5 @@
 
 
   </div>
+{% endblock %}
+

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

@@ -31,7 +31,7 @@
     <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 }}
+                <h2 class="mb-1">{{ playlist.name }} <small>{% if playlist.user_label %}a.k.a <kbd>{{ playlist.user_label }}</kbd>{% endif %}</small>
                     <small>
 
                         <input class="form-control me-1 visually-hidden" id="pl-{{ playlist.playlist_id }}" value="{{ playlist.playlist_id }}">
@@ -83,6 +83,7 @@
                     <button type="button" class="btn {% if not playlist.view_in_grid_mode %}btn-info {% else %}btn-outline-info{% endif %}">List</button>
                 </div>
                 -->
+                {% if videos.count != 0 %}
                 <div class="btn-group me-2">
                     <button type="button" class="btn btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
                         Sort By
@@ -101,6 +102,7 @@
 
                     </ul>
                 </div>
+                {% endif %}
 
 
                     <div class="btn-group me-2">
@@ -145,10 +147,11 @@
                         </a>
                     </div>
 
-
+                    {% if videos.count != 0 %}
                     <div class="btn-group me-2">
                         <button type="button" class="btn btn-light" onclick="row1_hide()">Manage</button>
                     </div>
+                    {% endif %}
             </div>
         </div>
     </div>
@@ -266,6 +269,8 @@
 
     <div class="table-responsive" id="videos-div">
         <br>
+
+        {% if videos %}
         <div class="list-group" id="video-checkboxes">
           {% for video in videos %}
 
@@ -373,6 +378,20 @@
         {% endfor %}
 
         </div>
+        {% else %}
+                  <div class="card bg-dark text-white mb-3">
+            <div class="card-body">
+                <div class="d-flex justify-content-center align-content-center">
+                    Playlist is empty ;-;
+                </div>
+                <div class="d-flex justify-content-center align-content-center">
+                    Consider moving/copying videos from other playlists into this playlist by copying and using this playlist's ID.
+                </div>
+
+            </div>
+            </div>
+
+        {% endif %}
     </div>
     {% endif %}
 

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

@@ -25,14 +25,14 @@
                       <h6 class="mb-0">Playlist Name</h6>
                     </div>
                     <div class="col-sm-9 text-white-50">
-                        <input type="text" class="form-control" name="username" id="username" value="{{ playlist.name }}">
+                        <input type="text" class="form-control" name="playlistTitle" value="{{ playlist.name }}">
                     </div>
                   </div>
                     <hr>
 
                       <div class="row">
                         <div class="col-sm-3">
-                          <h6 class="mb-0">Owned By</h6>
+                          <h6 class="mb-0">Created By</h6>
                         </div>
                         <div class="col-sm-9 text-white-50">
                             <input type="text" class="form-control" id="fullname" value="{{ playlist.channel_name }}" disabled>
@@ -44,7 +44,7 @@
                           <h6 class="mb-0">Playlist Description</h6>
                         </div>
                         <div class="col-sm-9 text-white-50">
-                            <textarea class="form-control form-text" id="summary" rows="6" placeholder="Enter a playlist description here!">{{ playlist.description }}</textarea>
+                            <textarea class="form-control form-text" name="playlistDesc" rows="6" placeholder="Enter a playlist description here!">{{ playlist.description }}</textarea>
                         </div>
                         </div>
                       <hr>
@@ -53,27 +53,18 @@
                           <h6 class="mb-0">Playlist Privacy</h6>
                         </div>
                         <div class="col-sm-9 text-white-50">
-                            <select  class="form-select w-25" id="playlistPrivacy">
+                            <select  class="form-select w-25" name="playlistPrivacy">
                                   <option value="Public" {% if not playlist.is_private_on_yt %}selected{% endif %}>Public</option>
                                   <option value="Private" {% if playlist.is_private_on_yt %}selected{% endif %}>Private</option>
                             </select>
                         </div>
                         </div>
                       <hr>
-                        <div class="row">
+                    <div class="row">
                         <div class="col-sm-3">
                             <h6 class="mb-0">Preferences</h6>
                         </div>
                         <div class="col-sm-9 text-white">
-                            <div class="mb-3 form-check form-switch">
-                            <input class="form-check-input" name="open search in new tab" type="checkbox" id="openSearchCheckDefault" {% if user.profile.open_search_new_tab %} checked {% endif %}>
-                            <label class="form-check-label" for="openSearchCheckDefault">Open Search in new tab</label>
-                            </div>
-
-                            <div class="mb-3 form-check form-switch">
-                            <input class="form-check-input" name="auto refresh playlists" type="checkbox" id="flexSwitchCheckDefault">
-                            <label class="form-check-label" for="flexSwitchCheckDefault">Automatically refresh playlists on visit</label>
-                            </div>
 
                             <div class="mb-3 form-check form-switch">
                             <input class="form-check-input" name="hide videos" type="checkbox" id="flexSwitchCheckChecked" checked>
@@ -102,11 +93,12 @@
               </div>
 
             <div class="d-flex justify-content-center">
-                            <div class="btn-group">
-                <a href="{% url 'playlist' playlist.playlist_id %}" class="btn btn-secondary me-2">Back</a>
+                <div class="btn-group">
+                    <a href="{% url 'playlist' playlist.playlist_id %}" class="btn btn-secondary me-2">Back</a>
 
-                <button type="button" class="btn btn-success me-2">Save</button>
-                                </div>
+                    <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>
+                </div>
               </div>
         </div>
 
@@ -126,7 +118,7 @@
                       <h6 class="mb-0">Playlist Name on YouTube</h6>
                     </div>
                     <div class="col-sm-9 text-white-50">
-                        <input type="text" class="form-control" name="username" id="username" value="{{ playlist.name }}">
+                        <input type="text" class="form-control" name="username" id="username" value="{{ playlist.name }}" disabled>
                     </div>
                   </div>
                     <hr>
@@ -142,7 +134,7 @@
                       <hr>
                       <div class="row">
                         <div class="col-sm-3">
-                          <h6 class="mb-0">Owned By</h6>
+                          <h6 class="mb-0">Created By</h6>
                         </div>
                         <div class="col-sm-9 text-white-50">
                             <input type="text" class="form-control" id="fullname" value="{{ playlist.channel_name }}" disabled>
@@ -154,15 +146,6 @@
                             <h6 class="mb-0">Preferences</h6>
                         </div>
                         <div class="col-sm-9 text-white">
-                            <div class="mb-3 form-check form-switch">
-                            <input class="form-check-input" name="open search in new tab" type="checkbox" id="openSearchCheckDefault" {% if user.profile.open_search_new_tab %} checked {% endif %}>
-                            <label class="form-check-label" for="openSearchCheckDefault">Open Search in new tab</label>
-                            </div>
-
-                            <div class="mb-3 form-check form-switch">
-                            <input class="form-check-input" name="auto refresh playlists" type="checkbox" id="flexSwitchCheckDefault">
-                            <label class="form-check-label" for="flexSwitchCheckDefault">Automatically refresh playlists on visit</label>
-                            </div>
 
                             <div class="mb-3 form-check form-switch">
                             <input class="form-check-input" name="hide videos" type="checkbox" id="flexSwitchCheckChecked" checked>
@@ -195,7 +178,8 @@
                             <div class="btn-group">
                 <a href="{% url 'playlist' playlist.playlist_id %}" class="btn btn-secondary me-2">Back</a>
 
-                <button type="button" class="btn btn-success me-2">Save</button>
+                <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>
                                 </div>
               </div>
         </div>

+ 1 - 0
apps/main/urls.py

@@ -25,6 +25,7 @@ urlpatterns = [
     path("playlist/<slug:playlist_id>/mark-as/<slug:mark_as>", views.mark_playlist_as,
          name='mark_playlist_as'),
     path("playlist/<slug:playlist_id>/update/<slug:type>", views.update_playlist, name="update_playlist"),
+    path("playlist/<slug:playlist_id>/update-settings", views.update_playlist_settings, name="update_playlist_settings"),
 
     ### STUFF RELATED TO PLAYLISTS IN BULK
     path("search/playlists/<slug:playlist_type>", views.search_playlists, name="search_playlists"),

+ 81 - 34
apps/main/views.py

@@ -16,29 +16,39 @@ from django.template import Context, loader
 @login_required
 def home(request):
     user_profile = request.user.profile
-    user_playlists = user_profile.playlists.filter(is_in_db=True).order_by("-num_of_accesses")
+    user_playlists = user_profile.playlists.filter(Q(is_in_db=True) & Q(num_of_accesses__gt=0)).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
-    if user_profile.just_joined:
-        if user_profile.import_in_progress:
-            return render(request, "import_in_progress.html")
-        else:
-            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
-                user_profile.refresh_token = user_social_token.token_secret
-                user_profile.expires_at = user_social_token.expires_at
-                request.user.save()
-
-            user_profile.just_joined = False
-            user_profile.save()
-
+    if user_profile.show_import_page:
+        """
+        Logic:
+        show_import_page is True by default. When a user logs in for the first time (infact anytime), google 
+        redirects them to 'home' url. Since, show_import_page is True by default, the user is then redirected
+        from 'home' to 'import_in_progress' url
+        
+        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.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
+            user_profile.refresh_token = user_social_token.token_secret
+            user_profile.expires_at = user_social_token.expires_at
+            request.user.save()
+
+        if user_profile.imported_yt_playlists:
+            user_profile.show_import_page = False  # after user imports all their YT playlists no need to show_import_page again
+            user_profile.save(update_fields=['show_import_page'])
             return render(request, "home.html", {"import_successful": True})
 
+        return render(request, "import_in_progress.html")
+
         # if Playlist.objects.getUserYTChannelID(request.user) == -1:  # user channel not found
         #    channel_found = False
         # else:
@@ -284,8 +294,6 @@ def playlists_home(request):
 def delete_videos(request, playlist_id, command):
     video_ids = request.POST.getlist("video-id", default=[])
 
-    print(request.META, request.META["HTTP_HOST"])
-
     if command == "confirm":
         print(video_ids)
         num_vids = len(video_ids)
@@ -305,7 +313,10 @@ def delete_videos(request, playlist_id, command):
             f'<div class="spinner-border text-light" role="status" hx-post="/from/{playlist_id}/delete-videos/start" hx-trigger="load" hx-swap="outerHTML"></div>')
     elif command == "start":
         Playlist.objects.deletePlaylistItems(request.user, playlist_id, video_ids)
-        return HttpResponse(f'<div hx-get="http://{request.META["HTTP_HOST"]}/update_playlist/{playlist_id}/manual" hx-target="#view_playlist" hx-trigger="load">Done!</div>')
+        playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+        playlist.has_playlist_changed = True
+        playlist.save(update_fields=['has_playlist_changed'])
+        return HttpResponse(f'Done! Refreshing page...<meta http-equiv="refresh" content="1" />')
 
 
 @login_required
@@ -323,19 +334,22 @@ def search_playlists(request, playlist_type):
         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) & Q(is_in_db=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(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) & Q(is_in_db=True))
+            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(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) & Q(is_in_db=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(Q(is_favorite=True) & Q(is_in_db=True))
         playlist_type_display = "Your Favorites"
@@ -390,7 +404,8 @@ def search_UnTube(request):
     contains = False
 
     if request.POST['search-settings'] == 'starts-with':
-        playlists = request.user.profile.playlists.filter(Q(name__startswith=search_query) & Q(is_in_db=True)) 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:
@@ -402,7 +417,8 @@ def search_UnTube(request):
 
         starts_with = True
     else:
-        playlists = request.user.profile.playlists.filter(Q(name__contains=search_query) & Q(is_in_db=True)) 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:
@@ -430,15 +446,12 @@ def manage_playlists(request):
 @login_required
 def manage_view_page(request, page):
     if page == "import":
-        return HttpResponse(loader.get_template("intercooler/manage_playlists_import.html")
-            .render(
-            {"manage_playlists_import_textarea": request.user.profile.manage_playlists_import_textarea}))
+        return render(request, "manage_playlists_import.html",
+                      {"manage_playlists_import_textarea": request.user.profile.manage_playlists_import_textarea})
     elif page == "create":
-        return HttpResponse(loader.get_template("intercooler/manage_playlists_create.html")
-            .render(
-            {}))
+        return render(request, "manage_playlists_create.html")
     else:
-        return redirect('home')
+        return HttpResponse('Working on this!')
 
 
 @login_required
@@ -507,6 +520,40 @@ def manage_create_playlist(request):
     return HttpResponse("")
 
 
+@login_required
+@require_POST
+def update_playlist_settings(request, playlist_id):
+
+    message_type = "success"
+    message_content = "Saved!"
+
+    if "user_label" in request.POST:
+        playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
+        playlist.user_label = request.POST["user_label"]
+        playlist.save(update_fields=['user_label'])
+
+        return HttpResponse(loader.get_template("intercooler/messages.html")
+            .render(
+            {"message_type": message_type,
+             "message_content": message_content}))
+
+    details = {
+        "title": request.POST['playlistTitle'],
+        "description": request.POST['playlistDesc'],
+        "privacyStatus": True if request.POST['playlistPrivacy'] == "Private" else False
+    }
+
+    status = Playlist.objects.updatePlaylistDetails(request.user, playlist_id, details)
+    if status == -1:
+        message_type = "error"
+        message_content = "Could not save :("
+
+    return HttpResponse(loader.get_template("intercooler/messages.html")
+        .render(
+        {"message_type": message_type,
+         "message_content": message_content}))
+
+
 @login_required
 def update_playlist(request, playlist_id, type):
     playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
@@ -586,7 +633,8 @@ def update_playlist(request, playlist_id, type):
                 </div>""")
 
     print("Attempting to update playlist")
-    status, deleted_video_ids, unavailable_videos, added_videos = Playlist.objects.updatePlaylist(request.user, playlist_id)
+    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)
 
@@ -600,7 +648,6 @@ def update_playlist(request, playlist_id, type):
                     </div>
             """)
 
-
     print("Updated playlist")
     playlist_changed_text = []
 
@@ -609,7 +656,7 @@ def update_playlist(request, playlist_id, type):
         for video in added_videos:
             playlist_changed_text.append(f"--> {video.name}")
 
-        #if len(added_videos) > 3:
+        # if len(added_videos) > 3:
         #    playlist_changed_text.append(f"+ {len(added_videos) - 3} more")
 
     if len(unavailable_videos) != 0:
@@ -643,4 +690,4 @@ def update_playlist(request, playlist_id, type):
 def view_playlist_settings(request, playlist_id):
     playlist = request.user.profile.playlists.get(playlist_id=playlist_id)
 
-    return render(request, 'view_playlist_settings.html', {"playlist": playlist})
+    return render(request, 'view_playlist_settings.html', {"playlist": playlist})

+ 3 - 2
apps/users/models.py

@@ -31,9 +31,10 @@ class Profile(models.Model):
 
     # manage user
     objects = ProfileManager()
-    just_joined = models.BooleanField(default=True)
+    show_import_page = models.BooleanField(default=True)  # shows the user tips for a week
     yt_channel_id = models.CharField(max_length=420, default='')
-    import_in_progress = models.BooleanField(default=True)
+    import_in_progress = models.BooleanField(default=False)  # if True, will not let the user access main site until they import their YT playlists
+    imported_yt_playlists = models.BooleanField(default=False)  # True if user imported all their YT playlists
 
     # google api token details
     access_token = models.TextField(default="")

+ 43 - 18
apps/users/templates/profile.html

@@ -47,8 +47,10 @@
                 <span class="text-white-50">0</span>
               </li>
               <li class="list-group-item bg-dark d-flex justify-content-between align-items-center flex-wrap">
-                <h6 class="mb-0 text-white">Imported YouTube Playlists</h6>
-                  <small><a class="btn btn-success">Import</a></small>
+                <h6 class="mb-0 text-white">Imported their YouTube Playlists</h6>
+                      <span class="text-white-50">
+                          {% if user.profile.imported_yt_playlists %}Yes{% else %}No{% endif %}
+                      </span>
               </li>
             </ul>
           </div>
@@ -61,29 +63,52 @@
 
 
           <div class="row gutters-sm">
+              {% if not user.profile.imported_yt_playlists %}
+              <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
+                  <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
+                    <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
+                  </symbol>
+                  <symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
+                    <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
+                  </symbol>
+                  <symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
+                    <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
+                  </symbol>
+              </svg>
+
+                <div class="alert alert-primary d-flex align-items-center" role="alert">
+                  <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>
+                  <div>
+                      You have not imported your YouTube playlists yet!
+                      <a class="btn btn-success btn-sm" href="{% url 'import_user_yt_playlists' %}">Import</a>
+                  </div>
+                </div>
+              {% endif %}
             <div class="col-sm-6 mb-3">
               <div class="card h-100 bg-dark text-white">
                 <div class="card-body">
                   <h6 class="d-flex align-items-center mb-3"><span class="text-info me-2">{{ user.profile.playlists.count }}</span>Playlists Statistics</h6>
-                  <small>Public</small>
+
+                  <small>Public <span class="text-warning ms-1">{{ statistics.public_x }}%</span></small>
                   <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: 80%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
+                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.public_x }}%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
                   </div>
-                  <small>Private</small>
+
+                  <small>Private <span class="text-warning ms-1">{{ statistics.private_x }}%</span></small>
                   <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: 72%" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100"></div>
+                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.private_x }}%" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100"></div>
                   </div>
-                  <small>Favorites</small>
+                  <small>Favorites <span class="text-warning ms-1">{{ statistics.favorites_x }}%</span></small>
                   <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: 89%" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100"></div>
+                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.favorites_x }}%" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100"></div>
                   </div>
-                  <small>Watching</small>
+                  <small>Watching <span class="text-warning ms-1">{{ statistics.watching_x }}%</span></small>
                   <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: 55%" aria-valuenow="55" aria-valuemin="0" aria-valuemax="100"></div>
+                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.watching_x }}%" aria-valuenow="55" aria-valuemin="0" aria-valuemax="100"></div>
                   </div>
-                  <small>Imported</small>
+                  <small>Imported <span class="text-warning ms-1">{{ statistics.imported_x }}%</span></small>
                   <div class="progress mb-3" style="height: 5px">
-                    <div class="progress-bar bg-primary" role="progressbar" style="width: 66%" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100"></div>
+                    <div class="progress-bar bg-primary" role="progressbar" style="width: {{ statistics.imported_x }}%" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100"></div>
                   </div>
                 </div>
               </div>
@@ -117,14 +142,14 @@
             </div>
           </div>
           <div class="card bg-dark text-white mb-3">
-<div class="card-body">
-    <div class="d-flex justify-content-center">
-        No activity yet!
-    </div>
+            <div class="card-body">
+                <div class="d-flex justify-content-center">
+                    No activity yet!
+                </div>
 
 
-</div>
-</div>
+            </div>
+            </div>
 
 
 

+ 2 - 0
apps/users/urls.py

@@ -11,8 +11,10 @@ urlpatterns = [
     path("delete/account", views.delete_account, name='delete_account'),
     path('settings/', views.settings, name="settings"),
 
+    path("import/init", views.import_user_yt_playlists, name='import_user_yt_playlists'),
     path("import/start", views.start_import, name='start'),
     path("import/continue", views.continue_import, name='continue'),
+    path("import/cancel", views.cancel_import, name='cancel'),
 
     path("updates/user-playlists/<slug:action>", views.user_playlists_updates, name='user_playlists_updates'),
 ]

+ 55 - 6
apps/users/views.py

@@ -21,7 +21,31 @@ def index(request):
 
 @login_required
 def profile(request):
-    return render(request, 'profile.html')
+    user_playlists = request.user.profile.playlists.all()
+    total_num_playlists = user_playlists.count()
+
+    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
+
+    return render(request, 'profile.html', {"statistics": statistics})
+
+
+@login_required
+def settings(request):
+    return render(request, 'settings.html')
 
 
 @require_POST
@@ -68,6 +92,31 @@ def log_out(request):
     return redirect('/')
 
 
+def cancel_import(request):
+    user_profile = request.user.profile
+
+    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
+        user_profile.refresh_token = user_social_token.token_secret
+        user_profile.expires_at = user_social_token.expires_at
+
+        # request.user.save()
+
+    user_profile.imported_yt_playlists = False
+    user_profile.show_import_page = False
+    user_profile.save()
+
+    return redirect('home')
+
+
+def import_user_yt_playlists(request):
+    request.user.profile.show_import_page = True
+    request.user.profile.save(update_fields=['show_import_page'])
+
+    return render(request, 'import_in_progress.html')
+
+
 @login_required
 def start_import(request):
     '''
@@ -114,6 +163,9 @@ def start_import(request):
         if request.user.profile.yt_channel_id == "":
             Playlist.objects.getUserYTChannelID(request.user)
 
+        user_profile.import_in_progress = True
+        user_profile.save()
+
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
             {"total_playlists": result["num_of_playlists"],
              "playlist_name": result["first_playlist_name"],
@@ -123,11 +175,6 @@ def start_import(request):
         ))
 
 
-@login_required
-def settings(request):
-    return render(request, 'settings.html')
-
-
 @login_required
 def continue_import(request):
     if request.user.profile.import_in_progress is False:
@@ -155,6 +202,8 @@ def continue_import(request):
     else:
         # request.user.profile.just_joined = False
         request.user.profile.import_in_progress = False
+        request.user.profile.imported_yt_playlists = True
+        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()
 
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(

+ 7 - 5
templates/base.html

@@ -1,4 +1,5 @@
 {% load static %}
+{% with domain="http://127.0.0.1:8000/" %}
 <!doctype html>
 <html lang="en">
     <head>
@@ -49,7 +50,10 @@
         <link href="{% static 'bootstrap5.0.1/css/bootstrap.min.css' %}" rel="stylesheet">
 
     </head>
-    <body class="bg-dark text-white"  style="font-family: 'Fredoka One',fantasy; filter: contrast(80%);">
+    <body class="bg-dark text-white"  style="font-family: 'Fredoka One', monospace; filter: contrast(80%);">
+        {% if user.profile.show_import_page %}
+            <meta http-equiv="refresh" content="0;url={{ domain }}import/init" />
+        {% endif %}
         <nav class="navbar navbar-expand-lg navbar-light" id="navbar">
             <div class="container-fluid">
                 <a class="navbar-brand" href="{% url 'home' %}"><h3><kbd>UnTube</kbd></h3></a>
@@ -117,10 +121,7 @@
             <main class="ms-lg-auto px-lg-5">
     {% block content %}
     {% endblock %}
-
             </main>
-
-
       </div>
 
 
@@ -263,4 +264,5 @@
         -->
 
     </body>
-</html>
+</html>
+{% endwith %}