We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
MetricsModelMixin is missing from several models


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `concordia/models.py`
Content:
```
1 from logging import getLogger
2 
3 from django.conf import settings
4 from django.contrib.auth.models import User
5 from django.contrib.postgres.fields import JSONField
6 from django.core.validators import RegexValidator
7 from django.db import models
8 from django.urls import reverse
9 from django.utils import timezone
10 from django_prometheus_metrics.models import MetricsModelMixin
11 
12 
13 logger = getLogger(__name__)
14 
15 metadata_default = dict
16 
17 User._meta.get_field("email").__dict__["_unique"] = True
18 
19 
20 class UserProfile(MetricsModelMixin("userprofile"), models.Model):
21     user = models.ForeignKey(User, on_delete=models.CASCADE)
22 
23 
24 class Status:
25     # FIXME: determine whether this is actually universally applicable to all of
26     # our models or should be split into subsets
27     EDIT = "Edit"
28     SUBMITTED = "Submitted"
29     COMPLETED = "Completed"
30     INACTIVE = "Inactive"
31     ACTIVE = "Active"
32 
33     DEFAULT = EDIT
34     CHOICES = (
35         (EDIT, "Open for Edit"),
36         (SUBMITTED, "Submitted for Review"),
37         (COMPLETED, "Transcription Completed"),
38         (INACTIVE, "Inactive"),
39         (ACTIVE, "Active"),
40     )
41 
42     #: Convenience lookup dictionary for CHOICES:
43     CHOICE_MAP = dict(CHOICES)
44 
45 
46 class MediaType:
47     IMAGE = "IMG"
48     AUDIO = "AUD"
49     VIDEO = "VID"
50 
51     CHOICES = ((IMAGE, "Image"), (AUDIO, "Audio"), (VIDEO, "Video"))
52 
53 
54 class PublicationManager(models.Manager):
55     def published(self):
56         return self.get_queryset().filter(published=True)
57 
58     def unpublished(self):
59         return self.get_queryset().filter(published=False)
60 
61 
62 class Campaign(MetricsModelMixin("campaign"), models.Model):
63     objects = PublicationManager()
64 
65     published = models.BooleanField(default=False, blank=True)
66     status = models.CharField(
67         max_length=10, choices=Status.CHOICES, default=Status.DEFAULT
68     )
69 
70     title = models.CharField(max_length=80)
71     slug = models.SlugField(max_length=80, unique=True)
72     description = models.TextField(blank=True)
73 
74     start_date = models.DateTimeField(null=True, blank=True)
75     end_date = models.DateTimeField(null=True, blank=True)
76 
77     metadata = JSONField(default=metadata_default, blank=True, null=True)
78 
79     def __str__(self):
80         return self.title
81 
82     def get_absolute_url(self):
83         # FIXME: change this with https://github.com/LibraryOfCongress/concordia/issues/242
84         return reverse("transcriptions:campaign", args=(self.slug,))
85 
86 
87 class Project(models.Model):
88     objects = PublicationManager()
89 
90     campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
91     title = models.CharField(max_length=80)
92     slug = models.SlugField(max_length=80)
93 
94     category = models.CharField(max_length=12, blank=True)
95     metadata = JSONField(default=metadata_default, blank=True, null=True)
96     status = models.CharField(
97         max_length=10, choices=Status.CHOICES, default=Status.DEFAULT
98     )
99     published = models.BooleanField(default=False, blank=True)
100 
101     class Meta:
102         unique_together = (("slug", "campaign"),)
103         ordering = ["title"]
104 
105     def __str__(self):
106         return self.title
107 
108     def get_absolute_url(self):
109         return reverse(
110             "transcriptions:project-detail",
111             kwargs={"campaign_slug": self.campaign.slug, "slug": self.slug},
112         )
113 
114 
115 class Item(models.Model):
116     objects = PublicationManager()
117 
118     project = models.ForeignKey(
119         Project, on_delete=models.CASCADE, blank=True, null=True
120     )
121 
122     published = models.BooleanField(default=False, blank=True)
123 
124     title = models.CharField(max_length=300)
125     item_url = models.URLField(max_length=255)
126     item_id = models.CharField(
127         max_length=100, help_text="Unique item ID assigned by the upstream source"
128     )
129     description = models.TextField(blank=True)
130     metadata = JSONField(
131         default=metadata_default,
132         blank=True,
133         null=True,
134         help_text="Raw metadata returned by the remote API",
135     )
136     thumbnail_url = models.URLField(max_length=255, blank=True, null=True)
137     status = models.CharField(
138         max_length=10, choices=Status.CHOICES, default=Status.DEFAULT
139     )
140 
141     class Meta:
142         unique_together = (("item_id", "project"),)
143 
144     def __str__(self):
145         return f"{self.item_id}: {self.title}"
146 
147     def get_absolute_url(self):
148         return reverse(
149             "transcriptions:item",
150             kwargs={
151                 "campaign_slug": self.project.campaign.slug,
152                 "project_slug": self.project.slug,
153                 "item_id": self.item_id,
154             },
155         )
156 
157 
158 class Asset(models.Model):
159     item = models.ForeignKey(Item, on_delete=models.CASCADE)
160 
161     title = models.CharField(max_length=100)
162     slug = models.SlugField(max_length=100)
163 
164     description = models.TextField(blank=True)
165     # TODO: do we really need this given that we import in lock-step sequence
166     #       numbers with a fixed extension?
167     media_url = models.TextField("Path component of the URL", max_length=255)
168     media_type = models.CharField(
169         max_length=4, choices=MediaType.CHOICES, db_index=True
170     )
171     sequence = models.PositiveIntegerField(default=1)
172 
173     # The original ID of the image resource on loc.gov
174     resource_id = models.CharField(max_length=100, blank=True, null=True)
175     # The URL used to download this image from loc.gov
176     download_url = models.CharField(max_length=255, blank=True, null=True)
177 
178     metadata = JSONField(default=metadata_default, blank=True, null=True)
179     status = models.CharField(
180         max_length=10, choices=Status.CHOICES, default=Status.DEFAULT
181     )
182 
183     class Meta:
184         unique_together = (("slug", "item"),)
185         ordering = ["title", "sequence"]
186 
187     def __str__(self):
188         return self.title
189 
190     def get_absolute_url(self):
191         return reverse(
192             "transcriptions:asset-detail",
193             kwargs={
194                 "campaign_slug": self.item.project.campaign.slug,
195                 "project_slug": self.item.project.slug,
196                 "item_id": self.item.item_id,
197                 "slug": self.slug,
198             },
199         )
200 
201 
202 class Tag(models.Model):
203     TAG_VALIDATOR = RegexValidator(r"^[- _'\w]{1,50}$")
204     value = models.CharField(max_length=50, validators=[TAG_VALIDATOR])
205 
206     def __str__(self):
207         return self.value
208 
209 
210 class UserAssetTagCollection(models.Model):
211     asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
212 
213     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
214 
215     tags = models.ManyToManyField(Tag, blank=True)
216     created_on = models.DateTimeField(auto_now_add=True)
217     updated_on = models.DateTimeField(auto_now=True)
218 
219     def __str__(self):
220         return "{} - {}".format(self.asset, self.user)
221 
222 
223 class Transcription(models.Model):
224     asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
225 
226     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
227 
228     text = models.TextField(blank=True)
229     status = models.CharField(
230         max_length=10, choices=Status.CHOICES, default=Status.DEFAULT
231     )
232 
233     created_on = models.DateTimeField(auto_now_add=True)
234     updated_on = models.DateTimeField(auto_now=True)
235 
236     def __str__(self):
237         return str(self.asset)
238 
239 
240 class AssetTranscriptionReservation(models.Model):
241     """
242     Records a user's reservation to transcribe a particular asset
243     """
244 
245     asset = models.OneToOneField(Asset, on_delete=models.CASCADE)
246     user = models.ForeignKey(User, on_delete=models.CASCADE)
247 
248     created_on = models.DateTimeField(editable=False, auto_now_add=True)
249     updated_on = models.DateTimeField(auto_now=True)
250
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/concordia/models.py b/concordia/models.py
--- a/concordia/models.py
+++ b/concordia/models.py
@@ -84,7 +84,7 @@
         return reverse("transcriptions:campaign", args=(self.slug,))
 
 
-class Project(models.Model):
+class Project(MetricsModelMixin("project"), models.Model):
     objects = PublicationManager()
 
     campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
@@ -112,7 +112,7 @@
         )
 
 
-class Item(models.Model):
+class Item(MetricsModelMixin("item"), models.Model):
     objects = PublicationManager()
 
     project = models.ForeignKey(
@@ -155,7 +155,7 @@
         )
 
 
-class Asset(models.Model):
+class Asset(MetricsModelMixin("asset"), models.Model):
     item = models.ForeignKey(Item, on_delete=models.CASCADE)
 
     title = models.CharField(max_length=100)
@@ -199,7 +199,7 @@
         )
 
 
-class Tag(models.Model):
+class Tag(MetricsModelMixin("tag"), models.Model):
     TAG_VALIDATOR = RegexValidator(r"^[- _'\w]{1,50}$")
     value = models.CharField(max_length=50, validators=[TAG_VALIDATOR])
 
@@ -207,7 +207,9 @@
         return self.value
 
 
-class UserAssetTagCollection(models.Model):
+class UserAssetTagCollection(
+    MetricsModelMixin("user_asset_tag_collection"), models.Model
+):
     asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
 
     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
@@ -220,7 +222,7 @@
         return "{} - {}".format(self.asset, self.user)
 
 
-class Transcription(models.Model):
+class Transcription(MetricsModelMixin("transcription"), models.Model):
     asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
 
     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

{"golden_diff": "diff --git a/concordia/models.py b/concordia/models.py\n--- a/concordia/models.py\n+++ b/concordia/models.py\n@@ -84,7 +84,7 @@\n         return reverse(\"transcriptions:campaign\", args=(self.slug,))\n \n \n-class Project(models.Model):\n+class Project(MetricsModelMixin(\"project\"), models.Model):\n     objects = PublicationManager()\n \n     campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)\n@@ -112,7 +112,7 @@\n         )\n \n \n-class Item(models.Model):\n+class Item(MetricsModelMixin(\"item\"), models.Model):\n     objects = PublicationManager()\n \n     project = models.ForeignKey(\n@@ -155,7 +155,7 @@\n         )\n \n \n-class Asset(models.Model):\n+class Asset(MetricsModelMixin(\"asset\"), models.Model):\n     item = models.ForeignKey(Item, on_delete=models.CASCADE)\n \n     title = models.CharField(max_length=100)\n@@ -199,7 +199,7 @@\n         )\n \n \n-class Tag(models.Model):\n+class Tag(MetricsModelMixin(\"tag\"), models.Model):\n     TAG_VALIDATOR = RegexValidator(r\"^[- _'\\w]{1,50}$\")\n     value = models.CharField(max_length=50, validators=[TAG_VALIDATOR])\n \n@@ -207,7 +207,9 @@\n         return self.value\n \n \n-class UserAssetTagCollection(models.Model):\n+class UserAssetTagCollection(\n+    MetricsModelMixin(\"user_asset_tag_collection\"), models.Model\n+):\n     asset = models.ForeignKey(Asset, on_delete=models.CASCADE)\n \n     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\n@@ -220,7 +222,7 @@\n         return \"{} - {}\".format(self.asset, self.user)\n \n \n-class Transcription(models.Model):\n+class Transcription(MetricsModelMixin(\"transcription\"), models.Model):\n     asset = models.ForeignKey(Asset, on_delete=models.CASCADE)\n \n     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\n", "issue": "MetricsModelMixin is missing from several models\n\n", "before_files": [{"content": "from logging import getLogger\n\nfrom django.conf import settings\nfrom django.contrib.auth.models import User\nfrom django.contrib.postgres.fields import JSONField\nfrom django.core.validators import RegexValidator\nfrom django.db import models\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django_prometheus_metrics.models import MetricsModelMixin\n\n\nlogger = getLogger(__name__)\n\nmetadata_default = dict\n\nUser._meta.get_field(\"email\").__dict__[\"_unique\"] = True\n\n\nclass UserProfile(MetricsModelMixin(\"userprofile\"), models.Model):\n    user = models.ForeignKey(User, on_delete=models.CASCADE)\n\n\nclass Status:\n    # FIXME: determine whether this is actually universally applicable to all of\n    # our models or should be split into subsets\n    EDIT = \"Edit\"\n    SUBMITTED = \"Submitted\"\n    COMPLETED = \"Completed\"\n    INACTIVE = \"Inactive\"\n    ACTIVE = \"Active\"\n\n    DEFAULT = EDIT\n    CHOICES = (\n        (EDIT, \"Open for Edit\"),\n        (SUBMITTED, \"Submitted for Review\"),\n        (COMPLETED, \"Transcription Completed\"),\n        (INACTIVE, \"Inactive\"),\n        (ACTIVE, \"Active\"),\n    )\n\n    #: Convenience lookup dictionary for CHOICES:\n    CHOICE_MAP = dict(CHOICES)\n\n\nclass MediaType:\n    IMAGE = \"IMG\"\n    AUDIO = \"AUD\"\n    VIDEO = \"VID\"\n\n    CHOICES = ((IMAGE, \"Image\"), (AUDIO, \"Audio\"), (VIDEO, \"Video\"))\n\n\nclass PublicationManager(models.Manager):\n    def published(self):\n        return self.get_queryset().filter(published=True)\n\n    def unpublished(self):\n        return self.get_queryset().filter(published=False)\n\n\nclass Campaign(MetricsModelMixin(\"campaign\"), models.Model):\n    objects = PublicationManager()\n\n    published = models.BooleanField(default=False, blank=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    title = models.CharField(max_length=80)\n    slug = models.SlugField(max_length=80, unique=True)\n    description = models.TextField(blank=True)\n\n    start_date = models.DateTimeField(null=True, blank=True)\n    end_date = models.DateTimeField(null=True, blank=True)\n\n    metadata = JSONField(default=metadata_default, blank=True, null=True)\n\n    def __str__(self):\n        return self.title\n\n    def get_absolute_url(self):\n        # FIXME: change this with https://github.com/LibraryOfCongress/concordia/issues/242\n        return reverse(\"transcriptions:campaign\", args=(self.slug,))\n\n\nclass Project(models.Model):\n    objects = PublicationManager()\n\n    campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)\n    title = models.CharField(max_length=80)\n    slug = models.SlugField(max_length=80)\n\n    category = models.CharField(max_length=12, blank=True)\n    metadata = JSONField(default=metadata_default, blank=True, null=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n    published = models.BooleanField(default=False, blank=True)\n\n    class Meta:\n        unique_together = ((\"slug\", \"campaign\"),)\n        ordering = [\"title\"]\n\n    def __str__(self):\n        return self.title\n\n    def get_absolute_url(self):\n        return reverse(\n            \"transcriptions:project-detail\",\n            kwargs={\"campaign_slug\": self.campaign.slug, \"slug\": self.slug},\n        )\n\n\nclass Item(models.Model):\n    objects = PublicationManager()\n\n    project = models.ForeignKey(\n        Project, on_delete=models.CASCADE, blank=True, null=True\n    )\n\n    published = models.BooleanField(default=False, blank=True)\n\n    title = models.CharField(max_length=300)\n    item_url = models.URLField(max_length=255)\n    item_id = models.CharField(\n        max_length=100, help_text=\"Unique item ID assigned by the upstream source\"\n    )\n    description = models.TextField(blank=True)\n    metadata = JSONField(\n        default=metadata_default,\n        blank=True,\n        null=True,\n        help_text=\"Raw metadata returned by the remote API\",\n    )\n    thumbnail_url = models.URLField(max_length=255, blank=True, null=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    class Meta:\n        unique_together = ((\"item_id\", \"project\"),)\n\n    def __str__(self):\n        return f\"{self.item_id}: {self.title}\"\n\n    def get_absolute_url(self):\n        return reverse(\n            \"transcriptions:item\",\n            kwargs={\n                \"campaign_slug\": self.project.campaign.slug,\n                \"project_slug\": self.project.slug,\n                \"item_id\": self.item_id,\n            },\n        )\n\n\nclass Asset(models.Model):\n    item = models.ForeignKey(Item, on_delete=models.CASCADE)\n\n    title = models.CharField(max_length=100)\n    slug = models.SlugField(max_length=100)\n\n    description = models.TextField(blank=True)\n    # TODO: do we really need this given that we import in lock-step sequence\n    #       numbers with a fixed extension?\n    media_url = models.TextField(\"Path component of the URL\", max_length=255)\n    media_type = models.CharField(\n        max_length=4, choices=MediaType.CHOICES, db_index=True\n    )\n    sequence = models.PositiveIntegerField(default=1)\n\n    # The original ID of the image resource on loc.gov\n    resource_id = models.CharField(max_length=100, blank=True, null=True)\n    # The URL used to download this image from loc.gov\n    download_url = models.CharField(max_length=255, blank=True, null=True)\n\n    metadata = JSONField(default=metadata_default, blank=True, null=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    class Meta:\n        unique_together = ((\"slug\", \"item\"),)\n        ordering = [\"title\", \"sequence\"]\n\n    def __str__(self):\n        return self.title\n\n    def get_absolute_url(self):\n        return reverse(\n            \"transcriptions:asset-detail\",\n            kwargs={\n                \"campaign_slug\": self.item.project.campaign.slug,\n                \"project_slug\": self.item.project.slug,\n                \"item_id\": self.item.item_id,\n                \"slug\": self.slug,\n            },\n        )\n\n\nclass Tag(models.Model):\n    TAG_VALIDATOR = RegexValidator(r\"^[- _'\\w]{1,50}$\")\n    value = models.CharField(max_length=50, validators=[TAG_VALIDATOR])\n\n    def __str__(self):\n        return self.value\n\n\nclass UserAssetTagCollection(models.Model):\n    asset = models.ForeignKey(Asset, on_delete=models.CASCADE)\n\n    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\n\n    tags = models.ManyToManyField(Tag, blank=True)\n    created_on = models.DateTimeField(auto_now_add=True)\n    updated_on = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return \"{} - {}\".format(self.asset, self.user)\n\n\nclass Transcription(models.Model):\n    asset = models.ForeignKey(Asset, on_delete=models.CASCADE)\n\n    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\n\n    text = models.TextField(blank=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    created_on = models.DateTimeField(auto_now_add=True)\n    updated_on = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return str(self.asset)\n\n\nclass AssetTranscriptionReservation(models.Model):\n    \"\"\"\n    Records a user's reservation to transcribe a particular asset\n    \"\"\"\n\n    asset = models.OneToOneField(Asset, on_delete=models.CASCADE)\n    user = models.ForeignKey(User, on_delete=models.CASCADE)\n\n    created_on = models.DateTimeField(editable=False, auto_now_add=True)\n    updated_on = models.DateTimeField(auto_now=True)\n", "path": "concordia/models.py"}], "after_files": [{"content": "from logging import getLogger\n\nfrom django.conf import settings\nfrom django.contrib.auth.models import User\nfrom django.contrib.postgres.fields import JSONField\nfrom django.core.validators import RegexValidator\nfrom django.db import models\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django_prometheus_metrics.models import MetricsModelMixin\n\n\nlogger = getLogger(__name__)\n\nmetadata_default = dict\n\nUser._meta.get_field(\"email\").__dict__[\"_unique\"] = True\n\n\nclass UserProfile(MetricsModelMixin(\"userprofile\"), models.Model):\n    user = models.ForeignKey(User, on_delete=models.CASCADE)\n\n\nclass Status:\n    # FIXME: determine whether this is actually universally applicable to all of\n    # our models or should be split into subsets\n    EDIT = \"Edit\"\n    SUBMITTED = \"Submitted\"\n    COMPLETED = \"Completed\"\n    INACTIVE = \"Inactive\"\n    ACTIVE = \"Active\"\n\n    DEFAULT = EDIT\n    CHOICES = (\n        (EDIT, \"Open for Edit\"),\n        (SUBMITTED, \"Submitted for Review\"),\n        (COMPLETED, \"Transcription Completed\"),\n        (INACTIVE, \"Inactive\"),\n        (ACTIVE, \"Active\"),\n    )\n\n    #: Convenience lookup dictionary for CHOICES:\n    CHOICE_MAP = dict(CHOICES)\n\n\nclass MediaType:\n    IMAGE = \"IMG\"\n    AUDIO = \"AUD\"\n    VIDEO = \"VID\"\n\n    CHOICES = ((IMAGE, \"Image\"), (AUDIO, \"Audio\"), (VIDEO, \"Video\"))\n\n\nclass PublicationManager(models.Manager):\n    def published(self):\n        return self.get_queryset().filter(published=True)\n\n    def unpublished(self):\n        return self.get_queryset().filter(published=False)\n\n\nclass Campaign(MetricsModelMixin(\"campaign\"), models.Model):\n    objects = PublicationManager()\n\n    published = models.BooleanField(default=False, blank=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    title = models.CharField(max_length=80)\n    slug = models.SlugField(max_length=80, unique=True)\n    description = models.TextField(blank=True)\n\n    start_date = models.DateTimeField(null=True, blank=True)\n    end_date = models.DateTimeField(null=True, blank=True)\n\n    metadata = JSONField(default=metadata_default, blank=True, null=True)\n\n    def __str__(self):\n        return self.title\n\n    def get_absolute_url(self):\n        # FIXME: change this with https://github.com/LibraryOfCongress/concordia/issues/242\n        return reverse(\"transcriptions:campaign\", args=(self.slug,))\n\n\nclass Project(MetricsModelMixin(\"project\"), models.Model):\n    objects = PublicationManager()\n\n    campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)\n    title = models.CharField(max_length=80)\n    slug = models.SlugField(max_length=80)\n\n    category = models.CharField(max_length=12, blank=True)\n    metadata = JSONField(default=metadata_default, blank=True, null=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n    published = models.BooleanField(default=False, blank=True)\n\n    class Meta:\n        unique_together = ((\"slug\", \"campaign\"),)\n        ordering = [\"title\"]\n\n    def __str__(self):\n        return self.title\n\n    def get_absolute_url(self):\n        return reverse(\n            \"transcriptions:project-detail\",\n            kwargs={\"campaign_slug\": self.campaign.slug, \"slug\": self.slug},\n        )\n\n\nclass Item(MetricsModelMixin(\"item\"), models.Model):\n    objects = PublicationManager()\n\n    project = models.ForeignKey(\n        Project, on_delete=models.CASCADE, blank=True, null=True\n    )\n\n    published = models.BooleanField(default=False, blank=True)\n\n    title = models.CharField(max_length=300)\n    item_url = models.URLField(max_length=255)\n    item_id = models.CharField(\n        max_length=100, help_text=\"Unique item ID assigned by the upstream source\"\n    )\n    description = models.TextField(blank=True)\n    metadata = JSONField(\n        default=metadata_default,\n        blank=True,\n        null=True,\n        help_text=\"Raw metadata returned by the remote API\",\n    )\n    thumbnail_url = models.URLField(max_length=255, blank=True, null=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    class Meta:\n        unique_together = ((\"item_id\", \"project\"),)\n\n    def __str__(self):\n        return f\"{self.item_id}: {self.title}\"\n\n    def get_absolute_url(self):\n        return reverse(\n            \"transcriptions:item\",\n            kwargs={\n                \"campaign_slug\": self.project.campaign.slug,\n                \"project_slug\": self.project.slug,\n                \"item_id\": self.item_id,\n            },\n        )\n\n\nclass Asset(MetricsModelMixin(\"asset\"), models.Model):\n    item = models.ForeignKey(Item, on_delete=models.CASCADE)\n\n    title = models.CharField(max_length=100)\n    slug = models.SlugField(max_length=100)\n\n    description = models.TextField(blank=True)\n    # TODO: do we really need this given that we import in lock-step sequence\n    #       numbers with a fixed extension?\n    media_url = models.TextField(\"Path component of the URL\", max_length=255)\n    media_type = models.CharField(\n        max_length=4, choices=MediaType.CHOICES, db_index=True\n    )\n    sequence = models.PositiveIntegerField(default=1)\n\n    # The original ID of the image resource on loc.gov\n    resource_id = models.CharField(max_length=100, blank=True, null=True)\n    # The URL used to download this image from loc.gov\n    download_url = models.CharField(max_length=255, blank=True, null=True)\n\n    metadata = JSONField(default=metadata_default, blank=True, null=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    class Meta:\n        unique_together = ((\"slug\", \"item\"),)\n        ordering = [\"title\", \"sequence\"]\n\n    def __str__(self):\n        return self.title\n\n    def get_absolute_url(self):\n        return reverse(\n            \"transcriptions:asset-detail\",\n            kwargs={\n                \"campaign_slug\": self.item.project.campaign.slug,\n                \"project_slug\": self.item.project.slug,\n                \"item_id\": self.item.item_id,\n                \"slug\": self.slug,\n            },\n        )\n\n\nclass Tag(MetricsModelMixin(\"tag\"), models.Model):\n    TAG_VALIDATOR = RegexValidator(r\"^[- _'\\w]{1,50}$\")\n    value = models.CharField(max_length=50, validators=[TAG_VALIDATOR])\n\n    def __str__(self):\n        return self.value\n\n\nclass UserAssetTagCollection(\n    MetricsModelMixin(\"user_asset_tag_collection\"), models.Model\n):\n    asset = models.ForeignKey(Asset, on_delete=models.CASCADE)\n\n    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\n\n    tags = models.ManyToManyField(Tag, blank=True)\n    created_on = models.DateTimeField(auto_now_add=True)\n    updated_on = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return \"{} - {}\".format(self.asset, self.user)\n\n\nclass Transcription(MetricsModelMixin(\"transcription\"), models.Model):\n    asset = models.ForeignKey(Asset, on_delete=models.CASCADE)\n\n    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\n\n    text = models.TextField(blank=True)\n    status = models.CharField(\n        max_length=10, choices=Status.CHOICES, default=Status.DEFAULT\n    )\n\n    created_on = models.DateTimeField(auto_now_add=True)\n    updated_on = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return str(self.asset)\n\n\nclass AssetTranscriptionReservation(models.Model):\n    \"\"\"\n    Records a user's reservation to transcribe a particular asset\n    \"\"\"\n\n    asset = models.OneToOneField(Asset, on_delete=models.CASCADE)\n    user = models.ForeignKey(User, on_delete=models.CASCADE)\n\n    created_on = models.DateTimeField(editable=False, auto_now_add=True)\n    updated_on = models.DateTimeField(auto_now=True)\n", "path": "concordia/models.py"}]}