Skip to content

Commit

Permalink
mature -> sensitive code changes for the API (#3769)
Browse files Browse the repository at this point in the history
* Make database table name explicit for "mature" tables

* Rename mature to sensitive

* Use the sensitive source field in the serializer

Also correctly reference mature vs sensitive depending on what type of object is being handed into the serializer
  • Loading branch information
AetherUnbound authored Feb 28, 2024
1 parent 2cffcb9 commit d242940
Show file tree
Hide file tree
Showing 16 changed files with 141 additions and 71 deletions.
4 changes: 2 additions & 2 deletions api/api/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from api.admin.site import openverse_admin
from api.models import PENDING, Audio, AudioReport, ContentProvider, Image, ImageReport
from api.models.media import AbstractDeletedMedia, AbstractMatureMedia
from api.models.media import AbstractDeletedMedia, AbstractSensitiveMedia


admin.site = openverse_admin
Expand Down Expand Up @@ -72,7 +72,7 @@ def has_add_permission(self, *args, **kwargs):


for klass in [
*AbstractMatureMedia.__subclasses__(),
*AbstractSensitiveMedia.__subclasses__(),
*AbstractDeletedMedia.__subclasses__(),
]:
admin.site.register(klass, MediaSubreportAdmin)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.9 on 2024-02-08 01:52

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('api', '0054_throttledapplication_post_logout_redirect_uris'),
]

operations = [
migrations.AlterModelTable(
name='matureaudio',
table='api_matureaudio',
),
migrations.AlterModelTable(
name='matureimage',
table='api_matureimage',
),
]
27 changes: 27 additions & 0 deletions api/api/migrations/0056_rename_mature_to_sensitive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 4.2.9 on 2024-02-08 18:26

import api.models.media
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('api', '0055_alter_matureaudio_table_alter_matureimage_table'),
]

operations = [
migrations.RenameModel('MatureImage', 'SensitiveImage'),
migrations.RenameModel('MatureAudio', 'SensitiveAudio'),
migrations.AlterField(
model_name='sensitiveaudio',
name='media_obj',
field=models.OneToOneField(db_column='identifier', db_constraint=False, help_text='The reference to the sensitive audio.', on_delete=django.db.models.deletion.DO_NOTHING, primary_key=True, related_name='sensitive_audio', serialize=False, to='api.audio', to_field='identifier'),
),
migrations.AlterField(
model_name='sensitiveimage',
name='media_obj',
field=models.OneToOneField(db_column='identifier', db_constraint=False, help_text='The reference to the sensitive image.', on_delete=django.db.models.deletion.DO_NOTHING, primary_key=True, related_name='sensitive_image', serialize=False, to='api.image', to_field='identifier'),
),
]
4 changes: 2 additions & 2 deletions api/api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
AudioReport,
AudioSet,
DeletedAudio,
MatureAudio,
SensitiveAudio,
)
from api.models.image import DeletedImage, Image, ImageList, ImageReport, MatureImage
from api.models.image import DeletedImage, Image, ImageList, ImageReport, SensitiveImage
from api.models.media import (
DEINDEXED,
DMCA,
Expand Down
13 changes: 7 additions & 6 deletions api/api/models/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
from api.models.media import (
AbstractAltFile,
AbstractDeletedMedia,
AbstractMatureMedia,
AbstractMedia,
AbstractMediaList,
AbstractMediaReport,
AbstractSensitiveMedia,
)
from api.models.mixins import FileMixin, ForeignIdentifierMixin, MediaMixin
from api.utils.waveform import generate_peaks
Expand Down Expand Up @@ -189,8 +189,8 @@ class Audio(AudioFileMixin, AbstractMedia):
)

@property
def mature(self) -> bool:
return hasattr(self, "mature_audio")
def sensitive(self) -> bool:
return hasattr(self, "sensitive_audio")

@property
def alternative_files(self):
Expand Down Expand Up @@ -260,7 +260,7 @@ class Meta:
verbose_name_plural = "Deleted audio"


