Coverage for api/models/image.py: 91%
55 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 06:14 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 06:14 +0000
1from django.conf import settings
2from django.db import models
4from uuslug import uuslug
6from api.constants.media_types import IMAGE_TYPE
7from api.models.media import (
8 AbstractDeletedMedia,
9 AbstractMedia,
10 AbstractMediaDecision,
11 AbstractMediaDecisionThrough,
12 AbstractMediaList,
13 AbstractMediaReport,
14 AbstractSensitiveMedia,
15)
16from api.models.mixins import FileMixin
19class ImageFileMixin(FileMixin):
20 """
21 This mixin adds fields related to image resolution to the standard file mixin.
23 Do not use this as the sole base class.
24 """
26 width = models.IntegerField(
27 blank=True,
28 null=True,
29 help_text="The width of the image in pixels. Not always available.",
30 )
31 height = models.IntegerField(
32 blank=True,
33 null=True,
34 help_text="The height of the image in pixels. Not always available.",
35 )
37 @property
38 def resolution_in_mp(self): # ~ MP or megapixels
39 return (self.width * self.height) / 1e6
41 class Meta:
42 abstract = True
45class Image(ImageFileMixin, AbstractMedia):
46 """
47 One image media instance.
49 Inherited fields
50 ================
51 category: eg. photograph, digitized_artwork & illustration
52 """
54 class Meta(AbstractMedia.Meta):
55 db_table = "image"
57 def get_absolute_url(self):
58 """Enable the "View on site" link in the Django Admin."""
60 from django.urls import reverse
62 return reverse("image-detail", args=[str(self.identifier)])
64 @property
65 def sensitive(self) -> bool:
66 return hasattr(self, "sensitive_image")
69class DeletedImage(AbstractDeletedMedia):
70 """
71 Images deleted from the upstream source.
73 Do not create instances of this model manually. Create an ``ImageReport`` instance
74 instead.
75 """
77 media_class = Image
78 es_index = settings.MEDIA_INDEX_MAPPING[IMAGE_TYPE]
80 media_obj = models.OneToOneField(
81 to="Image",
82 to_field="identifier",
83 on_delete=models.DO_NOTHING,
84 primary_key=True,
85 db_constraint=False,
86 db_column="identifier",
87 related_name="deleted_image",
88 help_text="The reference to the deleted image.",
89 )
92class SensitiveImage(AbstractSensitiveMedia):
93 """
94 Images with verified sensitivity reports.
96 Do not create instances of this model manually. Create an ``ImageReport`` instance
97 instead.
98 """
100 media_class = Image
101 es_index = settings.MEDIA_INDEX_MAPPING[IMAGE_TYPE]
103 media_obj = models.OneToOneField(
104 to="Image",
105 to_field="identifier",
106 on_delete=models.DO_NOTHING,
107 primary_key=True,
108 db_constraint=False,
109 db_column="identifier",
110 related_name="sensitive_image",
111 help_text="The reference to the sensitive image.",
112 )
114 class Meta:
115 db_table = "api_matureimage"
118class ImageReport(AbstractMediaReport):
119 """
120 User-submitted report of an image.
122 This contains an ``ImageDecision`` as well, if moderators have made a decision
123 for this report.
124 """
126 media_class = Image
128 media_obj = models.ForeignKey(
129 to="Image",
130 to_field="identifier",
131 on_delete=models.DO_NOTHING,
132 db_constraint=False,
133 db_column="identifier",
134 related_name="image_report",
135 help_text="The reference to the image being reported.",
136 )
137 decision = models.ForeignKey(
138 to="ImageDecision",
139 on_delete=models.SET_NULL,
140 blank=True,
141 null=True,
142 help_text="The moderation decision for this report.",
143 )
145 class Meta:
146 db_table = "nsfw_reports"
149class ImageDecision(AbstractMediaDecision):
150 """Moderation decisions taken for images."""
152 media_class = Image
154 media_objs = models.ManyToManyField(
155 to="Image",
156 through="ImageDecisionThrough",
157 help_text="The image items being moderated.",
158 )
161class ImageDecisionThrough(AbstractMediaDecisionThrough):
162 """
163 Many-to-many reference table for image decisions.
165 This is made explicit (rather than using Django's default) so that the image can
166 be referenced by `identifier` rather than an arbitrary `id`.
167 """
169 media_class = Image
170 sensitive_media_class = SensitiveImage
171 deleted_media_class = DeletedImage
173 media_obj = models.ForeignKey(
174 Image,
175 to_field="identifier",
176 on_delete=models.DO_NOTHING,
177 db_column="identifier",
178 db_constraint=False,
179 )
180 decision = models.ForeignKey(ImageDecision, on_delete=models.CASCADE)
183class ImageList(AbstractMediaList):
184 """A list of images. Currently unused."""
186 images = models.ManyToManyField(
187 Image,
188 related_name="lists",
189 help_text="A list of identifier keys corresponding to images.",
190 )
192 class Meta:
193 db_table = "imagelist"
195 def save(self, *args, **kwargs):
196 self.slug = uuslug(self.title, instance=self)
197 super().save(*args, **kwargs)