diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be45c67 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 GeorgiaTechTeam18 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 9737b44..a445b11 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ -# SpotifyWrapper \ No newline at end of file +# SpotifyWrapper + +## Deliverables + +### Team Website +https://team18gt.weebly.com/ + +### Spotify Wrapper GitHub repo +https://github.com/GeorgiaTechTeam18/SpotifyWrapper + +### Project management board +https://github.com/orgs/GeorgiaTechTeam18/projects/2/ + +## command reference +#### run the project +```bash +. .venv/bin/activate +python manage.py migrate +python manage.py runserver +``` +#### regenerate files after modifications +```bash +pip3 freeze > requirements.txt +python manage.py makemigrations +``` diff --git a/UserAuth/static/UserAuth/styles.css b/UserAuth/static/UserAuth/styles.css index 2a93f95..9d5beb0 100644 --- a/UserAuth/static/UserAuth/styles.css +++ b/UserAuth/static/UserAuth/styles.css @@ -715,6 +715,23 @@ ul { } } +/* Share buttons */ +.share-container { + margin-left: auto; +} + +.share-button { + border-radius: .3em; + padding: .3em; + background: #f8f9fa; + margin: 0; + font-size: large; +} + +.share-button:hover { + background: #e3e3e4; +} + /* View public and liked wraps styling*/ .header-container { display: flex; @@ -745,6 +762,7 @@ ul { .spotify-button:hover { background-color: #1ed760; } + @media (min-width: 768px) { #hamburger { display: none !important; diff --git a/UserAuth/urls.py b/UserAuth/urls.py index d05dd16..6b0bb70 100644 --- a/UserAuth/urls.py +++ b/UserAuth/urls.py @@ -13,6 +13,5 @@ path("unlink/", views.delete_token, name="unlink_token"), path("view_wraps/", include("Wrapped.urls"), name="view_wraps"), path("delete-wrap//", views.delete_wrap, name="delete_wrap"), - path("contact/", views.contact, name="contact"), ] diff --git a/Wrapped/migrations/0007_spotifywrap_liked_by.py b/Wrapped/migrations/0007_spotifywrap_liked_by.py index 5714398..333d723 100644 --- a/Wrapped/migrations/0007_spotifywrap_liked_by.py +++ b/Wrapped/migrations/0007_spotifywrap_liked_by.py @@ -8,14 +8,19 @@ class Migration(migrations.Migration): dependencies = [ - ('Wrapped', '0006_alter_spotifywrap_uuid'), + ("Wrapped", "0006_alter_spotifywrap_uuid"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.AddField( - model_name='spotifywrap', - name='liked_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='liked_by', to=settings.AUTH_USER_MODEL), + model_name="spotifywrap", + name="liked_by", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="liked_by", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/Wrapped/migrations/0008_remove_spotifywrap_liked_by_spotifywrap_liked_by.py b/Wrapped/migrations/0008_remove_spotifywrap_liked_by_spotifywrap_liked_by.py index 62a4b70..5db60c6 100644 --- a/Wrapped/migrations/0008_remove_spotifywrap_liked_by_spotifywrap_liked_by.py +++ b/Wrapped/migrations/0008_remove_spotifywrap_liked_by_spotifywrap_liked_by.py @@ -7,18 +7,21 @@ class Migration(migrations.Migration): dependencies = [ - ('Wrapped', '0007_spotifywrap_liked_by'), + ("Wrapped", "0007_spotifywrap_liked_by"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.RemoveField( - model_name='spotifywrap', - name='liked_by', + model_name="spotifywrap", + name="liked_by", ), migrations.AddField( - model_name='spotifywrap', - name='liked_by', - field=models.ManyToManyField(blank=True, related_name='liked_wraps', to=settings.AUTH_USER_MODEL), + model_name="spotifywrap", + name="liked_by", + field=models.ManyToManyField( + blank=True, + related_name="liked_wraps", + to=settings.AUTH_USER_MODEL), ), ] diff --git a/Wrapped/migrations/0009_alter_spotifywrap_is_public.py b/Wrapped/migrations/0009_alter_spotifywrap_is_public.py index f492dd6..c9e18ad 100644 --- a/Wrapped/migrations/0009_alter_spotifywrap_is_public.py +++ b/Wrapped/migrations/0009_alter_spotifywrap_is_public.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('Wrapped', '0008_remove_spotifywrap_liked_by_spotifywrap_liked_by'), + ("Wrapped", "0008_remove_spotifywrap_liked_by_spotifywrap_liked_by"), ] operations = [ migrations.AlterField( - model_name='spotifywrap', - name='is_public', + model_name="spotifywrap", + name="is_public", field=models.BooleanField(default=False), ), ] diff --git a/Wrapped/models.py b/Wrapped/models.py index 3f67c72..2943cb2 100644 --- a/Wrapped/models.py +++ b/Wrapped/models.py @@ -17,7 +17,8 @@ class SpotifyWrap(models.Model): audio_features = models.TextField(default="{}") is_public = models.BooleanField(default=False) likes = models.IntegerField(default=0) - liked_by = models.ManyToManyField(User, related_name="liked_wraps", blank=True) + liked_by = models.ManyToManyField( + User, related_name="liked_wraps", blank=True) def set_top_artists(self, artists_data): self.artists = json.dumps(artists_data) diff --git a/Wrapped/urls.py b/Wrapped/urls.py index 4759a1f..86fba95 100644 --- a/Wrapped/urls.py +++ b/Wrapped/urls.py @@ -3,8 +3,9 @@ from UserAuth.views import delete_account -from .views import (create_wrap, like_unlike_wrap, make_wraps_public, - view_public_wraps, view_wrap, view_wraps, view_liked_wraps, make_wraps_private) +from .views import (create_wrap, like_unlike_wrap, make_wraps_private, + make_wraps_public, view_liked_wraps, view_public_wraps, + view_wrap, view_wraps) urlpatterns = [ path("view_wraps/", view_wraps, name="view_wraps"), @@ -15,6 +16,6 @@ path("make_wraps_public/", make_wraps_public, name="make_wraps_public"), path("view_public_wraps/", view_public_wraps, name="view_public_wraps"), path("delete_account/", delete_account, name="delete_account"), - path('liked-wraps/', view_liked_wraps, name='view_liked_wraps'), + path("liked-wraps/", view_liked_wraps, name="view_liked_wraps"), path("make_wraps_private/", make_wraps_private, name="make_wraps_private"), ] diff --git a/Wrapped/views.py b/Wrapped/views.py index 67da27a..b0f9a4c 100644 --- a/Wrapped/views.py +++ b/Wrapped/views.py @@ -3,17 +3,15 @@ from datetime import datetime import requests -from django.http import JsonResponse, HttpResponseServerError -from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth.models import AnonymousUser -from django.http import HttpResponseRedirect -from django.http import HttpResponseNotFound, JsonResponse +from django.db.models import Exists, OuterRef +from django.http import (HttpResponseNotFound, HttpResponseRedirect, + HttpResponseServerError, JsonResponse) from django.shortcuts import get_object_or_404, redirect, render from UserAuth.models import SpotifyToken from UserAuth.util import get_user_tokens -from django.db.models import Exists, OuterRef from .models import SpotifyWrap @@ -33,6 +31,7 @@ def make_wraps_public(request): return redirect("profile") + def make_wraps_private(request): wrap_ids = request.POST.getlist("wrap_ids") action = request.POST.get("action") @@ -61,8 +60,8 @@ def view_liked_wraps(request): public_wraps = public_wraps.filter(is_liked_by_user=True) - if request.method == 'POST': - wrap_uuid = request.POST.get('wrap_uuid') + if request.method == "POST": + wrap_uuid = request.POST.get("wrap_uuid") wrap = get_object_or_404(SpotifyWrap, uuid=wrap_uuid) liked = None @@ -77,14 +76,21 @@ def view_liked_wraps(request): wrap.save() print(liked) - return JsonResponse({ - "success": True, - "liked": liked, - "wrap_uuid": wrap.uuid, - }) - return render(request, "Wrapped/view_liked_wraps.html", { - "wraps": public_wraps, - }) + return JsonResponse( + { + "success": True, + "liked": liked, + "wrap_uuid": wrap.uuid, + } + ) + return render( + request, + "Wrapped/view_liked_wraps.html", + { + "wraps": public_wraps, + }, + ) + def view_public_wraps(request): public_wraps = SpotifyWrap.objects.filter(is_public=True).annotate( @@ -96,9 +102,8 @@ def view_public_wraps(request): ) ) - - if request.method == 'POST': - wrap_uuid = request.POST.get('wrap_uuid') + if request.method == "POST": + wrap_uuid = request.POST.get("wrap_uuid") wrap = get_object_or_404(SpotifyWrap, uuid=wrap_uuid) liked = None @@ -113,14 +118,20 @@ def view_public_wraps(request): wrap.save() print(liked) - return JsonResponse({ - "success": True, - "liked": liked, - "wrap_uuid": wrap.uuid, - }) - return render(request, "Wrapped/view_public_wraps.html", { - "wraps": public_wraps, - }) + return JsonResponse( + { + "success": True, + "liked": liked, + "wrap_uuid": wrap.uuid, + } + ) + return render( + request, + "Wrapped/view_public_wraps.html", + { + "wraps": public_wraps, + }, + ) @login_required @@ -140,14 +151,17 @@ def like_unlike_wrap(request, wrap_id): wrap.save() - return JsonResponse({ - "success": True, - "liked": liked, - "wrap_uuid": wrap.uuid, - }) + return JsonResponse( + { + "success": True, + "liked": liked, + "wrap_uuid": wrap.uuid, + } + ) return JsonResponse({"success": False, "message": "Invalid request."}) + def view_wraps(request): wraps = SpotifyWrap.objects.filter(user=request.user) return render(request, "Wrapped/view_wraps.html", {"wraps": wraps}) @@ -185,6 +199,9 @@ def view_wrap(request, wrap_id): wrap.get_audio_features() ) selected_tracks = select_tracks(tracks, artists, genres) + spotify_webplayback_token = "" + if request.user.is_authenticated and get_user_tokens(request.user): + spotify_webplayback_token = get_user_tokens(request.user).access_token return render( request, "Wrapped/view_wrap.html", @@ -198,10 +215,11 @@ def view_wrap(request, wrap_id): "audio_features_graphs": audio_features_graphs, "audio_features_list": audio_features_list, "selected_tracks": selected_tracks, - "access_token": get_user_tokens(request.user).access_token, + "access_token": spotify_webplayback_token, }, ) + def like_wrap(request, wrap_id): wrap = get_object_or_404(SpotifyWrap, uuid=wrap_id) @@ -213,7 +231,7 @@ def like_wrap(request, wrap_id): message = "Liked" wrap.save() - return JsonResponse({'message': message}) + return JsonResponse({"message": message}) key_map = { diff --git a/templates/UserAuth/login.html b/templates/UserAuth/login.html index 551b965..3daca2a 100644 --- a/templates/UserAuth/login.html +++ b/templates/UserAuth/login.html @@ -2,36 +2,28 @@ {% load static %} {% block body %}
-
- {% csrf_token %} -

