meringue 1.2.0.dev8__tar.gz → 1.3.0.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/PKG-INFO +12 -12
  2. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/README.md +6 -5
  3. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/__init__.py +1 -1
  4. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/handlers.py +2 -1
  5. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/utils.py +1 -0
  6. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/conf/__init__.py +2 -1
  7. meringue-1.3.0.dev1/meringue/core/db.py +77 -0
  8. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/query.py +1 -3
  9. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/protected/fields.py +7 -5
  10. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/protected/views.py +7 -6
  11. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/drf_fields.py +24 -14
  12. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/images.py +1 -1
  13. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/shortcuts.py +2 -2
  14. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/pyproject.toml +86 -58
  15. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/.gitignore +0 -0
  16. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/AUTHORS +0 -0
  17. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/LICENSE +0 -0
  18. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/__init__.py +0 -0
  19. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/apps.py +0 -0
  20. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/docs/__init__.py +0 -0
  21. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/docs/patchers.py +0 -0
  22. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/docs/views.py +0 -0
  23. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/api/routers.py +0 -0
  24. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/conf/default_settings.py +0 -0
  25. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/__init__.py +0 -0
  26. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/apps.py +0 -0
  27. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/locale/en/LC_MESSAGES/django.po +0 -0
  28. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/locale/ru/LC_MESSAGES/django.po +0 -0
  29. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/models.py +0 -0
  30. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/options.py +0 -0
  31. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/templatetags/__init__.py +0 -0
  32. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/templatetags/meringue_base.py +0 -0
  33. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/translation.py +0 -0
  34. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/upload_handlers.py +0 -0
  35. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/utils/__init__.py +0 -0
  36. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/utils/crypt.py +0 -0
  37. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/utils/datetime.py +0 -0
  38. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/utils/frontend.py +0 -0
  39. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/core/views.py +0 -0
  40. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/protected/__init__.py +0 -0
  41. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/protected/apps.py +0 -0
  42. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/protected/utils.py +0 -0
  43. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/__init__.py +0 -0
  44. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/actions.py +0 -0
  45. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/apps.py +0 -0
  46. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/constants.py +0 -0
  47. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/exceptions.py +0 -0
  48. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/generators.py +0 -0
  49. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/properties.py +0 -0
  50. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/storage.py +0 -0
  51. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/templatetags/__init__.py +0 -0
  52. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/templatetags/m_thumbnails.py +0 -0
  53. {meringue-1.2.0.dev8 → meringue-1.3.0.dev1}/meringue/thumbnail/types.py +0 -0
@@ -1,22 +1,17 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: meringue
3
- Version: 1.2.0.dev8
3
+ Version: 1.3.0.dev1
4
4
  Summary: A set of various functionality for a Django based web application.
5
5
  Project-URL: Documentation, https://dd.github.io/Meringue
6
6
  Project-URL: Repository, https://github.com/dd/Meringue
7
7
  Project-URL: Changelog, https://github.com/dd/Meringue/blob/master/CHANGELOG.md
8
8
  Project-URL: Bug Tracker, https://github.com/dd/Meringue/issues
9
9
  Author-email: Dmitry Dobrynin <dd@tovarisch.engineer>
10
- License-Expression: LGPL-3.0
11
- License-File: AUTHORS
12
- License-File: LICENSE
13
10
  Keywords: django,utils
14
11
  Classifier: Development Status :: 5 - Production/Stable
15
12
  Classifier: Environment :: Plugins
16
13
  Classifier: Environment :: Web Environment
17
14
  Classifier: Framework :: Django
18
- Classifier: Framework :: Django :: 1
19
- Classifier: Framework :: Django :: 1.11
20
15
  Classifier: Framework :: Django :: 2
21
16
  Classifier: Framework :: Django :: 2.0
22
17
  Classifier: Framework :: Django :: 2.1
@@ -29,6 +24,7 @@ Classifier: Framework :: Django :: 4
29
24
  Classifier: Framework :: Django :: 4.0
30
25
  Classifier: Framework :: Django :: 4.1
31
26
  Classifier: Framework :: Django :: 4.2
27
+ Classifier: Framework :: Django :: 5.0
32
28
  Classifier: Intended Audience :: Developers
33
29
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
34
30
  Classifier: Natural Language :: English
@@ -38,6 +34,7 @@ Classifier: Programming Language :: Python :: 3
38
34
  Classifier: Programming Language :: Python :: 3 :: Only
39
35
  Classifier: Programming Language :: Python :: 3.10
40
36
  Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
41
38
  Classifier: Programming Language :: Python :: Implementation :: CPython
42
39
  Classifier: Programming Language :: Python :: Implementation :: PyPy
43
40
  Classifier: Topic :: Internet :: WWW/HTTP