class MatureAudio(AbstractMatureMedia):
class SensitiveAudio(AbstractSensitiveMedia):
"""
Stores all audio tracks that have been flagged as 'mature'.
Expand All @@ -278,17 +278,18 @@ class MatureAudio(AbstractMatureMedia):
primary_key=True,
db_constraint=False,
db_column="identifier",
related_name="mature_audio",
related_name="sensitive_audio",
help_text="The reference to the sensitive audio.",
)

class Meta:
db_table = "api_matureaudio"
verbose_name_plural = "Mature audio"


class AudioReport(AbstractMediaReport):
media_class = Audio
mature_class = MatureAudio
sensitive_class = SensitiveAudio
deleted_class = DeletedAudio

media_obj = models.ForeignKey(
Expand Down
15 changes: 9 additions & 6 deletions api/api/models/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from api.constants.media_types import IMAGE_TYPE
from api.models.media import (
AbstractDeletedMedia,
AbstractMatureMedia,
AbstractMedia,
AbstractMediaList,
AbstractMediaReport,
AbstractSensitiveMedia,
)
from api.models.mixins import FileMixin

Expand Down Expand Up @@ -53,8 +53,8 @@ class Meta(AbstractMedia.Meta):
db_table = "image"

@property
def mature(self) -> bool:
return hasattr(self, "mature_image")
def sensitive(self) -> bool:
return hasattr(self, "sensitive_image")


class DeletedImage(AbstractDeletedMedia):
Expand All @@ -80,7 +80,7 @@ class DeletedImage(AbstractDeletedMedia):
)


class MatureImage(AbstractMatureMedia):
class SensitiveImage(AbstractSensitiveMedia):
"""
Stores all images that have been flagged as 'mature'.
Expand All @@ -98,14 +98,17 @@ class MatureImage(AbstractMatureMedia):
primary_key=True,
db_constraint=False,
db_column="identifier",
related_name="mature_image",
related_name="sensitive_image",
help_text="The reference to the sensitive image.",
)

class Meta:
db_table = "api_matureimage"


class ImageReport(AbstractMediaReport):
media_class = Image
mature_class = MatureImage
sensitive_class = SensitiveImage
deleted_class = DeletedImage