Sign in

-
+ + {% csrf_token %} +

Sign in

+
{% if form.email.errors %}
- {% for error in form.email.errors %} -

{{ error }}

- {% endfor %} + {% for error in form.email.errors %}

{{ error }}

{% endfor %}
{% endif %} -
{{ form.email }}
- {% if form.password.errors %}
- {% for error in form.password.errors %} -

{{ error }}

- {% endfor %} + {% for error in form.password.errors %}

{{ error }}

{% endfor %}
{% endif %} - - diff --git a/templates/UserAuth/profile.html b/templates/UserAuth/profile.html index c7498b6..10285e5 100644 --- a/templates/UserAuth/profile.html +++ b/templates/UserAuth/profile.html @@ -35,30 +35,42 @@

Spotify Accounts

{% if wraps is not none %}

Spotify Wraps

    - {% for wrap in wraps %} -
  • -

    {{ wrap.title }}

    - View this wrap -

    Posted by: {{ wrap.user.username }}

    -
    - - {% csrf_token %} - - -
    - {% csrf_token %} - - {% if wrap.is_public %} - - - {% else %} - - - {% endif %} -
    -
    -
  • - {% endfor %} + {% for wrap in wraps %} +
  • +

    {{ wrap.title }}

    + View this wrap +

    Posted by: {{ wrap.user.username }}

    +
    +
    + {% csrf_token %} + +
    +
    + {% csrf_token %} + + {% if wrap.is_public %} + + + {% else %} + + + {% endif %} +
    + +
    +
  • + {% endfor %}
{% else %}
@@ -96,6 +108,16 @@

