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

implemented plan to watch button for videos as well and created the planned to watch page

sleepytaco 3 жил өмнө
parent
commit
380cf45914

+ 18 - 0
apps/main/migrations/0048_video_is_planned_to_watch.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2021-08-02 01:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0047_playlist_auto_check_for_updates'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='video',
+            name='is_planned_to_watch',
+            field=models.BooleanField(default=False),
+        ),
+    ]

+ 1 - 0
apps/main/models.py

@@ -1319,6 +1319,7 @@ class Video(models.Model):
         default=False)  # True if the video was unavailable (private/deleted) when the API call was first made
     was_deleted_on_yt = models.BooleanField(default=False)  # True if video became unavailable on a subsequent API call
 
+    is_planned_to_watch = models.BooleanField(default=False)  # mark video as plan to watch later
     is_marked_as_watched = models.BooleanField(default=False)  # mark video as watched
     is_favorite = models.BooleanField(default=False, blank=True)  # mark video as favorite
     num_of_accesses = models.IntegerField(default=0)  # tracks num of times this video was clicked on by user

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

@@ -4,7 +4,7 @@
 
 
         <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 mb-3">
-            <h1 class="h2"> <span style="border-bottom: 3px #e24949 dashed;" class="pt-3">Playlists Tagged as</span> <kbd>{{ tag.name }}</kbd> <span class="badge bg-warning rounded-pill">{{ playlists.count }}</span> <a href="{% url 'search' %}?tag={{ tag.name }}"><i class="fas fa-search" style="color: black"></i></a></h1>
+            <h1 class="h2"> <span style="border-bottom: 3px #e24949 dashed;" class="pt-3">Playlists Tagged as</span> <kbd>{{ tag.name }}</kbd> <span class="badge bg-warning rounded-pill">{{ playlists.count }}</span> </h1>
 
         </div>
 

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

@@ -138,7 +138,7 @@
                 <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 'library' 'plan-to-watch' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                            <a href="{% url 'planned_to_watch' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
                                 Planned to Watch
                             </a>
                         </h2>

+ 24 - 3
apps/main/templates/intercooler/playlist_items.html

@@ -79,7 +79,7 @@
                 <div class="ms-5">
                 {% if playlist_item.video.is_unavailable_on_yt or playlist_item.video.was_deleted_on_yt %}
                 <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
-                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist_item.video.video_id %}"
+                <button title="Mark or unmark video favorite!" class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist_item.video.video_id %}"
                         hx-target="#video-{{ page }}-{{ forloop.counter }}-fav">
                     <div id="video-{{ page }}-{{ forloop.counter }}-fav">
                         {% if playlist_item.video.is_favorite %}
@@ -89,6 +89,15 @@
                         {% endif %}
                     </div>
                 </button>
+                <button title="Mark or unmark video as plan to watch!" class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_planned_to_watch' playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-planned">
+                    <div id="video-{{ forloop.counter }}-planned">
+                        {% if playlist_item.video.is_planned_to_watch %}
+                            <i class="fas fa-clock" style="color: #000000"></i>
+                        {% else %}
+                            <i class="far fa-clock"></i>
+                        {% endif %}
+                    </div>
+                </button>
                 {% else %}
 
                 <a class="btn btn-sm btn-info mb-1" type="button" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" class="btn btn-info me-1" target="_blank"><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
@@ -97,7 +106,7 @@
                     <i class="far fa-copy" aria-hidden="true"></i>
                 </button>
                 <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
-                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist_item.video.video_id %}" hx-target="#video-{{ page }}-{{ forloop.counter }}-fav">
+                <button title="Mark or unmark video as favorite!" class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist_item.video.video_id %}" hx-target="#video-{{ page }}-{{ forloop.counter }}-fav">
                     <div id="video-{{ page }}-{{ forloop.counter }}-fav">
                         {% if playlist_item.video.is_favorite %}
                             <i class="fas fa-heart" style="color: #fafa06"></i>
