accrete 0.0.58__py3-none-any.whl → 0.0.60__py3-none-any.whl

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.
@@ -158,7 +158,9 @@ class Filter:
158
158
  'PositiveSmallIntegerField': self.int_param,
159
159
  'DateTimeField': self.date_time_param,
160
160
  'DateField': self.date_param,
161
- 'ForeignKey': self.foreign_key_param
161
+ 'ForeignKey': self.foreign_key_param,
162
+ 'FileField': self.file_param,
163
+ 'ImageField': self.file_param
162
164
  }
163
165
 
164
166
  def parse_choices(self, choices):
@@ -269,6 +271,9 @@ class Filter:
269
271
  return self.null_param(key, value)
270
272
  return ''
271
273
 
274
+ def file_param(self, key, value):
275
+ return self.null_param(key, value)
276
+
272
277
  def null_param(self, key, value):
273
278
  options = self.parse_choices([
274
279
  ('true', _('True')),
accrete/middleware.py CHANGED
@@ -82,10 +82,12 @@ class HtmxRedirectMiddleware(MiddlewareMixin):
82
82
  @staticmethod
83
83
  def process_response(request, response):
84
84
  is_htmx = request.headers.get('HX-Request', 'false') == 'true'
85
- is_redirect = isinstance(
86
- response, (HttpResponseRedirect, HttpResponsePermanentRedirect)
87
- )
88
- if is_htmx and is_redirect:
89
- response['HX-Redirect'] = response['Location']
85
+ if not is_htmx:
86
+ return response
87
+ is_redirect = isinstance(response, HttpResponseRedirect)
88
+ is_permanent_redirect = isinstance(response, HttpResponsePermanentRedirect)
89
+ if is_redirect or is_permanent_redirect:
90
90
  response.status_code = 200
91
+ header = 'HX-Location' if is_redirect else 'HX-Redirect'
92
+ response[header] = response['Location']
91
93
  return response
accrete/storage.py ADDED
@@ -0,0 +1,37 @@
1
+ import os
2
+ from urllib.parse import urljoin
3
+ from django.core.files.storage import FileSystemStorage
4
+ from django.utils.encoding import filepath_to_uri
5
+ from django.utils.deconstruct import deconstructible
6
+ from django.conf import settings
7
+ from accrete.tenant import get_tenant
8
+
9
+
10
+ @deconstructible(path="django.core.files.storage.FileSystemStorage")
11
+ class TenantFileSystemStorage(FileSystemStorage):
12
+
13
+ @property
14
+ def base_location(self):
15
+ tenant = get_tenant()
16
+ base_dir = f'{settings.MEDIA_ROOT}/{tenant.id}'
17
+ os.makedirs(os.path.dirname(base_dir), exist_ok=True)
18
+ return base_dir
19
+
20
+ @property
21
+ def location(self):
22
+ return os.path.abspath(self.base_location)
23
+
24
+ @property
25
+ def base_url(self):
26
+ if self._base_url is not None and not self._base_url.endswith("/"):
27
+ self._base_url += "/"
28
+ res = self._value_or_setting(self._base_url, f'{settings.MEDIA_URL.strip("/")}/{get_tenant().id}')
29
+ return res
30
+
31
+ def url(self, name):
32
+ if self.base_url is None:
33
+ raise ValueError("This file is not accessible via a URL.")
34
+ url = filepath_to_uri(name)
35
+ if url is not None:
36
+ url = url.lstrip("/")
37
+ return urljoin(f'/{self.base_url}/', url)
accrete/urls.py ADDED
@@ -0,0 +1,8 @@
1
+ from django.urls import path, include
2
+ from django.conf import settings
3
+ from accrete.views import get_tenant_file
4
+
5
+
6
+ urlpatterns = [
7
+ path(f'{settings.MEDIA_URL.strip("/")}/<int:tenant_id>/<path:filepath>', get_tenant_file)
8
+ ]
accrete/views.py CHANGED
@@ -1,31 +1,15 @@
1
+ import os
1
2
  from functools import wraps
3
+ from django.http import HttpResponse, HttpResponseNotFound
2
4
  from django.contrib.auth.mixins import LoginRequiredMixin
3
5
  from django.contrib.auth.views import login_required
4
6
  from django.core.exceptions import ImproperlyConfigured
5
7
  from django.shortcuts import redirect
6
8
  from django.conf import settings
9
+ from accrete.tenant import get_tenant
7
10
  from . import config
8
11
 
9
12
 
10
- def tenant_required(
11
- redirect_field_name: str = None,
12
- login_url: str = None
13
- ):
14
- def decorator(f):
15
- @wraps(f)
16
- @login_required(
17
- redirect_field_name=redirect_field_name,
18
- login_url=login_url
19
- )
20
- def _wrapped_view(request, *args, **kwargs):
21
- tenant = request.tenant
22
- if not tenant:
23
- return redirect(config.ACCRETE_TENANT_NOT_SET_URL)
24
- return f(request, *args, **kwargs)
25
- return _wrapped_view
26
- return decorator
27
-
28
-
29
13
  class TenantRequiredMixin(LoginRequiredMixin):
30
14
 
31
15
  tenant_missing_url = None
@@ -80,3 +64,34 @@ class TenantRequiredMixin(LoginRequiredMixin):
80
64
  f"{cls_name}.get_member_not_authorized_url()."
81
65
  )
82
66
  return url
67
+
68
+
69
+ def tenant_required(
70
+ redirect_field_name: str = None,
71
+ login_url: str = None
72
+ ):
73
+ def decorator(f):
74
+ @wraps(f)
75
+ @login_required(
76
+ redirect_field_name=redirect_field_name,
77
+ login_url=login_url
78
+ )
79
+ def _wrapped_view(request, *args, **kwargs):
80
+ tenant = request.tenant
81
+ if not tenant:
82
+ return redirect(config.ACCRETE_TENANT_NOT_SET_URL)
83
+ return f(request, *args, **kwargs)
84
+ return _wrapped_view
85
+ return decorator
86
+
87
+
88
+ @tenant_required()
89
+ def get_tenant_file(request, tenant_id, filepath):
90
+ tenant = get_tenant()
91
+ if tenant.id != tenant_id:
92
+ return HttpResponseNotFound()
93
+ filepath = f'{settings.MEDIA_ROOT}/{tenant_id}/{filepath}'
94
+ if not os.path.exists(filepath):
95
+ return HttpResponseNotFound()
96
+ with open(filepath, 'rb') as f:
97
+ return HttpResponse(f)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: accrete
3
- Version: 0.0.58
3
+ Version: 0.0.60
4
4
  Summary: Django Shared Schema Multi Tenant
5
5
  Author-email: Benedikt Jilek <benedikt.jilek@pm.me>
6
6
  License: Copyright (c) 2023 Benedikt Jilek
@@ -5,11 +5,13 @@ accrete/apps.py,sha256=F7ynMLHJr_6bRujWtZVUzCliY2CGKiDvyUmL4F68L2E,146
5
5
  accrete/config.py,sha256=eJUbvyBO3DvAD6xkVKjTAzlXy7V7EK9bVyb91girfUs,299
6
6
  accrete/forms.py,sha256=2vUh80qNvPDD8Zl3agKBSJEQeY7bXVLOx_SAB34wf8E,1359
7
7
  accrete/managers.py,sha256=CaIJLeBry4NYIXaVUrdUjp7zx4sEWgv-1-ssI1m-EOs,1156
8
- accrete/middleware.py,sha256=bUsvhdVdUlbqB-Hd5Y5w6WL8rO8It1VSGA5EZaEPA3o,3266
8
+ accrete/middleware.py,sha256=IABs2pAV-kXjp3n5_Wsc7reXGjU5FhmCbvFB8cC4ZRM,3422
9
9
  accrete/models.py,sha256=grvRNXg0ZYAJU3KAIX-svuZXeXlfqP4qEJ00nlbV594,5145
10
+ accrete/storage.py,sha256=z7pHdQFw0hFGrrbfqIh7KFxabQ_JGqoPebmiX9TLmeU,1254
10
11
  accrete/tenant.py,sha256=g3ZuTrQr2zqmIopNBRQeCmHEK2R3dlUme_hOV765J6U,1778
11
12
  accrete/tests.py,sha256=Agltbzwwh5htvq_Qi9vqvxutzmg_GwgPS_N19xJZRlw,7197
12
- accrete/views.py,sha256=9-sgCFe_CyG-wllAcIOLujyueiq66C-zg0U7Uf5Y2wU,2954
13
+ accrete/urls.py,sha256=goDFR-yhOlLLy7AMi9pmh2aBkxdtZtwXNg6mwI2zPhU,227
14
+ accrete/views.py,sha256=fFvga5kniVYCV5J7PIYKPNK4EWAXX3I1eEw22y0A1_E,3432
13
15
  accrete/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
16
  accrete/contrib/sequence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
17
  accrete/contrib/sequence/admin.py,sha256=mTjab5cVklRUIQcSrsUo-_JgtXEsSdcFj_gfWhlStS4,273
@@ -37,7 +39,7 @@ accrete/contrib/ui/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,6
37
39
  accrete/contrib/ui/apps.py,sha256=E0ao2ox6PQ3ldfeR17FXJUUJuGiWjm2DPCxHbPXGzls,152
38
40
  accrete/contrib/ui/context.py,sha256=tb4x_G4VsIa1LKIWl-CvLVtEd_DCbrClA-0Wmnne9bc,8363
39
41
  accrete/contrib/ui/elements.py,sha256=0F5q0-XLdAWQjdYLMQt6RXqz4xPD_ECrhs0kcBfYkvo,1856
40
- accrete/contrib/ui/filter.py,sha256=L7sBpmk454kaSZIQXe9hNj1Xbna8hJ2P-YTvmM7T5FE,12243
42
+ accrete/contrib/ui/filter.py,sha256=UAIkUNKu2nVQLafwZlkQu4PDfOPyFM61e_mE7OWRhYs,12410
41
43
  accrete/contrib/ui/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
42
44
  accrete/contrib/ui/urls.py,sha256=TUBlz_CGs9InTZoxM78GSnucA73I8knoh_obt12RUHM,186
43
45
  accrete/contrib/ui/views.py,sha256=WpBKMsxFFG8eG4IN7TW_TPE6i3OFF7gnLDTK7JMKti8,191
@@ -220,7 +222,7 @@ accrete/utils/dates.py,sha256=apM6kt6JhGrKgoT0jfav1W-8AUVTxNc9xt3fJQ2n0JI,1492
220
222
  accrete/utils/forms.py,sha256=Lll-DvAhKZDw72XeuCtb4wxQEJNFp7lQWh_Z1GyH3Zk,3049
221
223
  accrete/utils/http.py,sha256=mAtQRgADv7zu1_j7A-EKVyb-oqa5a21i4Gd0QfjzGV0,3540
222
224
  accrete/utils/models.py,sha256=EEhv7-sQVtQD24PEb3XcDUAh3VVhVFoMMLyFrDjGEaI,706
223
- accrete-0.0.58.dist-info/METADATA,sha256=uI3yeGaVRRZWlYA_BK6VrLY9dZpkpELDU4qRh5Xwxcs,4892
224
- accrete-0.0.58.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
225
- accrete-0.0.58.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
226
- accrete-0.0.58.dist-info/RECORD,,
225
+ accrete-0.0.60.dist-info/METADATA,sha256=EmTPYxeLQvtOr0nPLX-AcCzbzJViET1tufDFhgqPfak,4892
226
+ accrete-0.0.60.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
227
+ accrete-0.0.60.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
228
+ accrete-0.0.60.dist-info/RECORD,,