Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#8} For you page #24

Merged
merged 14 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/django.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Django CI

on: [push]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.12.4

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Apply migrations
run: |
python manage.py migrate
- name: Run tests
run: |
python manage.py test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ env/
__pycache__/
*.py[cod]
db.sqlite3
media/audio_files/
4 changes: 2 additions & 2 deletions audioapp/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@


class AudioappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'audioapp'
default_auto_field = "django.db.models.BigAutoField"
name = "audioapp"
6 changes: 4 additions & 2 deletions audioapp/forms.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from django import forms
from .models import AudioFile, Comment


class AudioFileForm(forms.ModelForm):
class Meta:
model = AudioFile
fields = ['title', 'description', 'file']
fields = ["title", "description", "file"]


class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
fields = ["text"]
Empty file added audioapp/management/__init__.py
Empty file.
Empty file.
47 changes: 47 additions & 0 deletions audioapp/management/commands/seed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from audioapp.models import AudioFile
from django.core.files import File
import os
import random


class Command(BaseCommand):
help = "Seed the database with initial data"

def handle(self, *args, **options):
self.stdout.write(self.style.SUCCESS("Deleting old data..."))

# Delete all existing users and audio files
User.objects.all().delete()
AudioFile.objects.all().delete()

self.stdout.write(self.style.SUCCESS("Seeding data..."))

# Create users
users = []
for i in range(10):
user = User.objects.create_user(
username=f"user{i}", email=f"user{i}@example.com", password="password"
)
users.append(user)

# Get the sample .mp3 file
sample_audio_path = os.path.join("audioapp", "sample_audio", "baby_shark.mp3")
if not os.path.exists(sample_audio_path):
self.stdout.write(self.style.ERROR("Sample audio file not found."))
return

# Create audio files
for i in range(50):
user = random.choice(users)
audio_file = AudioFile(
user=user,
title=f"Audio Title {i}",
description=f"This is a description for audio {i}.",
)
# Use the sample .mp3 file
with open(sample_audio_path, "rb") as f:
audio_file.file.save(f"audio{i}.mp3", File(f), save=True)

self.stdout.write(self.style.SUCCESS("Database seeded successfully!"))
28 changes: 21 additions & 7 deletions audioapp/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,28 @@ class Migration(migrations.Migration):

