kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
See #170: store and compute modification date on artists
rodzic
b5297150f0
commit
1654044a9f
|
@ -28,10 +28,17 @@ class ChannelFilter(moderation_filters.HiddenContentFilterSet):
|
||||||
subscribed = django_filters.BooleanFilter(
|
subscribed = django_filters.BooleanFilter(
|
||||||
field_name="_", method="filter_subscribed"
|
field_name="_", method="filter_subscribed"
|
||||||
)
|
)
|
||||||
|
ordering = django_filters.OrderingFilter(
|
||||||
|
# tuple-mapping retains order
|
||||||
|
fields=(
|
||||||
|
("creation_date", "creation_date"),
|
||||||
|
("artist__modification_date", "modification_date"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Channel
|
model = models.Channel
|
||||||
fields = ["q", "scope", "tag", "subscribed"]
|
fields = ["q", "scope", "tag", "subscribed", "ordering"]
|
||||||
hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["CHANNEL"]
|
hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["CHANNEL"]
|
||||||
|
|
||||||
def filter_subscribed(self, queryset, name, value):
|
def filter_subscribed(self, queryset, name, value):
|
||||||
|
|
|
@ -384,7 +384,12 @@ def get_channel_from_rss_url(url, raise_exception=False):
|
||||||
library=channel.library,
|
library=channel.library,
|
||||||
delete_existing=True,
|
delete_existing=True,
|
||||||
)
|
)
|
||||||
|
latest_upload_date = max([upload.creation_date for upload in uploads])
|
||||||
|
if (
|
||||||
|
not channel.artist.modification_date
|
||||||
|
or channel.artist.modification_date < latest_upload_date
|
||||||
|
):
|
||||||
|
common_utils.update_modification_date(channel.artist)
|
||||||
return channel, uploads
|
return channel, uploads
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ class RelatedField(serializers.RelatedField):
|
||||||
self.display_value(item),
|
self.display_value(item),
|
||||||
)
|
)
|
||||||
for item in queryset
|
for item in queryset
|
||||||
|
if self.serializer
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
|
|
||||||
|
@ -14,6 +16,7 @@ from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django import urls
|
from django import urls
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -405,3 +408,17 @@ def get_mimetype_from_ext(path):
|
||||||
def get_audio_mimetype(mt):
|
def get_audio_mimetype(mt):
|
||||||
aliases = {"audio/x-mp3": "audio/mpeg", "audio/mpeg3": "audio/mpeg"}
|
aliases = {"audio/x-mp3": "audio/mpeg", "audio/mpeg3": "audio/mpeg"}
|
||||||
return aliases.get(mt, mt)
|
return aliases.get(mt, mt)
|
||||||
|
|
||||||
|
|
||||||
|
def update_modification_date(obj, field="modification_date"):
|
||||||
|
IGNORE_DELAY = 60
|
||||||
|
current_value = getattr(obj, field)
|
||||||
|
now = timezone.now()
|
||||||
|
ignore = current_value is not None and current_value < now - datetime.timedelta(
|
||||||
|
seconds=IGNORE_DELAY
|
||||||
|
)
|
||||||
|
if ignore:
|
||||||
|
setattr(obj, field, now)
|
||||||
|
obj.__class__.objects.filter(pk=obj.pk).update(**{field: now})
|
||||||
|
|
||||||
|
return now
|
||||||
|
|
|
@ -5,7 +5,7 @@ from . import models
|
||||||
|
|
||||||
@admin.register(models.Artist)
|
@admin.register(models.Artist)
|
||||||
class ArtistAdmin(admin.ModelAdmin):
|
class ArtistAdmin(admin.ModelAdmin):
|
||||||
list_display = ["name", "mbid", "creation_date"]
|
list_display = ["name", "mbid", "creation_date", "modification_date"]
|
||||||
search_fields = ["name", "mbid"]
|
search_fields = ["name", "mbid"]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 3.0.4 on 2020-03-19 12:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('music', '0050_auto_20200129_1344'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='album',
|
||||||
|
name='cover',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='artist',
|
||||||
|
name='modification_date',
|
||||||
|
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='upload',
|
||||||
|
name='import_status',
|
||||||
|
field=models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending'), ('finished', 'Finished'), ('errored', 'Errored'), ('skipped', 'Skipped')], default='pending', max_length=25),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='uploadversion',
|
||||||
|
name='mimetype',
|
||||||
|
field=models.CharField(choices=[('audio/mpeg3', 'mp3'), ('audio/x-mp3', 'mp3'), ('audio/mpeg', 'mp3'), ('video/ogg', 'ogg'), ('audio/ogg', 'ogg'), ('audio/opus', 'opus'), ('audio/x-m4a', 'aac'), ('audio/x-m4a', 'm4a'), ('audio/x-flac', 'flac'), ('audio/flac', 'flac')], max_length=50),
|
||||||
|
),
|
||||||
|
]
|
|
@ -251,7 +251,7 @@ class Artist(APIModelMixin):
|
||||||
choices=ARTIST_CONTENT_CATEGORY_CHOICES,
|
choices=ARTIST_CONTENT_CATEGORY_CHOICES,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
modification_date = models.DateTimeField(default=timezone.now, db_index=True)
|
||||||
api = musicbrainz.api.artists
|
api = musicbrainz.api.artists
|
||||||
objects = ArtistQuerySet.as_manager()
|
objects = ArtistQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ def serialize_artist_simple(artist):
|
||||||
"mbid": str(artist.mbid),
|
"mbid": str(artist.mbid),
|
||||||
"name": artist.name,
|
"name": artist.name,
|
||||||
"creation_date": DATETIME_FIELD.to_representation(artist.creation_date),
|
"creation_date": DATETIME_FIELD.to_representation(artist.creation_date),
|
||||||
|
"modification_date": DATETIME_FIELD.to_representation(artist.modification_date),
|
||||||
"is_local": artist.is_local,
|
"is_local": artist.is_local,
|
||||||
"content_category": artist.content_category,
|
"content_category": artist.content_category,
|
||||||
}
|
}
|
||||||
|
|
|
@ -289,6 +289,8 @@ def process_upload(upload, update_denormalization=True):
|
||||||
"bitrate",
|
"bitrate",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
if channel:
|
||||||
|
common_utils.update_modification_date(channel.artist)
|
||||||
|
|
||||||
if update_denormalization:
|
if update_denormalization:
|
||||||
models.TrackActor.create_entries(
|
models.TrackActor.create_entries(
|
||||||
|
|
|
@ -129,7 +129,7 @@ class ArtistViewSet(
|
||||||
required_scope = "libraries"
|
required_scope = "libraries"
|
||||||
anonymous_policy = "setting"
|
anonymous_policy = "setting"
|
||||||
filterset_class = filters.ArtistFilter
|
filterset_class = filters.ArtistFilter
|
||||||
ordering_fields = ("id", "name", "creation_date")
|
ordering_fields = ("id", "name", "creation_date", "modification_date")
|
||||||
|
|
||||||
fetches = federation_decorators.fetches_route()
|
fetches = federation_decorators.fetches_route()
|
||||||
mutations = common_decorators.mutations_route(types=["update"])
|
mutations = common_decorators.mutations_route(types=["update"])
|
||||||
|
@ -186,7 +186,12 @@ class AlbumViewSet(
|
||||||
permission_classes = [oauth_permissions.ScopePermission]
|
permission_classes = [oauth_permissions.ScopePermission]
|
||||||
required_scope = "libraries"
|
required_scope = "libraries"
|
||||||
anonymous_policy = "setting"
|
anonymous_policy = "setting"
|
||||||
ordering_fields = ("creation_date", "release_date", "title")
|
ordering_fields = (
|
||||||
|
"creation_date",
|
||||||
|
"release_date",
|
||||||
|
"title",
|
||||||
|
"artist__modification_date",
|
||||||
|
)
|
||||||
filterset_class = filters.AlbumFilter
|
filterset_class = filters.AlbumFilter
|
||||||
|
|
||||||
fetches = federation_decorators.fetches_route()
|
fetches = federation_decorators.fetches_route()
|
||||||
|
@ -335,6 +340,7 @@ class TrackViewSet(
|
||||||
"position",
|
"position",
|
||||||
"disc_number",
|
"disc_number",
|
||||||
"artist__name",
|
"artist__name",
|
||||||
|
"artist__modification_date",
|
||||||
)
|
)
|
||||||
fetches = federation_decorators.fetches_route()
|
fetches = federation_decorators.fetches_route()
|
||||||
mutations = common_decorators.mutations_route(types=["update"])
|
mutations = common_decorators.mutations_route(types=["update"])
|
||||||
|
|
|
@ -834,9 +834,9 @@ def test_get_channel_from_rss_url(db, r_mock, mocker):
|
||||||
</rss>
|
</rss>
|
||||||
"""
|
"""
|
||||||
parsed_feed = feedparser.parse(xml_payload)
|
parsed_feed = feedparser.parse(xml_payload)
|
||||||
|
|
||||||
r_mock.get(rss_url, text=xml_payload)
|
r_mock.get(rss_url, text=xml_payload)
|
||||||
|
|
||||||
|
update_modification_date = mocker.spy(common_utils, "update_modification_date")
|
||||||
feed_init = mocker.spy(serializers.RssFeedSerializer, "__init__")
|
feed_init = mocker.spy(serializers.RssFeedSerializer, "__init__")
|
||||||
feed_save = mocker.spy(serializers.RssFeedSerializer, "save")
|
feed_save = mocker.spy(serializers.RssFeedSerializer, "save")
|
||||||
item_init = mocker.spy(serializers.RssFeedItemSerializer, "__init__")
|
item_init = mocker.spy(serializers.RssFeedItemSerializer, "__init__")
|
||||||
|
@ -865,6 +865,7 @@ def test_get_channel_from_rss_url(db, r_mock, mocker):
|
||||||
library=channel.library,
|
library=channel.library,
|
||||||
delete_existing=True,
|
delete_existing=True,
|
||||||
)
|
)
|
||||||
|
update_modification_date.assert_called_once_with(channel.artist)
|
||||||
|
|
||||||
|
|
||||||
def test_get_channel_from_rss_honor_mrf_inbox_before_http(
|
def test_get_channel_from_rss_honor_mrf_inbox_before_http(
|
||||||
|
|
|
@ -6,6 +6,7 @@ import uuid
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from funkwhale_api.common import utils as common_utils
|
||||||
from funkwhale_api.federation import serializers as federation_serializers
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.federation import jsonld
|
from funkwhale_api.federation import jsonld
|
||||||
from funkwhale_api.federation import utils as federation_utils
|
from funkwhale_api.federation import utils as federation_utils
|
||||||
|
@ -1040,6 +1041,8 @@ def test_process_channel_upload_forces_artist_and_attributed_to(
|
||||||
factories, mocker, faker
|
factories, mocker, faker
|
||||||
):
|
):
|
||||||
channel = factories["audio.Channel"](attributed_to__local=True)
|
channel = factories["audio.Channel"](attributed_to__local=True)
|
||||||
|
update_modification_date = mocker.spy(common_utils, "update_modification_date")
|
||||||
|
|
||||||
attachment = factories["common.Attachment"](actor=channel.attributed_to)
|
attachment = factories["common.Attachment"](actor=channel.attributed_to)
|
||||||
import_metadata = {
|
import_metadata = {
|
||||||
"title": "Real title",
|
"title": "Real title",
|
||||||
|
@ -1081,6 +1084,8 @@ def test_process_channel_upload_forces_artist_and_attributed_to(
|
||||||
assert upload.track.attributed_to == channel.attributed_to
|
assert upload.track.attributed_to == channel.attributed_to
|
||||||
assert upload.track.attachment_cover == attachment
|
assert upload.track.attachment_cover == attachment
|
||||||
|
|
||||||
|
update_modification_date.assert_called_once_with(channel.artist)
|
||||||
|
|
||||||
|
|
||||||
def test_process_upload_uses_import_metadata_if_valid(factories, mocker):
|
def test_process_upload_uses_import_metadata_if_valid(factories, mocker):
|
||||||
track = factories["music.Track"]()
|
track = factories["music.Track"]()
|
||||||
|
|
Ładowanie…
Reference in New Issue