Преглед изворни кода

added import liked videos feature

sleepytaco пре 3 година
родитељ
комит
838f4edae3

+ 18 - 0
apps/main/migrations/0041_video_liked.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.3 on 2021-07-22 00:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0040_remove_playlist_has_duplicate_videos'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='video',
+            name='liked',
+            field=models.BooleanField(default=False),
+        ),
+    ]

+ 32 - 9
apps/main/models.py

@@ -112,6 +112,7 @@ class PlaylistManager(models.Manager):
                 result["status"] = -1
                 return result
 
+            print(pl_response)
             if pl_response["pageInfo"]["totalResults"] == 0:
                 print("No playlists created yet on youtube.")
                 result["status"] = -2
@@ -138,6 +139,7 @@ class PlaylistManager(models.Manager):
         for item in playlist_items:
             playlist_id = item["id"]
             playlist_ids.append(playlist_id)
+
             # check if this playlist already exists in user's untube collection
             if user.playlists.filter(playlist_id=playlist_id).exists():
                 playlist = user.playlists.get(playlist_id=playlist_id)
@@ -395,7 +397,7 @@ class PlaylistManager(models.Manager):
                 vid_request = youtube.videos().list(
                     part="contentDetails,player,snippet,statistics",  # get details of eac video
                     id=video_ids_string,
-                    maxResults=50
+                    maxResults=50,
                 )
 
                 vid_response = vid_request.execute()
@@ -404,6 +406,9 @@ class PlaylistManager(models.Manager):
                     duration = item['contentDetails']['duration']
                     vid = playlist.videos.get(video_id=item['id'])
 
+                    if playlist_id == "LL":
+                        vid.liked = True
+
                     vid.name = item['snippet']['title']
                     vid.description = item['snippet']['description']
                     vid.thumbnail_url = getThumbnailURL(item['snippet']['thumbnails'])
@@ -931,8 +936,8 @@ class PlaylistManager(models.Manager):
         playlist_items = user.playlists.get(playlist_id=playlist_id).playlist_items.select_related('video').filter(
             playlist_item_id__in=playlist_item_ids)
 