@@ -106,8 +115,20 @@
                         {% endif %}
                     </div>
                 </button>
+                    {% if not playlist_item.is_duplicate %}
+                    <button title="Mark or unmark video as plan to watch!" class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_planned_to_watch' playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-planned">
+                            <div id="video-{{ forloop.counter }}-planned">
+                                {% if playlist_item.video.is_planned_to_watch %}
+                                    <i class="fas fa-clock" style="color: #000000"></i>
+                                {% else %}
+                                    <i class="far fa-clock"></i>
+                                {% endif %}
+                            </div>
+                        </button>
+                    {% endif %}
                     {% if playlist.marked_as == "watching" and not playlist_item.is_duplicate %}
-                    <button class="btn btn-sm btn-light mb-1" type="button" hx-get="{% url 'mark_video_watched' playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ page }}-{{ forloop.counter }}-watched">
+
+                    <button title="Mark or unmark video as watched!" class="btn btn-sm btn-light mb-1" type="button" hx-get="{% url 'mark_video_watched' playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ page }}-{{ forloop.counter }}-watched">
                         <div id="video-{{ page }}-{{ forloop.counter }}-watched">
                             {% if playlist_item.video.is_marked_as_watched %}
                                 <i class="fas fa-check-circle"></i>

+ 10 - 1
apps/main/templates/intercooler/video_cards.html

@@ -40,7 +40,7 @@
                             <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">
+                            <button class="btn btn-dark me-1" 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>
@@ -49,6 +49,15 @@
                                     {% endif %}
                                 </div>
                             </button>
+                            <button title="Mark or unmark video as plan to watch!" class="btn btn-warning" type="button" hx-get="{% url 'mark_video_planned_to_watch' video.video_id %}" hx-target="#video-{{ forloop.counter }}-planned">
+                                <div id="video-{{ forloop.counter }}-planned">
+                                    {% if video.is_planned_to_watch %}
+                                        <i class="fas fa-clock" style="color: #000000"></i>
+                                    {% else %}
+                                        <i class="far fa-clock"></i>
+                                    {% endif %}
+                                </div>
+                            </button>
                         </span>
 
                     </div>

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

@@ -124,7 +124,7 @@
     <div class="card bg-dark text-white">
             <div class="card-header">
                 <div class="d-flex justify-content-center">
-                    <h3><span style="border-bottom: 3px #ffffff dashed;">Your Playlist Tags</span><small class="ms-2"><span class="badge bg-warning rounded-pill">{{ user.playlist_tags.all.count }}</span></small> </h3>
+                    <h3><span style="border-bottom: 3px #ffffff dashed;">Your Playlist Tags</span><small class="ms-2"><span class="badge bg-warning text-black-50 rounded-pill">{{ user.playlist_tags.all.count }}</span></small> </h3>
                 </div>
             </div>
             <div class="card-body">

+ 64 - 0
apps/main/templates/planned_to_watch.html

