commit 727cfc9a14c2c03a1254af904f00f000c7c89461 Author: Mark Hoekveen Date: Mon Feb 5 02:41:16 2024 +0100 initial commit No real output to terminal yet, but spotify api works. mostly. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..985b8fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.env +.venv/ +db.sqlite3 +*.pyc +*/__pycache__/ +*/migrations/ diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..cf27b8f --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'muzak.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/muzak/__init__.py b/muzak/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/muzak/asgi.py b/muzak/asgi.py new file mode 100644 index 0000000..daa6808 --- /dev/null +++ b/muzak/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for muzak project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'muzak.settings') + +application = get_asgi_application() diff --git a/muzak/settings.py b/muzak/settings.py new file mode 100644 index 0000000..582cd20 --- /dev/null +++ b/muzak/settings.py @@ -0,0 +1,82 @@ +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRET_KEY = 'oefjei1lwe918Alkfwuf3ionwu-@kt)6m1e)ah$&^_i9y!qffhm-a$#m6+++' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +INSTALLED_APPS = [ + 'playlist.apps.PlaylistConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'muzak.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'muzak.wsgi.application' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_TZ = True +STATIC_URL = 'static/' +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/muzak/urls.py b/muzak/urls.py new file mode 100644 index 0000000..8026509 --- /dev/null +++ b/muzak/urls.py @@ -0,0 +1,23 @@ +""" +URL configuration for muzak project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('playlist.urls')), +] diff --git a/muzak/wsgi.py b/muzak/wsgi.py new file mode 100644 index 0000000..0aba50c --- /dev/null +++ b/muzak/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for muzak project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'muzak.settings') + +application = get_wsgi_application() diff --git a/playlist/__init__.py b/playlist/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/playlist/admin.py b/playlist/admin.py new file mode 100644 index 0000000..b8b863a --- /dev/null +++ b/playlist/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from .models import Track, Artist, Album + +class TrackAdmin(admin.ModelAdmin): + pass +class ArtistAdmin(admin.ModelAdmin): + pass +class AlbumAdmin(admin.ModelAdmin): + pass + +admin.site.register(Track, TrackAdmin) +admin.site.register(Artist, ArtistAdmin) +admin.site.register(Album, AlbumAdmin) diff --git a/playlist/apps.py b/playlist/apps.py new file mode 100644 index 0000000..312db9f --- /dev/null +++ b/playlist/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PlaylistConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'playlist' diff --git a/playlist/models.py b/playlist/models.py new file mode 100644 index 0000000..e2d26c7 --- /dev/null +++ b/playlist/models.py @@ -0,0 +1,30 @@ +from django.db import models + +class Artist(models.Model): + id = models.CharField(primary_key=True, max_length=128) + name = models.CharField(max_length=255) + popularity = models.SmallIntegerField(null=True, blank=True) + genres = models.JSONField() + +class Album(models.Model): + id = models.CharField(primary_key=True, max_length=128) + name = models.CharField(max_length=255) + artists = models.ManyToManyField(Artist) + album_type = models.CharField(max_length=255) + total_tracks = models.PositiveSmallIntegerField(null=True) + image = models.ImageField(upload_to="albums/") + + +class Track(models.Model): + id = models.CharField(primary_key=True, max_length=128) + title = models.CharField(max_length=255) + artists = models.ManyToManyField(Artist) + album = models.ForeignKey(Album, null=True, on_delete=models.SET_NULL) + + explicit = models.BooleanField(default=False) + duration_ms = models.PositiveIntegerField(null=True) + popularity = models.PositiveIntegerField(null=True) + + banter = models.TextField() + +#class TrackFeatures(models.Model): diff --git a/playlist/spotify.py b/playlist/spotify.py new file mode 100644 index 0000000..cee121d --- /dev/null +++ b/playlist/spotify.py @@ -0,0 +1,45 @@ +import requests +import os +from datetime import datetime, timedelta +from dotenv import load_dotenv + +load_dotenv() + +class SpotifyFactory(): + def __init__(self): + self.token = None + self.valid_until = None + + def token_valid(self): + if self.token and self.valid_until and (datetime.now() < self.valid_until): + return True + return False + + def get_token(self): + if self.token_valid(): + print("Token still valid") + return self.token + print("New token") + url = 'https://accounts.spotify.com/api/token' + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + os.getenv('SPOTIFY_ENCODED_ID') + } + data = { + 'grant_type': 'client_credentials' + } + r = requests.post(url, headers=headers, data=data) + data = r.json() + self.token = data["access_token"] + self.valid_until = datetime.now() + timedelta(seconds=data["expires_in"]-300) + return self.token + + def get_song_info(self, spotify_id): + token = self.get_token() + url = 'https://api.spotify.com/v1/tracks/' + spotify_id + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + token + } + r = requests.get(url, headers=headers) + return r.json() diff --git a/playlist/urls.py b/playlist/urls.py new file mode 100644 index 0000000..004c626 --- /dev/null +++ b/playlist/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('tracks/', views.TrackView.as_view()), +] diff --git a/playlist/views.py b/playlist/views.py new file mode 100644 index 0000000..df67480 --- /dev/null +++ b/playlist/views.py @@ -0,0 +1,11 @@ +from django.shortcuts import render +from django.views import View +from django.http import HttpResponse +from . import models +from . import spotify + +class TrackView(View): + def get(self, request): + return HttpResponse("Hello world!") + def post(self, request): + return HttpResponse("Bye world") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0928ea4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Django +requests +pillow +python-dotenv