Coverage for paperless/models.py: 99%
104 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 09:07 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 09:07 +0000
1from django.core.validators import FileExtensionValidator
2from django.core.validators import MinValueValidator
3from django.db import models
4from django.utils.translation import gettext_lazy as _
6DEFAULT_SINGLETON_INSTANCE_ID = 1
9class AbstractSingletonModel(models.Model):
10 class Meta:
11 abstract = True
13 def save(self, *args, **kwargs):
14 """
15 Always save as the first and only model
16 """
17 self.pk = DEFAULT_SINGLETON_INSTANCE_ID
18 super().save(*args, **kwargs)
21class OutputTypeChoices(models.TextChoices):
22 """
23 Matches to --output-type
24 """
26 PDF = ("pdf", _("pdf"))
27 PDF_A = ("pdfa", _("pdfa"))
28 PDF_A1 = ("pdfa-1", _("pdfa-1"))
29 PDF_A2 = ("pdfa-2", _("pdfa-2"))
30 PDF_A3 = ("pdfa-3", _("pdfa-3"))
33class ModeChoices(models.TextChoices):
34 """
35 Matches to --skip-text, --redo-ocr, --force-ocr
36 and our own custom setting
37 """
39 AUTO = ("auto", _("auto"))
40 FORCE = ("force", _("force"))
41 REDO = ("redo", _("redo"))
42 OFF = ("off", _("off"))
45class ArchiveFileGenerationChoices(models.TextChoices):
46 """
47 Settings to control creation of an archive PDF file
48 """
50 AUTO = ("auto", _("auto"))
51 ALWAYS = ("always", _("always"))
52 NEVER = ("never", _("never"))
55class CleanChoices(models.TextChoices):
56 """
57 Matches to --clean, --clean-final
58 """
60 CLEAN = ("clean", _("clean"))
61 FINAL = ("clean-final", _("clean-final"))
62 NONE = ("none", _("none"))
65class ColorConvertChoices(models.TextChoices):
66 """
67 Refer to the Ghostscript documentation for valid options
68 """
70 UNCHANGED = ("LeaveColorUnchanged", _("LeaveColorUnchanged"))
71 RGB = ("RGB", _("RGB"))
72 INDEPENDENT = ("UseDeviceIndependentColor", _("UseDeviceIndependentColor"))
73 GRAY = ("Gray", _("Gray"))
74 CMYK = ("CMYK", _("CMYK"))
77class RemoteOCREngine(models.TextChoices):
78 """
79 Matches to PAPERLESS_REMOTE_OCR_ENGINE
80 """
82 AZURE_AI = ("azureai", _("Azure AI Document Intelligence"))
85class RemoteOCRMode(models.TextChoices):
86 """
87 Matches to PAPERLESS_REMOTE_OCR_MODE
88 """
90 ALWAYS = ("always", _("All supported documents"))
91 WORKFLOW_ONLY = ("workflow_only", _("Only when a workflow enables it"))
94class LLMEmbeddingBackend(models.TextChoices):
95 OPENAI_LIKE = ("openai-like", _("OpenAI-compatible"))
96 HUGGINGFACE = ("huggingface", _("Huggingface"))
97 OLLAMA = ("ollama", _("Ollama"))
100class LLMBackend(models.TextChoices):
101 """
102 Matches to --llm-backend
103 """
105 OPENAI_LIKE = ("openai-like", _("OpenAI-compatible"))
106 OLLAMA = ("ollama", _("Ollama"))
109class ApplicationConfiguration(AbstractSingletonModel):
110 """
111 Settings which are common across more than 1 parser
112 """
114 output_type = models.CharField(
115 verbose_name=_("Sets the output PDF type"),
116 null=True,
117 blank=True,
118 max_length=8,
119 choices=OutputTypeChoices.choices,
120 )
122 """
123 Settings for the Tesseract based OCR parser
124 """
126 pages = models.PositiveSmallIntegerField(
127 verbose_name=_("Do OCR from page 1 to this value"),
128 null=True,
129 validators=[MinValueValidator(1)],
130 )
132 language = models.CharField(
133 verbose_name=_("Do OCR using these languages"),
134 null=True,
135 blank=True,
136 max_length=32,
137 )
139 mode = models.CharField(
140 verbose_name=_("Sets the OCR mode"),
141 null=True,
142 blank=True,
143 max_length=16,
144 choices=ModeChoices.choices,
145 )
147 archive_file_generation = models.CharField(
148 verbose_name=_("Controls archive file generation"),
149 null=True,
150 blank=True,
151 max_length=8,
152 choices=ArchiveFileGenerationChoices.choices,
153 )
155 image_dpi = models.PositiveSmallIntegerField(
156 verbose_name=_("Sets image DPI fallback value"),
157 null=True,
158 validators=[MinValueValidator(1)],
159 )
161 # Can't call it clean, that's a model method
162 unpaper_clean = models.CharField(
163 verbose_name=_("Controls the unpaper cleaning"),
164 null=True,
165 blank=True,
166 max_length=16,
167 choices=CleanChoices.choices,
168 )
170 deskew = models.BooleanField(verbose_name=_("Enables deskew"), null=True)
172 rotate_pages = models.BooleanField(
173 verbose_name=_("Enables page rotation"),
174 null=True,
175 )
177 rotate_pages_threshold = models.FloatField(
178 verbose_name=_("Sets the threshold for rotation of pages"),
179 null=True,
180 validators=[MinValueValidator(0.0)],
181 )
183 max_image_pixels = models.FloatField(
184 verbose_name=_("Sets the maximum image size for decompression"),
185 null=True,
186 validators=[MinValueValidator(0.0)],
187 )
189 color_conversion_strategy = models.CharField(
190 verbose_name=_("Sets the Ghostscript color conversion strategy"),
191 blank=True,
192 null=True,
193 max_length=32,
194 choices=ColorConvertChoices.choices,
195 )
197 user_args = models.JSONField(
198 verbose_name=_("Adds additional user arguments for OCRMyPDF"),
199 null=True,
200 )
202 """
203 Settings for the Paperless application
204 """
206 app_title = models.CharField(
207 verbose_name=_("Application title"),
208 null=True,
209 blank=True,
210 max_length=48,
211 )
213 app_logo = models.FileField(
214 verbose_name=_("Application logo"),
215 null=True,
216 blank=True,
217 validators=[
218 FileExtensionValidator(allowed_extensions=["jpg", "png", "gif", "svg"]),
219 ],
220 upload_to="logo/",
221 )
223 """
224 Settings for the barcode scanner
225 """
227 # PAPERLESS_CONSUMER_ENABLE_BARCODES
228 barcodes_enabled = models.BooleanField(
229 verbose_name=_("Enables barcode scanning"),
230 null=True,
231 )
233 # PAPERLESS_CONSUMER_BARCODE_TIFF_SUPPORT
234 barcode_enable_tiff_support = models.BooleanField(
235 verbose_name=_("Enables barcode TIFF support"),
236 null=True,
237 )
239 # PAPERLESS_CONSUMER_BARCODE_STRING
240 barcode_string = models.CharField(
241 verbose_name=_("Sets the barcode string"),
242 null=True,
243 blank=True,
244 max_length=32,
245 )
247 # PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES
248 barcode_retain_split_pages = models.BooleanField(
249 verbose_name=_("Retains split pages"),
250 null=True,
251 )
253 # PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE
254 barcode_enable_asn = models.BooleanField(
255 verbose_name=_("Enables ASN barcode"),
256 null=True,
257 )
259 # PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX
260 barcode_asn_prefix = models.CharField(
261 verbose_name=_("Sets the ASN barcode prefix"),
262 null=True,
263 blank=True,
264 max_length=32,
265 )
267 # PAPERLESS_CONSUMER_BARCODE_UPSCALE
268 barcode_upscale = models.FloatField(
269 verbose_name=_("Sets the barcode upscale factor"),
270 null=True,
271 validators=[MinValueValidator(1.0)],
272 )
274 # PAPERLESS_CONSUMER_BARCODE_DPI
275 barcode_dpi = models.PositiveSmallIntegerField(
276 verbose_name=_("Sets the barcode DPI"),
277 null=True,
278 validators=[MinValueValidator(1)],
279 )
281 # PAPERLESS_CONSUMER_BARCODE_MAX_PAGES
282 barcode_max_pages = models.PositiveSmallIntegerField(
283 verbose_name=_("Sets the maximum pages for barcode"),
284 null=True,
285 validators=[MinValueValidator(1)],
286 )
288 # PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE
289 barcode_enable_tag = models.BooleanField(
290 verbose_name=_("Enables tag barcode"),
291 null=True,
292 )
294 # PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING
295 barcode_tag_mapping = models.JSONField(
296 verbose_name=_("Sets the tag barcode mapping"),
297 null=True,
298 )
300 # PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT
301 barcode_tag_split = models.BooleanField(
302 verbose_name=_("Enables splitting on tag barcodes"),
303 null=True,
304 )
306 # PAPERLESS_CONSUMER_STORE_BARCODE_VALUES
307 barcode_store_values = models.BooleanField(
308 verbose_name=_("Stores the values of detected barcodes"),
309 null=True,
310 )
312 """
313 Settings for the remote OCR parser
314 """
316 # PAPERLESS_REMOTE_OCR_ENGINE
317 remote_ocr_engine = models.CharField(
318 verbose_name=_("Sets the remote OCR engine"),
319 blank=True,
320 null=True,
321 max_length=32,
322 choices=RemoteOCREngine.choices,
323 )
325 # PAPERLESS_REMOTE_OCR_API_KEY
326 remote_ocr_api_key = models.CharField(
327 verbose_name=_("Sets the remote OCR API key"),
328 blank=True,
329 null=True,
330 max_length=1024,
331 )
333 # PAPERLESS_REMOTE_OCR_ENDPOINT
334 remote_ocr_endpoint = models.CharField(
335 verbose_name=_("Sets the remote OCR endpoint"),
336 blank=True,
337 null=True,
338 max_length=256,
339 )
341 # PAPERLESS_REMOTE_OCR_MODE
342 remote_ocr_mode = models.CharField(
343 verbose_name=_("Sets which documents are sent to the remote OCR engine"),
344 blank=True,
345 null=True,
346 max_length=32,
347 choices=RemoteOCRMode.choices,
348 )
350 """
351 AI related settings
352 """
354 ai_enabled = models.BooleanField(
355 verbose_name=_("Enables AI features"),
356 null=True,
357 )
359 llm_embedding_backend = models.CharField(
360 verbose_name=_("Sets the LLM embedding backend"),
361 blank=True,
362 null=True,
363 max_length=128,
364 choices=LLMEmbeddingBackend.choices,
365 )
367 llm_embedding_model = models.CharField(
368 verbose_name=_("Sets the LLM embedding model"),
369 blank=True,
370 null=True,
371 max_length=128,
372 )
374 llm_embedding_api_key = models.CharField(
375 verbose_name=_("Sets the LLM embedding API key"),
376 blank=True,
377 null=True,
378 max_length=1024,
379 )
381 llm_embedding_endpoint = models.CharField(
382 verbose_name=_("Sets the LLM embedding endpoint, optional"),
383 blank=True,
384 null=True,
385 max_length=256,
386 )
388 llm_embedding_chunk_size = models.PositiveSmallIntegerField(
389 verbose_name=_("Sets the LLM embedding chunk size"),
390 null=True,
391 validators=[MinValueValidator(1)],
392 )
394 llm_context_size = models.PositiveIntegerField(
395 verbose_name=_("Sets the LLM context size"),
396 null=True,
397 validators=[MinValueValidator(1)],
398 )
400 llm_backend = models.CharField(
401 verbose_name=_("Sets the LLM backend"),
402 blank=True,
403 null=True,
404 max_length=128,
405 choices=LLMBackend.choices,
406 )
408 llm_model = models.CharField(
409 verbose_name=_("Sets the LLM model"),
410 blank=True,
411 null=True,
412 max_length=128,
413 )
415 llm_api_key = models.CharField(
416 verbose_name=_("Sets the LLM API key"),
417 blank=True,
418 null=True,
419 max_length=1024,
420 )
422 llm_endpoint = models.CharField(
423 verbose_name=_("Sets the LLM endpoint, optional"),
424 blank=True,
425 null=True,
426 max_length=256,
427 )
429 llm_output_language = models.CharField(
430 verbose_name=_("Sets the LLM output language"),
431 blank=True,
432 null=True,
433 max_length=32,
434 )
436 llm_request_timeout = models.PositiveSmallIntegerField(
437 verbose_name=_("Sets the LLM timeout in seconds"),
438 null=True,
439 validators=[MinValueValidator(1)],
440 )
442 class Meta:
443 verbose_name = _("paperless application settings")
444 permissions = [
445 ("view_global_statistics", "Can view global object counts"),
446 ("view_system_monitoring", "Can view system status information"),
447 ]
449 def __str__(self) -> str: # pragma: no cover
450 return "ApplicationConfiguration"