{{ wrap.title }}

} }); }); + + function share(text, url) { + const shareData = { + title: "SpotifyWrapped", + text: text, + url: url, + }; + + navigator.share(shareData); + }
Delete Account diff --git a/templates/Wrapped/view_liked_wraps.html b/templates/Wrapped/view_liked_wraps.html index 93ecbf4..a482a09 100644 --- a/templates/Wrapped/view_liked_wraps.html +++ b/templates/Wrapped/view_liked_wraps.html @@ -1,7 +1,8 @@ {% extends 'UserAuth/base.html' %} {% block title %}Liked Wraps{% endblock %} {% block body %} -

+
+

Favorite Wraps

@@ -11,23 +12,35 @@

Favorite Wraps

{% if wraps %}
    {% for wrap in wraps %} -
  • -

    {{ wrap.title }}

    - View this wrap -

    Posted by: {{ wrap.user.username }}

    -
    - -
    -
  • +
  • +

    {{ wrap.title }}

    + View this wrap +

    Posted by: {{ wrap.user.username }}

    +
    + + +
    +
  • {% endfor %}
{% else %} @@ -35,47 +48,56 @@

{{ wrap.title }}

You haven't liked any wraps yet. Check out some public wraps!

{% endif %} + + + function share(text, url) { + const shareData = { + title: "SpotifyWrapped", + text: text, + url: url, + }; + + navigator.share(shareData); + } + {% endblock %} diff --git a/templates/Wrapped/view_public_wraps.html b/templates/Wrapped/view_public_wraps.html index df9dd7b..2485140 100644 --- a/templates/Wrapped/view_public_wraps.html +++ b/templates/Wrapped/view_public_wraps.html @@ -1,85 +1,106 @@ {% extends 'UserAuth/base.html' %} {% block title %}Public Wraps{% endblock %} {% block body %} -

+
+

All Wraps

- {% if wraps %} -

Take a look at some wraps generated by the community.

- {% endif %} + {% if wraps %}

Take a look at some wraps generated by the community.

{% endif %}
Favorites
{% if wraps %}
    {% for wrap in wraps %} -
  • -

    {{ wrap.title }}

    - View this wrap -

    Posted by: {{ wrap.user.username }}

    -
    - -
    -
  • +
  • +

    {{ wrap.title }}

    + View this wrap +

    Posted by: {{ wrap.user.username }}

    +
    + + +
    +
  • {% endfor %}
{% else %} -

+
+

There are no public wraps yet. Be the first!

{% endif %} + + + function share(text, url) { + const shareData = { + title: "SpotifyWrapped", + text: text, + url: url, + }; + + navigator.share(shareData); + } + {% endblock %}