2
0

views.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import bleach
  2. from allauth.socialaccount.models import SocialApp
  3. from django.conf import settings
  4. from django.contrib import messages
  5. from django.contrib.auth import logout
  6. from django.contrib.auth.decorators import login_required
  7. from django.contrib.auth.models import User
  8. from django.contrib.sites.models import Site
  9. from django.db.models import Q
  10. from django.http import HttpResponse
  11. from django.shortcuts import redirect, render
  12. from django.template import loader
  13. from django.views.decorators.http import require_POST
  14. from backend.main.models import Playlist
  15. from .models import Untube
  16. import logging
  17. logger = logging.getLogger(__name__)
  18. # Create your views here.
  19. def index(request):
  20. if Untube.objects.all().count() == 0:
  21. untube = Untube.objects.create()
  22. untube.save()
  23. if settings.GOOGLE_OAUTH_CLIENT_ID is NotImplemented or settings.GOOGLE_OAUTH_CLIENT_SECRET is NotImplemented:
  24. messages.error(request, 'Please fill in your Google OAuth credentials in the local/settings.dev.py file')
  25. else:
  26. # messages.success(request, 'Thanks for filling in the YouTube API key')
  27. if not Site.objects.filter(domain=settings.GOOGLE_OAUTH_URI).exists():
  28. Site.objects.create(domain=settings.GOOGLE_OAUTH_URI, name=settings.GOOGLE_OAUTH_URI)
  29. if not SocialApp.objects.filter(provider='google').exists(): # create google OAuth app
  30. print('Creating Google social app...')
  31. app = SocialApp.objects.create(
  32. provider='google',
  33. name='UnTube OAuth',
  34. client_id=settings.GOOGLE_OAUTH_CLIENT_ID,
  35. secret=settings.GOOGLE_OAUTH_CLIENT_SECRET
  36. )
  37. site_uri = Site.objects.get(domain=settings.GOOGLE_OAUTH_URI)
  38. app.sites.add(site_uri)
  39. if not request.session.exists(request.session.session_key):
  40. request.session.create()
  41. request.session['liked_untube'] = False
  42. return render(
  43. request, 'index.html', {
  44. 'likes': Untube.objects.all().first().page_likes,
  45. 'users_joined': User.objects.all().count()
  46. }
  47. )
  48. # if request.user.is_anonymous:
  49. # return render(request, 'index.html', {'likes': Untube.objects.all().first().page_likes,
  50. # 'users_joined': User.objects.all().count()})
  51. # else:
  52. # return redirect('home')
  53. def about(request):
  54. return render(request, 'about.html')
  55. @login_required
  56. def profile(request):
  57. user_playlists = request.user.playlists.all()
  58. watching = user_playlists.filter(marked_as='watching')
  59. total_num_playlists = user_playlists.count()
  60. statistics = {'public_x': 0, 'private_x': 0, 'favorites_x': 0, 'watching_x': 0, 'imported_x': 0}
  61. if total_num_playlists != 0:
  62. # x means percentage
  63. statistics['public_x'
  64. ] = round(user_playlists.filter(is_private_on_yt=False).count() / total_num_playlists, 1) * 100
  65. statistics['private_x'
  66. ] = round(user_playlists.filter(is_private_on_yt=True).count() / total_num_playlists, 1) * 100
  67. statistics['favorites_x'
  68. ] = round(user_playlists.filter(is_favorite=True).count() / total_num_playlists, 1) * 100
  69. statistics['watching_x'
  70. ] = round(user_playlists.filter(marked_as='watching').count() / total_num_playlists, 1) * 100
  71. statistics['imported_x'
  72. ] = round(user_playlists.filter(is_user_owned=False).count() / total_num_playlists, 1) * 100
  73. return render(
  74. request, 'profile.html', {
  75. 'total_num_playlists': total_num_playlists,
  76. 'statistics': statistics,
  77. 'watching': watching
  78. }
  79. )
  80. @login_required
  81. def user_settings(request):
  82. return render(request, 'settings.html')
  83. @require_POST
  84. def update_settings(request):
  85. print(request.POST)
  86. user = request.user
  87. username_input = bleach.clean(request.POST['username'].strip())
  88. message_content = 'Saved!'
  89. # message_type = 'success'
  90. if username_input != user.username:
  91. if User.objects.filter(username__exact=username_input).count() != 0:
  92. # message_type = 'danger'
  93. message_content = f'Username {username_input} already taken'
  94. messages.error(request, message_content)
  95. else:
  96. user.username = username_input
  97. # user.save()
  98. message_content = f'Username updated to {username_input}!'
  99. messages.success(request, message_content)
  100. if 'open search in new tab' in request.POST and user.profile.open_search_new_tab is False:
  101. user.profile.open_search_new_tab = True
  102. elif 'open search in new tab' not in request.POST and user.profile.open_search_new_tab is True:
  103. user.profile.open_search_new_tab = False
  104. if 'enable gradient bg' in request.POST and user.profile.enable_gradient_bg is False:
  105. user.profile.enable_gradient_bg = True
  106. elif 'enable gradient bg' not in request.POST and user.profile.enable_gradient_bg is True:
  107. user.profile.enable_gradient_bg = False
  108. if 'confirm before deleting' in request.POST and user.profile.confirm_before_deleting is False:
  109. user.profile.confirm_before_deleting = True
  110. elif 'confirm before deleting' not in request.POST and user.profile.confirm_before_deleting is True:
  111. user.profile.confirm_before_deleting = False
  112. if 'hide videos' in request.POST and user.profile.hide_unavailable_videos is False:
  113. user.profile.hide_unavailable_videos = True
  114. elif 'hide videos' not in request.POST and user.profile.hide_unavailable_videos is True:
  115. user.profile.hide_unavailable_videos = False
  116. user.save()
  117. if message_content == 'Saved!':
  118. messages.success(request, message_content)
  119. return redirect('settings')
  120. @login_required
  121. def delete_account(request):
  122. request.user.playlists.all().delete()
  123. request.user.videos.all().delete()
  124. request.user.playlist_tags.all().delete()
  125. request.user.profile.delete()
  126. request.user.delete()
  127. request.session.flush()
  128. messages.success(request, 'Account data deleted successfully.')
  129. return redirect('index')
  130. @login_required
  131. def log_out(request):
  132. request.session.flush() # delete all stored session keys
  133. logout(request) # log out authenticated user
  134. if 'troll' in request.GET:
  135. print('TROLLED')
  136. messages.success(request, 'Hee Hee')
  137. else:
  138. messages.success(request, 'Successfully logged out. Hope to see you back again!')
  139. return redirect('/')
  140. @login_required
  141. def cancel_import(request):
  142. user_profile = request.user.profile
  143. user_profile.imported_yt_playlists = False
  144. user_profile.show_import_page = False
  145. user_profile.save()
  146. return redirect('home')
  147. @login_required
  148. def import_user_yt_playlists(request):
  149. request.user.profile.show_import_page = True
  150. request.user.profile.save(update_fields=['show_import_page'])
  151. return render(request, 'import_in_progress.html')
  152. @login_required
  153. def start_import(request):
  154. """
  155. Initializes only the user's playlist data in the database. Returns the progress bar, which will
  156. keep calling continue_import
  157. """
  158. user_profile = request.user.profile
  159. result = Playlist.objects.initializePlaylist(request.user)
  160. if result['status'] == -1:
  161. print('User has no YT channel')
  162. return HttpResponse(
  163. loader.get_template('intercooler/progress_bar.html').render({
  164. 'channel_found': False,
  165. 'error_message': result['error_message']
  166. })
  167. )
  168. elif result['status'] == -2:
  169. user_profile.import_in_progress = False
  170. user_profile.imported_yt_playlists = True
  171. user_profile.show_import_page = True
  172. user_profile.save()
  173. print('User has no playlists on YT')
  174. # if request.user.profile.yt_channel_id == '':
  175. # Playlist.objects.getUserYTChannelID(request.user)
  176. Playlist.objects.initializePlaylist(request.user, 'LL')
  177. return HttpResponse(
  178. loader.get_template('intercooler/progress_bar.html').render({
  179. 'total_playlists': 0,
  180. 'playlists_imported': 0,
  181. 'done': True,
  182. 'progress': 100,
  183. 'channel_found': True
  184. })
  185. )
  186. else:
  187. # if request.user.profile.yt_channel_id == '':
  188. # Playlist.objects.getUserYTChannelID(request.user)
  189. Playlist.objects.initializePlaylist(request.user, 'LL')
  190. user_profile.import_in_progress = True
  191. user_profile.save()
  192. return HttpResponse(
  193. loader.get_template('intercooler/progress_bar.html').render({
  194. 'total_playlists': result['num_of_playlists'],
  195. 'playlist_name': result['first_playlist_name'],
  196. 'playlists_imported': 0,
  197. 'progress': 0,
  198. 'channel_found': True
  199. })
  200. )
  201. @login_required
  202. def continue_import(request):
  203. if request.user.profile.import_in_progress is False:
  204. return redirect('home')
  205. num_of_playlists = request.user.playlists.filter(Q(is_user_owned=True)).exclude(playlist_id='LL').count()
  206. logger.debug('NUM OF PLAYLISTS', num_of_playlists)
  207. try:
  208. remaining_playlists = request.user.playlists.filter(Q(is_user_owned=True) &
  209. Q(is_in_db=False)).exclude(playlist_id='LL')
  210. logger.debug(remaining_playlists.count(), 'REMAINING PLAYLISTS')
  211. playlists_imported = num_of_playlists - remaining_playlists.count() + 1
  212. playlist = remaining_playlists.order_by('created_at')[0]
  213. playlist_name = playlist.name
  214. playlist_id = playlist.playlist_id
  215. Playlist.objects.getAllVideosForPlaylist(request.user, playlist_id)
  216. except Exception:
  217. logger.debug('NO REMAINING PLAYLISTS')
  218. playlist_id = -1
  219. if playlist_id != -1:
  220. return HttpResponse(
  221. loader.get_template('intercooler/progress_bar.html').render({
  222. 'total_playlists': num_of_playlists,
  223. 'playlists_imported': playlists_imported,
  224. 'playlist_name': playlist_name,
  225. 'progress': round((playlists_imported / num_of_playlists) * 100, 1),
  226. 'channel_found': True
  227. })
  228. )
  229. else:
  230. # request.user.profile.just_joined = False
  231. request.user.profile.import_in_progress = False
  232. request.user.profile.imported_yt_playlists = True
  233. request.user.profile.show_import_page = True # set back to true again so as to show users the welcome screen on 'home'
  234. request.user.save()
  235. user_pl_count = request.user.playlists.filter(Q(is_user_owned=True) &
  236. Q(is_in_db=True)).exclude(playlist_id='LL').count()
  237. return HttpResponse(
  238. loader.get_template('intercooler/progress_bar.html').render({
  239. 'total_playlists': user_pl_count,
  240. 'playlists_imported': user_pl_count,
  241. 'done': True,
  242. 'progress': 100,
  243. 'channel_found': True
  244. })
  245. )
  246. @login_required
  247. def user_playlists_updates(request, action):
  248. """
  249. Gets all user created playlist's ids from YouTube and checks them with the user playlists imported on UnTube.
  250. If any playlist id is on UnTube but not on YouTube, deletes the playlist from YouTube.
  251. If any new playlist id, imports it to UnTube
  252. """
  253. if action == 'check-for-updates':
  254. user_playlists_on_UnTube = request.user.playlists.filter(Q(is_user_owned=True) &
  255. Q(is_in_db=True)).exclude(playlist_id='LL')
  256. result = Playlist.objects.initializePlaylist(request.user)
  257. logger.debug(result)
  258. youtube_playlist_ids = result['playlist_ids']
  259. untube_playlist_ids = []
  260. for playlist in user_playlists_on_UnTube:
  261. untube_playlist_ids.append(playlist.playlist_id)
  262. deleted_playlist_ids = []
  263. deleted_playlist_names = []
  264. for pl_id in untube_playlist_ids:
  265. if pl_id not in youtube_playlist_ids: # ie this playlist was deleted on youtube
  266. deleted_playlist_ids.append(pl_id)
  267. pl = request.user.playlists.get(playlist_id__exact=pl_id)
  268. deleted_playlist_names.append(f'{pl.name} (had {pl.video_count} videos)')
  269. pl.delete()
  270. if result['num_of_playlists'] == user_playlists_on_UnTube.count() and len(deleted_playlist_ids) == 0:
  271. logger.info('No new updates')
  272. playlists = []
  273. else:
  274. playlists = request.user.playlists.filter(Q(is_user_owned=True) &
  275. Q(is_in_db=False)).exclude(playlist_id='LL')
  276. logger.info(
  277. f'New updates found! {playlists.count()} newly added and {len(deleted_playlist_ids)} playlists deleted!'
  278. )
  279. logger.info(deleted_playlist_names)
  280. return HttpResponse(
  281. loader.get_template('intercooler/user_playlist_updates.html').render({
  282. 'playlists': playlists,
  283. 'deleted_playlist_names': deleted_playlist_names
  284. })
  285. )
  286. elif action == 'init-update':
  287. unimported_playlists = request.user.playlists.filter(Q(is_user_owned=True) &
  288. Q(is_in_db=False)).exclude(playlist_id='LL').count()
  289. return HttpResponse(
  290. f"""
  291. <div hx-get='/updates/user-playlists/start-update' hx-trigger='load' hx-target='#user-pl-updates'>
  292. <div class='alert alert-dismissible fade show' role='alert' style='background-color: cadetblue'>
  293. <div class='d-flex justify-content-center mt-4 mb-3 ms-2' id='loading-sign' >
  294. <img src='/static/svg-loaders/spinning-circles.svg' width='40' height='40'>
  295. <h5 class='mt-2 ms-2 text-black'>Importing {unimported_playlists} new playlists into UnTube, please wait!</h5>
  296. </div>
  297. </div>
  298. </div>
  299. """
  300. )
  301. elif action == 'start-update':
  302. unimported_playlists = request.user.playlists.filter(Q(is_user_owned=True) &
  303. Q(is_in_db=False)).exclude(playlist_id='LL')
  304. for playlist in unimported_playlists:
  305. Playlist.objects.getAllVideosForPlaylist(request.user, playlist.playlist_id)
  306. return HttpResponse(
  307. """
  308. <div class='alert alert-success alert-dismissible fade show d-flex justify-content-center' role='alert'>
  309. <h4 class=''>Successfully imported new playlists into UnTube!</h4>
  310. <meta http-equiv='refresh' content='0;url=/home/#recent-playlists' />
  311. <meta http-equiv='refresh' content='2;url=/home/' />
  312. <button type='button' class='btn-close' data-bs-dismiss='alert' aria-la bel='Close'></button>
  313. </div>
  314. """
  315. )
  316. @login_required
  317. def get_user_liked_videos_playlist(request):
  318. if not request.user.playlists.filter(Q(playlist_id='LL') & Q(is_in_db=True)).exists():
  319. Playlist.objects.initializePlaylist(request.user, 'LL')
  320. Playlist.objects.getAllVideosForPlaylist(request.user, 'LL')
  321. messages.success(request, 'Successfully imported your Liked Videos playlist!')
  322. return HttpResponse("""
  323. <script>
  324. window.location.reload();
  325. </script>
  326. """)
  327. # FOR INDEX.HTML
  328. @require_POST
  329. def like_untube(request):
  330. untube = Untube.objects.all().first()
  331. untube.page_likes += 1
  332. untube.save()
  333. request.session['liked_untube'] = True
  334. request.session.save()
  335. return HttpResponse(
  336. f"""
  337. <a hx-post='/unlike-untube/' hx-swap='outerHTML' style='text-decoration: none; color: black'>
  338. <i class='fas fa-heart' style='color: #d02e2e'></i> {untube.page_likes} likes (p.s glad you liked it!)
  339. </a>
  340. """
  341. )
  342. @require_POST
  343. def unlike_untube(request):
  344. untube = Untube.objects.all().first()
  345. untube.page_likes -= 1
  346. untube.save()
  347. request.session['liked_untube'] = False
  348. request.session.save()
  349. return HttpResponse(
  350. f"""
  351. <a hx-post='/like-untube/' hx-swap='outerHTML' style='text-decoration: none; color: black'>
  352. <i class='fas fa-heart'></i> {untube.page_likes} likes (p.s :/)
  353. </a>
  354. """
  355. )