@@ -45,9 +42,11 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
45
42
  Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
46
43
  Classifier: Topic :: Software Development :: Libraries
47
44
  Requires-Python: >=3.10
48
- Requires-Dist: django>=1.11.17
45
+ Requires-Dist: django<6,>=2.0.0
49
46
  Provides-Extra: cryptodome
50
47
  Requires-Dist: pycryptodome==3.20.0; extra == 'cryptodome'
48
+ Provides-Extra: django-hosts
49
+ Requires-Dist: django-hosts<7,>=5.2; extra == 'django-hosts'
51
50
  Provides-Extra: drf
52
51
  Requires-Dist: djangorestframework<4,>=3.13; extra == 'drf'
53
52
  Provides-Extra: drf-spectacular
@@ -74,6 +73,9 @@ Description-Content-Type: text/markdown
74
73
  <a href="https://pypi.org/project/meringue">
75
74
  <img src="https://img.shields.io/pypi/pyversions/meringue.svg" alt="PyPI - Python Version" />
76
75
  </a>
76
+ <a href="https://pypi.org/project/meringue">
77
+ <img src="https://img.shields.io/pypi/frameworkversions/django/meringue" alt="PyPI - Versions from Framework Classifiers" />
78
+ </a>
77
79
  <!-- <a href="https://pypi.org/project/meringue">
78
80
  <img src="https://img.shields.io/pypi/format/meringue.svg" alt="PyPI - Format" />
79
81
  </a> -->
@@ -94,7 +96,7 @@ Description-Content-Type: text/markdown
94
96
  <img src="https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg" alt="Hatch project" />
95
97
  </a>
96
98
  <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank">
97
- <img src="https://img.shields.io/badge/docs-mkdocs_material-blue?logo=mdbook&logoColor=white" alt="Material for MkDocs" />
99
+ <img src="https://img.shields.io/badge/-Material_for_MkDocs-526CFE?logo=MaterialForMkDocs&logoColor=white&labelColor=gray" alt="Built with Material for MkDocs" />
98
100
  </a>
99
101
  <a href="https://github.com/charliermarsh/ruff" target="_blank">
100
102
  <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="linting - Ruff" />
@@ -110,11 +112,9 @@ Description-Content-Type: text/markdown
110
112
  </a>
111
113
  </p>
112
114
 
113
- Package with various functional (such as mixins, form utils, upload handlers and other) for Django Framework.
114
-
115
- This library is a set of various functionality that I use from project to project.
115
+ A package providing various utilities for Django, such as mixins, form tools, upload handlers, and more.
116
116
 
117
- The main task of this package is to clean up this functionality, test it, and also organize the documentation so that colleagues can understand how and what works.
117
+ This library is a collection of reusable components that I frequently use across different projects. Its primary purpose is to clean up and standardize these tools, ensure they are well-tested, and provide clear documentation to make it easy for colleagues to understand and use.
118
118
 
