photo-objects 0.0.6__tar.gz → 0.0.7__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.
- {photo_objects-0.0.6 → photo_objects-0.0.7}/PKG-INFO +1 -1
- photo_objects-0.0.7/photo_objects/django/views/ui/configuration.py +188 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/ui/photo.py +3 -3
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects.egg-info/PKG-INFO +1 -1
- {photo_objects-0.0.6 → photo_objects-0.0.7}/pyproject.toml +1 -1
- photo_objects-0.0.6/photo_objects/django/views/ui/configuration.py +0 -117
- {photo_objects-0.0.6 → photo_objects-0.0.7}/LICENSE +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/README.md +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/config.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/admin.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/api/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/api/album.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/api/auth.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/api/photo.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/api/utils.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/apps.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/context_processors.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/forms.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/management/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/management/commands/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/management/commands/create-initial-admin-account.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/management/commands/create-site-albums.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/migrations/0001_initial.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/migrations/0002_created_at_updated_at.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/migrations/0003_admin_visibility.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/migrations/0004_camera_setup_and_settings.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/migrations/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/models.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/objsto.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/signals.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/templatetags/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/templatetags/photo_objects_extras.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/tests/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/tests/test_album.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/tests/test_auth.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/tests/test_photo.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/tests/test_utils.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/tests/utils.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/urls.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/api/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/api/album.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/api/auth.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/api/photo.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/api/utils.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/ui/__init__.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/ui/album.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/ui/utils.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/views/utils.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/error.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/img.py +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects.egg-info/SOURCES.txt +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects.egg-info/dependency_links.txt +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects.egg-info/requires.txt +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects.egg-info/top_level.txt +0 -0
- {photo_objects-0.0.6 → photo_objects-0.0.7}/setup.cfg +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from django.http import HttpRequest
|
|
4
|
+
from django.shortcuts import render
|
|
5
|
+
from django.urls import reverse
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
|
|
8
|
+
from photo_objects.django.models import Album
|
|
9
|
+
from photo_objects.django.views.utils import BackLink, render_markdown
|
|
10
|
+
|
|
11
|
+
from .utils import json_problem_as_html
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Validation:
|
|
16
|
+
check: str
|
|
17
|
+
status: str
|
|
18
|
+
detail: str = None
|
|
19
|
+
|
|
20
|
+
def __post_init__(self):
|
|
21
|
+
self.detail = render_markdown(self.detail)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def status(ok: bool, warning=False) -> str:
|
|
25
|
+
if ok:
|
|
26
|
+
return _("OK")
|
|
27
|
+
if warning:
|
|
28
|
+
return _("Warning")
|
|
29
|
+
return _("Error")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def uses_https(request: HttpRequest) -> Validation:
|
|
33
|
+
ok = request.is_secure()
|
|
34
|
+
warning = False
|
|
35
|
+
detail = (
|
|
36
|
+
'The request received by the API server was '
|
|
37
|
+
f'{"" if ok else "not "}secure.')
|
|
38
|
+
|
|
39
|
+
if not ok:
|
|
40
|
+
referer = request.META.get("HTTP_REFERER", "")
|
|
41
|
+
if request.site.domain in referer and referer.startswith("https://"):
|
|
42
|
+
warning = True
|
|
43
|
+
|
|
44
|
+
detail += _(
|
|
45
|
+
' If you are running the API server behind a reverse proxy or '
|
|
46
|
+
'a load-balancer, ensure that HTTPS termination is configured '
|
|
47
|
+
f'correctly. ({request.META})')
|
|
48
|
+
|
|
49
|
+
return Validation(
|
|
50
|
+
check=_("Site is served over HTTPS"),
|
|
51
|
+
status=status(ok, warning),
|
|
52
|
+
detail=detail
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def site_is_configured(request: HttpRequest) -> Validation:
|
|
57
|
+
detail = (
|
|
58
|
+
'Site domain is configured to a non-default value: '
|
|
59
|
+
f'`{request.site.domain}`'
|
|
60
|
+
)
|
|
61
|
+
try:
|
|
62
|
+
ok = request.site.domain != "example.com"
|
|
63
|
+
if not ok:
|
|
64
|
+
detail = (
|
|
65
|
+
'Site domain is set to `example.com`. This is a placeholder '
|
|
66
|
+
'domain and should be changed to the actual domain of the '
|
|
67
|
+
'site.')
|
|
68
|
+
except Exception as e:
|
|
69
|
+
ok = False
|
|
70
|
+
detail = (
|
|
71
|
+
f"Failed to resolve site domain: got `{str(e)}`. Check that sites "
|
|
72
|
+
"framework is installed, site middleware is configured, and that "
|
|
73
|
+
"the site exists in the database.")
|
|
74
|
+
|
|
75
|
+
return Validation(
|
|
76
|
+
check=_("Site is configured"),
|
|
77
|
+
status=status(ok),
|
|
78
|
+
detail=detail,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def domain_matches_request(request: HttpRequest) -> Validation:
|
|
83
|
+
detail = None
|
|
84
|
+
try:
|
|
85
|
+
host = request.get_host().lower()
|
|
86
|
+
domain = request.site.domain.lower()
|
|
87
|
+
ok = request.get_host() == request.site.domain
|
|
88
|
+
if not ok:
|
|
89
|
+
detail = (
|
|
90
|
+
'Host in the request does not match domain configured for '
|
|
91
|
+
f'the site: expected `{domain}`, got `{host}`.')
|
|
92
|
+
else:
|
|
93
|
+
detail = (
|
|
94
|
+
f'Host in the request, `{host}`, matches domain configured '
|
|
95
|
+
f'for the site, `{domain}`.'
|
|
96
|
+
)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
ok = False
|
|
99
|
+
detail = (
|
|
100
|
+
f"Failed to resolve host or domain: got `{str(e)}`. Check that "
|
|
101
|
+
"sites framework is installed, site middleware is configured, "
|
|
102
|
+
"and that the site exists in the database.")
|
|
103
|
+
|
|
104
|
+
return Validation(
|
|
105
|
+
check=_("Configured domain matches host in request"),
|
|
106
|
+
status=status(ok),
|
|
107
|
+
detail=detail,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def site_preview_configured(
|
|
112
|
+
request: HttpRequest,
|
|
113
|
+
album: Album | Exception) -> Validation:
|
|
114
|
+
detail = None
|
|
115
|
+
|
|
116
|
+
if isinstance(album, Exception):
|
|
117
|
+
ok = False
|
|
118
|
+
detail = f'Failed to resolve site or album: `{str(album)}`'
|
|
119
|
+
else:
|
|
120
|
+
ok = album.cover_photo is not None
|
|
121
|
+
if ok:
|
|
122
|
+
detail = (
|
|
123
|
+
f'The `{album.key}` album has a cover photo configured. This '
|
|
124
|
+
'photo will be used as the site preview image.'
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
detail = (
|
|
128
|
+
f'Set cover photo for `{album.key}` album to configure '
|
|
129
|
+
'the preview image.')
|
|
130
|
+
|
|
131
|
+
return Validation(
|
|
132
|
+
check=_("Site has a default preview image"),
|
|
133
|
+
status=status(ok),
|
|
134
|
+
detail=detail,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def site_description_configured(
|
|
139
|
+
request: HttpRequest,
|
|
140
|
+
album: Album | Exception) -> Validation:
|
|
141
|
+
detail = None
|
|
142
|
+
|
|
143
|
+
if isinstance(album, Exception):
|
|
144
|
+
ok = False
|
|
145
|
+
detail = f'Failed to resolve site or album: `{str(album)}`'
|
|
146
|
+
else:
|
|
147
|
+
ok = album.description is not None and len(album.description) > 0
|
|
148
|
+
if ok:
|
|
149
|
+
detail = (
|
|
150
|
+
f'The `{album.key}` album has a description. This description '
|
|
151
|
+
'will be used as the site description.'
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
detail = (
|
|
155
|
+
f'Set description for `{album.key}` album to configure '
|
|
156
|
+
'the site description.')
|
|
157
|
+
|
|
158
|
+
return Validation(
|
|
159
|
+
check=_("Site has a default description"),
|
|
160
|
+
status=status(ok),
|
|
161
|
+
detail=detail,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@json_problem_as_html
|
|
166
|
+
def configuration(request: HttpRequest):
|
|
167
|
+
try:
|
|
168
|
+
site_id = request.site.id
|
|
169
|
+
album_key = f"_site_{site_id}"
|
|
170
|
+
album = Album.objects.get(key=album_key)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
album = e
|
|
173
|
+
|
|
174
|
+
validations = [
|
|
175
|
+
uses_https(request),
|
|
176
|
+
site_is_configured(request),
|
|
177
|
+
domain_matches_request(request),
|
|
178
|
+
site_preview_configured(request, album),
|
|
179
|
+
site_description_configured(request, album),
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
back = BackLink("Back to albums", reverse('photo_objects:list_albums'))
|
|
183
|
+
|
|
184
|
+
return render(request, "photo_objects/configuration.html", {
|
|
185
|
+
"title": "Configuration",
|
|
186
|
+
"validations": validations,
|
|
187
|
+
"back": back,
|
|
188
|
+
})
|
|
@@ -44,9 +44,9 @@ def upload_photos(request: HttpRequest, album_key: str):
|
|
|
44
44
|
def _camera(photo: Photo):
|
|
45
45
|
if photo.camera_make or photo.camera_model:
|
|
46
46
|
return " ".join(i for i in [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
photo.camera_make,
|
|
48
|
+
photo.camera_model,
|
|
49
|
+
] if i)
|
|
50
50
|
return None
|
|
51
51
|
|
|
52
52
|
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
|
|
3
|
-
from django.http import HttpRequest
|
|
4
|
-
from django.shortcuts import render
|
|
5
|
-
from django.urls import reverse
|
|
6
|
-
from django.utils.translation import gettext_lazy as _
|
|
7
|
-
|
|
8
|
-
from photo_objects.django.models import Album
|
|
9
|
-
from photo_objects.django.views.utils import BackLink
|
|
10
|
-
|
|
11
|
-
from .utils import json_problem_as_html
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@dataclass
|
|
15
|
-
class Validation:
|
|
16
|
-
check: str
|
|
17
|
-
status: str
|
|
18
|
-
detail: str = None
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def status(b: bool) -> str:
|
|
22
|
-
return _("OK") if b else _("Error")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def uses_https(request: HttpRequest) -> Validation:
|
|
26
|
-
return Validation(
|
|
27
|
-
check=_("Site is served over HTTPS"),
|
|
28
|
-
status=status(request.is_secure()),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def site_is_configured(request: HttpRequest) -> Validation:
|
|
33
|
-
detail = None
|
|
34
|
-
try:
|
|
35
|
-
ok = request.site.domain != "example.com"
|
|
36
|
-
if not ok:
|
|
37
|
-
detail = (
|
|
38
|
-
f'Site domain is set to "example.com". This is a placeholder '
|
|
39
|
-
'domain and should be changed to the actual domain of the '
|
|
40
|
-
'site.')
|
|
41
|
-
except Exception as e:
|
|
42
|
-
ok = False
|
|
43
|
-
detail = (
|
|
44
|
-
f"Failed to resolve site domain: {str(e)}. Check that sites "
|
|
45
|
-
"framework is installed, site middleware is configured, and that "
|
|
46
|
-
"the site exists in the database.")
|
|
47
|
-
|
|
48
|
-
return Validation(
|
|
49
|
-
check=_("Site is configured"),
|
|
50
|
-
status=status(ok),
|
|
51
|
-
detail=detail,
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def domain_matches_request(request: HttpRequest) -> Validation:
|
|
56
|
-
detail = None
|
|
57
|
-
try:
|
|
58
|
-
host = request.get_host().lower()
|
|
59
|
-
domain = request.site.domain.lower()
|
|
60
|
-
ok = request.get_host() == request.site.domain
|
|
61
|
-
if not ok:
|
|
62
|
-
detail = (
|
|
63
|
-
'Host in the request does not match domain configured for '
|
|
64
|
-
f'the site: expected "{domain}", got "{host}".')
|
|
65
|
-
except Exception as e:
|
|
66
|
-
ok = False
|
|
67
|
-
detail = (
|
|
68
|
-
f"Failed to resolve host or domain: {str(e)}. Check that "
|
|
69
|
-
"sites framework is installed, site middleware is configured, "
|
|
70
|
-
"and that the site exists in the database.")
|
|
71
|
-
|
|
72
|
-
return Validation(
|
|
73
|
-
check=_("Configured domain matches host in request"),
|
|
74
|
-
status=status(ok),
|
|
75
|
-
detail=detail,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def site_preview_configured(request: HttpRequest) -> Validation:
|
|
80
|
-
detail = None
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
site_id = request.site.id
|
|
84
|
-
album_key = f"_site_{site_id}"
|
|
85
|
-
album = Album.objects.get(key=album_key)
|
|
86
|
-
ok = album.cover_photo is not None
|
|
87
|
-
if not ok:
|
|
88
|
-
detail = (
|
|
89
|
-
f'Set cover photo for "{album_key}" album to configure '
|
|
90
|
-
'the preview image.')
|
|
91
|
-
except Exception as e:
|
|
92
|
-
ok = False
|
|
93
|
-
detail = f'Failed to resolve site or album: {str(e)}'
|
|
94
|
-
|
|
95
|
-
return Validation(
|
|
96
|
-
check=_("Site has a default preview image"),
|
|
97
|
-
status=status(ok),
|
|
98
|
-
detail=detail,
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@json_problem_as_html
|
|
103
|
-
def configuration(request: HttpRequest):
|
|
104
|
-
validations = [
|
|
105
|
-
uses_https(request),
|
|
106
|
-
site_is_configured(request),
|
|
107
|
-
domain_matches_request(request),
|
|
108
|
-
site_preview_configured(request),
|
|
109
|
-
]
|
|
110
|
-
|
|
111
|
-
back = BackLink("Back to albums", reverse('photo_objects:list_albums'))
|
|
112
|
-
|
|
113
|
-
return render(request, "photo_objects/configuration.html", {
|
|
114
|
-
"title": "Configuration",
|
|
115
|
-
"validations": validations,
|
|
116
|
-
"back": back,
|
|
117
|
-
})
|
|
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
|
{photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/management/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{photo_objects-0.0.6 → photo_objects-0.0.7}/photo_objects/django/migrations/0003_admin_visibility.py
RENAMED
|
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
|