if not "userid" in session:
return redirect("/login") # Must be logged in to edit
+ userid = session["userid"]
+
error = validate_song_form()
if not error:
- userid = session["userid"]
if "songid" in request.args:
error = update_song()
else:
if not error:
username = session["username"]
app.logger.info(f"{username} uploaded/modified a song")
- return redirect(f"/users/{username}")
+ if "songid" in request.args:
+ # After editing an existing song, go back to song page
+ return redirect(f"/song/{userid}/{request.args['songid']}?action=view")
+ else:
+ # After creating a new song, go back to profile
+ return redirect(f"/users/{username}")
else:
username = session["username"]
app.logger.info(f"{session['username']} deleted song: {song_data['title']}")
flash_and_log(f"Deleted '{song_data['title']}'", "success")
- return redirect(request.referrer)
+ return redirect(f"/users/{session['username']}")
@app.get("/song/<int:userid>/<int:songid>")
def song(userid, songid):
gap: 10px;
}
-div.song {
+.song-list .song {
box-shadow: 0px 0px 5px 0px;
border-radius: 10px;
}
flex-direction: column;
gap: 15px;
align-items: left;
+}
+
+.song .song-details {
margin: 10px;
}
{% if session["userid"] == userid -%}
<p class="playlist-actions">
-<button class="button" onclick="showPlaylistEditor()">Edit</button>
-<a href="/delete-playlist/{{ playlistid }}" class="button" onclick="return confirm('Are you sure you want to delete this playlist?')">Delete</a>
+<button class="song-list-button" onclick="showPlaylistEditor()" title="Edit"><img class="lsp_btn_edit02" /></button>
+<a href="/delete-playlist/{{ playlistid }}" class="song-list-button" onclick="return confirm('Are you sure you want to delete this playlist?')" title="Delete"><img class="lsp_btn_delete02" /></a>
</p>
<script>
{% if session["userid"] == userid %}
<div class="profile-action">
- <button class="button" onclick="showEditForm()" id="profile-bio-edit-btn">Edit Profile</button>
+ <button class="song-list-button" onclick="showEditForm()" id="profile-bio-edit-btn" title="Edit Profile"><img class="lsp_btn_edit02" /></button>
</div>
<form id="profile-edit-form" action="/edit-profile" method="post" enctype="multipart/form-data" hidden>
<!-- Add Playlist button/form -->
{% if session["userid"] == userid -%}
<div class="profile-action">
- <button type="button" class="button" id="add-playlist-button" onclick="showAddPlaylist()">Add Playlist</button>
+ <button type="button" class="song-list-button" id="add-playlist-button" onclick="showAddPlaylist()" title="Add Playlist"><img class="lsp_btn_add02" /></button>
<form action="/create-playlist" method="post" id="create-playlist-form" hidden>
<label for="name">Playlist Name</label><br>
<input name="name" type="text" maxlength="100" /><br>
<!-- Add Song button -->
{% if session["userid"] == userid %}
<div class="profile-action">
- <a class="button" href="/edit-song">Add Song</a>
+ <a class="song-list-button" href="/edit-song" title="Add Song"><img class="lsp_btn_add02" /></a>
</div>
{% endif %}
+{% from "song-macros.html" import song_info, song_details %}
+
<div class="song-list">
{% for song in songs %}
<div class="song" data-song="{{ song.json() }}">
{%- endif %}
</div>
- <div class="song-info">
- <!-- Song Title -->
- <div class="song-title"><a href="/song/{{ song.userid }}/{{ song.songid }}?action=view">{{ song.title }}</a></div>
-
- <!-- Separator -->
- <div class="song-info-sep">
- -
- </div>
+ {{ song_info(song) | indent(12) }}
- <!-- Song Artist(s) -->
- <div class="song-artist">
- <a href="/users/{{ song.username }}" class="profile-link">{{ song.username }}</a>
-
- <!-- Song Collaborators -->
- {% for collab in song.collaborators %}
- {% if collab.startswith("@") -%}
- <a href="/users/{{ collab[1:] }}" class="profile-link">{{ collab[1:] }}</a>
- {%- else -%}
- <span class="collab-name">{{ collab }}</span>
- {%- endif %}
- {% endfor %}
- </div>
- </div>
<div class="song-buttons">
-
- {% if session["userid"] == song.userid and is_profile_song_list -%}
- <!-- Owner-Specific Buttons (Edit/Delete) -->
- <a href="/edit-song?songid={{ song.songid }}" class="song-list-button">
- <img class="lsp_btn_edit02" alt="Edit">
- </a>
- <a href="/delete-song/{{ song.songid }}" onclick="return confirm("Are you sure you want to delete this song?")" class="song-list-button">
- <img class="lsp_btn_delete02" alt="Delete">
- </a>
- {%- endif %}
-
<!-- Details Button -->
- <button onclick="return showDetails(event)" class="song-list-button details-toggle">
+ <button onclick="return showDetails(event)" class="song-list-button details-toggle" title="Toggle Details">
<img class="lsp_btn_show02" alt="Show Details">
</button>
<!-- Play Button -->
- <button onclick="return play(event)" class="song-list-button">
+ <button onclick="return play(event)" class="song-list-button" title="Play">
<img class="lsp_btn_play02" alt="Play">
</button>
</div>
</div>
-
- <div class="song-details" {% if request.endpoint != 'song' %}hidden{% endif %}>
- {% if current_user_playlists -%}
- <!-- Add to Playlist Buttons -->
- <div class="song-playlist-controls">
- <form action="/append-to-playlist" method="post">
- <input type="hidden" name="songid" value="{{ song.songid }}" id="playlist-selector-songid"/>
- <select name="playlistid" onchange="this.closest('form').requestSubmit()">
- <option value="-1">Add to Playlist...</option>
- {% for plist in current_user_playlists -%}
- <option value="{{ plist.playlistid }}">{{ plist['name'] }}</option>
- {%- endfor %}
- </select>
- </form>
- </div>
- {%- endif %}
-
- {% if song.description -%}
- <!-- Song Description -->
- <div class="song-description">{{ (song.description.replace("\n", "<br>"))|safe }}</div>
- {%- endif %}
-
- {% if song.tags -%}
- <!-- Song Tags -->
- <div class="song-tags">
- Tags:
- {% for tag in song.tags %}
- <a href="/songs?user={{ song.username }}&tag={{ tag }}">{{ tag }}</a>
- {% endfor %}
- </div>
- {%- endif %}
-
- <div class="song-date">
- Uploaded {{ song.created }}
- </div>
-
- <!-- Song Comments -->
- <div class="song-comments">
- Comments:<br>
- {% if session['userid'] %}
- <a href="/comment?songid={{ song.songid }}">Add a Comment</a>
- {% endif %}
-
- {% for comment in song.get_comments() %}
- <div class="top-level-comment">
-
- <a href="/users/{{ comment['username'] }}" class="profile-link">{{ comment['username'] }}</a>:
- {{ (comment['content'].replace("\n", "<br>"))|safe }}
-
- {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
- <div class="comment-button-container">
- {% endif %}
-
- <!-- Only commenter can edit comment -->
- {% if session['userid'] == comment['userid'] %}
- <a href="/comment?commentid={{ comment['commentid'] }}&songid={{ song.songid }}" class="comment-button">
- Edit
- </a>
- {% endif %}
-
- <!-- Commenter and song owner can delete comment -->
- {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
- <a href="/delete-comment/{{ comment['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="comment-button">
- Delete
- </a>
- {% endif %}
-
- {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
- </div>
- {% endif %}
-
- {% for reply in comment['replies'] %}
- <div class="reply-comment">
-
- <a href="/users/{{ reply['username'] }}" class="profile-link">{{ reply['username'] }}</a>:
- {{ reply['content'] }}
-
- {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
- <div class="comment-button-container">
- {% endif %}
-
- <!-- Only commenter can edit comment -->
- {% if session['userid'] == reply['userid'] %}
- <a href="/comment?commentid={{ reply['commentid'] }}&songid={{ song.songid }}&replytoid={{ comment['commentid'] }}" class="comment-button">
- Edit
- </a>
- {% endif %}
-
- <!-- Commenter and song owner can delete comment -->
- {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
- <a href="/delete-comment/{{ reply['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="comment-button">
- Delete
- </a>
- {% endif %}
-
- {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
- </div>
- {% endif %}
- </div>
- {% endfor %}
-
- <div class="comment-button-container">
- <a href="/comment?songid={{ song.songid }}&replytoid={{ comment['commentid'] }}">Reply</a>
- </div>
- </div>
- {% endfor %}
- </div>
- </div>
+ {{ song_details(song, current_user_playlists) | indent(8) }}
</div>
{% endfor %}
</div>
--- /dev/null
+{% macro song_artist(song) %}
+<span class="song-artist">
+ <a href="/users/{{ song.username }}" class="profile-link">{{ song.username }}</a>
+
+ <!-- Song Collaborators -->
+ {% for collab in song.collaborators %}
+ {% if collab.startswith("@") -%}
+ <a href="/users/{{ collab[1:] }}" class="profile-link">{{ collab[1:] }}</a>
+ {%- else -%}
+ <span class="collab-name">{{ collab }}</span>
+ {%- endif %}
+ {% endfor %}
+</span>
+{% endmacro %}
+
+{% macro song_info(song) %}
+<div class="song-info">
+ <!-- Song Title -->
+ <div class="song-title"><a href="/song/{{ song.userid }}/{{ song.songid }}?action=view">{{ song.title }}</a></div>
+
+ <!-- Separator -->
+ <div class="song-info-sep">
+ -
+ </div>
+
+ <!-- Song Artist(s) -->
+ {{ song_artist(song) | indent(4) }}
+</div>
+{% endmacro %}
+
+{% macro song_details(song, current_user_playlists, hidden=True) %}
+<div class="song-details" {% if hidden %}hidden{% endif %}>
+ {% if current_user_playlists -%}
+ <!-- Add to Playlist Buttons -->
+ <div class="song-playlist-controls">
+ <form action="/append-to-playlist" method="post">
+ <input type="hidden" name="songid" value="{{ song.songid }}" id="playlist-selector-songid"/>
+ <select name="playlistid" onchange="this.closest('form').requestSubmit()">
+ <option value="-1">Add to Playlist...</option>
+ {% for plist in current_user_playlists -%}
+ <option value="{{ plist.playlistid }}">{{ plist['name'] }}</option>
+ {%- endfor %}
+ </select>
+ </form>
+ </div>
+ {%- endif %}
+
+ {% if song.description -%}
+ <!-- Song Description -->
+ <div class="song-description">{{ (song.description.replace("\n", "<br>"))|safe }}</div>
+ {%- endif %}
+
+ {% if song.tags -%}
+ <!-- Song Tags -->
+ <div class="song-tags">
+ Tags:
+ {% for tag in song.tags %}
+ <a href="/songs?user={{ song.username }}&tag={{ tag }}">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ {%- endif %}
+
+ <div class="song-date">
+ Uploaded {{ song.created }}
+ </div>
+
+ <!-- Song Comments -->
+ <div class="song-comments">
+ Comments:<br>
+ {% if session['userid'] %}
+ <a href="/comment?songid={{ song.songid }}" class="song-list-button" title="Add a Comment"><img class="lsp_btn_add02" /></a>
+ {% endif %}
+
+ {% for comment in song.get_comments() %}
+ <div class="top-level-comment">
+
+ <a href="/users/{{ comment['username'] }}" class="profile-link">{{ comment['username'] }}</a>:
+ {{ (comment['content'].replace("\n", "<br>"))|safe }}
+
+ {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
+ <div class="comment-button-container">
+ {% endif %}
+
+ <!-- Only commenter can edit comment -->
+ {% if session['userid'] == comment['userid'] %}
+ <a href="/comment?commentid={{ comment['commentid'] }}&songid={{ song.songid }}" class="comment-button">
+ Edit
+ </a>
+ {% endif %}
+
+ <!-- Commenter and song owner can delete comment -->
+ {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
+ <a href="/delete-comment/{{ comment['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="comment-button">
+ Delete
+ </a>
+ {% endif %}
+
+ {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
+ </div>
+ {% endif %}
+
+ {% for reply in comment['replies'] %}
+ <div class="reply-comment">
+
+ <a href="/users/{{ reply['username'] }}" class="profile-link">{{ reply['username'] }}</a>:
+ {{ reply['content'] }}
+
+ {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
+ <div class="comment-button-container">
+ {% endif %}
+
+ <!-- Only commenter can edit comment -->
+ {% if session['userid'] == reply['userid'] %}
+ <a href="/comment?commentid={{ reply['commentid'] }}&songid={{ song.songid }}&replytoid={{ comment['commentid'] }}" class="comment-button">
+ Edit
+ </a>
+ {% endif %}
+
+ <!-- Commenter and song owner can delete comment -->
+ {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
+ <a href="/delete-comment/{{ reply['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="comment-button">
+ Delete
+ </a>
+ {% endif %}
+
+ {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
+ </div>
+ {% endif %}
+ </div>
+ {% endfor %}
+
+ <div class="comment-button-container">
+ <a href="/comment?songid={{ song.songid }}&replytoid={{ comment['commentid'] }}">Reply</a>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+</div>
+{% endmacro %}
{% extends "base.html" %}
+{% from "song-macros.html" import song_artist, song_details %}
{% block head %}
<meta property="og:title" content="{{ song.title }}" />
{% block body %}
-{% include "song-list.html" %}
+<h1>{{ song.title }}</h1>
+
+<p>Song by {{ song_artist(song) }}</p>
+
+<p class="song-actions">
+<!-- Play Button -->
+<span class="song" data-song="{{ song.json() }}">
+ <button onclick="return play(event)" class="song-list-button" title="Play">
+ <img class="lsp_btn_play02" alt="Play">
+ </button>
+</span>
+{% if session["userid"] == song.userid -%}
+<a href="/edit-song?songid={{ song.songid }}" class="song-list-button" title="Edit"><img class="lsp_btn_edit02" /></a>
+<a href="/delete-song/{{ song.songid }}" class="song-list-button" onclick="return confirm('Are you sure you want to delete this song?')" title="Delete"><img class="lsp_btn_delete02" /></a>
+{%- endif %}
+</p>
+
+{{ song_details(song, current_user_playlists, hidden=False) }}
{% endblock %}
_create_user_and_song(client)
response = client.get("/delete-song/1")
assert response.status_code == 302
- assert response.headers["Location"] == "None"
+ assert response.headers["Location"] == "/users/user"
response = client.get("/")
assert b"Deleted 'song title'" in response.data
_create_user_song_and_comment(client, "comment text here")
response = client.get("/delete-song/1")
assert response.status_code == 302
- assert response.headers["Location"] == "None" # No previous page, use homepage
+ assert response.headers["Location"] == "/users/user"
response = client.get("/song/1/1?action=view")
assert response.status_code == 404 # Song deleted
NOW
SOON
+- Hide playlists & songs while editing profile
+- Use edit/delete icons in comments
- Break up main.py, test_offline.py
- Pinned profile playlists
- Image support in comments, descriptions, bios, etc.