-        # new_playlist_duration_in_seconds = playlist.playlist_duration_in_seconds
-        # new_playlist_video_count = playlist.video_count
+        new_playlist_duration_in_seconds = playlist.playlist_duration_in_seconds
+        new_playlist_video_count = playlist.video_count
         with build('youtube', 'v3', credentials=credentials) as youtube:
             for playlist_item in playlist_items:
                 pl_request = youtube.playlistItems().delete(
@@ -958,16 +963,32 @@ class PlaylistManager(models.Manager):
                     playlist.videos.remove(video)
 
                 # video = playlist.videos.get(playlist_item_id=playlist_item_id)
-                # new_playlist_video_count -= 1
-                # new_playlist_duration_in_seconds -= video.duration_in_seconds
+                new_playlist_video_count -= 1
+                new_playlist_duration_in_seconds -= video.duration_in_seconds
                 # video.delete()
 
-        # playlist.video_count = new_playlist_video_count
-        # playlist.playlist_duration_in_seconds = new_playlist_duration_in_seconds
-        # playlist.playlist_duration = getHumanizedTimeString(new_playlist_duration_in_seconds)
-        # playlist.save(update_fields=['video_count', 'playlist_duration', 'playlist_duration_in_seconds'])
+        playlist.video_count = new_playlist_video_count
+        playlist.playlist_duration_in_seconds = new_playlist_duration_in_seconds
+        playlist.playlist_duration = getHumanizedTimeString(new_playlist_duration_in_seconds)
+        playlist.save(update_fields=['video_count', 'playlist_duration', 'playlist_duration_in_seconds'])
         # time.sleep(2)
 
+        playlist_items = playlist.playlist_items.select_related('video').order_by("video_position")
+        counter = 0
+        videos = []
+        for playlist_item in playlist_items:
+            playlist_item.video_position = counter
+
+            is_duplicate = False
+            if playlist_item.video_id in videos:
+                is_duplicate = True
+            else:
+                videos.append(playlist_item.video_id)
+
+            playlist_item.is_duplicate = is_duplicate
+            playlist_item.save(update_fields=['video_position', 'is_duplicate'])
+            counter += 1
+
     def updatePlaylistDetails(self, user, playlist_id, details):
         """
         Takes in playlist itemids for the videos in a particular playlist
@@ -1056,6 +1077,7 @@ class PlaylistManager(models.Manager):
         return 0
 
 
+
 class Tag(models.Model):
     name = models.CharField(max_length=69)
     created_by = models.ForeignKey(User, related_name="playlist_tags", on_delete=models.CASCADE, null=True)
@@ -1097,6 +1119,7 @@ class Video(models.Model):
     published_at = models.DateTimeField(blank=True, null=True)
     description = models.CharField(max_length=420, default="")
     has_cc = models.BooleanField(default=False, blank=True, null=True)
+    liked = models.BooleanField(default=False)  # whether this video liked on YouTube by user or not
 
     # video stats
     public_stats_viewable = models.BooleanField(default=True)

+ 49 - 13
apps/main/templates/home.html

@@ -15,6 +15,7 @@
             </p>
         </div>
     {% endif %}
+
     {% if import_successful %}
         <br>
         <br>
@@ -85,30 +86,61 @@
         </div>
 
         <div class="row" ><!--data-masonry='{"percentPosition": true }'-->
-            {% for playlist in user_playlists|slice:"0:3" %}
                 <div class="col-sm-6 col-lg-4 mb-4">
                     <div class="card card-cover h-100 overflow-hidden text-white gradient-bg-3 rounded-5 shadow-lg" style="">
                         <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1">
-                            <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold"><a href="{% url 'playlist' playlist.playlist_id %}" class="stretched-link" style="text-decoration: none; color: #fafafa">{{ playlist.name }}</a> </h2>
+                            <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold">
+                                <a href="{% url 'all_playlists' 'all' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                                    All Playlists</a>
+                            </h2>
                             <ul class="d-flex list-unstyled mt-auto">
                                 <li class="me-auto">
                                     <h3>
-                                        <i class="fas fa-thumbtack" style="color: red"></i>
+                                        <i class="fas fa-burn fa-lg" style="color: #b300ff"></i>
                                     </h3>
                                 </li>
-                                <li class="d-flex align-items-center me-3">
-                                    <small>by {{ playlist.channel_name }}</small>
-                                </li>
-                                <li class="d-flex align-items-center">
-                                    <i class="fas fa-eye me-1"></i>
-                                    <small>{{ playlist.num_of_accesses }} clicks</small>
-
-                                </li>
                             </ul>
                         </div>
                     </div>
                 </div>
-            {% endfor %}
+
+                <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card card-cover h-100 overflow-hidden text-white gradient-bg-3 rounded-5 shadow-lg" style="">
+                <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1">
+                <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold">
+                <a href="{% url 'playlist' 'LL' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                    Liked Videos
+                </a>
+                </h2>
+                <ul class="d-flex list-unstyled mt-auto">
+                <li class="me-auto">
+                    <h3>
+                        <i class="fas fa-thumbs-up fa-lg" style="color: #0090ff"></i>
+                    </h3>
+                </li>
+                </ul>
+                </div>
+                </div>
+                </div>
+
+                <div class="col-sm-6 col-lg-4 mb-4">
+                <div class="card card-cover h-100 overflow-hidden text-white gradient-bg-3 rounded-5 shadow-lg" style="">
+                <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1">
+                <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold">
+                <a href="{% url 'playlist' 'LL' %}" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                    Your Pins
+                </a>
+                </h2>
+                <ul class="d-flex list-unstyled mt-auto">
+                <li class="me-auto">
+                    <h3>
+                        <i class="fas fa-thumbtack fa-lg" style="color: #db4747"></i>
+                    </h3>
+                </li>
+                </ul>
+                </div>
+                </div>
+                </div>
 
             <!-- FULL IMAGE CARD: might be useful
             <div class="col-sm-6 col-lg-4 mb-4">
@@ -126,7 +158,11 @@
             <div class="col">
                 <div class="card card-cover h-100 overflow-hidden text-white bg-dark 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">All/Your Pins <i class="fas fa-thumbtack" style="color: red"></i> </h2>
+                        <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold">
+                            <a href="{% url 'log_out' %}?troll=yes" class="stretched-link" style="text-decoration: none; color: #fafafa">
+                                DO NOT PRESS <i class="fas fa-cat" style="color: red"></i>
+                            </a>
+                         </h2>
                     </div>
                 </div>
                 <!-- Implement later

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

@@ -12,7 +12,7 @@
         <div class="card" style="background-color: #25641a;">
             <div class="card-body">
                 <h4 class="card-title">Import Playlists</h4>
-                    <p class="card-text">Add public playlists to your own collection. Unfortunately, the YouTube API doesn't let us retrieve your liked videos and watch later playlists :(</p>
+                    <p class="card-text">Add public playlists to your own collection. Unfortunately, the YouTube API doesn't let us retrieve your watch later playlist :(</p>
             </div>
         </div>
         </a>

+ 22 - 10
apps/main/templates/view_playlist.html

@@ -6,6 +6,16 @@
 {% block content %}
 
     <div id="view_playlist">
+        {% if not_imported_LL %}
+            <div hx-get="/import/liked-videos-playlist" hx-trigger="load" hx-swap="outerHTML">
+            <div class="alert alert-dismissible fade show" role="alert" style="background-color: cadetblue">
+                <div class="d-flex justify-content-center mt-4 mb-3 ms-2" id="loading-sign" >
+                    <img src="/static/svg-loaders/spinning-circles.svg" width="40" height="40">
+                    <h5 class="mt-2 ms-2 text-black">Importing your Liked Videos playlist into UnTube, do not refresh this page!</h5>
+                </div>
+            </div>
+        </div>
+        {% else %}
         {% if playlist.has_playlist_changed %}
             <div hx-get="{% url 'update_playlist' playlist.playlist_id 'auto' %}" hx-trigger="load" hx-swap="outerHTML">
                 <div class="d-flex justify-content-center mt-4 mb-3" id="loading-sign">
@@ -330,7 +340,7 @@
 
                         <h5>{% if playlist.is_user_owned %}Move or {% endif %}Copy videos to another playlist!</h5>
 
-                        <div class="d-flex justify-content-start">
+                        <div class="row d-flex justify-content-start">
 
 
                             <div class="col-md-8 text-dark">
@@ -344,23 +354,27 @@
                                 </select>
                             </div>
 
-                        </div>
-
-                        <div class="d-flex justify-content-start mt-2">
+                            <div class="col-md d-flex justify-content-start">
 
                             {% if playlist.is_user_owned %}
-                                <div class="btn-group me-2">
+                                <div class="btn-group mt-1">
                                     <button hx-post="{% url 'playlist_move_copy_videos' playlist.playlist_id 'move' %}" hx-include="#playlists-to-move-to, #video-checkboxes" hx-target="#move-copy-videos-box" type="button" class="btn btn-primary">
                                         Move!
                                     </button>
                                 </div>
                             {% endif %}
-                            <div class="btn-group">
+                            <div class="btn-group ms-1 mt-1">
                                 <button hx-post="{% url 'playlist_move_copy_videos' playlist.playlist_id 'copy' %}" hx-include="#playlists-to-move-to, #video-checkboxes" hx-target="#move-copy-videos-box" type="button" class="btn btn-info">
                                     Copy!
                                 </button>
                             </div>
-                            <div id="move-copy-videos-box" class="d-flex align-items-end ms-2">
+
+                            </div>
+
+                        </div>
+
+                        <div class="d-flex justify-content-start mt-2">
+              <div id="move-copy-videos-box" class="d-flex align-items-end ms-2">
                                 <span class="text-warning">Note: Move will delete the selected videos from this playlist. Copy won't.</span>
                             </div>
                         </div>
@@ -549,9 +563,7 @@
         <div class="d-flex justify-content-center">
             <img id="load-more-videos-spinner" class="htmx-indicator mt-4" src="{% static 'svg-loaders/spinning-circles.svg' %}" width="40" height="40">
         </div>
-
-
-
+        {% endif %}
     </div>
 
     <button class="scrollToTopBtn sticky-top">

+ 11 - 6
apps/main/views.py

@@ -146,6 +146,8 @@ def view_playlist(request, playlist_id):
             playlist.last_accessed_on = datetime.datetime.now(pytz.utc)
         playlist.save()
     else:
+        if playlist_id == "LL":  # liked videos playlist hasnt been imported yet
+            return render(request, 'view_playlist.html', {"not_imported_LL": True})
         messages.error(request, "No such playlist found!")
         return redirect('home')
 
@@ -445,7 +447,7 @@ def delete_videos(request, playlist_id, command):
     if command == "confirm":
         print(playlist_item_ids)
 
-        if num_vids == request.user.playlists.get(playlist_id=playlist_id).videos.all().count():
+        if num_vids == request.user.playlists.get(playlist_id=playlist_id).playlist_items.all().count():
             delete_text = "ALL VIDEOS"
             extra_text = " This will not delete the playlist itself, will only make the playlist empty. "
         else:
@@ -453,12 +455,12 @@ def delete_videos(request, playlist_id, command):
         return HttpResponse(
             f"""<h5>
                 Are you sure you want to delete {delete_text} from your YouTube playlist?{extra_text}This cannot be undone.</h5>
-                <button hx-post="/from/{playlist_id}/delete-videos/confirmed" hx-include="[id='video-checkboxes']" hx-target="#delete-videos-confirm-box" type="button" class="btn btn-outline-danger btn-sm">Confirm</button>
+                <button hx-post="/playlist/{playlist_id}/delete-videos/confirmed" hx-include="[id='video-checkboxes']" hx-target="#delete-videos-confirm-box" type="button" class="btn btn-outline-danger btn-sm">Confirm</button>
                 <hr>
             """)
     elif command == "confirmed":
         print(playlist_item_ids)
-        url = f"/from/{playlist_id}/delete-videos/start"
+        url = f"/playlist/{playlist_id}/delete-videos/start"
         return HttpResponse(
             f"""
             <div class="spinner-border text-light" role="status" hx-post="{url}" hx-trigger="load" hx-include="[id='video-checkboxes']" hx-target="#delete-videos-confirm-box"></div><hr>
@@ -470,8 +472,8 @@ def delete_videos(request, playlist_id, command):
         # playlist.has_playlist_changed = True
         # playlist.save(update_fields=['has_playlist_changed'])
         return HttpResponse(f"""
-        <h5 hx-get="/playlist/{playlist_id}/update/checkforupdates" hx-trigger="load delay:3s" hx-target="#checkforupdates">
-            Done deleting selected videos from your playlist on YouTube. Playlist on UnTube will update soon.
+        <h5 hx-get="/playlist/{playlist_id}/update/checkforupdates" hx-trigger="load delay:2s" hx-target="#checkforupdates">
+            Done deleting selected videos from your playlist on YouTube. Refresh page!
         </h5>
         <hr>
         """)
@@ -929,6 +931,9 @@ def update_playlist(request, playlist_id, type):
             video = playlist_item.video
             playlist_changed_text.append(f"--> {playlist_item.video.name}")
             playlist_item.delete()
+            if playlist_id == "LL":
+                video.liked = False
+                video.save(update_fields=['liked'])
             if not playlist.playlist_items.filter(video__video_id=video.video_id).exists():
                 playlist.videos.remove(video)
 
@@ -1122,7 +1127,7 @@ def playlist_move_copy_videos(request, playlist_id, action):
                 <span class="text-danger">First select some videos to {action}!</span>""")
 
     success_message = f"""
-                <span class="text-success">Successfully {'moved' if action == 'move' else 'copied'} {len(playlist_item_ids)} videos to {len(playlist_ids)} other playlist!</span>"""
+                <span class="text-success">Successfully {'moved' if action == 'move' else 'copied'} {len(playlist_item_ids)} videos to {len(playlist_ids)} other playlist! Refresh depage!</span>"""
     if action == "move":
         status = Playlist.objects.moveCopyVideosFromPlaylist(request.user,
                                                              from_playlist_id=playlist_id,

+ 1 - 1
apps/users/templates/intercooler/user_playlist_updates.html

@@ -32,7 +32,7 @@
                 {% endfor %}
             </ul>
         {% endif %}
-            <button class="btn btn-success" hx-get="{% url 'user_playlists_updates' 'init-update' %}" hx-trigger="click" hx-target="#user-pl-updates">
+            <button class="btn btn-success" {% if deleted_playlist_names and playlists %}hx-get="{% url 'user_playlists_updates' 'init-update' %}" hx-trigger="click" hx-target="#user-pl-updates"{% else %}type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"{% endif %}>
                 {% if deleted_playlist_names and not playlists %}
                     OK
                 {% else %}

+ 1 - 0
apps/users/urls.py

@@ -14,6 +14,7 @@ urlpatterns = [
     path("delete/account", views.delete_account, name='delete_account'),
     path('settings/', views.settings, name="settings"),
 
+    path("import/liked-videos-playlist", views.get_user_liked_videos_playlist, name="get_user_liked_videos_playlist"),
     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'),

+ 34 - 5
apps/users/views.py

@@ -104,6 +104,11 @@ def delete_account(request):
 def log_out(request):
     request.session.flush()  # delete all stored session keys
     logout(request)  # log out authenticated user
+
+    if "troll" in request.GET:
+        print("TROLLED")
+        messages.success(request, "Hee Hee")
+
     return redirect('/')
 
 
@@ -165,6 +170,8 @@ def start_import(request):
 
         print("User has no playlists on YT")
 
+        Playlist.objects.initializePlaylist(request.user, "LL")
+
         if request.user.profile.yt_channel_id == "":
             Playlist.objects.getUserYTChannelID(request.user)
 
@@ -178,6 +185,8 @@ def start_import(request):
         if request.user.profile.yt_channel_id == "":
             Playlist.objects.getUserYTChannelID(request.user)
 
+        Playlist.objects.initializePlaylist(request.user)
+
         user_profile.import_in_progress = True
         user_profile.save()
 
@@ -195,10 +204,10 @@ def continue_import(request):
     if request.user.profile.import_in_progress is False:
         return redirect('home')
 
-    num_of_playlists = request.user.playlists.all().count()
+    num_of_playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False)).exclude(playlist_id="LL").count()
 
     try:
-        remaining_playlists = request.user.playlists.filter(is_in_db=False)
+        remaining_playlists = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=False)).exclude(playlist_id="LL")
         playlists_imported = num_of_playlists - remaining_playlists.count() + 1
         playlist = remaining_playlists.order_by("created_at")[0]
         playlist_name = playlist.name