media_obj = models.ForeignKey(
Expand Down
24 changes: 12 additions & 12 deletions api/api/models/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ class AbstractMediaReport(models.Model):
"""
Generic model from which to inherit all reported media classes.
'Reported' here refers to content reports such as mature, copyright-violating or
deleted content. Subclasses must populate ``media_class``, ``mature_class`` and
'Reported' here refers to content reports such as sensitive, copyright-violating or
deleted content. Subclasses must populate ``media_class``, ``sensitive_class`` and
``deleted_class`` fields.
"""

media_class: type[models.Model] = None
"""the model class associated with this media type e.g. ``Image`` or ``Audio``"""
mature_class: type[models.Model] = None
"""the class storing mature media e.g. ``MatureImage`` or ``MatureAudio``"""
sensitive_class: type[models.Model] = None
"""the class storing sensitive media e.g. ``SensitiveImage`` or ``SensitiveAudio``"""
deleted_class: type[models.Model] = None
"""the class storing deleted media e.g. ``DeletedImage`` or ``DeletedAudio``"""

Expand Down Expand Up @@ -213,18 +213,18 @@ def save(self, *args, **kwargs):
Extend the built-in ``save()`` functionality of Django with Elasticsearch
integration to update records and refresh indices.
Media marked as mature or deleted also leads to instantiation of their
corresponding mature or deleted classes.
Media marked as sensitive or deleted also leads to instantiation of their
corresponding sensitive or deleted classes.
"""

self.clean()

super().save(*args, **kwargs)

if self.status == MATURE_FILTERED:
# Create an instance of the mature class for this media. This will
# Create an instance of the sensitive class for this media. This will
# automatically set the ``mature`` field in the ES document.
self.mature_class.objects.create(media_obj=self.media_obj)
self.sensitive_class.objects.create(media_obj=self.media_obj)
elif self.status == DEINDEXED:
# Create an instance of the deleted class for this media, so that we don't
# reindex it later. This will automatically delete the ES document and the
Expand Down Expand Up @@ -290,7 +290,7 @@ class AbstractDeletedMedia(PerformIndexUpdateMixin, OpenLedgerModel):
Generic model from which to inherit all deleted media classes.
'Deleted' here refers to media which has been deleted at the source or intentionally
de-indexed by us. Unlike mature reports, this action is irreversible. Subclasses
de-indexed by us. Unlike sensitive reports, this action is irreversible. Subclasses
must populate ``media_class`` and ``es_index`` fields.
"""

Expand Down Expand Up @@ -329,9 +329,9 @@ def save(self, *args, **kwargs):
self.media_obj.delete() # remove the actual model instance


class AbstractMatureMedia(PerformIndexUpdateMixin, models.Model):
class AbstractSensitiveMedia(PerformIndexUpdateMixin, models.Model):
"""
Generic model from which to inherit all mature media classes.
Generic model from which to inherit all sensitive media classes.
Subclasses must populate ``media_class`` and ``es_index`` fields.
"""
Expand All @@ -350,7 +350,7 @@ class AbstractMatureMedia(PerformIndexUpdateMixin, models.Model):
primary_key=True,
db_constraint=False,
db_column="identifier",
related_name="mature_abstract_media",
related_name="sensitive_abstract_media",
help_text="The reference to the sensitive media.",
)
"""
Expand Down
17 changes: 14 additions & 3 deletions api/api/serializers/media_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ class Meta:

mature = serializers.BooleanField(
help_text="Whether the media item is marked as mature",
source="sensitive",
)

# This should be promoted to a stable field alongside
Expand All @@ -540,9 +541,9 @@ def get_unstable__sensitivity(self, obj: Hit | AbstractMedia) -> list[str]:
):
result.append(sensitivity.TEXT)

# ``obj.mature`` will either be `mature` from the ES document
# or the ``mature`` property on the Image or Audio model.
if obj.mature:
# ``obj.sensitive`` will either be `mature` from the ES document (see below)
# or the ``sensitive`` property on the Image or Audio model.
if obj.sensitive:
# We do not currently have any documents marked `mature=true`
# that were not marked so as a result of a confirmed user report.
# This is despite the fact that the ingestion server _does_ copy
Expand All @@ -569,6 +570,16 @@ def get_unstable__sensitivity(self, obj: Hit | AbstractMedia) -> list[str]:
return result

def to_representation(self, *args, **kwargs):
# This serializer adapts both ES Hits *and* Media instances. Currently,
# ES has a `mature` field on it which represents if maturity was present on
# the record in the database. The attributes in the code have been renamed
# to `sensitive`, but for the time being this flag still exists on the ES index.
# In order to prevent failures in serialization (since the serializer is looking
# for the `sensitive` attribute), we rename it here.
obj = args[0]
if isinstance(obj, Hit):
obj.sensitive = obj.mature

output = super().to_representation(*args, **kwargs)

# Ensure lists are ``[]`` instead of ``None``
Expand Down
2 changes: 1 addition & 1 deletion api/api/views/audio_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class AudioViewSet(MediaViewSet):
collection_serializer_class = AudioCollectionRequestSerializer

def get_queryset(self):
return super().get_queryset().select_related("mature_audio", "audioset")
return super().get_queryset().select_related("sensitive_audio", "audioset")

# Extra actions
@creator_collection
Expand Down
2 changes: 1 addition & 1 deletion api/api/views/image_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class ImageViewSet(MediaViewSet):
}

def get_queryset(self):
return super().get_queryset().select_related("mature_image")
return super().get_queryset().select_related("sensitive_image")

# Extra actions
@creator_collection
Expand Down
4 changes: 2 additions & 2 deletions api/test/factory/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
AudioAddOnFactory,
AudioFactory,
AudioReportFactory,
MatureAudioFactory,
SensitiveAudioFactory,
)
from test.factory.models.image import (
ImageFactory,
ImageReportFactory,
MatureImageFactory,
SensitiveImageFactory,
)
from test.factory.models.oauth2 import (
AccessTokenFactory,
Expand Down
8 changes: 4 additions & 4 deletions api/test/factory/models/audio.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import factory
from factory.django import DjangoModelFactory

from api.models.audio import Audio, AudioAddOn, AudioReport, MatureAudio
from api.models.audio import Audio, AudioAddOn, AudioReport, SensitiveAudio
from test.factory.faker import Faker
from test.factory.models.media import IdentifierFactory, MediaFactory


class MatureAudioFactory(DjangoModelFactory):
class SensitiveAudioFactory(DjangoModelFactory):
class Meta:
model = MatureAudio
model = SensitiveAudio

media_obj = factory.SubFactory("test.factory.models.audio.AudioFactory")


class AudioFactory(MediaFactory):
_mature_factory = MatureAudioFactory
_sensitive_factory = SensitiveAudioFactory

class Meta:
model = Audio
Expand Down
8 changes: 4 additions & 4 deletions api/test/factory/models/image.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import factory
from factory.django import DjangoModelFactory

from api.models.image import Image, ImageReport, MatureImage
from api.models.image import Image, ImageReport, SensitiveImage
from test.factory.models.media import MediaFactory, MediaReportFactory


class MatureImageFactory(DjangoModelFactory):
class SensitiveImageFactory(DjangoModelFactory):
class Meta:
model = MatureImage
model = SensitiveImage

media_obj = factory.SubFactory("test.factory.models.image.ImageFactory")


class ImageFactory(MediaFactory):
_mature_factory = MatureImageFactory
_sensitive_factory = SensitiveImageFactory

class Meta:
model = Image
Expand Down
6 changes: 3 additions & 3 deletions api/test/factory/models/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class MediaFactory(DjangoModelFactory):
)

# Sub-factories must set this to their corresponding
# ``AbstractMatureMedia`` subclass
_mature_factory = None
# ``AbstractSensitiveMedia`` subclass
_sensitive_factory = None

_highest_pre_existing_pk = None

Expand Down Expand Up @@ -126,7 +126,7 @@ def create(cls, *args, **kwargs) -> AbstractMedia | tuple[AbstractMedia, Hit]:
hit = None

if mature_reported:
cls._mature_factory.create(media_obj=model)
cls._sensitive_factory.create(media_obj=model)

if pook_active:
# Reactivate pook if it was active
Expand Down
Loading

0 comments on commit d242940

Please sign in to comment.