Skip to content

Commit

Permalink
Use explicit through table for media/decision many-to-many field
Browse files Browse the repository at this point in the history
  • Loading branch information
AetherUnbound committed May 17, 2024
1 parent 30f1df1 commit 55a88c5
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 19 deletions.
54 changes: 54 additions & 0 deletions api/api/migrations/0062_decision_through_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Generated by Django 4.2.11 on 2024-05-10 19:30

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


class Migration(migrations.Migration):

dependencies = [
('api', '0061_convert_varchar_to_text'),
]

operations = [
migrations.CreateModel(
name='ImageDecisionThrough',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('decision', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.imagedecision')),
('media_obj', models.ForeignKey(db_column='identifier', on_delete=django.db.models.deletion.CASCADE, to='api.image', to_field='identifier')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='AudioDecisionThrough',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('decision', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.audiodecision')),
('media_obj', models.ForeignKey(db_column='identifier', on_delete=django.db.models.deletion.CASCADE, to='api.audio', to_field='identifier')),
],
options={
'abstract': False,
},
),
migrations.RemoveField(
model_name='audiodecision',
name='media_objs',
),
migrations.AddField(
model_name='audiodecision',
name='media_objs',
field=models.ManyToManyField(help_text='The audio items being moderated.', through='api.AudioDecisionThrough', to='api.audio'),
),
migrations.RemoveField(
model_name='imagedecision',
name='media_objs',
),
migrations.AddField(
model_name='imagedecision',
name='media_objs',
field=models.ManyToManyField(help_text='The image items being moderated.', through='api.ImageDecisionThrough', to='api.image'),
),
]
20 changes: 19 additions & 1 deletion api/api/models/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
AbstractDeletedMedia,
AbstractMedia,
AbstractMediaDecision,
AbstractMediaDecisionThrough,
AbstractMediaList,
AbstractMediaReport,
AbstractSensitiveMedia,
Expand Down Expand Up @@ -342,11 +343,28 @@ class AudioDecision(AbstractMediaDecision):

media_objs = models.ManyToManyField(
to="Audio",
db_constraint=False,
through="AudioDecisionThrough",
help_text="The audio items being moderated.",
)


class AudioDecisionThrough(AbstractMediaDecisionThrough):
"""
Many-to-many reference table for audio decisions.
This is made explicit (rather than using Django's default) so that the audio can
be referenced by `identifier` rather than an arbitrary `id`.
"""

media_obj = models.ForeignKey(
Audio,
to_field="identifier",
on_delete=models.CASCADE,
db_column="identifier",
)
decision = models.ForeignKey(AudioDecision, on_delete=models.CASCADE)


class AudioList(AbstractMediaList):
"""A list of audio files. Currently unused."""

Expand Down
20 changes: 19 additions & 1 deletion api/api/models/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
AbstractDeletedMedia,
AbstractMedia,
AbstractMediaDecision,
AbstractMediaDecisionThrough,
AbstractMediaList,
AbstractMediaReport,
AbstractSensitiveMedia,
Expand Down Expand Up @@ -145,11 +146,28 @@ class ImageDecision(AbstractMediaDecision):

media_objs = models.ManyToManyField(
to="Image",
db_constraint=False,
through="ImageDecisionThrough",
help_text="The image items being moderated.",
)


class ImageDecisionThrough(AbstractMediaDecisionThrough):
"""
Many-to-many reference table for image decisions.
This is made explicit (rather than using Django's default) so that the image can
be referenced by `identifier` rather than an arbitrary `id`.
"""

media_obj = models.ForeignKey(
Image,
to_field="identifier",
on_delete=models.CASCADE,
db_column="identifier",
)
decision = models.ForeignKey(ImageDecision, on_delete=models.CASCADE)


class ImageList(AbstractMediaList):
"""A list of images. Currently unused."""

Expand Down
25 changes: 24 additions & 1 deletion api/api/models/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ class AbstractMediaDecision(OpenLedgerModel):

media_objs = models.ManyToManyField(
to="AbstractMedia",
db_constraint=False,
through="AbstractMediaDecisionThrough",
help_text="The media items being moderated.",
)
"""
Expand All @@ -332,6 +332,29 @@ class Meta:
# TODO: Implement ``clean`` and ``save``, if needed.


class AbstractMediaDecisionThrough(models.Model):
"""
Generic model for the many-to-many reference table between media and decisions.
This is made explicit (rather than using Django's default) so that the media can
be referenced by `identifier` rather than an arbitrary `id`.
"""

media_class: type[models.Model] = None
"""the model class associated with this media type e.g. ``Image`` or ``Audio``"""

media_obj = models.ForeignKey(
AbstractMedia,
to_field="identifier",
on_delete=models.CASCADE,
db_column="identifier",
)
decision = models.ForeignKey(AbstractMediaDecision, on_delete=models.CASCADE)

class Meta:
abstract = True


class PerformIndexUpdateMixin:
@property
def indexes(self):
Expand Down
2 changes: 1 addition & 1 deletion api/latest_migrations/api
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# If you have a merge conflict in this file, it means you need to run:
# manage.py makemigrations --merge
# in order to resolve the conflict between migrations.
0061_convert_varchar_to_text
0062_decision_through_tables
52 changes: 37 additions & 15 deletions documentation/meta/media_properties/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ value). Note that relation fields are always nullable.

### Relations

| Name | Type | DB type | Nature | To |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | ------------ | ---------------- |
| [`audio_report`](#Audio-audio_report-notes) | [`ForeignKey`](https://docs.djangoproject.com/en/stable/ref/models/fields/#foreignkey) | `uuid` | One To Many | `AudioReport` |
| [`audiodecision`](#Audio-audiodecision-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `AudioDecision` |
| [`audioset`](#Audio-audioset-notes) | `ForeignObject` | | Many To One | `AudioSet` |
| [`deleted_audio`](#Audio-deleted_audio-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `DeletedAudio` |
| [`lists`](#Audio-lists-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `AudioList` |
| [`sensitive_audio`](#Audio-sensitive_audio-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `SensitiveAudio` |
| Name | Type | DB type | Nature | To |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | ------------ | ---------------------- |
| [`audio_report`](#Audio-audio_report-notes) | [`ForeignKey`](https://docs.djangoproject.com/en/stable/ref/models/fields/#foreignkey) | `uuid` | One To Many | `AudioReport` |
| [`audiodecision`](#Audio-audiodecision-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `AudioDecision` |
| [`audiodecisionthrough`](#Audio-audiodecisionthrough-notes) | [`ForeignKey`](https://docs.djangoproject.com/en/stable/ref/models/fields/#foreignkey) | `uuid` | One To Many | `AudioDecisionThrough` |
| [`audioset`](#Audio-audioset-notes) | `ForeignObject` | | Many To One | `AudioSet` |
| [`deleted_audio`](#Audio-deleted_audio-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `DeletedAudio` |
| [`lists`](#Audio-lists-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `AudioList` |
| [`sensitive_audio`](#Audio-sensitive_audio-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `SensitiveAudio` |

### Values

Expand Down Expand Up @@ -109,6 +110,16 @@ report.

**`AudioDecision` docstring:** Moderation decisions taken for audio tracks.

(Audio-audiodecisionthrough-notes)=

#### `audiodecisionthrough`

**`AudioDecisionThrough` docstring:** Many-to-many reference table for audio
decisions.

This is made explicit (rather than using Django's default) so that the audio can
be referenced by `identifier` rather than an arbitrary `id`.

(Audio-audioset-notes)=

#### `audioset`
Expand Down Expand Up @@ -323,13 +334,14 @@ Note that only `name` and `accuracy` are presently surfaced in API results.

### Relations

| Name | Type | DB type | Nature | To |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | ------------ | ---------------- |
| [`deleted_image`](#Image-deleted_image-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `DeletedImage` |
| [`image_report`](#Image-image_report-notes) | [`ForeignKey`](https://docs.djangoproject.com/en/stable/ref/models/fields/#foreignkey) | `uuid` | One To Many | `ImageReport` |
| [`imagedecision`](#Image-imagedecision-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `ImageDecision` |
| [`lists`](#Image-lists-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `ImageList` |
| [`sensitive_image`](#Image-sensitive_image-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `SensitiveImage` |
| Name | Type | DB type | Nature | To |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | ------------ | ---------------------- |
| [`deleted_image`](#Image-deleted_image-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `DeletedImage` |
| [`image_report`](#Image-image_report-notes) | [`ForeignKey`](https://docs.djangoproject.com/en/stable/ref/models/fields/#foreignkey) | `uuid` | One To Many | `ImageReport` |
| [`imagedecision`](#Image-imagedecision-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `ImageDecision` |
| [`imagedecisionthrough`](#Image-imagedecisionthrough-notes) | [`ForeignKey`](https://docs.djangoproject.com/en/stable/ref/models/fields/#foreignkey) | `uuid` | One To Many | `ImageDecisionThrough` |
| [`lists`](#Image-lists-notes) | [`ManyToManyField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield) | | Many To Many | `ImageList` |
| [`sensitive_image`](#Image-sensitive_image-notes) | [`OneToOneField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#onetoonefield) | `uuid` | One To One | `SensitiveImage` |

### Values

Expand Down Expand Up @@ -449,6 +461,16 @@ this report.

**`ImageDecision` docstring:** Moderation decisions taken for images.

(Image-imagedecisionthrough-notes)=

#### `imagedecisionthrough`

**`ImageDecisionThrough` docstring:** Many-to-many reference table for image
decisions.

This is made explicit (rather than using Django's default) so that the image can
be referenced by `identifier` rather than an arbitrary `id`.

(Image-last_synced_with_source-notes)=

#### `last_synced_with_source`
Expand Down

0 comments on commit 55a88c5

Please sign in to comment.