diff --git a/api/api/migrations/0061_convert_varchar_to_text.py b/api/api/migrations/0061_convert_varchar_to_text.py new file mode 100644 index 00000000000..cd907a4bcb7 --- /dev/null +++ b/api/api/migrations/0061_convert_varchar_to_text.py @@ -0,0 +1,124 @@ +# Generated by Django 4.2.11 on 2024-05-10 22:07 + +import api.models.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0060_fill_out_help_text'), + ] + + operations = [ + migrations.AlterField( + model_name='audio', + name='audio_set_foreign_identifier', + field=models.TextField(blank=True, help_text='Reference to set of which this track is a part.', null=True), + ), + migrations.AlterField( + model_name='audio', + name='creator', + field=models.TextField(blank=True, help_text='The name of the media creator.', null=True), + ), + migrations.AlterField( + model_name='audio', + name='creator_url', + field=api.models.fields.URLTextField(blank=True, help_text='A direct link to the media creator.', max_length=2000, null=True), + ), + migrations.AlterField( + model_name='audio', + name='foreign_identifier', + field=models.TextField(blank=True, db_index=True, help_text='The identifier provided by the upstream source.', null=True), + ), + migrations.AlterField( + model_name='audio', + name='foreign_landing_url', + field=api.models.fields.URLTextField(blank=True, help_text='The landing page of the work.', null=True), + ), + migrations.AlterField( + model_name='audio', + name='thumbnail', + field=api.models.fields.URLTextField(blank=True, help_text='The thumbnail for the media.', null=True), + ), + migrations.AlterField( + model_name='audio', + name='title', + field=models.TextField(blank=True, help_text='The name of the media.', null=True), + ), + migrations.AlterField( + model_name='audio', + name='url', + field=api.models.fields.URLTextField(blank=True, help_text='The actual URL to the media file.', max_length=1000, null=True, unique=True), + ), + migrations.AlterField( + model_name='audioset', + name='creator', + field=models.TextField(blank=True, help_text='The name of the media creator.', null=True), + ), + migrations.AlterField( + model_name='audioset', + name='creator_url', + field=api.models.fields.URLTextField(blank=True, help_text='A direct link to the media creator.', max_length=2000, null=True), + ), + migrations.AlterField( + model_name='audioset', + name='foreign_identifier', + field=models.TextField(blank=True, db_index=True, help_text='The identifier provided by the upstream source.', null=True), + ), + migrations.AlterField( + model_name='audioset', + name='foreign_landing_url', + field=api.models.fields.URLTextField(blank=True, help_text='The landing page of the work.', null=True), + ), + migrations.AlterField( + model_name='audioset', + name='thumbnail', + field=api.models.fields.URLTextField(blank=True, help_text='The thumbnail for the media.', null=True), + ), + migrations.AlterField( + model_name='audioset', + name='title', + field=models.TextField(blank=True, help_text='The name of the media.', null=True), + ), + migrations.AlterField( + model_name='audioset', + name='url', + field=api.models.fields.URLTextField(blank=True, help_text='The actual URL to the media file.', max_length=1000, null=True, unique=True), + ), + migrations.AlterField( + model_name='image', + name='creator', + field=models.TextField(blank=True, help_text='The name of the media creator.', null=True), + ), + migrations.AlterField( + model_name='image', + name='creator_url', + field=api.models.fields.URLTextField(blank=True, help_text='A direct link to the media creator.', max_length=2000, null=True), + ), + migrations.AlterField( + model_name='image', + name='foreign_identifier', + field=models.TextField(blank=True, db_index=True, help_text='The identifier provided by the upstream source.', null=True), + ), + migrations.AlterField( + model_name='image', + name='foreign_landing_url', + field=api.models.fields.URLTextField(blank=True, help_text='The landing page of the work.', null=True), + ), + migrations.AlterField( + model_name='image', + name='thumbnail', + field=api.models.fields.URLTextField(blank=True, help_text='The thumbnail for the media.', null=True), + ), + migrations.AlterField( + model_name='image', + name='title', + field=models.TextField(blank=True, help_text='The name of the media.', null=True), + ), + migrations.AlterField( + model_name='image', + name='url', + field=api.models.fields.URLTextField(blank=True, help_text='The actual URL to the media file.', max_length=1000, null=True, unique=True), + ), + ] diff --git a/api/api/models/audio.py b/api/api/models/audio.py index 1c41e5633d5..0ae06fe9d5e 100644 --- a/api/api/models/audio.py +++ b/api/api/models/audio.py @@ -163,8 +163,7 @@ class Audio(AudioFileMixin, AbstractMedia): ) # Replaces the foreign key to AudioSet - audio_set_foreign_identifier = models.CharField( - max_length=1000, + audio_set_foreign_identifier = models.TextField( blank=True, null=True, help_text="Reference to set of which this track is a part.", diff --git a/api/api/models/fields.py b/api/api/models/fields.py new file mode 100644 index 00000000000..208d262362a --- /dev/null +++ b/api/api/models/fields.py @@ -0,0 +1,21 @@ +from django import forms +from django.core import validators +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class URLTextField(models.TextField): + """URL field which uses the underlying Postgres TEXT column type.""" + + default_validators = [validators.URLValidator()] + description = _("URL") + + def formfield(self, **kwargs): + # As with CharField, this will cause URL validation to be performed + # twice. + return super().formfield( + **{ + "form_class": forms.URLField, + **kwargs, + } + ) diff --git a/api/api/models/mixins.py b/api/api/models/mixins.py index 406fe3ba423..4594cd24f10 100644 --- a/api/api/models/mixins.py +++ b/api/api/models/mixins.py @@ -2,6 +2,8 @@ from django.db import models +from api.models import fields + class IdentifierMixin(models.Model): """ @@ -32,11 +34,10 @@ class ForeignIdentifierMixin(models.Model): This mixin adds - - foreign_identifier: CharField + - foreign_identifier: TextField """ - foreign_identifier = models.CharField( - max_length=1000, + foreign_identifier = models.TextField( blank=True, null=True, db_index=True, @@ -55,34 +56,31 @@ class MediaMixin(models.Model): The mixin adds - - title: CharField - - foreign_landing_url: CharField - - creator: CharField - - creator_url: CharField - - thumbnail: URLField + - title: TextField + - foreign_landing_url: URLTextField + - creator: TextField + - creator_url: URLTextField + - thumbnail: URLTextField - provider: CharField """ - title = models.CharField( - max_length=2000, + title = models.TextField( blank=True, null=True, help_text="The name of the media.", ) - foreign_landing_url = models.CharField( - max_length=1000, + foreign_landing_url = fields.URLTextField( blank=True, null=True, help_text="The landing page of the work.", ) - creator = models.CharField( - max_length=2000, + creator = models.TextField( blank=True, null=True, help_text="The name of the media creator.", ) - creator_url = models.URLField( + creator_url = fields.URLTextField( max_length=2000, blank=True, null=True, @@ -92,8 +90,7 @@ class MediaMixin(models.Model): # Because all forms of media have a thumbnail for visual representation # For images, this field is not used as images are generated using Photon. # For audio, this field points to the artwork, or is ``null``. - thumbnail = models.URLField( - max_length=1000, + thumbnail = fields.URLTextField( blank=True, null=True, help_text="The thumbnail for the media.", @@ -120,12 +117,12 @@ class FileMixin(models.Model): This mixin adds - - url: URLField + - url: URLTextField - filesize: IntegerField - filetype: CharField """ - url = models.URLField( + url = fields.URLTextField( unique=True, max_length=1000, help_text="The actual URL to the media file.", diff --git a/api/latest_migrations/api b/api/latest_migrations/api index 59384984587..56319b5792c 100644 --- a/api/latest_migrations/api +++ b/api/latest_migrations/api @@ -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. -0060_fill_out_help_text +0061_convert_varchar_to_text diff --git a/api/latest_migrations/django_structlog b/api/latest_migrations/django_structlog new file mode 100644 index 00000000000..65df5b27cdc --- /dev/null +++ b/api/latest_migrations/django_structlog @@ -0,0 +1,5 @@ +# This file is autogenerated by makemigrations. +# 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. + diff --git a/documentation/meta/media_properties/api.md b/documentation/meta/media_properties/api.md index 6a9801f36fe..3a82cac43af 100644 --- a/documentation/meta/media_properties/api.md +++ b/documentation/meta/media_properties/api.md @@ -36,18 +36,18 @@ value). Note that relation fields are always nullable. | Name | Type | DB type | Constraints | Default | | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ----------------------------- | ------- | | [`alt_files`](#Audio-alt_files-notes) | [`JSONField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#jsonfield) | `jsonb` | | | -| [`audio_set_foreign_identifier`](#Audio-audio_set_foreign_identifier-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | | | +| [`audio_set_foreign_identifier`](#Audio-audio_set_foreign_identifier-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | | [`audio_set_position`](#Audio-audio_set_position-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | | | [`bit_rate`](#Audio-bit_rate-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | | | [`category`](#Audio-category-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(80)` | | | | `created_on` | [`DateTimeField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#datetimefield) | `timestamp with time zone` | not null | | -| [`creator`](#Audio-creator-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(2000)` | | | -| [`creator_url`](#Audio-creator_url-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(2000)` | | | +| [`creator`](#Audio-creator-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | +| [`creator_url`](#Audio-creator_url-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | | [`duration`](#Audio-duration-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | | | [`filesize`](#Audio-filesize-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | | | [`filetype`](#Audio-filetype-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(80)` | | | -| [`foreign_identifier`](#Audio-foreign_identifier-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | | | -| [`foreign_landing_url`](#Audio-foreign_landing_url-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | | | +| [`foreign_identifier`](#Audio-foreign_identifier-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | +| [`foreign_landing_url`](#Audio-foreign_landing_url-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | | [`genres`](#Audio-genres-notes) | [`ArrayField`](https://docs.djangoproject.com/en/stable/ref/contrib/postgres/fields/#arrayfield) of [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(80)[]` | not blank | | | [`id`](#Audio-id-notes) | [`AutoField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#autofield) | `integer` | not null; unique; primary key | | | [`identifier`](#Audio-identifier-notes) | [`UUIDField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#uuidfield) | `uuid` | not null; not blank; unique | | @@ -60,10 +60,10 @@ value). Note that relation fields are always nullable. | [`sample_rate`](#Audio-sample_rate-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | | | [`source`](#Audio-source-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(80)` | | | | [`tags`](#Audio-tags-notes) | [`JSONField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#jsonfield) | `jsonb` | | | -| [`thumbnail`](#Audio-thumbnail-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | | | -| [`title`](#Audio-title-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(2000)` | | | +| [`thumbnail`](#Audio-thumbnail-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | +| [`title`](#Audio-title-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | | `updated_on` | [`DateTimeField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#datetimefield) | `timestamp with time zone` | not null | | -| [`url`](#Audio-url-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | unique | | +| [`url`](#Audio-url-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | unique | | | [`view_count`](#Audio-view_count-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | `0` | | [`watermarked`](#Audio-watermarked-notes) | [`BooleanField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#booleanfield) | `boolean` | | | @@ -337,12 +337,12 @@ Note that only `name` and `accuracy` are presently surfaced in API results. | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------- | ----------------------------- | ------- | | [`category`](#Image-category-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(80)` | | | | `created_on` | [`DateTimeField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#datetimefield) | `timestamp with time zone` | not null | | -| [`creator`](#Image-creator-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(2000)` | | | -| [`creator_url`](#Image-creator_url-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(2000)` | | | +| [`creator`](#Image-creator-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | +| [`creator_url`](#Image-creator_url-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | | [`filesize`](#Image-filesize-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | | | [`filetype`](#Image-filetype-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(80)` | | | -| [`foreign_identifier`](#Image-foreign_identifier-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | | | -| [`foreign_landing_url`](#Image-foreign_landing_url-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | | | +| [`foreign_identifier`](#Image-foreign_identifier-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | +| [`foreign_landing_url`](#Image-foreign_landing_url-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | | [`height`](#Image-height-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | | | [`id`](#Image-id-notes) | [`AutoField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#autofield) | `integer` | not null; unique; primary key | | | [`identifier`](#Image-identifier-notes) | [`UUIDField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#uuidfield) | `uuid` | not null; not blank; unique | | @@ -354,10 +354,10 @@ Note that only `name` and `accuracy` are presently surfaced in API results. | [`removed_from_source`](#Image-removed_from_source-notes) | [`BooleanField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#booleanfield) | `boolean` | not null; not blank | `False` | | [`source`](#Image-source-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(80)` | | | | [`tags`](#Image-tags-notes) | [`JSONField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#jsonfield) | `jsonb` | | | -| [`thumbnail`](#Image-thumbnail-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | | | -| [`title`](#Image-title-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(2000)` | | | +| [`thumbnail`](#Image-thumbnail-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | +| [`title`](#Image-title-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | | | | `updated_on` | [`DateTimeField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#datetimefield) | `timestamp with time zone` | not null | | -| [`url`](#Image-url-notes) | [`CharField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield) | `varchar(1000)` | unique | | +| [`url`](#Image-url-notes) | [`TextField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield) | `text` | unique | | | [`view_count`](#Image-view_count-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | `0` | | [`watermarked`](#Image-watermarked-notes) | [`BooleanField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#booleanfield) | `boolean` | | | | [`width`](#Image-width-notes) | [`IntegerField`](https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield) | `integer` | | |