@@ -221,9 +230,11 @@ def continue_import(request):
         request.user.profile.show_import_page = True  # set back to true again so as to show users the welcome screen on 'home'
         request.user.save()
 
+        user_pl_count = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True)).exclude(playlist_id="LL").count()
+
         return HttpResponse(loader.get_template('intercooler/progress_bar.html').render(
-            {"total_playlists": num_of_playlists,
-             "playlists_imported": num_of_playlists,
+            {"total_playlists": user_pl_count,
+             "playlists_imported": user_pl_count,
              "done": True,
              "progress": 100,
              "channel_found": True}))
@@ -231,8 +242,13 @@ def continue_import(request):
 
 @login_required
 def user_playlists_updates(request, action):
+    """
+    Gets all user created playlist's ids from YouTube and checks them with the user playlists imported on UnTube.
+    If any playlist id is on UnTube but not on YouTube, deletes the playlist from YouTube.
+    If any new playlist id, imports it to UnTube
+    """
     if action == 'check-for-updates':
-        user_playlists_on_UnTube = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True))
+        user_playlists_on_UnTube = request.user.playlists.filter(Q(is_user_owned=True) & Q(is_in_db=True)).exclude(playlist_id="LL")
 
         result = Playlist.objects.initializePlaylist(request.user)
 
@@ -290,6 +306,19 @@ def user_playlists_updates(request, action):
         </div>
         """)
 
+@login_required
+def get_user_liked_videos_playlist(request):
+    if not request.user.playlists.filter(Q(playlist_id="LL") & Q(is_in_db=True)).exists():
+        Playlist.objects.initializePlaylist(request.user, "LL")
+        Playlist.objects.getAllVideosForPlaylist(request.user, "LL")
+        messages.success(request, "Successfully imported your Liked Videos playlist!")
+
+    return HttpResponse("""
+        <script>
+        window.location.reload();
+        </script>
+    """)
+
 
 ### FOR INDEX.HTML
 @require_POST