operations = [
migrations.CreateModel(
name='AudioFile',
name="AudioFile",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('description', models.CharField(max_length=250)),
('file', models.FileField(upload_to='audio_files/')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=100)),
("description", models.CharField(max_length=250)),
("file", models.FileField(upload_to="audio_files/")),
("uploaded_at", models.DateTimeField(auto_now_add=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
32 changes: 26 additions & 6 deletions audioapp/migrations/0002_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,40 @@
class Migration(migrations.Migration):

dependencies = [
('audioapp', '0001_initial'),
("audioapp", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Like',
name="Like",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('audio_file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='audioapp.audiofile')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"audio_file",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="audioapp.audiofile",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
'unique_together': {('user', 'audio_file')},
"unique_together": {("user", "audio_file")},
},
),
]
35 changes: 28 additions & 7 deletions audioapp/migrations/0003_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,40 @@
class Migration(migrations.Migration):

dependencies = [
('audioapp', '0002_like'),
("audioapp", "0002_like"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Comment',
name="Comment",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('audio_file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='audioapp.audiofile')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("text", models.TextField()),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"audio_file",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="audioapp.audiofile",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
11 changes: 8 additions & 3 deletions audioapp/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from django.db import models
from django.contrib.auth.models import User


class AudioFile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
description = models.CharField(max_length=250)
file = models.FileField(upload_to='audio_files/')
file = models.FileField(upload_to="audio_files/")
uploaded_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
Expand All @@ -14,16 +15,20 @@ def __str__(self):
def like_count(self):
return self.like_set.count()


class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
audio_file = models.ForeignKey(AudioFile, on_delete=models.CASCADE)

class Meta:
unique_together = ('user', 'audio_file')
unique_together = ("user", "audio_file")


class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
audio_file = models.ForeignKey(AudioFile, related_name='comments', on_delete=models.CASCADE)
audio_file = models.ForeignKey(
AudioFile, related_name="comments", on_delete=models.CASCADE
)
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)

Expand Down
Empty file.
Binary file added audioapp/sample_audio/test.mp3
Binary file not shown.
117 changes: 117 additions & 0 deletions audioapp/templates/audioapp/for_you.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{% extends 'base.html' %}

{% block content %}
<h1>For You</h1>
{% if audio_file %}
<h2 id="audio-title">{{ audio_file.title }}</h2>
<p id="audio-description">{{ audio_file.description }}</p>
<p id="audio-like-count">{{ audio_file.like_count }} Likes</p>

<button id="like-button" data-audio-id="{{ audio_file.id }}" data-action="like">
Like
</button>

<p id="audio-username">By {{ audio_file.user.username }}</p>
<audio controls id="audio-player" autoplay>
<source id="audio-source" src="{{ audio_file.file.url }}" type="audio/mp4">
Your browser does not support the audio element.
</audio>
<button id="previous-button" {% if fyp_index <= 0 %}disabled{% endif %}>Previous</button>
<button id="next-button" {% if reached_end %}disabled{% endif %}>Next</button>
<label>
<input type="checkbox" id="autoplay-checkbox" {% if autoplay %}checked{% endif %}> Autoplay
</label>

<form id="comment-form" method="post" action="{% url 'for_you' %}">
{% csrf_token %}
{{ comment_form.as_p }}
<button type="submit" name="action" value="comment">Add Comment</button>
</form>
<ul id="comments-list">
{% for comment in comments %}
<li>{{ comment.user.username }}: {{ comment.text }}</li>
{% endfor %}
</ul>
{% else %}
<p>No audio files available.</p>
{% endif %}

<script>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you or ChatGPT write this JS code? If ChatGPT wrote it, I think it would be a good exercise to go through it and describe what it is doing in detail in a comment. I think that would be beneficial because it is important that you understand this code in case ChatGPT is not 100% right and also this is meant to be a learning experience and using ChatGPT is not really learning in my opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first, I tried to use ChatGPT to do it all at once but so many things broke and debugging was a nightmare because I didn't understand how anything worked. After that, I restarted and went through and added each part of the page one by one and debugged until it worked. This took me a while and I used ChatGPT to debug but I feel like by the time I got likes working and updating I had a pretty decent understanding of what was going on.

Description:

getAudio function:
This is called every time you need to use a GET method. It talks to the server and updates everything on the page. The action param is used in the url so it knows which get method it should request. The line xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); sets the header which is later used in my views so it knows to give a JsonResponse with updated data. If the get method was successful it goes through and updates every element with corresponding data that came from the xhr response. It then disables the next or previous buttons if they are at the beginning or end. Additionally, It makes the audio player play if the autoplay box is checked. For comments, it deletes everything marked comments list and makes another comments list using the comments from xhr response.

postAudio:
It basically does the same thing as getAudio but for POST methods. The main difference is that a formData param is passed which is sent here:xhr.send(formData); I was thinking about combing getAudio and postAudio but didn't get to it today.

After that, there are a bunch of event listeners that call getAudio or postAudio with their necessary params.

The autoplay-checkbox event listener is the only one that doesn't call postAudio or getAudio because no fields on the screen need to be updated. It sends get update_autoplay which is a method that saves the current state of the checkbox in the session.

function sendAudioRequest(action, method = 'GET', formData = null) {
var xhr = new XMLHttpRequest();
xhr.open(method, '{% url "for_you" %}?action=' + action, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
if (method === 'POST') {
xhr.setRequestHeader('X-CSRFToken', '{{ csrf_token }}');
}
xhr.onload = function () {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
document.getElementById('audio-title').textContent = data.title;
document.getElementById('audio-description').textContent = data.description;
document.getElementById('audio-like-count').textContent = data.like_count + ' Likes';
document.getElementById('audio-username').textContent = 'By ' + data.username;
document.getElementById('audio-source').src = data.audio_file_url;
var audioPlayer = document.getElementById('audio-player');
audioPlayer.load();
if (document.getElementById('autoplay-checkbox').checked) {
audioPlayer.play();
}
document.getElementById('previous-button').disabled = data.fyp_index <= 0;
document.getElementById('next-button').disabled = data.reached_end;
if (data.reached_end) {
document.getElementById('autoplay-checkbox').checked = false;
}

var commentsList = document.getElementById('comments-list');
commentsList.innerHTML = '';
data.comments.forEach(function(comment) {
var li = document.createElement('li');
li.textContent = comment.user + ': ' + comment.text;
commentsList.appendChild(li);
});
}
};
xhr.send(formData);
}

document.getElementById('previous-button').addEventListener('click', function(event) {
event.preventDefault();
sendAudioRequest('previous');
});

document.getElementById('next-button').addEventListener('click', function(event) {
event.preventDefault();
sendAudioRequest('next');
});

document.getElementById('audio-player').addEventListener('ended', function() {
if (document.getElementById('autoplay-checkbox').checked) {
sendAudioRequest('next');
}
});

document.getElementById('like-button').addEventListener('click', function(event) {
event.preventDefault();
var formData = new FormData();
formData.append('action', 'like');
sendAudioRequest('like', 'POST', formData);
});

document.getElementById('comment-form').addEventListener('submit', function(event) {
event.preventDefault();
var formData = new FormData(this);
formData.append('action', 'comment');
sendAudioRequest('comment', 'POST', formData);
});

document.getElementById('autoplay-checkbox').addEventListener('change', function() {
var isChecked = this.checked;
var xhr = new XMLHttpRequest();
xhr.open('GET', '{% url "update_autoplay" %}?autoplay=' + isChecked, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.send();
});
</script>
{% endblock %}

Loading
Loading