From ace30f4dbdc61b84ce5b71ac09b21fe4ca05442d Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 17 Aug 2022 22:17:32 +0100 Subject: [PATCH] Add basic spotify page It currently doesn't handle pagination, or caching --- static/src/scss/_spotify.scss | 9 + static/src/scss/base.scss | 1 + website/settings.py | 3 + website/spotify/__init__.py | 0 website/spotify/client.py | 16 + website/spotify/migrations/0001_initial.py | 989 ++++++++++++++++++ website/spotify/migrations/__init__.py | 0 website/spotify/models.py | 38 + .../templates/spotify/spotify-item.html | 17 + .../templates/spotify/spotify-items.html | 3 + .../spotify/spotify_playlist_page.html | 24 + 11 files changed, 1100 insertions(+) create mode 100644 static/src/scss/_spotify.scss create mode 100644 website/spotify/__init__.py create mode 100644 website/spotify/client.py create mode 100644 website/spotify/migrations/0001_initial.py create mode 100644 website/spotify/migrations/__init__.py create mode 100644 website/spotify/models.py create mode 100644 website/spotify/templates/spotify/spotify-item.html create mode 100644 website/spotify/templates/spotify/spotify-items.html create mode 100644 website/spotify/templates/spotify/spotify_playlist_page.html diff --git a/static/src/scss/_spotify.scss b/static/src/scss/_spotify.scss new file mode 100644 index 0000000..7576d15 --- /dev/null +++ b/static/src/scss/_spotify.scss @@ -0,0 +1,9 @@ +body.page-spotifyplaylistpage { + table { + img { + min-width: 55px; + width: 55px; + height: 55px; + } + } +} diff --git a/static/src/scss/base.scss b/static/src/scss/base.scss index de610fe..0c39012 100644 --- a/static/src/scss/base.scss +++ b/static/src/scss/base.scss @@ -15,6 +15,7 @@ @import "blocks"; @import "shareon"; @import "search"; +@import "spotify"; html, body { diff --git a/website/settings.py b/website/settings.py index 916899f..0f1e515 100644 --- a/website/settings.py +++ b/website/settings.py @@ -9,6 +9,7 @@ env = environ.Env( DEBUG=(bool, False), BASE_HOSTNAME=(str, "example.com"), UNSPLASH_CLIENT_ID=(str, ""), + SPOTIFY_PROXY_HOST=(str, ""), ) # Read local secrets @@ -30,6 +31,7 @@ INSTALLED_APPS = [ "website.blog", "website.images", "website.contact", + "website.spotify", "website.contrib.code_block", "website.contrib.mermaid_block", "website.contrib.unsplash", @@ -178,6 +180,7 @@ WAGTAILEMBEDS_FINDERS = [ ] UNSPLASH_CLIENT_ID = env("UNSPLASH_CLIENT_ID") +SPOTIFY_PROXY_HOST = env("SPOTIFY_PROXY_HOST") if DEBUG: diff --git a/website/spotify/__init__.py b/website/spotify/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/spotify/client.py b/website/spotify/client.py new file mode 100644 index 0000000..ae8a3c8 --- /dev/null +++ b/website/spotify/client.py @@ -0,0 +1,16 @@ +import requests +from django.conf import settings + + +def is_valid_playlist(playlist_id: str) -> bool: + return requests.get( + f"https://{settings.SPOTIFY_PROXY_HOST}/v1/playlists/{playlist_id}" + ).ok + + +def get_playlist(playlist_id: str) -> dict: + response = requests.get( + f"https://{settings.SPOTIFY_PROXY_HOST}/v1/playlists/{playlist_id}" + ) + response.raise_for_status() + return response.json() diff --git a/website/spotify/migrations/0001_initial.py b/website/spotify/migrations/0001_initial.py new file mode 100644 index 0000000..451ff2a --- /dev/null +++ b/website/spotify/migrations/0001_initial.py @@ -0,0 +1,989 @@ +# Generated by Django 4.0.6 on 2022-08-17 20:20 + +import django.db.models.deletion +import wagtail.blocks +import wagtail.embeds.blocks +import wagtail.fields +import wagtail.images.blocks +from django.db import migrations, models + +import website.spotify.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("unsplash", "0002_unsplashphoto_created_and_more"), + ("wagtailcore", "0069_log_entry_jsonfield"), + ("images", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="SpotifyPlaylistPage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ("subtitle", models.CharField(blank=True, max_length=255)), + ( + "body", + wagtail.fields.StreamField( + [ + ("embed", wagtail.embeds.blocks.EmbedBlock()), + ( + "rich_text", + wagtail.blocks.RichTextBlock( + features=[ + "h2", + "h3", + "h4", + "h5", + "h6", + "bold", + "italic", + "ol", + "ul", + "link", + "document-link", + "code", + "strikethrough", + "snippet-link", + "snippet-embed", + ] + ), + ), + ( + "lorem", + wagtail.blocks.StructBlock( + [ + ( + "paragraphs", + wagtail.blocks.IntegerBlock(min_value=1), + ) + ] + ), + ), + ("html", wagtail.blocks.RawHTMLBlock()), + ( + "image", + wagtail.blocks.StructBlock( + [ + ( + "image", + wagtail.images.blocks.ImageChooserBlock(), + ), + ( + "caption", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "document-link", + "code", + "strikethrough", + "snippet-link", + ] + ), + ), + ] + ), + ), + ( + "code", + wagtail.blocks.StructBlock( + [ + ( + "language", + wagtail.blocks.ChoiceBlock( + choices=[ + ("ABAP", "ABAP"), + ("ABNF", "ABNF"), + ("ADL", "ADL"), + ("AMDGPU", "AMDGPU"), + ( + "ANSYS parametric design language", + "ANSYS parametric design language", + ), + ("ANTLR", "ANTLR"), + ( + "ANTLR With ActionScript Target", + "ANTLR With ActionScript Target", + ), + ( + "ANTLR With C# Target", + "ANTLR With C# Target", + ), + ( + "ANTLR With CPP Target", + "ANTLR With CPP Target", + ), + ( + "ANTLR With Java Target", + "ANTLR With Java Target", + ), + ( + "ANTLR With ObjectiveC Target", + "ANTLR With ObjectiveC Target", + ), + ( + "ANTLR With Perl Target", + "ANTLR With Perl Target", + ), + ( + "ANTLR With Python Target", + "ANTLR With Python Target", + ), + ( + "ANTLR With Ruby Target", + "ANTLR With Ruby Target", + ), + ("APL", "APL"), + ("ASCII armored", "ASCII armored"), + ("ActionScript", "ActionScript"), + ( + "ActionScript 3", + "ActionScript 3", + ), + ("Ada", "Ada"), + ("Agda", "Agda"), + ("Aheui", "Aheui"), + ("Alloy", "Alloy"), + ("AmbientTalk", "AmbientTalk"), + ("Ampl", "Ampl"), + ("Angular2", "Angular2"), + ("ApacheConf", "ApacheConf"), + ("AppleScript", "AppleScript"), + ("Arduino", "Arduino"), + ("Arrow", "Arrow"), + ("AspectJ", "AspectJ"), + ("Asymptote", "Asymptote"), + ("Augeas", "Augeas"), + ("AutoIt", "AutoIt"), + ("Awk", "Awk"), + ("BARE", "BARE"), + ("BBC Basic", "BBC Basic"), + ("BBCode", "BBCode"), + ("BC", "BC"), + ("BNF", "BNF"), + ("BST", "BST"), + ("BUGS", "BUGS"), + ("Base Makefile", "Base Makefile"), + ("Bash", "Bash"), + ("Bash Session", "Bash Session"), + ("Batchfile", "Batchfile"), + ("Bdd", "Bdd"), + ("Befunge", "Befunge"), + ("Berry", "Berry"), + ("BibTeX", "BibTeX"), + ("BlitzBasic", "BlitzBasic"), + ("BlitzMax", "BlitzMax"), + ("Boa", "Boa"), + ("Boo", "Boo"), + ("Boogie", "Boogie"), + ("Brainfuck", "Brainfuck"), + ("C", "C"), + ("C#", "C#"), + ("C++", "C++"), + ("CAmkES", "CAmkES"), + ("CBM BASIC V2", "CBM BASIC V2"), + ("CDDL", "CDDL"), + ("CFEngine3", "CFEngine3"), + ("CMake", "CMake"), + ("COBOL", "COBOL"), + ("COBOLFree", "COBOLFree"), + ("CPSA", "CPSA"), + ("CSS", "CSS"), + ( + "CSS+Django/Jinja", + "CSS+Django/Jinja", + ), + ( + "CSS+Genshi Text", + "CSS+Genshi Text", + ), + ("CSS+Lasso", "CSS+Lasso"), + ("CSS+Mako", "CSS+Mako"), + ("CSS+Myghty", "CSS+Myghty"), + ("CSS+PHP", "CSS+PHP"), + ("CSS+Ruby", "CSS+Ruby"), + ("CSS+Smarty", "CSS+Smarty"), + ("CSS+UL4", "CSS+UL4"), + ( + "CSS+mozpreproc", + "CSS+mozpreproc", + ), + ("CUDA", "CUDA"), + ("Cap'n Proto", "Cap'n Proto"), + ("CapDL", "CapDL"), + ("Ceylon", "Ceylon"), + ("ChaiScript", "ChaiScript"), + ("Chapel", "Chapel"), + ("Charmci", "Charmci"), + ("Cheetah", "Cheetah"), + ("Cirru", "Cirru"), + ("Clay", "Clay"), + ("Clean", "Clean"), + ("Clojure", "Clojure"), + ("ClojureScript", "ClojureScript"), + ("CoffeeScript", "CoffeeScript"), + ( + "Coldfusion CFC", + "Coldfusion CFC", + ), + ( + "Coldfusion HTML", + "Coldfusion HTML", + ), + ("Common Lisp", "Common Lisp"), + ( + "Component Pascal", + "Component Pascal", + ), + ("Coq", "Coq"), + ("Crmsh", "Crmsh"), + ("Croc", "Croc"), + ("Cryptol", "Cryptol"), + ("Crystal", "Crystal"), + ( + "Csound Document", + "Csound Document", + ), + ( + "Csound Orchestra", + "Csound Orchestra", + ), + ("Csound Score", "Csound Score"), + ("Cypher", "Cypher"), + ("Cython", "Cython"), + ("D", "D"), + ("DASM16", "DASM16"), + ("DTD", "DTD"), + ("Darcs Patch", "Darcs Patch"), + ("Dart", "Dart"), + ( + "Debian Control file", + "Debian Control file", + ), + ( + "Debian Sourcelist", + "Debian Sourcelist", + ), + ("Delphi", "Delphi"), + ("Devicetree", "Devicetree"), + ("Diff", "Diff"), + ("Django/Jinja", "Django/Jinja"), + ("Docker", "Docker"), + ("Duel", "Duel"), + ("Dylan", "Dylan"), + ("Dylan session", "Dylan session"), + ("DylanLID", "DylanLID"), + ("E-mail", "E-mail"), + ("EBNF", "EBNF"), + ("ECL", "ECL"), + ("ERB", "ERB"), + ("Earl Grey", "Earl Grey"), + ("Easytrieve", "Easytrieve"), + ("Eiffel", "Eiffel"), + ("Elixir", "Elixir"), + ( + "Elixir iex session", + "Elixir iex session", + ), + ("Elm", "Elm"), + ("Elpi", "Elpi"), + ("EmacsLisp", "EmacsLisp"), + ( + "Embedded Ragel", + "Embedded Ragel", + ), + ("Erlang", "Erlang"), + ( + "Erlang erl session", + "Erlang erl session", + ), + ("Evoque", "Evoque"), + ("Ezhil", "Ezhil"), + ("F#", "F#"), + ("FStar", "FStar"), + ("Factor", "Factor"), + ("Fancy", "Fancy"), + ("Fantom", "Fantom"), + ("Felix", "Felix"), + ("Fennel", "Fennel"), + ("Fish", "Fish"), + ("Flatline", "Flatline"), + ("FloScript", "FloScript"), + ("Forth", "Forth"), + ("Fortran", "Fortran"), + ("FortranFixed", "FortranFixed"), + ("FoxPro", "FoxPro"), + ("Freefem", "Freefem"), + ("Futhark", "Futhark"), + ("GAP", "GAP"), + ("GAS", "GAS"), + ("GDScript", "GDScript"), + ("GLSL", "GLSL"), + ("GSQL", "GSQL"), + ("Genshi", "Genshi"), + ("Genshi Text", "Genshi Text"), + ( + "Gettext Catalog", + "Gettext Catalog", + ), + ("Gherkin", "Gherkin"), + ("Gnuplot", "Gnuplot"), + ("Go", "Go"), + ("Golo", "Golo"), + ("GoodData-CL", "GoodData-CL"), + ("Gosu", "Gosu"), + ("Gosu Template", "Gosu Template"), + ("Graphviz", "Graphviz"), + ("Groff", "Groff"), + ("Groovy", "Groovy"), + ("HLSL", "HLSL"), + ("HSAIL", "HSAIL"), + ("HTML", "HTML"), + ( + "HTML + Angular2", + "HTML + Angular2", + ), + ("HTML+Cheetah", "HTML+Cheetah"), + ( + "HTML+Django/Jinja", + "HTML+Django/Jinja", + ), + ("HTML+Evoque", "HTML+Evoque"), + ("HTML+Genshi", "HTML+Genshi"), + ( + "HTML+Handlebars", + "HTML+Handlebars", + ), + ("HTML+Lasso", "HTML+Lasso"), + ("HTML+Mako", "HTML+Mako"), + ("HTML+Myghty", "HTML+Myghty"), + ("HTML+PHP", "HTML+PHP"), + ("HTML+Smarty", "HTML+Smarty"), + ("HTML+Twig", "HTML+Twig"), + ("HTML+UL4", "HTML+UL4"), + ("HTML+Velocity", "HTML+Velocity"), + ("HTTP", "HTTP"), + ("Haml", "Haml"), + ("Handlebars", "Handlebars"), + ("Haskell", "Haskell"), + ("Haxe", "Haxe"), + ("Hexdump", "Hexdump"), + ("Hspec", "Hspec"), + ("Hxml", "Hxml"), + ("Hy", "Hy"), + ("Hybris", "Hybris"), + ("IDL", "IDL"), + ("INI", "INI"), + ("IRC logs", "IRC logs"), + ("Icon", "Icon"), + ("Idris", "Idris"), + ("Igor", "Igor"), + ("Inform 6", "Inform 6"), + ( + "Inform 6 template", + "Inform 6 template", + ), + ("Inform 7", "Inform 7"), + ("Io", "Io"), + ("Ioke", "Ioke"), + ("Isabelle", "Isabelle"), + ("J", "J"), + ("JAGS", "JAGS"), + ("JCL", "JCL"), + ("JSGF", "JSGF"), + ("JSLT", "JSLT"), + ("JSON", "JSON"), + ("JSON-LD", "JSON-LD"), + ( + "JSONBareObject", + "JSONBareObject", + ), + ("Jasmin", "Jasmin"), + ("Java", "Java"), + ( + "Java Server Page", + "Java Server Page", + ), + ("JavaScript", "JavaScript"), + ( + "JavaScript+Cheetah", + "JavaScript+Cheetah", + ), + ( + "JavaScript+Django/Jinja", + "JavaScript+Django/Jinja", + ), + ( + "JavaScript+Genshi Text", + "JavaScript+Genshi Text", + ), + ( + "JavaScript+Lasso", + "JavaScript+Lasso", + ), + ( + "JavaScript+Mako", + "JavaScript+Mako", + ), + ( + "JavaScript+Myghty", + "JavaScript+Myghty", + ), + ( + "JavaScript+PHP", + "JavaScript+PHP", + ), + ( + "JavaScript+Ruby", + "JavaScript+Ruby", + ), + ( + "JavaScript+Smarty", + "JavaScript+Smarty", + ), + ( + "Javascript+UL4", + "Javascript+UL4", + ), + ( + "Javascript+mozpreproc", + "Javascript+mozpreproc", + ), + ("Julia", "Julia"), + ("Julia console", "Julia console"), + ("Juttle", "Juttle"), + ("K", "K"), + ("Kal", "Kal"), + ("Kconfig", "Kconfig"), + ("Kernel log", "Kernel log"), + ("Koka", "Koka"), + ("Kotlin", "Kotlin"), + ("Kuin", "Kuin"), + ("LLVM", "LLVM"), + ("LLVM-MIR", "LLVM-MIR"), + ("LLVM-MIR Body", "LLVM-MIR Body"), + ("LSL", "LSL"), + ("Lasso", "Lasso"), + ("Lean", "Lean"), + ("LessCss", "LessCss"), + ( + "Lighttpd configuration file", + "Lighttpd configuration file", + ), + ("LilyPond", "LilyPond"), + ("Limbo", "Limbo"), + ("Literate Agda", "Literate Agda"), + ( + "Literate Cryptol", + "Literate Cryptol", + ), + ( + "Literate Haskell", + "Literate Haskell", + ), + ( + "Literate Idris", + "Literate Idris", + ), + ("LiveScript", "LiveScript"), + ("Logos", "Logos"), + ("Logtalk", "Logtalk"), + ("Lua", "Lua"), + ("MAQL", "MAQL"), + ("MCFunction", "MCFunction"), + ("MIME", "MIME"), + ("MOOCode", "MOOCode"), + ("MQL", "MQL"), + ("MSDOS Session", "MSDOS Session"), + ("MXML", "MXML"), + ("Macaulay2", "Macaulay2"), + ("Makefile", "Makefile"), + ("Mako", "Mako"), + ("Markdown", "Markdown"), + ("Mask", "Mask"), + ("Mason", "Mason"), + ("Mathematica", "Mathematica"), + ("Matlab", "Matlab"), + ( + "Matlab session", + "Matlab session", + ), + ("Maxima", "Maxima"), + ("Meson", "Meson"), + ("MiniD", "MiniD"), + ("MiniScript", "MiniScript"), + ("Modelica", "Modelica"), + ("Modula-2", "Modula-2"), + ( + "MoinMoin/Trac Wiki markup", + "MoinMoin/Trac Wiki markup", + ), + ("Monkey", "Monkey"), + ("Monte", "Monte"), + ("MoonScript", "MoonScript"), + ("Mosel", "Mosel"), + ("Mscgen", "Mscgen"), + ("MuPAD", "MuPAD"), + ("MySQL", "MySQL"), + ("Myghty", "Myghty"), + ("NASM", "NASM"), + ("NCL", "NCL"), + ("NSIS", "NSIS"), + ("Nemerle", "Nemerle"), + ("NestedText", "NestedText"), + ("NewLisp", "NewLisp"), + ("Newspeak", "Newspeak"), + ( + "Nginx configuration file", + "Nginx configuration file", + ), + ("Nimrod", "Nimrod"), + ("Nit", "Nit"), + ("Nix", "Nix"), + ( + "Node.js REPL console session", + "Node.js REPL console session", + ), + ("Notmuch", "Notmuch"), + ("NuSMV", "NuSMV"), + ("NumPy", "NumPy"), + ("OCaml", "OCaml"), + ("ODIN", "ODIN"), + ( + "OMG Interface Definition Language", + "OMG Interface Definition Language", + ), + ("Objective-C", "Objective-C"), + ("Objective-C++", "Objective-C++"), + ("Objective-J", "Objective-J"), + ("Octave", "Octave"), + ("Ooc", "Ooc"), + ("Opa", "Opa"), + ("OpenEdge ABL", "OpenEdge ABL"), + ("PEG", "PEG"), + ("PHP", "PHP"), + ("PL/pgSQL", "PL/pgSQL"), + ("POVRay", "POVRay"), + ("PacmanConf", "PacmanConf"), + ("Pan", "Pan"), + ("ParaSail", "ParaSail"), + ("Pawn", "Pawn"), + ("Perl", "Perl"), + ("Perl6", "Perl6"), + ("Pig", "Pig"), + ("Pike", "Pike"), + ("PkgConfig", "PkgConfig"), + ("Pointless", "Pointless"), + ("Pony", "Pony"), + ("PostScript", "PostScript"), + ( + "PostgreSQL SQL dialect", + "PostgreSQL SQL dialect", + ), + ( + "PostgreSQL console (psql)", + "PostgreSQL console (psql)", + ), + ("PowerShell", "PowerShell"), + ( + "PowerShell Session", + "PowerShell Session", + ), + ("Praat", "Praat"), + ("Procfile", "Procfile"), + ("Prolog", "Prolog"), + ("PromQL", "PromQL"), + ("Properties", "Properties"), + ( + "Protocol Buffer", + "Protocol Buffer", + ), + ( + "PsySH console session for PHP", + "PsySH console session for PHP", + ), + ("Pug", "Pug"), + ("Puppet", "Puppet"), + ("PyPy Log", "PyPy Log"), + ("Python", "Python"), + ("Python 2.x", "Python 2.x"), + ( + "Python 2.x Traceback", + "Python 2.x Traceback", + ), + ( + "Python Traceback", + "Python Traceback", + ), + ( + "Python console session", + "Python console session", + ), + ("Python+UL4", "Python+UL4"), + ("Q", "Q"), + ("QBasic", "QBasic"), + ("QML", "QML"), + ("QVTO", "QVTO"), + ("Qlik", "Qlik"), + ("RConsole", "RConsole"), + ("REBOL", "REBOL"), + ("RHTML", "RHTML"), + ("RPMSpec", "RPMSpec"), + ("RQL", "RQL"), + ("RSL", "RSL"), + ("Racket", "Racket"), + ("Ragel", "Ragel"), + ( + "Ragel in C Host", + "Ragel in C Host", + ), + ( + "Ragel in CPP Host", + "Ragel in CPP Host", + ), + ( + "Ragel in D Host", + "Ragel in D Host", + ), + ( + "Ragel in Java Host", + "Ragel in Java Host", + ), + ( + "Ragel in Objective C Host", + "Ragel in Objective C Host", + ), + ( + "Ragel in Ruby Host", + "Ragel in Ruby Host", + ), + ( + "Raw token data", + "Raw token data", + ), + ("Rd", "Rd"), + ("ReasonML", "ReasonML"), + ("Red", "Red"), + ("Redcode", "Redcode"), + ( + "Relax-NG Compact", + "Relax-NG Compact", + ), + ( + "ResourceBundle", + "ResourceBundle", + ), + ("Rexx", "Rexx"), + ("Ride", "Ride"), + ("Rita", "Rita"), + ( + "Roboconf Graph", + "Roboconf Graph", + ), + ( + "Roboconf Instances", + "Roboconf Instances", + ), + ( + "RobotFramework", + "RobotFramework", + ), + ("Ruby", "Ruby"), + ( + "Ruby irb session", + "Ruby irb session", + ), + ("Rust", "Rust"), + ("S", "S"), + ("SARL", "SARL"), + ("SAS", "SAS"), + ("SCSS", "SCSS"), + ("SNBT", "SNBT"), + ("SPARQL", "SPARQL"), + ("SQL", "SQL"), + ("SWIG", "SWIG"), + ("Sass", "Sass"), + ("Savi", "Savi"), + ("Scala", "Scala"), + ( + "Scalate Server Page", + "Scalate Server Page", + ), + ("Scaml", "Scaml"), + ("Scheme", "Scheme"), + ("Scilab", "Scilab"), + ("Sed", "Sed"), + ("ShExC", "ShExC"), + ("Shen", "Shen"), + ("Sieve", "Sieve"), + ("Silver", "Silver"), + ("Singularity", "Singularity"), + ("Slash", "Slash"), + ("Slim", "Slim"), + ("Slurm", "Slurm"), + ("Smali", "Smali"), + ("Smalltalk", "Smalltalk"), + ( + "SmartGameFormat", + "SmartGameFormat", + ), + ("Smarty", "Smarty"), + ("Smithy", "Smithy"), + ("Snobol", "Snobol"), + ("Snowball", "Snowball"), + ("Solidity", "Solidity"), + ("Sophia", "Sophia"), + ("SourcePawn", "SourcePawn"), + ("Spice", "Spice"), + ("SquidConf", "SquidConf"), + ("Srcinfo", "Srcinfo"), + ("Stan", "Stan"), + ("Standard ML", "Standard ML"), + ("Stata", "Stata"), + ("SuperCollider", "SuperCollider"), + ("Swift", "Swift"), + ("TADS 3", "TADS 3"), + ("TAP", "TAP"), + ("TASM", "TASM"), + ("TOML", "TOML"), + ("Tal", "Tal"), + ("Tcl", "Tcl"), + ("Tcsh", "Tcsh"), + ("Tcsh Session", "Tcsh Session"), + ("TeX", "TeX"), + ("Tea", "Tea"), + ( + "Tera Term macro", + "Tera Term macro", + ), + ("Termcap", "Termcap"), + ("Terminfo", "Terminfo"), + ("Terraform", "Terraform"), + ("Text only", "Text only"), + ("Text output", "Text output"), + ("ThingsDB", "ThingsDB"), + ("Thrift", "Thrift"), + ("Todotxt", "Todotxt"), + ("TrafficScript", "TrafficScript"), + ("Transact-SQL", "Transact-SQL"), + ("Treetop", "Treetop"), + ("Turtle", "Turtle"), + ("Twig", "Twig"), + ("TypeScript", "TypeScript"), + ("TypoScript", "TypoScript"), + ( + "TypoScriptCssData", + "TypoScriptCssData", + ), + ( + "TypoScriptHtmlData", + "TypoScriptHtmlData", + ), + ( + "Typographic Number Theory", + "Typographic Number Theory", + ), + ("UL4", "UL4"), + ("USD", "USD"), + ("Unicon", "Unicon"), + ( + "Unix/Linux config files", + "Unix/Linux config files", + ), + ("UrbiScript", "UrbiScript"), + ("VB.net", "VB.net"), + ("VBScript", "VBScript"), + ("VCL", "VCL"), + ("VCLSnippets", "VCLSnippets"), + ("VCTreeStatus", "VCTreeStatus"), + ("VGL", "VGL"), + ("Vala", "Vala"), + ("Velocity", "Velocity"), + ("VimL", "VimL"), + ("WDiff", "WDiff"), + ("Web IDL", "Web IDL"), + ("WebAssembly", "WebAssembly"), + ("Whiley", "Whiley"), + ("X10", "X10"), + ("XML", "XML"), + ("XML+Cheetah", "XML+Cheetah"), + ( + "XML+Django/Jinja", + "XML+Django/Jinja", + ), + ("XML+Evoque", "XML+Evoque"), + ("XML+Lasso", "XML+Lasso"), + ("XML+Mako", "XML+Mako"), + ("XML+Myghty", "XML+Myghty"), + ("XML+PHP", "XML+PHP"), + ("XML+Ruby", "XML+Ruby"), + ("XML+Smarty", "XML+Smarty"), + ("XML+UL4", "XML+UL4"), + ("XML+Velocity", "XML+Velocity"), + ("XQuery", "XQuery"), + ("XSLT", "XSLT"), + ( + "XUL+mozpreproc", + "XUL+mozpreproc", + ), + ("Xorg", "Xorg"), + ("Xtend", "Xtend"), + ("YAML", "YAML"), + ("YAML+Jinja", "YAML+Jinja"), + ("YANG", "YANG"), + ("Zeek", "Zeek"), + ("Zephir", "Zephir"), + ("Zig", "Zig"), + ("aspx-cs", "aspx-cs"), + ("aspx-vb", "aspx-vb"), + ("autohotkey", "autohotkey"), + ("c-objdump", "c-objdump"), + ("cADL", "cADL"), + ( + "ca65 assembler", + "ca65 assembler", + ), + ("cfstatement", "cfstatement"), + ("cplint", "cplint"), + ("cpp-objdump", "cpp-objdump"), + ("d-objdump", "d-objdump"), + ("dg", "dg"), + ("eC", "eC"), + ("execline", "execline"), + ("g-code", "g-code"), + ("liquid", "liquid"), + ( + "mozhashpreproc", + "mozhashpreproc", + ), + ( + "mozpercentpreproc", + "mozpercentpreproc", + ), + ("nesC", "nesC"), + ("objdump", "objdump"), + ("objdump-nasm", "objdump-nasm"), + ( + "reStructuredText", + "reStructuredText", + ), + ("reg", "reg"), + ("scdoc", "scdoc"), + ("sqlite3con", "sqlite3con"), + ("systemverilog", "systemverilog"), + ("teal", "teal"), + ("tiddler", "tiddler"), + ("ucode", "ucode"), + ("verilog", "verilog"), + ("vhdl", "vhdl"), + ("xtlang", "xtlang"), + ] + ), + ), + ("source", wagtail.blocks.TextBlock()), + ] + ), + ), + ( + "tangent", + wagtail.blocks.StructBlock( + [ + ( + "name", + wagtail.blocks.CharBlock(max_length=64), + ), + ( + "content", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "ol", + "ul", + "link", + "document-link", + "code", + "strikethrough", + "snippet-link", + ] + ), + ), + ] + ), + ), + ( + "mermaid", + wagtail.blocks.StructBlock( + [ + ("source", wagtail.blocks.TextBlock()), + ( + "caption", + wagtail.blocks.RichTextBlock( + features=[ + "bold", + "italic", + "link", + "document-link", + "code", + "strikethrough", + "snippet-link", + ] + ), + ), + ] + ), + ), + ], + blank=True, + use_json_field=True, + ), + ), + ( + "spotify_playlist_id", + models.CharField( + max_length=32, + unique=True, + validators=[website.spotify.models.validate_playlist_id], + ), + ), + ( + "hero_image", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="images.customimage", + ), + ), + ( + "hero_unsplash_photo", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="unsplash.unsplashphoto", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + ] diff --git a/website/spotify/migrations/__init__.py b/website/spotify/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/spotify/models.py b/website/spotify/models.py new file mode 100644 index 0000000..289e25e --- /dev/null +++ b/website/spotify/models.py @@ -0,0 +1,38 @@ +from django.core.exceptions import ValidationError +from django.db import models +from django.http.request import HttpRequest +from wagtail.admin.panels import FieldPanel + +from website.common.models import BaseContentPage + +from . import client + + +def validate_playlist_id(playlist_id: str) -> None: + if not client.is_valid_playlist(playlist_id): + raise ValidationError("Unknown playlist id") + + +class SpotifyPlaylistPage(BaseContentPage): + subpage_types: list = [] + + spotify_playlist_id = models.CharField( + max_length=32, unique=True, validators=[validate_playlist_id] + ) + + content_panels = BaseContentPage.content_panels + [ + FieldPanel("spotify_playlist_id") + ] + + @property + def table_of_contents(self) -> list: + return [] + + @property + def reading_time(self) -> int: + return 0 + + def get_context(self, request: HttpRequest) -> dict: + context = super().get_context(request) + context["playlist"] = client.get_playlist(self.spotify_playlist_id) + return context diff --git a/website/spotify/templates/spotify/spotify-item.html b/website/spotify/templates/spotify/spotify-item.html new file mode 100644 index 0000000..867967e --- /dev/null +++ b/website/spotify/templates/spotify/spotify-item.html @@ -0,0 +1,17 @@ +{% with url=track.external_urls.spotify %} + + + + + + + + {% if track.preview_url %} + + {% endif %} + + {{ track.name }} + {% for artist in track.artists %}{% if not forloop.first %}, {% endif %}{{ artist.name }}{% endfor %} + {{ track.album.name }} + +{% endwith %} diff --git a/website/spotify/templates/spotify/spotify-items.html b/website/spotify/templates/spotify/spotify-items.html new file mode 100644 index 0000000..6d51644 --- /dev/null +++ b/website/spotify/templates/spotify/spotify-items.html @@ -0,0 +1,3 @@ +{% for track in tracks %} + {% include "spotify/spotify-item.html" with track=track.track %} +{% endfor %} diff --git a/website/spotify/templates/spotify/spotify_playlist_page.html b/website/spotify/templates/spotify/spotify_playlist_page.html new file mode 100644 index 0000000..10fa75d --- /dev/null +++ b/website/spotify/templates/spotify/spotify_playlist_page.html @@ -0,0 +1,24 @@ +{% extends "common/content_page.html" %} + +{% block post_content %} +
+

{{ playlist.name }}

+ + + + + + + + + + + + {% include "spotify/spotify-items.html" with tracks=playlist.tracks.items %} + +
TrackArtistAlbum
+
+
+ View on Spotify +
+{% endblock %}