Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a20d1212b | |||
| 83b6ba9cc3 | |||
| 4f0d5ad7f4 | |||
| 2b81b28bff |
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "vrobbler"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
description = ""
|
||||
authors = ["Colin Powell <colin@unbl.ink>"]
|
||||
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-11 03:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('music', '0003_album_uuid_artist_uuid_track_uuid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='artist',
|
||||
options={},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='album',
|
||||
name='musicbrainz_id',
|
||||
field=models.CharField(
|
||||
blank=True, max_length=255, null=True, unique=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='track',
|
||||
name='musicbrainz_id',
|
||||
field=models.CharField(
|
||||
blank=True, max_length=255, null=True, unique=True
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='artist',
|
||||
unique_together={('name', 'musicbrainz_id')},
|
||||
),
|
||||
]
|
||||
@ -1,25 +0,0 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-09 16:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('music', '0003_album_uuid_artist_uuid_track_uuid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='track',
|
||||
name='thumbs',
|
||||
field=models.IntegerField(
|
||||
choices=[
|
||||
(-1, 'Thumbs down'),
|
||||
(0, 'No opinion'),
|
||||
(1, 'Thumbs up'),
|
||||
],
|
||||
default=0,
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -16,7 +16,7 @@ class Album(TimeStampedModel):
|
||||
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||
name = models.CharField(max_length=255)
|
||||
year = models.IntegerField(**BNULL)
|
||||
musicbrainz_id = models.CharField(max_length=255, **BNULL)
|
||||
musicbrainz_id = models.CharField(max_length=255, unique=True, **BNULL)
|
||||
musicbrainz_releasegroup_id = models.CharField(max_length=255, **BNULL)
|
||||
musicbrainz_albumartist_id = models.CharField(max_length=255, **BNULL)
|
||||
|
||||
@ -33,6 +33,9 @@ class Artist(TimeStampedModel):
|
||||
name = models.CharField(max_length=255)
|
||||
musicbrainz_id = models.CharField(max_length=255, **BNULL)
|
||||
|
||||
class Meta:
|
||||
unique_together=[['name', 'musicbrainz_id']]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@ -51,10 +54,10 @@ class Track(TimeStampedModel):
|
||||
title = models.CharField(max_length=255, **BNULL)
|
||||
artist = models.ForeignKey(Artist, on_delete=models.DO_NOTHING)
|
||||
album = models.ForeignKey(Album, on_delete=models.DO_NOTHING, **BNULL)
|
||||
musicbrainz_id = models.CharField(max_length=255, **BNULL)
|
||||
musicbrainz_id = models.CharField(max_length=255, unique=True, **BNULL)
|
||||
run_time = models.CharField(max_length=8, **BNULL)
|
||||
run_time_ticks = models.PositiveBigIntegerField(**BNULL)
|
||||
thumbs = models.IntegerField(default=Opinion.NEUTRAL, choices=Opinion.choices)
|
||||
# thumbs = models.IntegerField(default=Opinion.NEUTRAL, choices=Opinion.choices)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} by {self.artist}"
|
||||
|
||||
@ -75,7 +75,7 @@ class RecentScrobbleList(ListView):
|
||||
def get_queryset(self):
|
||||
return Scrobble.objects.filter(
|
||||
track__isnull=False, in_progress=False
|
||||
).order_by('-timestamp')[:25]
|
||||
).order_by('-timestamp')[:15]
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.1.5 on 2023-01-11 03:23
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('videos', '0004_series_uuid_video_uuid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='video',
|
||||
options={},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='video',
|
||||
unique_together={('title', 'imdb_id')},
|
||||
),
|
||||
]
|
||||
@ -58,7 +58,8 @@ class Video(TimeStampedModel):
|
||||
imdb_id = models.CharField(max_length=20, **BNULL)
|
||||
tvrage_id = models.CharField(max_length=20, **BNULL)
|
||||
|
||||
# Metadata fields from TMDB
|
||||
class Meta:
|
||||
unique_together = [['title', 'imdb_id']]
|
||||
|
||||
def __str__(self):
|
||||
if self.video_type == self.VideoType.TV_EPISODE:
|
||||
@ -107,6 +108,7 @@ class Video(TimeStampedModel):
|
||||
video_dict["season_number"] = data_dict.get("SeasonNumber", "")
|
||||
|
||||
video, created = cls.objects.get_or_create(**video_dict)
|
||||
|
||||
if created:
|
||||
logger.debug(f"Created new video: {video}")
|
||||
else:
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
<main class="col-md-4 ms-sm-auto col-lg-10 px-md-4">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
@ -18,108 +18,131 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<canvas class="my-4 w-100" id="myChart" width="900" height="380"></canvas>
|
||||
<canvas class="my-4 w-100" id="myChart" width="900" height="220"></canvas>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<p>Today <b>{{counts.today}}</b> | This Week <b>{{counts.week}}</b> | This Month <b>{{counts.month}}</b> | This Year <b>{{counts.year}}</b> | All Time <b>{{counts.alltime}}</b></p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<h2>Top artists this week</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Artist</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for artist in top_weekly_artists %}
|
||||
<tr>
|
||||
<td>{{artist.num_scrobbles}}</td>
|
||||
<td>{{artist.name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#artists-week" type="button" role="tab" aria-controls="home" aria-selected="true">Weekly Artists</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#tracks-week" type="button" role="tab" aria-controls="profile" aria-selected="false">Weekly Tracks</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="myTabContent">
|
||||
<div class="tab-pane fade show" id="artists-week" role="tabpanel" aria-labelledby="artists-week-tab">
|
||||
<h2>Top artists this week</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Artist</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for artist in top_weekly_artists %}
|
||||
<tr>
|
||||
<td>{{artist.num_scrobbles}}</td>
|
||||
<td>{{artist.name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade show active" id="tracks-week" role="tabpanel" aria-labelledby="tracks-week-tab">
|
||||
<h2>Top tracks this week</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Track</th>
|
||||
<th scope="col">Artist</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for track in top_weekly_tracks %}
|
||||
<tr>
|
||||
<td>{{track.num_scrobbles}}</td>
|
||||
<td>{{track.title}}</td>
|
||||
<td>{{track.artist.name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg">
|
||||
<h2>Top tracks this week</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<div class="col-md">
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#latest-listened" type="button" role="tab" aria-controls="home" aria-selected="true">Latest Listened</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-watched" type="button" role="tab" aria-controls="profile" aria-selected="false">Latest Watched</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="myTabContent2">
|
||||
<div class="tab-pane fade show active" id="latest-listened" role="tabpanel" aria-labelledby="latest-listened-tab">
|
||||
<h2>Latest listened</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Time</th>
|
||||
<th scope="col">Track</th>
|
||||
<th scope="col">Artist</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for track in top_weekly_tracks %}
|
||||
<tr>
|
||||
<td>{{track.num_scrobbles}}</td>
|
||||
<td>{{track.title}}</td>
|
||||
<td>{{track.artist.name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for scrobble in object_list %}
|
||||
<tr>
|
||||
<td>{{scrobble.timestamp|naturaltime}}</td>
|
||||
<td>{{scrobble.track.title}}</td>
|
||||
<td>{{scrobble.track.artist.name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<h2>Latest listened</h2>
|
||||
<p>Today <b>{{counts.today}}</b> | This Week <b>{{counts.week}}</b> | This Month <b>{{counts.month}}</b> | This Year <b>{{counts.year}}</b> | All Time <b>{{counts.alltime}}</b></p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Time</th>
|
||||
<th scope="col">Track</th>
|
||||
<th scope="col">Artist</th>
|
||||
<th scope="col">Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for scrobble in object_list %}
|
||||
<tr>
|
||||
<td>{{scrobble.timestamp|naturaltime}}</td>
|
||||
<td>{{scrobble.track.title}}</td>
|
||||
<td>{{scrobble.track.artist.name}}</td>
|
||||
<td>{{scrobble.source}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg">
|
||||
<h2>Latest watched</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Time</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Series</th>
|
||||
<th scope="col">Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for scrobble in video_scrobble_list %}
|
||||
<tr>
|
||||
<td>{{scrobble.timestamp|naturaltime}}</td>
|
||||
<td>{% if scrobble.video.tv_series %}E{{scrobble.video.season_number}}S{{scrobble.video.season_number}} -{% endif %} {{scrobble.video.title}}</td>
|
||||
<td>{% if scrobble.video.tv_series %}{{scrobble.video.tv_series}}{% endif %}</td>
|
||||
<td>{{scrobble.source}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="tab-pane fade show" id="latest-watched" role="tabpanel" aria-labelledby="latest-watched-tab">
|
||||
<h2>Latest watched</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Time</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Series</th>
|
||||
<th scope="col">Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for scrobble in video_scrobble_list %}
|
||||
<tr>
|
||||
<td>{{scrobble.timestamp|naturaltime}}</td>
|
||||
<td>{% if scrobble.video.tv_series %}E{{scrobble.video.season_number}}S{{scrobble.video.season_number}} -{% endif %} {{scrobble.video.title}}</td>
|
||||
<td>{% if scrobble.video.tv_series %}{{scrobble.video.tv_series}}{% endif %}</td>
|
||||
<td>{{scrobble.source}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user