@@ -0,0 +1,64 @@
+{% extends 'base.html' %}
+{% 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" id="planned-to-watch-playlists"><a href="#planned-to-watch-videos" style="color: rebeccapurple" class="mt-1 me-3"><i class="fas fa-angle-double-down"></i></a>Planned to Watch Playlists
+            <span class="badge bg-primary rounded-pill">{{ playlists.count|default:"0" }}</span>
+
+        </h1>
+        {% if playlists %}
+                    <form method="get" action="{% url 'search' %}" >
+                    <input size="50" class="form-control border border-secondary" type="text"
+                   name="query" placeholder="Search your planned to watch playlists...">
+                    <input type="text" class="visually-hidden" name="mode" value="playlists">
+                    <input type="text" class="visually-hidden" name="type" value="plan-to-watch">
+                    <button type="submit" class="visually-hidden"></button>
+                    </form>
+            {% endif %}
+    </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">Nothing here :(</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" id="planned-to-watch-videos"><a href="#planned-to-watch-playlists" style="color: rebeccapurple" class="mt-1 me-3"><i class="fas fa-angle-double-up"></i></a>Planned to Watch Videos
+            <span class="badge bg-primary rounded-pill">{{ videos.count }}</span>
+
+        </h1>
+            {% if videos %}
+                    <form method="get" action="{% url 'search' %}" >
+                    <input size="50" class="form-control border border-secondary" type="text"
+                   name="query" placeholder="Search your planned to watch videos...">
+                    <input type="text" class="visually-hidden" name="mode" value="videos">
+                    <input type="text" class="visually-hidden" name="type" value="plan-to-watch">
+                    <button type="submit" class="visually-hidden"></button>
+                    </form>
+            {% endif %}
+    </div>
+
+    <div id="planned-to-watch-videos">
+        <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">Nothing here :(</h5>
+            {% endif %}
+        </div>
+    </div>
+
+
+    <button class="scrollToTopBtn sticky-top bg-danger">
+        <i class="fa fa-angle-double-up fa-lg"></i></button>
+
+{% endblock %}

+ 25 - 6
apps/main/templates/view_playlist.html

@@ -34,14 +34,12 @@
             {% else %}
                 <div class="sticky-top mb-3" style="top: 0.5rem;">
                     {% if not playlist.is_yt_mix %}
-                        {% if user.profile.auto_check_for_updates %}
-                            {% if playlist.auto_check_for_updates %}
+                        {% if user.profile.auto_check_for_updates or playlist.auto_check_for_updates %}
                                 <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...
                                     </div>
                                 </div>
-                            {% endif %}
                         {% endif %}
                     {% endif %}
                     {% if playlist.marked_as == "watching" %}
@@ -669,7 +667,7 @@
                                         <div class="ms-5">
                                             {% if playlist_item.video.is_unavailable_on_yt or playlist_item.video.was_deleted_on_yt %}
                                                 <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
-                                                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
+                                                <button title="Mark or unmark video favorite!" class="btn btn-sm btn-dark mb-1" 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>
@@ -678,6 +676,15 @@
                                                         {% endif %}
                                                     </div>
                                                 </button>
+                                                <button title="Mark or unmark video as plan to watch!" class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_planned_to_watch' playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-planned">
+                                                    <div id="video-{{ forloop.counter }}-planned">
+                                                        {% if playlist_item.video.is_planned_to_watch %}
+                                                            <i class="fas fa-clock" style="color: #000000"></i>
+                                                        {% else %}
+                                                            <i class="far fa-clock"></i>
+                                                        {% endif %}
+                                                    </div>
+                                                </button>
                                             {% else %}
 
                                                 <a class="btn btn-sm btn-info mb-1" type="button" href="https://www.youtube.com/watch?v={{ playlist_item.video.video_id }}&list={{ playlist.playlist_id }}" class="btn btn-info me-1" target="_blank"><i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
@@ -686,7 +693,7 @@
                                                     <i class="far fa-copy" aria-hidden="true"></i>
                                                 </button>
                                                 <a class="btn btn-sm  btn-primary mb-1" href="{% url 'video' playlist_item.video.video_id %}"><i class="fas fa-info"></i></a>
-                                                <button class="btn btn-sm btn-dark mb-1" type="button" hx-get="{% url 'mark_video_favorite' playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-fav">
+                                                <button title="Mark or unmark video favorite!" class="btn btn-sm btn-dark mb-1" 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>
@@ -695,8 +702,20 @@
                                                         {% endif %}
                                                     </div>
                                                 </button>
+                                                {% if not playlist_item.is_duplicate %}
+                                                <button title="Mark or unmark video as plan to watch!" class="btn btn-sm btn-warning mb-1" type="button" hx-get="{% url 'mark_video_planned_to_watch' playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-planned">
+                                                        <div id="video-{{ forloop.counter }}-planned">
+                                                            {% if playlist_item.video.is_planned_to_watch %}
+                                                                <i class="fas fa-clock" style="color: #000000"></i>
+                                                            {% else %}
+                                                                <i class="far fa-clock"></i>
+                                                            {% endif %}
+                                                        </div>
+                                                    </button>
+                                                {% endif %}
                                                 {% if playlist.marked_as == "watching" and not playlist_item.is_duplicate %}
-                                                    <button class="btn btn-sm btn-light mb-1" type="button" hx-get="{% url 'mark_video_watched' playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-watched">
+
+                                                    <button title="Mark or unmark video as watched!" class="btn btn-sm btn-light mb-1" type="button" hx-get="{% url 'mark_video_watched' playlist.playlist_id playlist_item.video.video_id %}" hx-target="#video-{{ forloop.counter }}-watched">
                                                         <div id="video-{{ forloop.counter }}-watched">
                                                             {% if playlist_item.video.is_marked_as_watched %}
                                                                 <i class="fas fa-check-circle"></i>

+ 26 - 8
apps/main/templates/view_video.html

@@ -43,6 +43,15 @@
                                     {% endif %}
                                 </div>
                             </button>
+                            <button title="Mark or unmark video as plan to watch!" class="btn btn-warning mt-3 ms-2" type="button" hx-get="{% url 'mark_video_planned_to_watch' video.video_id %}" hx-target="#video-{{ forloop.counter }}-planned">
+                                <div id="video-{{ forloop.counter }}-planned">
+                                    {% if video.is_planned_to_watch %}
+                                        <i class="fas fa-clock" style="color: #000000"></i>
+                                    {% else %}
+                                        <i class="far fa-clock"></i>
+                                    {% endif %}
+                                </div>
+                            </button>
                         </span>
                     </div>
                     {% endif %}
@@ -56,14 +65,23 @@
                                     </button>
                                     {% if video.is_unavailable_on_yt or video.was_deleted_on_yt %}
                                     <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>
+                                <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>
+                                        <button title="Mark or unmark video as plan to watch!" class="btn btn-warning" type="button" hx-get="{% url 'mark_video_planned_to_watch' video.video_id %}" hx-target="#video-{{ forloop.counter }}-planned">
+                                <div id="video-{{ forloop.counter }}-planned">
+                                    {% if video.is_planned_to_watch %}
+                                        <i class="fas fa-clock" style="color: #000000"></i>
+                                    {% else %}
+                                        <i class="far fa-clock"></i>
+                                    {% endif %}
+                                </div>
+                            </button>
                                     {% endif %}
                                     <br><small class="h4">{% if video.user_label %}a.k.a <span style="border-bottom: 3px #ffffff dashed;"> {{ video.user_label }}</span>{% endif %}</small>
 

+ 4 - 2
apps/main/urls.py

@@ -7,11 +7,13 @@ urlpatterns = [
     ### STUFF RELATED TO WHOLE SITE
     path("home/", views.home, name='home'),
     path("favorites", views.favorites, name="favorites"),
+    path("planned-to-watch", views.planned_to_watch, name="planned_to_watch"),
     path("library/<slug:library_type>", views.library, name='library'),
 
     ### STUFF RELATED TO INDIVIDUAL VIDEOS
     path("video/<slug:video_id>", views.view_video, name='video'),
-    path("video/<slug:video_id>/video-details/favorite", views.mark_video_favortie, name='mark_video_favorite'),
+    path("video/<slug:video_id>/mark/favorite", views.mark_video_favortie, name='mark_video_favorite'),
+    path("video/<slug:video_id>/mark/planned-to-watch", views.mark_video_planned_to_watch, name='mark_video_planned_to_watch'),
     path("video/<slug:video_id>/notes", views.video_notes, name='video_notes'),
     path("video/<slug:video_id>/get-video-completion-times", views.video_completion_times, name="video_completion_times"),
     path("video/<slug:video_id>/add-user-label", views.add_video_user_label, name='add_video_user_label'),
@@ -22,7 +24,7 @@ urlpatterns = [
     path("playlist/<slug:playlist_id>", views.view_playlist, name='playlist'),
     path("playlist/<slug:playlist_id>/add-user-label", views.add_playlist_user_label, name='add_playlist_user_label'),
 
-    path('playlist/<slug:playlist_id>/<slug:video_id>/video-details/watched', views.mark_video_watched,
+    path('playlist/<slug:playlist_id>/<slug:video_id>/mark/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,

+ 28 - 2
apps/main/views.py

@@ -84,6 +84,17 @@ def favorites(request):
                                               "videos": favorite_videos})
 
 
+@login_required
+def planned_to_watch(request):
+    planned_to_watch_playlists = request.user.playlists.filter(Q(marked_as='plan-to-watch') & Q(is_in_db=True)).order_by(
+        '-last_accessed_on')
+    planned_to_watch_videos = request.user.videos.filter(is_planned_to_watch=True).order_by('updated_at')
+
+    return render(request, 'planned_to_watch.html', {"playlists": planned_to_watch_playlists,
+                                              "videos": planned_to_watch_videos})
+
+
+
 @login_required
 def view_video(request, video_id):
     if request.user.videos.filter(video_id=video_id).exists():
@@ -236,11 +247,11 @@ def library(request, library_type):
             elif playlists_type == "Plan to Watch":
                 playlists = request.user.playlists.filter(Q(marked_as="plan-to-watch") & Q(is_in_db=True))
             else:
-                return redirect('/playlists/home')
+                return redirect('/library/home')
 
             if not playlists.exists():
                 messages.info(request, f"No playlists in {playlists_type}")
-                return redirect('/playlists/home')
+                return redirect('/library/home')
             random_playlist = random.choice(playlists)
             return redirect(f'/playlist/{random_playlist.playlist_id}')
         return render(request, 'library.html')
@@ -515,6 +526,21 @@ def mark_video_favortie(request, video_id):
         return HttpResponse('<i class="fas fa-heart" style="color: #fafa06"></i>')
 
 
+@login_required
+def mark_video_planned_to_watch(request, video_id):
+    video = request.user.videos.get(video_id=video_id)
+
+    if video.is_planned_to_watch:
+        video.is_planned_to_watch = False
+        video.save(update_fields=['is_planned_to_watch'])
+        return HttpResponse('<i class="far fa-clock"></i>')
+    else:
+        video.is_planned_to_watch = True
+        video.save(update_fields=['is_planned_to_watch'])
+        return HttpResponse('<i class="fas fa-clock" style="color: #000000"></i>')
+
+
+
 @login_required
 def mark_video_watched(request, playlist_id, video_id):
     playlist = request.user.playlists.get(playlist_id=playlist_id)

+ 0 - 1
apps/manage_playlists/urls.py

@@ -1,4 +1,3 @@
-from django.conf.urls import url
 from django.urls import path
 from . import views
 

+ 2 - 1
apps/search/templates/search_untube_page.html

@@ -101,7 +101,8 @@
                                     <option value="Liked" {% if item_type == "liked" %}selected{% endif %}>Liked</option>
                                     <option value="Favorite" {% if item_type == "favorite" %}selected{% endif %}>Favorite</option>
                                     <option value="Watched" {% if item_type == "watched" %}selected{% endif %}>Watched</option>
-                                     <option value="Unavailable" {% if item_type == "unavailable" %}selected{% endif %}>Unavailable</option>
+                                    <option value="Planned to Watch" {% if item_type == "plan-to-watch" %}selected{% endif %}>Planned to Watch</option>
+                                    <option value="Unavailable" {% if item_type == "unavailable" %}selected{% endif %}>Unavailable</option>
                                 </select>
                             </div>
                             <div class="ms-3">

+ 2 - 0
apps/search/views.py

@@ -112,6 +112,8 @@ def search_UnTube(request):
             all_videos = all_videos.filter(is_favorite=True)
         elif videos_type == "Watched":
             all_videos = all_videos.filter(is_marked_as_watched=True)
+        elif videos_type == "Planned to Watch":
+            all_videos = all_videos.filter(is_planned_to_watch=True)
         elif videos_type == "Unavailable":
             all_videos = all_videos.filter(Q(is_unavailable_on_yt=False) & Q(was_deleted_on_yt=True))
 

+ 97 - 0
apps/users/templates/about.html

@@ -0,0 +1,97 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+    <!-- Template taken from https://www.bootdey.com/snippets/view/profile-with-data-and-skills#html -->
+    <div class="d-flex justify-content-center flex-column">
+    <div class="d-flex justify-content-center">
+    <nav aria-label="breadcrumb">
+      <ol class="breadcrumb">
+        <li class="breadcrumb-item active">FAQ</li>
+      <li class="breadcrumb-item"><a href="#">Tips</a></li>
+
+        <li class="breadcrumb-item"><a href="#">Feedback</a></li>
+      </ol>
+    </nav>
+        </div>
+    <div class="accordion text-black-50" id="accordionExample">
+  <div class="accordion-item ">
+    <h2 class="accordion-header" id="headingOne">
+      <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
+        What is UnTube?
+      </button>
+    </h2>
+    <div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
+      <div class="accordion-body">
+        <strong>This is the first item's accordion body.</strong> It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.
+      </div>
+    </div>
+  </div>
+  <div class="accordion-item">
+    <h2 class="accordion-header" id="headingTwo">
+      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
+          What data of mine will be stored on UnTube?
+      </button>
+    </h2>
+    <div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
+      <div class="accordion-body">
+        <strong>This is the second item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.
+      </div>
+    </div>
+  </div>
+  <div class="accordion-item">
+    <h2 class="accordion-header" id="headingThree">
+      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
+        Where can I learn more about the site's features?
+      </button>
+    </h2>
+    <div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
+      <div class="accordion-body">
+        <strong>This is the third item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.
+      </div>
+    </div>
+  </div>
+          <div class="accordion-item">
+    <h2 class="accordion-header" id="headingFour">
+      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
+        What did you use to build this site? How long did it take to build this site?
+      </button>
+    </h2>
+    <div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour" data-bs-parent="#accordionExample">
+      <div class="accordion-body">
+        <strong>This website is primarily built using Django, Bootstrap 5 and htmx.</strong>
+            Django helped me swiftly set up the backend and organize them into separate apps. All of the UI you see on UnTube was built purely using Bootstrap 5. I started off with some free base templates
+          I've found online, and slowly began designing the site myself as I was getting used to seeing repetitive Bootstrap code over and over again.
+          By the end of this project, I've grown accustomed to using the official docs and figuring out a way to implement any feature that I've cooked up in my mind.
+          <br>
+          <strong>I cannot be thankful enough for the existence of htmx.</strong> Most of the dynamic interactivity on this site was made possible due to htmx.
+          When I began implementing core site features like moving, deleting, checking for updates etc. I found myself writing a LOT of AJAX code each and everytime, for even the simplest of interactivity. It was
+          when I found htmx my development process sped up like crazy. Who would have thought that instead of replacing the whole page with the response, just
+          replacing a particular target element with the HttpResponse would do wonders?
+          Some of the major places I've used htmx on this site:
+          <ul>
+              <li><strong>HTMX Triggers</strong> htmx offers various triggers upon which it sends requests. One example is the <code>load</code> trigger. <em>After</em> the dashboard finishes loading, I've set up htmx to automatically send a GET request to the backend (this only happens once upon the page load) to check for
+              playlist updates. From the backend, the update status is sent back via a HttpResponse which htmx promptly loads into a target div I specified. That way I did not need to check for updates inside
+              the main view that loaded the page. In a way, htmx helped me check for updates in the "background".
+                  I've used this <code>load</code> trigger many other places on the site, one other example is the playlist completion time info is loaded <em>after</em> the playlist page is loaded.
+              <br> Instead of sending requests on <code>load</code>, it is also possible to send htmx requests
+                  when any HTML element is <code>click</code>ed or <code>revealed</code>. I've used this <click>click</click> trigger on the mark playlist/video as favorite buttons and the mark videos as watched buttons. Below, I discuss about using the <code>revealed</code> to achieve infinite scrolling.
+              </li>
+              <li><strong>Active Search</strong> The whole search page heavily relies on htmx. On the frontend, via htmx the textfield (as well as the select and radio buttons) is hooked up to automatically send requests with the search query
+                  to the backend right after certain milliseconds of typing. In Django, I've set up a view to receive these htmx requests and send back a HttpResponse using a template loader.
+                  htmx then loads the received response into a target search results div that I've set up. Check out this <a href="https://htmx.org/examples/active-search/" target="_blank">search example</a> from htmx's website. </li>
+              <li><strong>Infinite Scrolling</strong> For playlists with more than 50 videos, the next 50 videos are automatically loaded onto the screen only when the user scrolls down to the bottom.
+                    htmx's <code>hx-trigger="revealed"</code> and <code>hx-swap="afterend"</code> attributes helped me achieve this functionality. When the 50th video is revealed on the screen, htmx makes a GET request to the backend to get the next
+                  50 videos (if available) and appends the HttpResponse after the 50th video. Until htmx gets back a response from the backend, it can be set up to show the user a loading spinner!
+                  <br>The main advantage of this feature was that for playlists with 100s or even 1000s of videos, it only needs to load the first 50 videos everytime and hence vastly improving the playlist page load speed.
+              </li>
+          </ul>
+          <strong>Any questions on how I've implemented some of the features on this site?</strong> Head over to <a href="">feedback</a> and send me your
+          questions. I will be happy to share my code and thought process to illustrate how I implemented the site's features using htmx.
+      </div>
+    </div>
+  </div>
+
+</div>
+    </div>
+{% endblock %}

+ 1 - 0
apps/users/urls.py

@@ -8,6 +8,7 @@ urlpatterns = [
     path("unlike-untube/", views.unlike_untube, name="unlike_untube"),
 
     path("profile/", views.profile, name='profile'),
+    path("about/", views.about, name='about'),
     path("logout/", views.log_out, name='log_out'),
     path("update/settings", views.update_settings, name='update_settings'),
     path('accounts/', include('allauth.urls')),

+ 12 - 5
apps/users/views.py

@@ -29,6 +29,10 @@ def index(request):
         return redirect('home')
 
 
+def about(request):
+    return render(request, 'about.html')
+
+
 @login_required
 def profile(request):
     user_playlists = request.user.playlists.all()
@@ -74,10 +78,10 @@ def update_settings(request):
     user = request.user
     username_input = request.POST['username'].strip()
     message_content = "Saved!"
-    #message_type = "success"
+    # message_type = "success"
     if username_input != user.username:
         if User.objects.filter(username__exact=username_input).count() != 0:
-            #message_type = "danger"
+            # message_type = "danger"
             message_content = f"Username {request.POST['username'].strip()} already taken"
             messages.error(request, message_content)
         else:
@@ -319,7 +323,8 @@ 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)).exclude(playlist_id="LL")
+            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)
@@ -328,7 +333,8 @@ 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)).exclude(playlist_id="LL").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">
@@ -341,7 +347,8 @@ 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)).exclude(playlist_id="LL")
+        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)

+ 1 - 1
templates/base.html

@@ -171,7 +171,7 @@
 
 
                         <li class="nav-item">
-                            <a class="nav-link" href="{% url 'settings' %}">About</a>
+                            <a class="nav-link" href="{% url 'about' %}">About</a>
                         </li>
 
                     </ul>