meringue 1.2.0.dev5__tar.gz → 1.2.0.dev7__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.
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/.gitignore +1 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/PKG-INFO +1 -1
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/__init__.py +1 -1
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/conf/__init__.py +1 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/conf/default_settings.py +5 -0
- meringue-1.2.0.dev7/meringue/protected/fields.py +126 -0
- meringue-1.2.0.dev7/meringue/protected/utils.py +6 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/protected/views.py +14 -14
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/pyproject.toml +2 -0
- meringue-1.2.0.dev5/meringue/protected/fields.py +0 -75
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/AUTHORS +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/LICENSE +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/README.md +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/apps.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/docs/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/docs/patchers.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/docs/views.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/handlers.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/routers.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/api/utils.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/apps.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/locale/en/LC_MESSAGES/django.po +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/locale/ru/LC_MESSAGES/django.po +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/models.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/options.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/query.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/templatetags/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/templatetags/meringue_base.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/translation.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/upload_handlers.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/utils/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/utils/crypt.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/utils/datetime.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/utils/frontend.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/core/views.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/protected/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/protected/apps.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/actions.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/apps.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/constants.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/drf_fields.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/exceptions.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/generators.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/images.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/properties.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/shortcuts.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/storage.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/templatetags/__init__.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/templatetags/m_thumbnails.py +0 -0
- {meringue-1.2.0.dev5 → meringue-1.2.0.dev7}/meringue/thumbnail/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: meringue
|
|
3
|
-
Version: 1.2.0.
|
|
3
|
+
Version: 1.2.0.dev7
|
|
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
|
|
@@ -125,3 +125,8 @@ response.
|
|
|
125
125
|
|
|
126
126
|
The view [protected_file_view][meringue.protected.views.protected_file_view] adds the X-Accel-Redirect header with a link to the file.
|
|
127
127
|
"""
|
|
128
|
+
|
|
129
|
+
PROTECTED_NGINX_LOCATION_GETTER: Final[str] = "meringue.protected.utils.nginx_location_getter"
|
|
130
|
+
"""
|
|
131
|
+
Default getter for the link to the file where nginx should serve it after access verification.
|
|
132
|
+
"""
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from django.db.models.fields.files import FileField
|
|
3
|
+
from django.db.models.fields.files import FieldFile
|
|
4
|
+
from django.db.models.fields.files import ImageField
|
|
5
|
+
from django.db.models.fields.files import ImageFieldFile
|
|
6
|
+
from django.urls import reverse
|
|
7
|
+
try:
|
|
8
|
+
from django_hosts.resolvers import reverse as hosts_reverse
|
|
9
|
+
except ImportError:
|
|
10
|
+
hosts_reverse = None
|
|
11
|
+
|
|
12
|
+
from meringue.conf import m_settings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProtectedFileMixin:
|
|
16
|
+
@property
|
|
17
|
+
def url(self):
|
|
18
|
+
"""
|
|
19
|
+
Link to the view with access verification to the file
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
self._require_file()
|
|
23
|
+
|
|
24
|
+
reverse_kwargs = {
|
|
25
|
+
"cid": ContentType.objects.get_for_model(self.instance.__class__).id,
|
|
26
|
+
"field": self.field.name,
|
|
27
|
+
"pk": self.instance.pk,
|
|
28
|
+
"disp": self.field.m_protected_disposition,
|
|
29
|
+
}
|
|
30
|
+
if self.field.m_protected_host_name:
|
|
31
|
+
result_url = hosts_reverse(
|
|
32
|
+
self.field.m_protected_view_name,
|
|
33
|
+
kwargs=reverse_kwargs,
|
|
34
|
+
host=self.field.m_protected_host_name,
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
result_url = reverse(self.field.m_protected_view_name, kwargs=reverse_kwargs)
|
|
38
|
+
return result_url
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def redirect_url(self):
|
|
42
|
+
"""
|
|
43
|
+
Link to the file where nginx should serve it after access verification.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
return self.field.m_protected_nginx_location_getter(self)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def original_url(self):
|
|
50
|
+
"""
|
|
51
|
+
The original link to the file.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
self._require_file()
|
|
55
|
+
return self.storage.url(self.name)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ProtectedFieldFile(ProtectedFileMixin, FieldFile):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ProtectedFileField(FileField):
|
|
63
|
+
"""
|
|
64
|
+
A field that adds the mechanism of a protected file.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
attr_class = ProtectedFieldFile
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
view_name,
|
|
72
|
+
verbose_name=None,
|
|
73
|
+
name=None,
|
|
74
|
+
upload_to="protected",
|
|
75
|
+
storage=None,
|
|
76
|
+
host_name=None,
|
|
77
|
+
disposition="attachment",
|
|
78
|
+
nginx_location_getter=m_settings.PROTECTED_NGINX_LOCATION_GETTER,
|
|
79
|
+
**kwargs,
|
|
80
|
+
):
|
|
81
|
+
self.m_protected_view_name = view_name
|
|
82
|
+
self.m_protected_host_name = host_name
|
|
83
|
+
self.m_protected_disposition = disposition
|
|
84
|
+
self.m_protected_nginx_location_getter = nginx_location_getter
|
|
85
|
+
super().__init__(verbose_name, name, upload_to, storage, **kwargs)
|
|
86
|
+
|
|
87
|
+
def deconstruct(self):
|
|
88
|
+
name, path, args, kwargs = super().deconstruct()
|
|
89
|
+
kwargs['view_name'] = self.m_protected_view_name
|
|
90
|
+
return name, path, args, kwargs
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ProtectedImageFieldFile(ProtectedFileMixin, ImageFieldFile):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ProtectedImageField(ImageField):
|
|
98
|
+
"""
|
|
99
|
+
A field that adds the mechanism of a protected image.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
attr_class = ProtectedImageFieldFile
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
view_name,
|
|
107
|
+
verbose_name=None,
|
|
108
|
+
name=None,
|
|
109
|
+
width_field=None,
|
|
110
|
+
height_field=None,
|
|
111
|
+
host_name=None,
|
|
112
|
+
disposition="attachment",
|
|
113
|
+
nginx_location_getter=m_settings.PROTECTED_NGINX_LOCATION_GETTER,
|
|
114
|
+
**kwargs,
|
|
115
|
+
):
|
|
116
|
+
kwargs.setdefault("upload_to", "protected")
|
|
117
|
+
self.m_protected_view_name = view_name
|
|
118
|
+
self.m_protected_host_name = host_name
|
|
119
|
+
self.m_protected_disposition = disposition
|
|
120
|
+
self.m_protected_nginx_location_getter = nginx_location_getter
|
|
121
|
+
super().__init__(verbose_name, name, width_field, height_field, **kwargs)
|
|
122
|
+
|
|
123
|
+
def deconstruct(self):
|
|
124
|
+
name, path, args, kwargs = super().deconstruct()
|
|
125
|
+
kwargs['view_name'] = self.m_protected_view_name
|
|
126
|
+
return name, path, args, kwargs
|
|
@@ -15,21 +15,18 @@ from meringue.protected.fields import ProtectedFieldFile
|
|
|
15
15
|
from meringue.protected.fields import ProtectedImageFieldFile
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def x_accel_redirect_view(request, cid, field, pk, disp="inline"):
|
|
19
19
|
"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
alias /home/rpl/rpl-2023-back/public/media/protected/;
|
|
28
|
-
}
|
|
29
|
-
```
|
|
20
|
+
The view checks the user's access to view and serves the file.
|
|
21
|
+
|
|
22
|
+
If you enable the parameter
|
|
23
|
+
[PROTECTED_SERVE_WITH_NGINX][meringue.conf.default_settings.PROTECTED_SERVE_WITH_NGINX], the
|
|
24
|
+
file will not be served; instead, the response will contain the X-Accel-Redirect header
|
|
25
|
+
pointing to the original file. It is expected that nginx will return the file using the
|
|
26
|
+
[internal](https://nginx.org/en/docs/http/ngx_http_core_module.html#internal) parameter.
|
|
30
27
|
"""
|
|
31
28
|
|
|
32
|
-
contenttype = ContentType.objects.get(id=
|
|
29
|
+
contenttype = ContentType.objects.get(id=cid)
|
|
33
30
|
|
|
34
31
|
if not request.user.has_perm(f"{contenttype.app_label}.view_{contenttype.model}"):
|
|
35
32
|
raise PermissionDenied()
|
|
@@ -44,7 +41,7 @@ def protected_file_view(request, contenttype_id, field, pk):
|
|
|
44
41
|
file_name = Path(file.name).name
|
|
45
42
|
response = HttpResponse()
|
|
46
43
|
response["Content-Type"] = mimetypes.guess_type(file.path)[0]
|
|
47
|
-
response["Content-Disposition"] = f"
|
|
44
|
+
response["Content-Disposition"] = f"{disp}; filename={quote(file_name)}"
|
|
48
45
|
|
|
49
46
|
if isinstance(file, ProtectedFieldFile | ProtectedImageFieldFile):
|
|
50
47
|
redirect_url = file.original_url
|
|
@@ -54,4 +51,7 @@ def protected_file_view(request, contenttype_id, field, pk):
|
|
|
54
51
|
response["X-Accel-Redirect"] = redirect_url
|
|
55
52
|
return response
|
|
56
53
|
|
|
57
|
-
return FileResponse(
|
|
54
|
+
return FileResponse(
|
|
55
|
+
open(file.path, "rb"),
|
|
56
|
+
as_attachment = disp == "attachment",
|
|
57
|
+
)
|
|
@@ -157,6 +157,7 @@ dependencies = [
|
|
|
157
157
|
"djangorestframework-simplejwt==5.3.1",
|
|
158
158
|
"drf-spectacular==0.26.4",
|
|
159
159
|
"pillow==10.3.0",
|
|
160
|
+
"django_hosts==5.2",
|
|
160
161
|
]
|
|
161
162
|
[tool.hatch.envs.test.env-vars]
|
|
162
163
|
DJANGO_SETTINGS_MODULE = "test_project.settings"
|
|
@@ -177,6 +178,7 @@ dependencies = [
|
|
|
177
178
|
"pycryptodome==3.20.0",
|
|
178
179
|
"drf-spectacular==0.26.4",
|
|
179
180
|
"pillow==10.3.0",
|
|
181
|
+
"django_hosts==5.2",
|
|
180
182
|
]
|
|
181
183
|
[tool.hatch.envs.mtest.overrides]
|
|
182
184
|
matrix.django.dependencies = [
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
from django.contrib.contenttypes.models import ContentType
|
|
2
|
-
from django.db.models.fields.files import FileField
|
|
3
|
-
from django.db.models.fields.files import FieldFile
|
|
4
|
-
from django.db.models.fields.files import ImageField
|
|
5
|
-
from django.db.models.fields.files import ImageFieldFile
|
|
6
|
-
from django.urls import reverse
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ProtectedFileMixin:
|
|
10
|
-
@property
|
|
11
|
-
def url(self):
|
|
12
|
-
self._require_file()
|
|
13
|
-
result_url = reverse(
|
|
14
|
-
self.field.m_protected_view_name,
|
|
15
|
-
kwargs={
|
|
16
|
-
"contenttype_id": ContentType.objects.get_for_model(self.instance.__class__).id,
|
|
17
|
-
"field": self.field.name,
|
|
18
|
-
"pk": self.instance.pk,
|
|
19
|
-
},
|
|
20
|
-
)
|
|
21
|
-
return result_url
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def original_url(self):
|
|
25
|
-
self._require_file()
|
|
26
|
-
return self.storage.url(self.name)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ProtectedFieldFile(ProtectedFileMixin, FieldFile):
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ProtectedFileField(FileField):
|
|
34
|
-
"""
|
|
35
|
-
A field that adds the mechanism of a protected file.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
attr_class = ProtectedFieldFile
|
|
39
|
-
|
|
40
|
-
def __init__(
|
|
41
|
-
self,
|
|
42
|
-
verbose_name=None,
|
|
43
|
-
name=None,
|
|
44
|
-
upload_to="protected",
|
|
45
|
-
storage=None,
|
|
46
|
-
protected_view_name="meringue-protected-file",
|
|
47
|
-
**kwargs,
|
|
48
|
-
):
|
|
49
|
-
self.m_protected_view_name = protected_view_name
|
|
50
|
-
super().__init__(verbose_name, name, upload_to, storage, **kwargs)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class ProtectedImageFieldFile(ProtectedFileMixin, ImageFieldFile):
|
|
54
|
-
pass
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class ProtectedImageField(ImageField):
|
|
58
|
-
"""
|
|
59
|
-
A field that adds the mechanism of a protected image.
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
attr_class = ProtectedImageFieldFile
|
|
63
|
-
|
|
64
|
-
def __init__(
|
|
65
|
-
self,
|
|
66
|
-
verbose_name=None,
|
|
67
|
-
name=None,
|
|
68
|
-
width_field=None,
|
|
69
|
-
height_field=None,
|
|
70
|
-
protected_view_name="meringue-protected-file",
|
|
71
|
-
**kwargs,
|
|
72
|
-
):
|
|
73
|
-
kwargs.setdefault("upload_to", "protected")
|
|
74
|
-
self.m_protected_view_name = protected_view_name
|
|
75
|
-
super().__init__(verbose_name, name, width_field, height_field, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|