119
119
  However, if someone decides to use this functionality in their project, and even more so to add functionality or change the implementation to a more correct, beautiful or understandable one, I will only be happy, do not worry and feel free to write to me by [mail](mailto:dd@tovarisch.engineer), create an [issue](https://github.com/dd/Meringue/issues) or [pull request](https://github.com/dd/Meringue/pulls) on [github](https://github.com/dd/Meringue).
120
120
 
@@ -16,6 +16,9 @@
16
16
  <a href="https://pypi.org/project/meringue">
17
17
  <img src="https://img.shields.io/pypi/pyversions/meringue.svg" alt="PyPI - Python Version" />
18
18
  </a>
19
+ <a href="https://pypi.org/project/meringue">
20
+ <img src="https://img.shields.io/pypi/frameworkversions/django/meringue" alt="PyPI - Versions from Framework Classifiers" />
21
+ </a>
19
22
  <!-- <a href="https://pypi.org/project/meringue">
20
23
  <img src="https://img.shields.io/pypi/format/meringue.svg" alt="PyPI - Format" />
21
24
  </a> -->
@@ -36,7 +39,7 @@
36
39
  <img src="https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg" alt="Hatch project" />
37
40
  </a>
38
41
  <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank">
39
- <img src="https://img.shields.io/badge/docs-mkdocs_material-blue?logo=mdbook&logoColor=white" alt="Material for MkDocs" />
42
+ <img src="https://img.shields.io/badge/-Material_for_MkDocs-526CFE?logo=MaterialForMkDocs&logoColor=white&labelColor=gray" alt="Built with Material for MkDocs" />
40
43
  </a>
41
44
  <a href="https://github.com/charliermarsh/ruff" target="_blank">
42
45
  <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="linting - Ruff" />
@@ -52,11 +55,9 @@
52
55
  </a>
53
56
  </p>
54
57
 
55
- Package with various functional (such as mixins, form utils, upload handlers and other) for Django Framework.
56
-
57
- This library is a set of various functionality that I use from project to project.
58
+ A package providing various utilities for Django, such as mixins, form tools, upload handlers, and more.
58
59
 
59
- The main task of this package is to clean up this functionality, test it, and also organize the documentation so that colleagues can understand how and what works.
60
+ This library is a collection of reusable components that I frequently use across different projects. Its primary purpose is to clean up and standardize these tools, ensure they are well-tested, and provide clear documentation to make it easy for colleagues to understand and use.
60
61
 
61
62
  However, if someone decides to use this functionality in their project, and even more so to add functionality or change the implementation to a more correct, beautiful or understandable one, I will only be happy, do not worry and feel free to write to me by [mail](mailto:dd@tovarisch.engineer), create an [issue](https://github.com/dd/Meringue/issues) or [pull request](https://github.com/dd/Meringue/pulls) on [github](https://github.com/dd/Meringue).
62
63
 
@@ -1,4 +1,4 @@
1
- __version__ = "1.2.0.dev8"
1
+ __version__ = "1.3.0.dev1"
2
2
  """
3
3
  To update the version, use [hatch version](https://hatch.pypa.io/latest/version/#updating)
4
4
 
@@ -6,6 +6,7 @@ from rest_framework import views
6
6
 
7
7
  from meringue.api.utils import render_error_details
8
8
 
9
+
9
10
  try:
10
11
  from rest_framework_simplejwt.exceptions import DetailDictMixin
11
12
  except ImportError:
@@ -24,7 +25,7 @@ def exception_handler(exc, context):
24
25
  if response is None:
25
26
  return response
26
27
 
27
- if isinstance(exc, (Http404, PermissionDenied)):
28
+ if isinstance(exc, Http404 | PermissionDenied):
28
29
  # django Http404 and PermissionDenied errors are substituted for drf errors,
29
30
  # in `rest_framework.views.exception_handler` method.
30
31
  response.data = render_error_details(response.data["detail"])
@@ -1,5 +1,6 @@
1
1
  from rest_framework.exceptions import ErrorDetail
2
2
 
3
+
3
4
  try:
4
5
  from rest_framework_simplejwt.exceptions import DetailDictMixin
5
6
  except ImportError:
@@ -202,7 +202,8 @@ class Settings:
202
202
  """
203
203
 
204
204
  if attr not in self.defaults:
205
- raise AttributeError("Invalid setting key: '%s'" % attr)
205
+ msg = f"Invalid setting key: '{attr}'"
206
+ raise AttributeError(msg)
206
207
 
207
208
  if attr in self.deprecated_params:
208
209
  warnings.warn(self.deprecated_params[attr], DeprecationWarning, stacklevel=2)
@@ -0,0 +1,77 @@
1
+ import warnings
2
+ from functools import wraps
3
+
4
+ from django.conf import settings
5
+ from django.db import connections
6
+
7
+
8
+ class PgAdvisoryLock:
9
+ """
10
+ A context manager and decorator for using PostgreSQL advisory locks in Django.
11
+
12
+ This class provides a way to use PostgreSQL advisory locks, ensuring that the lock
13
+ is acquired and released correctly. It supports usage as both a context manager
14
+ and a function decorator. The class also checks if the database is PostgreSQL and
15
+ issues a warning if it is not.
16
+ """
17
+
18
+ def __init__(self, table: str, value: str, field: str = "id", using: str = "default"):
19
+ """
20
+ Attributes:
21
+ table: The name of the table to query for the lock ID.
22
+ value: The value to match in the field to retrieve the lock ID.
23
+ field: The name of the field to query for the lock ID.
24
+ using: The database alias to use.
25
+ """
26
+
27
+ self.table = table
28
+ self.value = value
29
+ self.field = field
30
+ self.db_name = using
31
+ self.is_postgresql = self._check_postgresql()
32
+
33
+ def __enter__(self):
34
+ self.lock()
35
+ return self
36
+
37
+ def __exit__(self, exc_type, exc_val, exc_tb):
38
+ self.unlock()
39
+ return False
40
+
41
+ def __call__(self, func):
42
+ @wraps(func)
43
+ def wrapped(*args, **kwargs):
44
+ with self:
45
+ return func(*args, **kwargs)
46
+
47
+ return wrapped
48
+
49
+ def _check_postgresql(self) -> bool:
50
+ engine = settings.DATABASES[self.db_name]["ENGINE"]
51
+ return "postgresql" in engine
52
+
53
+ def lock(self) -> None:
54
+ """
55
+ Acquires the advisory lock.
56
+ """
57
+
58
+ if not self.is_postgresql:
59
+ msg = "pg_advisory_lock is only supported with PostgreSQL databases."
60
+ warnings.warn(msg, UserWarning, stacklevel=2)
61
+ return
62
+
63
+ sql = f"SELECT pg_advisory_lock({self.field}) FROM {self.table} WHERE {self.field} = %s" # noqa: S608
64
+ with connections[self.db_name].cursor() as cursor:
65
+ cursor.execute(sql, [self.value])
66
+
67
+ def unlock(self) -> None:
68
+ """
69
+ Releases the advisory lock.
70
+ """
71
+
72
+ if not self.is_postgresql:
73
+ return
74
+
75
+ sql = f"SELECT pg_advisory_unlock({self.field}) FROM {self.table} WHERE {self.field} = %s" # noqa: S608
76
+ with connections[self.db_name].cursor() as cursor:
77
+ cursor.execute(sql, [self.value])
@@ -18,12 +18,10 @@ class SortingQuerySet(QuerySet):
18
18
  The selection for updating sorting can be pre-limited by filtering the list.
19
19
  """
20
20
  items = []
21
- sorting = 0
22
- for item in self:
21
+ for sorting, item in enumerate(self):
23
22
  if item.sorting != sorting:
24
23
  item.sorting = sorting
25
24
  items.append(item)
26
- sorting += 1
27
25
 
28
26
  return self.bulk_update(items, ["sorting"])
29
27
 
@@ -1,16 +1,18 @@
1
1
  from django.contrib.contenttypes.models import ContentType
2
- from django.db.models.fields.files import FileField
3
2
  from django.db.models.fields.files import FieldFile
3
+ from django.db.models.fields.files import FileField
4
4
  from django.db.models.fields.files import ImageField
5
5
  from django.db.models.fields.files import ImageFieldFile
6
6
  from django.urls import reverse
7
+
8
+ from meringue.conf import m_settings
9
+
10
+
7
11
  try:
8
12
  from django_hosts.resolvers import reverse as hosts_reverse
9
13
  except ImportError:
10
14
  hosts_reverse = None
11
15
 
12
- from meringue.conf import m_settings
13
-
14
16
 
15
17
  class ProtectedFileMixin:
16
18
  @property
@@ -86,7 +88,7 @@ class ProtectedFileField(FileField):
86
88
 
87
89
  def deconstruct(self):
88
90
  name, path, args, kwargs = super().deconstruct()
89
- kwargs['view_name'] = self.m_protected_view_name
91
+ kwargs["view_name"] = self.m_protected_view_name
90
92
  return name, path, args, kwargs
91
93
 
92
94
 
@@ -122,5 +124,5 @@ class ProtectedImageField(ImageField):
122
124
 
123
125
  def deconstruct(self):
124
126
  name, path, args, kwargs = super().deconstruct()
125
- kwargs['view_name'] = self.m_protected_view_name
127
+ kwargs["view_name"] = self.m_protected_view_name
126
128
  return name, path, args, kwargs
@@ -1,9 +1,7 @@
1
1
  import mimetypes
2
2
  from pathlib import Path
3
3
  from urllib.parse import quote
4
- from urllib.parse import urljoin
5
4
 
6
- from django.conf import settings
7
5
  from django.contrib.contenttypes.models import ContentType
8
6
  from django.core.exceptions import PermissionDenied
9
7
  from django.http import FileResponse
@@ -51,7 +49,10 @@ def x_accel_redirect_view(request, cid, field, pk, disp="inline"):
51
49
  response["X-Accel-Redirect"] = redirect_url
52
50
  return response
53
51
 
54
- return FileResponse(
55
- open(file.path, "rb"),
56
- as_attachment = disp == "attachment",
57
- )
52
+ with open(file.path, "rb") as tmp_file:
53
+ response = FileResponse(
54
+ tmp_file,
55
+ as_attachment=disp == "attachment",
56
+ )
57
+
58
+ return response
@@ -1,6 +1,5 @@
1
1
  import logging
2
- import os
3
- from pathlib import PurePath
2
+ from pathlib import Path
4
3
 
5
4
  from rest_framework.fields import ImageField
6
5
 
@@ -13,7 +12,7 @@ logger = logging.getLogger("meringue.thumbnail")
13
12
 
14
13
 
15
14
  def get_format_from_path(path):
16
- return FORMATS_BY_EXTENSIONS[PurePath(path).suffix.lower()]
15
+ return FORMATS_BY_EXTENSIONS[path.suffix.lower()]
17
16
 
18
17
 
19
18
  class BaseImageField(ImageField):
@@ -53,13 +52,15 @@ class MImageField(BaseImageField):
53
52
  if not value:
54
53
  return None
55
54
 
56
- if os.path.isfile(value.path):
57
- thumbnail = DefaultThumbnailer(value.path, job_chain=self.job_chain)
58
- optimized_image_url = thumbnail.get_image(get_format_from_path(value.path)).url
55
+ path = Path(value.path)
56
+
57
+ if path.exists():
58
+ thumbnail = DefaultThumbnailer(path, job_chain=self.job_chain)
59
+ optimized_image_url = thumbnail.get_image(get_format_from_path(path)).url
59
60
  result = optimized_image_url
60
61
 
61
62
  else:
62
- logger.error(f"File `{value.path}` not found")
63
+ logger.error(f"File `{path}` not found")
63
64
  result = _dummyimage([])
64
65
 
65
66
  return result
@@ -76,16 +77,23 @@ class MImageSetField(ImageField):
76
77
  def __init__(
77
78
  self,
78
79
  size=None,
79
- dimensions=None,
80
+ dimensions=(1, 2),
80
81
  base_job_chain=None,
81
82
  job_chains=None,
82
83
  **kwargs,
83
84
  ):
84
- if bool(size and dimensions) and bool(job_chains):
85
- msg = "Need to set `size` and `dimensions` or `job_chains` attribute (not both)."
85
+ if size and job_chains:
86
+ msg = "Need to set 'size' or 'job_chains' attribute (not both)."
86
87
  raise Exception(msg)
87
88
 
88
89
  elif size:
90
+ if not dimensions:
91
+ msg = (
92
+ "If you specify the 'sizes' attribute, "
93
+ "then the 'dimensions' attribute cannot be empty."
94
+ )
95
+ raise Exception(msg)
96
+
89
97
  self.job_chains = {}
90
98
 
91
99
  if 1 not in dimensions:
@@ -147,8 +155,10 @@ class MImageSetField(ImageField):
147
155
  if not value:
148
156
  return None
149
157
 
150
- if not os.path.isfile(value.path):
151
- logger.error(f"File `{value.path}` not found")
158
+ path = Path(value.path)
159
+
160
+ if not path.exists():
161
+ logger.error(f"File `{path}` not found")
152
162
  return [
153
163
  {
154
164
  "url": _dummyimage(self.job_chains[1]),
@@ -156,11 +166,11 @@ class MImageSetField(ImageField):
156
166
  },
157
167
  ]
158
168
 
159
- original_format = get_format_from_path(value.path)
169
+ original_format = get_format_from_path(path)
160
170
  result = []
161
171
 
162
172
  for dimension, job_chain in self.job_chains.items():
163
- thumbnail = DefaultThumbnailer(value.path, job_chain=job_chain)
173
+ thumbnail = DefaultThumbnailer(path, job_chain=job_chain)
164
174
  original = thumbnail.get_image(original_format)
165
175
  webp_thumbnail = thumbnail.get_image("WEBP")
166
176
 
@@ -131,7 +131,7 @@ class ThumbnailImage(AltersData):
131
131
  if self.out_format == constants.FORMAT_WEBP:
132
132
  return "image/webp"
133
133
 
134
- guess_type = mimetypes.guess_type(str(self.absolute_path))[0]
134
+ guess_type = mimetypes.guess_type(self.filename)
135
135
  return guess_type[0]
136
136
 
137
137
  @property
@@ -42,8 +42,8 @@ def _dummyimage(job_chain: str) -> str:
42
42
  height=size[1],
43
43
  )
44
44
 
45
- return "%simages/noise.png" % settings.STATIC_URL
46
- # return "%simages/none.gif" % settings.STATIC_URL
45
+ return f"{settings.STATIC_URL}images/noise.png"
46
+ # return f"{settings.STATIC_URL}images/none.gif"
47
47
 
48
48
 
49
49
  def get_thumbnail(
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling>=1.17.1"]
2
+ requires = ["hatchling>=1.22.5"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
@@ -17,8 +17,6 @@ classifiers = [
17
17
  "Environment :: Plugins",
18
18
  "Environment :: Web Environment",
19
19
  "Framework :: Django",
20
- "Framework :: Django :: 1",
21
- "Framework :: Django :: 1.11",
22
20
  "Framework :: Django :: 2",
23
21
  "Framework :: Django :: 2.0",
24
22
  "Framework :: Django :: 2.1",
@@ -31,6 +29,7 @@ classifiers = [
31
29
  "Framework :: Django :: 4.0",
32
30
  "Framework :: Django :: 4.1",
33
31
  "Framework :: Django :: 4.2",
32
+ "Framework :: Django :: 5.0",
34
33
  "Intended Audience :: Developers",
35
34
  "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
36
35
  "Natural Language :: English",
@@ -40,6 +39,7 @@ classifiers = [
40
39
  "Programming Language :: Python :: 3",
41
40
  "Programming Language :: Python :: 3.10",
42
41
  "Programming Language :: Python :: 3.11",
42
+ "Programming Language :: Python :: 3.12",
43
43
  "Programming Language :: Python :: 3 :: Only",
44
44
  "Programming Language :: Python :: Implementation :: CPython",
45
45
  "Programming Language :: Python :: Implementation :: PyPy",
@@ -49,7 +49,7 @@ classifiers = [
49
49
  "Topic :: Software Development :: Libraries",
50
50
  ]
51
51
  dependencies = [
52
- "Django>=1.11.17",
52
+ "Django>=2.0.0,<6",
53
53
  ]
54
54
  dynamic = ["version"]
55
55
 
@@ -58,6 +58,7 @@ modeltranslation = ["django-modeltranslation>=0.17,<0.19"]
58
58
  drf = ["djangorestframework>=3.13,<4"]
59
59
  drf-spectacular = ["drf-spectacular>=0.26.3,<1"]
60
60
  cryptodome = ["pycryptodome==3.20.0"]
61
+ django_hosts = ["django_hosts>=5.2,<7"]
61
62
 
62
63
  [project.urls]
63
64
  "Documentation" = "https://dd.github.io/Meringue"
@@ -85,15 +86,17 @@ exclude = [
85
86
  description = "Dev environment"
86
87
  python = "3.12"
87
88
  dependencies = [
88
- "pre-commit==3.5.0",
89
- "ipython==8.17.1",
90
- "django==4.2.7",
91
- "pytz==2023.3.post1",
89
+ "pre-commit==3.7.0",
90
+ "ipython==8.23.0",
91
+ "django==5.0.4",
92
+ "pytz==2024.1",
92
93
  "django-modeltranslation==0.18.11",
93
94
  "pycryptodome==3.20.0",
94
- "djangorestframework==3.14.0",
95
- "drf-spectacular==0.26.5",
96
- "Pillow==10.1.0",
95
+ "djangorestframework==3.15.1",
96
+ "djangorestframework-simplejwt==5.3.1",
97
+ "drf-spectacular==0.27.2",
98
+ "Pillow==10.3.0",
99
+ "django_hosts==6.0",
97
100
  ]
98
101
  [tool.hatch.envs.default.env-vars]
99
102
  DJANGO_SETTINGS_MODULE = "test_project.settings"
@@ -121,43 +124,48 @@ remove = [
121
124
  ]
122
125
  makemessages = "cd meringue/core && django-admin makemessages -l en -l ru --no-obsolete {args}"
123
126
  compilemessages = "cd meringue && django-admin compilemessages -l en -l ru {args}"
127
+ changelog-update = [
128
+ "git fetch origin --tags",
129
+ "gitmoji-changelog update \"$(hatch version)\" --preset generic --group-similar-commits",
130
+ ]
131
+ release-tag = "git tag -a \"v$(hatch version)\" -m \"v$(hatch version)\""
124
132
 
125
133
  [tool.hatch.envs.lint]
126
134
  description = "Lint environment"
127
135
  detached = true
128
- python = "3.11"
136
+ python = "3.12"
129
137
  dependencies = [
130
- "ruff==0.3.4",
131
- "black==24.3.0",
138
+ "ruff==0.4.9",
139
+ "black==24.4.2",
132
140
  ]
133
141
  [tool.hatch.envs.lint.scripts]
134
142
  check = [
135
- "ruff {args:.}",
143
+ "ruff check {args:.}",
136
144
  "black --check --diff --exclude=\".*migrations\\/.*$\" {args:.}",
137
145
  ]
138
146
  format = [
139
- "ruff --fix {args:.}",
147
+ "ruff format {args:.}",
140
148
  "black --exclude=\".*migrations\\/.*$\" {args:.}",
141
149
  ]
142
150
 
143
151
  [tool.hatch.envs.test]
144
152
  description = "Tests environment"
145
153
  detached = true
146
- python = "3.11"
154
+ python = "3.12"
147
155
  dependencies = [
148
- "pytest==7.4.2",
149
- "pytest-django==4.5.2",
150
- "pytest-cov==4.1.0",
151
- "Faker==19.6.1",
152
- "django==4.2",
153
- "pytz==2023.3.post1",
154
- "django-modeltranslation==0.18.11",
156
+ "pytest==8.1.1",
157
+ "pytest-django==4.8.0",
158
+ "pytest-cov==5.0.0", # for ci tests with cover
159
+ "Faker==24.7.1",
160
+ "pytz==2024.1",
155
161
  "pycryptodome==3.20.0",
156
- "djangorestframework==3.14.0",
157
- "djangorestframework-simplejwt==5.3.1",
158
- "drf-spectacular==0.26.4",
162
+ "drf-spectacular==0.27.2",
159
163
  "pillow==10.3.0",
160
- "django_hosts==5.2",
164
+ "django==5.0.4",
165
+ "django-modeltranslation==0.18.11",
166
+ "djangorestframework==3.15.1",
167
+ "djangorestframework-simplejwt==5.3.1",
168
+ "django_hosts==6.0",
161
169
  ]
162
170
  [tool.hatch.envs.test.env-vars]
163
171
  DJANGO_SETTINGS_MODULE = "test_project.settings"
@@ -170,15 +178,14 @@ makemigrations = "django-admin makemigrations {args}"
170
178
  description = "Test matrix environment"
171
179
  detached = true
172
180
  dependencies = [
173
- "pytest==7.4.2",
174
- "pytest-django==4.5.2",
175
- "pytest-cov==4.1.0", # for ci tests with cover
176
- "Faker==19.6.1",
177
- "pytz==2023.3.post1",
181
+ "pytest==8.1.1",
182
+ "pytest-django==4.8.0",
183
+ "pytest-cov==5.0.0", # for ci tests with cover
184
+ "Faker==24.7.1",
185
+ "pytz==2024.1",
178
186
  "pycryptodome==3.20.0",
179
- "drf-spectacular==0.26.4",
187
+ "drf-spectacular==0.27.2",
180
188
  "pillow==10.3.0",
181
- "django_hosts==5.2",
182
189
  ]
183
190
  [tool.hatch.envs.mtest.overrides]
184
191
  matrix.django.dependencies = [
@@ -186,6 +193,7 @@ matrix.django.dependencies = [
186
193
  { value = "django-modeltranslation=={matrix:modeltranslation}" },
187
194
  { value = "djangorestframework=={matrix:djangorestframework}" },
188
195
  { value = "djangorestframework-simplejwt=={matrix:simplejwt}" },
196
+ { value = "django_hosts=={matrix:django_hosts}" },
189
197
  ]
190
198
  [tool.hatch.envs.mtest.env-vars]
191
199
  DJANGO_SETTINGS_MODULE = "test_project.settings"
@@ -195,39 +203,56 @@ check = "pytest {args:-q}"
195
203
  [[tool.hatch.envs.mtest.matrix]]
196
204
  python = ["3.10", "3.11"]
197
205
  django = ["2.0"]
198
- modeltranslation = ["0.17.0"]
206
+ modeltranslation = ["0.17.0", "0.18.2"]
199
207
  djangorestframework = ["3.13.0"]
200
208
  simplejwt = ["5.2.0"]
209
+ django_hosts = ["5.2", "6.0"]
201
210
  [[tool.hatch.envs.mtest.matrix]]
202
211
  python = ["3.10", "3.11"]
203
212
  django = ["3.0"]
204
- modeltranslation = ["0.17.0"]
213
+ modeltranslation = ["0.17.0", "0.18.2"]
205
214
  djangorestframework = ["3.13.0", "3.14.0", "3.15.0"]
206
215
  simplejwt = ["5.2.0", "5.3.0"]
216
+ django_hosts = ["5.2", "6.0"]
207
217
  [[tool.hatch.envs.mtest.matrix]]
208
218
  python = ["3.10", "3.11"]
209
219
  django = ["4.0"]
210
- modeltranslation = ["0.17.0"]
211
- djangorestframework = ["3.14.0"]
220
+ modeltranslation = ["0.17.0", "0.18.2"]
221
+ djangorestframework = ["3.14.0", "3.15.0"]
212
222
  simplejwt = ["5.2.0", "5.3.0"]
223
+ django_hosts = ["5.2", "6.0"]
224
+ [[tool.hatch.envs.mtest.matrix]]
225
+ python = ["3.12"]
226
+ django = ["4.0"]
227
+ modeltranslation = ["0.17.0", "0.18.2"]
228
+ djangorestframework = ["3.14.0", "3.15.0"]
229
+ simplejwt = ["5.3.1"]
230
+ django_hosts = ["6.0"]
231
+ [[tool.hatch.envs.mtest.matrix]]
232
+ python = ["3.10", "3.11", "3.12"]
233
+ django = ["5.0"]
234
+ modeltranslation = ["0.17.0", "0.18.2"]
235
+ djangorestframework = ["3.15.0"]
236
+ simplejwt = ["5.3.1"]
237
+ django_hosts = ["6.0"]
213
238
 
214
239
  [tool.hatch.envs.docs]
215
240
  description = "Docs environment"
216
241
  detached = true
217
- python = "3.11"
242
+ python = "3.12"
218
243
  dependencies = [
219
- "mkdocs[i18n]==1.5.3",
244
+ "mkdocs[i18n]==1.6.1",
220
245
  "mkdocs-literate-nav==0.6.1",
221
- "mkdocs-material==9.5.16",
222
- "mkdocs-git-revision-date-localized-plugin==1.2.4",
223
- "mkdocs-git-authors-plugin==0.8.0",
224
- "mkdocstrings[python]==0.24.1",
225
- "black==24.3.0",
246
+ "mkdocs-material==9.5.40",
247
+ "mkdocs-git-revision-date-localized-plugin==1.2.9",
248
+ "mkdocs-git-authors-plugin==0.9.0",
249
+ "mkdocstrings[python]==0.26.1",
250
+ "black==24.10.0",
226
251
  "mkdocs-minify-plugin==0.8.0",
227
252
  "mkdocs-gen-files==0.5.0",
228
- "Pygments==2.17.2",
229
- "mike==1.1.2",
230
- "linkchecker==10.4.0",
253
+ "Pygments==2.18.0",
254
+ "mike==2.1.3",
255
+ "linkchecker==10.5.0",
231
256
  ]
232
257
  [tool.hatch.envs.docs.env-vars]
233
258
  MERINGUE_MKDOCS_ENABLE_MINIFY = "false"
@@ -235,7 +260,7 @@ MKDOCS_CONFIG = "mkdocs.yml"
235
260
  [tool.hatch.envs.docs.scripts]
236
261
  build = "mkdocs build --config-file {env:MKDOCS_CONFIG} --clean --strict {args}"
237
262
  serve = "mkdocs serve --config-file {env:MKDOCS_CONFIG} --dev-addr localhost:8000 {args}"
238
- ci-build = "mike deploy --config-file {env:MKDOCS_CONFIG} --update-aliases {args}"
263
+ ci-build = "mike deploy --config-file {env:MKDOCS_CONFIG} {args}"
239
264
  validate = "linkchecker --config .linkcheckerrc docs/dist"
240
265
  build-check = [
241
266
  "build --no-directory-urls",
@@ -244,13 +269,14 @@ build-check = [
244
269
 
245
270
  [tool.black]
246
271
  line-length = 100
247
- target-version = ["py311"]
272
+ target-version = ["py310"]
248
273
 
249
274
  [tool.ruff]
250
- target-version = "py311"
275
+ target-version = "py310"
251
276
  line-length = 100
252
277
  show-fixes = true
253
278
  # update-check = true
279
+ [tool.ruff.lint]
254
280
  select = [
255
281
  "A",
256
282
  "B",
@@ -292,32 +318,34 @@ ignore = [
292
318
  # "PLC1901", # empty string comparisons
293
319
  # "PLW2901", # `for` loop variable overwritten
294
320
  "SIM114", # Combine `if` branches using logical `or` operator
321
+ "ISC001", # Disabled as ruff recomendation from warning
295
322
  ]
296
323
  # unfixable = [
297
324
  # # Don't touch unused imports
298
325
  # "F401",
299
326
  # ]
300
- [tool.ruff.extend-per-file-ignores]
327
+ [tool.ruff.lint.per-file-ignores]
301
328
  "__init__.py" = ["F401", "F403"]
302
329
  "test_*.py" = ["S101", "PLR2004", "DTZ001", "RUF012"]
303
330
  "*/migrations/*" = ["I", "E", "Q", "RUF"]
304
331
  "test_project/*models.py" = ["RUF012"]
305
332
  "test_project/*views.py" = ["RUF012"]
306
- [tool.ruff.flake8-import-conventions]
307
- [tool.ruff.flake8-import-conventions.extend-aliases]
333
+ [tool.ruff.lint.flake8-import-conventions.extend-aliases]
308
334
  "datetime" = "dt"
309
335
  # [tool.ruff.flake8-quotes]
310
336
  # inline-quotes = "single"
311
- [tool.ruff.flake8-unused-arguments]
337
+ [tool.ruff.lint.flake8-unused-arguments]
312
338
  ignore-variadic-names = true
313
- [tool.ruff.isort]
339
+ [tool.ruff.lint.isort]
314
340
  force-single-line = true
315
341
  known-first-party = ["meringue"]
316
342
  lines-after-imports = 2
317
343
  no-lines-before = ["local-folder"]
318
344
  section-order = ["future", "standard-library", "django", "third-party", "first-party", "local-folder"]
319
- [tool.ruff.isort.sections]
345
+ [tool.ruff.lint.isort.sections]
320
346
  django = ["django"]
347
+ [tool.ruff.format]
348
+ exclude = ["*/migrations/*"]
321
349
 
322
350
  [tool.isort]
323
351
  known_first_party = ["meringue"]
File without changes
File without changes
File without changes