photo-objects 0.4.1__py3-none-any.whl → 0.6.0__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.
@@ -1,6 +1,7 @@
1
1
  from django.contrib import admin
2
2
 
3
- from .models import Album, Photo
3
+ from .models import Album, Photo, SiteSettings
4
4
 
5
5
  admin.site.register(Album)
6
6
  admin.site.register(Photo)
7
+ admin.site.register(SiteSettings)
@@ -1,6 +1,3 @@
1
- import re
2
-
3
- from django.contrib.sites.models import Site
4
1
  from django.db.models.deletion import ProtectedError
5
2
  from django.http import HttpRequest
6
3
 
@@ -16,24 +13,6 @@ from .utils import (
16
13
  )
17
14
 
18
15
 
19
- def get_site_album(site: Site) -> tuple[Album, bool]:
20
- album_key = f'_site_{site.id}'
21
- return Album.objects.get_or_create(
22
- key=album_key,
23
- defaults={
24
- 'title': site.name,
25
- 'visibility': Album.Visibility.ADMIN,
26
- })
27
-
28
-
29
- def parse_site_id(album_key: str) -> int | None:
30
- key_match = re.match(r'_site_([0-9]+)', album_key)
31
- if not key_match:
32
- return None
33
-
34
- return int(key_match.group(1))
35
-
36
-
37
16
  def get_albums(request: HttpRequest):
38
17
  if not request.user.is_authenticated:
39
18
  return Album.objects.filter(visibility=Album.Visibility.PUBLIC)
@@ -30,20 +30,25 @@ class PhotoSizeDimensions:
30
30
  max_width: int = None
31
31
  max_height: int = None
32
32
  max_aspect_ratio: float = None
33
+ image_format: str = None
33
34
 
34
35
 
35
36
  DEFAULT_SM = dict(
36
37
  max_width=512,
37
38
  max_height=512,
38
- max_aspect_ratio=1.5
39
+ max_aspect_ratio=1.5,
40
+ image_format='WEBP'
39
41
  )
40
42
  DEFAULT_MD = dict(
41
- max_width=2048,
42
- max_height=2048
43
+ max_width=1024,
44
+ max_height=1024,
45
+ max_aspect_ratio=1.5,
46
+ image_format="JPEG"
43
47
  )
44
48
  DEFAULT_LG = dict(
45
- max_width=4096,
46
- max_height=4096
49
+ max_width=2048,
50
+ max_height=2048,
51
+ image_format='WEBP'
47
52
  )
48
53
 
49
54
 
@@ -0,0 +1,27 @@
1
+ # Generated by Django 5.2 on 2025-08-26 18:39
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('photo_objects', '0004_camera_setup_and_settings'),
11
+ ('sites', '0002_alter_domain_unique'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='SiteSettings',
17
+ fields=[
18
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('description', models.TextField(blank=True)),
20
+ ('preview_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='photo_objects.photo')),
21
+ ('site', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='sites.site')),
22
+ ],
23
+ options={
24
+ 'verbose_name_plural': 'site settings',
25
+ },
26
+ ),
27
+ ]
@@ -1,4 +1,6 @@
1
1
  from django.db import models
2
+ from django.db.models.signals import pre_delete, pre_save
3
+ from django.contrib.sites.models import Site
2
4
  from django.core.validators import RegexValidator
3
5
  from django.utils.translation import gettext_lazy as _
4
6
 
@@ -146,3 +148,43 @@ class Photo(BaseModel):
146
148
  exposure_time=self.exposure_time,
147
149
  iso_speed=self.iso_speed,
148
150
  )
151
+
152
+
153
+ SETTINGS_CACHE = {}
154
+
155
+
156
+ class SiteSettingsManager(models.Manager):
157
+ def get(self, site: Site):
158
+ cached = SETTINGS_CACHE.get(site.id)
159
+ if cached:
160
+ return cached
161
+ settings, _ = self.get_or_create(site=site)
162
+ SETTINGS_CACHE[site.id] = settings
163
+ return settings
164
+
165
+
166
+ class SiteSettings(models.Model):
167
+ class Meta:
168
+ verbose_name_plural = "site settings"
169
+
170
+ site = models.OneToOneField(
171
+ Site, on_delete=models.CASCADE, related_name="settings")
172
+
173
+ description = models.TextField(blank=True)
174
+ preview_image = models.ForeignKey(
175
+ Photo, blank=True, null=True, on_delete=models.SET_NULL)
176
+
177
+ objects = SiteSettingsManager()
178
+
179
+ def __str__(self):
180
+ return f"Settings for {self.site.name}"
181
+
182
+
183
+ def clear_cached_settings(sender, **kwargs):
184
+ site_id = kwargs.get("instance").site.id
185
+ if site_id in SETTINGS_CACHE:
186
+ del SETTINGS_CACHE[site_id]
187
+
188
+
189
+ pre_save.connect(clear_cached_settings, sender=SiteSettings)
190
+ pre_delete.connect(clear_cached_settings, sender=SiteSettings)
@@ -2,6 +2,7 @@ from dataclasses import asdict
2
2
  from io import BytesIO
3
3
  import json
4
4
  import mimetypes
5
+ import re
5
6
  import urllib3
6
7
 
7
8
  from minio import Minio, S3Error
@@ -60,8 +61,30 @@ def photo_path(album_key, photo_key, size_key):
60
61
  return f"{size_key}/{album_key}/{photo_key}"
61
62
 
62
63
 
63
- def put_photo(album_key, photo_key, size_key, photo_file):
64
- content_type = mimetypes.guess_type(photo_key)[0]
64
+ def _photo_filename(photo_key: str, image_format: str = None) -> str:
65
+ if image_format:
66
+ filename = re.sub(r'\.[^.]+$', '', photo_key)
67
+ return f"{filename}.{image_format.lower()}"
68
+
69
+ return photo_key
70
+
71
+
72
+ def photo_content_headers(
73
+ photo_key: str,
74
+ image_format: str = None,
75
+ ) -> tuple[str, dict[str, str]]:
76
+ filename = _photo_filename(photo_key, image_format)
77
+
78
+ content_type = mimetypes.guess_type(filename, strict=False)[0]
79
+ headers = {
80
+ "Content-Disposition": f"inline; filename={filename}"
81
+ }
82
+
83
+ return content_type, headers
84
+
85
+
86
+ def put_photo(album_key, photo_key, size_key, photo_file, image_format=None):
87
+ content_type, headers = photo_content_headers(photo_key, image_format)
65
88
 
66
89
  client, bucket = _objsto_access()
67
90
  return client.put_object(
@@ -71,6 +94,7 @@ def put_photo(album_key, photo_key, size_key, photo_file):
71
94
  length=-1,
72
95
  part_size=10 * MEGABYTE,
73
96
  content_type=content_type,
97
+ metadata=headers
74
98
  )
75
99
 
76
100
 
@@ -1,9 +1,7 @@
1
- from django.contrib.sites.models import Site
2
1
  from django.db.models.signals import post_save, post_delete
3
2
  from django.dispatch import receiver
4
3
 
5
- from .api.album import parse_site_id, get_site_album
6
- from .models import Album, Photo
4
+ from .models import Photo
7
5
 
8
6
 
9
7
  @receiver(post_save, sender=Photo)
@@ -58,30 +56,3 @@ def update_album_on_photo_delete(sender, **kwargs):
58
56
 
59
57
  if needs_save:
60
58
  album.save()
61
-
62
-
63
- @receiver(post_save, sender=Site)
64
- def update_album_on_site_save(sender, **kwargs):
65
- site = kwargs.get('instance', None)
66
- album, created = get_site_album(site)
67
-
68
- if not created and album.title != site.name:
69
- album.title = site.name
70
- album.save()
71
-
72
-
73
- @receiver(post_save, sender=Album)
74
- def update_site_on_album_save(sender, **kwargs):
75
- album = kwargs.get('instance', None)
76
- site_id = parse_site_id(album.key)
77
- if site_id is None:
78
- return
79
-
80
- try:
81
- site = Site.objects.get(id=site_id)
82
- except Site.DoesNotExist:
83
- return
84
-
85
- if site.name != album.title:
86
- site.name = album.title
87
- site.save()
@@ -1,7 +1,7 @@
1
1
  from datetime import datetime
2
2
  from django import template
3
3
 
4
- from photo_objects.django.api.album import get_site_album
4
+ from photo_objects.django.models import SiteSettings
5
5
  from photo_objects.django.views.utils import meta_description
6
6
 
7
7
 
@@ -48,13 +48,14 @@ def meta_og(context):
48
48
  try:
49
49
  request = context.get("request")
50
50
  site = request.site
51
- album, _ = get_site_album(site)
51
+
52
+ settings = SiteSettings.objects.get(site)
52
53
 
53
54
  return {
54
55
  'request': request,
55
- "title": album.title or site.name,
56
- "description": meta_description(request, album.description),
57
- "photo": album.cover_photo,
56
+ "title": site.name,
57
+ "description": meta_description(request, settings.description),
58
+ "photo": settings.preview_image,
58
59
  }
59
60
  except Exception:
60
61
  return context
@@ -3,7 +3,6 @@ from time import sleep
3
3
 
4
4
  from django.contrib.auth import get_user_model
5
5
  from django.contrib.auth.models import Permission
6
- from django.contrib.sites.models import Site
7
6
 
8
7
  from photo_objects.django.models import Album
9
8
  from photo_objects.img import utcnow
@@ -376,25 +375,3 @@ class AlbumViewTests(TestCase):
376
375
 
377
376
  response = self.client.delete("/api/albums/copenhagen")
378
377
  self.assertStatus(response, 204)
379
-
380
- def test_site_config_album_title_change(self):
381
- site = Site.objects.get(id=1)
382
- album, _ = Album.objects.get_or_create(
383
- key="_site_1",
384
- defaults={
385
- "title": "Site 1",
386
- "visibility": Album.Visibility.ADMIN,
387
- }
388
- )
389
-
390
- site.name = "Changed in site"
391
- site.save()
392
-
393
- album.refresh_from_db()
394
- self.assertEqual(album.title, "Changed in site")
395
-
396
- album.title = "Changed in album"
397
- album.save()
398
-
399
- site.refresh_from_db()
400
- self.assertEqual(site.name, "Changed in album")
@@ -91,14 +91,23 @@ def get_img(request: HttpRequest, album_key: str, photo_key: str):
91
91
  404 if code == "NoSuchKey" else 500,
92
92
  ).json_response
93
93
 
94
- sizes = photo_sizes()
94
+ size_params = getattr(photo_sizes(), size)
95
95
  # TODO: handle error
96
96
  scaled_photo = scale_photo(
97
- original_photo, photo_key, **asdict(getattr(sizes, size)))
97
+ original_photo, photo_key, **asdict(size_params))
98
98
 
99
99
  # TODO: handle error
100
100
  scaled_photo.seek(0)
101
- objsto.put_photo(album_key, photo_key, size, scaled_photo)
101
+ objsto.put_photo(
102
+ album_key,
103
+ photo_key,
104
+ size,
105
+ scaled_photo,
106
+ size_params.image_format)
107
+
108
+ content_type, headers = objsto.photo_content_headers(
109
+ photo_key, size_params.image_format)
102
110
 
103
111
  scaled_photo.seek(0)
104
- return HttpResponse(scaled_photo.read(), content_type=content_type)
112
+ return HttpResponse(
113
+ scaled_photo.read(), content_type=content_type, headers=headers)
@@ -4,7 +4,6 @@ from django.urls import reverse
4
4
  from django.utils.translation import gettext_lazy as _
5
5
 
6
6
  from photo_objects.django import api
7
- from photo_objects.django.api.album import parse_site_id
8
7
  from photo_objects.django.api.utils import FormValidationFailed
9
8
  from photo_objects.django.forms import CreateAlbumForm, ModifyAlbumForm
10
9
  from photo_objects.django.models import Album
@@ -51,15 +50,7 @@ def new_album(request: HttpRequest):
51
50
 
52
51
 
53
52
  def get_info(request: HttpRequest, album_key: str):
54
- site_id = parse_site_id(album_key)
55
- if site_id is not None and request.site:
56
- return render_markdown(
57
- "This is a special album for configuring site metadata for "
58
- f"**{request.site.name}**. Use album title to change the site "
59
- "name, albums cover photo to configure the preview image, and "
60
- "album description to configure the site description. The album "
61
- "title is automatically updated when the related sites name is "
62
- "changed and vice versa.")
53
+ # TODO: Remove this later if not needed
63
54
  return None
64
55
 
65
56
 
@@ -5,7 +5,7 @@ from django.shortcuts import render
5
5
  from django.urls import reverse
6
6
  from django.utils.translation import gettext_lazy as _
7
7
 
8
- from photo_objects.django.models import Album
8
+ from photo_objects.django.api.utils import JsonProblem
9
9
  from photo_objects.django.views.utils import BackLink, render_markdown
10
10
 
11
11
  from .utils import json_problem_as_html
@@ -108,25 +108,20 @@ def domain_matches_request(request: HttpRequest) -> Validation:
108
108
  )
109
109
 
110
110
 
111
- def site_preview_configured(
112
- request: HttpRequest,
113
- album: Album | Exception) -> Validation:
111
+ def site_preview_configured(request: HttpRequest) -> Validation:
114
112
  detail = None
115
113
 
116
- if isinstance(album, Exception):
117
- ok = False
118
- detail = f'Failed to resolve site or album: `{str(album)}`'
114
+ ok = request.site.settings.preview_image is not None
115
+ if ok:
116
+ detail = (
117
+ f'The site settings for `{request.site.domain}` configure a '
118
+ 'preview image.'
119
+ )
119
120
  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.')
121
+ detail = (
122
+ 'Configure a preview image in site settings for '
123
+ f'`{request.site.domain}`.'
124
+ )
130
125
 
131
126
  return Validation(
132
127
  check=_("Site has a default preview image"),
@@ -135,25 +130,21 @@ def site_preview_configured(
135
130
  )
136
131
 
137
132
 
138
- def site_description_configured(
139
- request: HttpRequest,
140
- album: Album | Exception) -> Validation:
133
+ def site_description_configured(request: HttpRequest) -> Validation:
141
134
  detail = None
142
135
 
143
- if isinstance(album, Exception):
144
- ok = False
145
- detail = f'Failed to resolve site or album: `{str(album)}`'
136
+ settings = request.site.settings
137
+ ok = settings.description is not None and len(settings.description) > 0
138
+ if ok:
139
+ detail = (
140
+ f'The site settings for `{request.site.domain}` configure a '
141
+ 'description.'
142
+ )
146
143
  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.')
144
+ detail = (
145
+ 'Configure a description in site settings for '
146
+ f'`{request.site.domain}`.'
147
+ )
157
148
 
158
149
  return Validation(
159
150
  check=_("Site has a default description"),
@@ -164,19 +155,15 @@ def site_description_configured(
164
155
 
165
156
  @json_problem_as_html
166
157
  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
158
+ if not request.user.is_staff:
159
+ raise JsonProblem("Page not found", status=404)
173
160
 
174
161
  validations = [
175
162
  uses_https(request),
176
163
  site_is_configured(request),
177
164
  domain_matches_request(request),
178
- site_preview_configured(request, album),
179
- site_description_configured(request, album),
165
+ site_preview_configured(request),
166
+ site_description_configured(request),
180
167
  ]
181
168
 
182
169
  back = BackLink("Back to albums", reverse('photo_objects:list_albums'))
@@ -2,18 +2,18 @@ from django.contrib.auth import views as auth_views
2
2
  from django.http import HttpRequest
3
3
  from django.urls import reverse_lazy
4
4
 
5
- from photo_objects.django.api.album import get_site_album
5
+ from photo_objects.django.models import SiteSettings
6
6
  from photo_objects.django.views.utils import BackLink
7
7
 
8
8
 
9
9
  def login(request: HttpRequest):
10
- album, _ = get_site_album(request.site)
10
+ settings = SiteSettings.objects.get(request.site)
11
11
 
12
12
  return auth_views.LoginView.as_view(
13
13
  template_name="photo_objects/form.html",
14
14
  extra_context={
15
15
  "title": "Login",
16
- "photo": album.cover_photo,
16
+ "photo": settings.preview_image,
17
17
  "action": "Login",
18
18
  "back": BackLink(
19
19
  'Back to albums',
photo_objects/img.py CHANGED
@@ -66,7 +66,10 @@ def _read_camera_setup_and_settings(image: Image) -> dict:
66
66
  raise e
67
67
 
68
68
 
69
- def _image_format(filename):
69
+ def _image_format(image_format, filename):
70
+ if image_format:
71
+ return image_format
72
+
70
73
  image_format = filename.split('.')[-1].upper()
71
74
 
72
75
  if image_format == "JPG":
@@ -131,7 +134,8 @@ def scale_photo(
131
134
  filename,
132
135
  max_width=None,
133
136
  max_height=None,
134
- max_aspect_ratio=None):
137
+ max_aspect_ratio=None,
138
+ image_format=None):
135
139
  image = Image.open(photo_file)
136
140
  width, height = image.size
137
141
 
@@ -164,5 +168,5 @@ def scale_photo(
164
168
  resized = image.resize(new_size, Image.Resampling.LANCZOS)
165
169
 
166
170
  b = BytesIO()
167
- resized.save(b, _image_format(filename))
171
+ resized.save(b, _image_format(image_format, filename))
168
172
  return b
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: photo-objects
3
- Version: 0.4.1
3
+ Version: 0.6.0
4
4
  Summary: Application for storing photos in S3 compatible object-storage.
5
5
  Author: Toni Kangas
6
6
  License: MIT License
@@ -1,20 +1,20 @@
1
1
  photo_objects/__init__.py,sha256=I1508w_ntomEqTFQgC74SurhxVXfCiDWZLRsny2f59g,60
2
2
  photo_objects/config.py,sha256=0-Aeo-z-d_fxx-cjAjxSwPJZUgYaAi7NTodiErlxIXo,861
3
3
  photo_objects/error.py,sha256=7afLYjxM0EaYioxVw_XUqHTvfSMSuQPUwwle0OVlaDY,45
4
- photo_objects/img.py,sha256=8FNxKFBVuSyxO_jA3W3PMag3bZ7sEXYKG78rpgxq71Q,4515
4
+ photo_objects/img.py,sha256=2HVGS2g7rS2hnomozYL92oxrcN6zjDTHvWNr-UAqtGQ,4620
5
5
  photo_objects/utils.py,sha256=sYliXid-bv2EgNA97woRaOnWU75yZFq7fuqzWaseiDg,139
6
6
  photo_objects/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- photo_objects/django/admin.py,sha256=sRnKTODk-8s6TAfwvsa2YAd7ECIfVmoOTR6PDASsvGg,122
7
+ photo_objects/django/admin.py,sha256=ubYfhsWQ85_2moYGZCVneLQ2DVEetbVpvBNisIrtuhw,170
8
8
  photo_objects/django/apps.py,sha256=Apqu6o6fpoxda18NQgKupvQRvTAZxVviIK_-dUR3rck,1444
9
- photo_objects/django/conf.py,sha256=s5lUbSLpjRWKlE2eYqAZmz60q4Oe10-e7JbIERPa6qI,2463
9
+ photo_objects/django/conf.py,sha256=ZpeIulEc1tpr8AO52meNKOF30Xf5osbDtDyHvQRtkx4,2593
10
10
  photo_objects/django/context_processors.py,sha256=DLhLZHAvGlrpxYegaLiKEx9X7rpyZUFiAnAVF9jjkFA,165
11
11
  photo_objects/django/forms.py,sha256=XiCNqjVqPDn_aV4nGM1z5NfLKHd6_ipm7y0YRt3sXgY,6505
12
- photo_objects/django/models.py,sha256=tRHBqswASWMnyz8rwiKsmHDqEvQX54ly9k8tvU7qNOM,4597
13
- photo_objects/django/objsto.py,sha256=5z4F8pft01WKLBipyE3Ro9Ac9AvWbuV_rG9Iiour90E,3588
14
- photo_objects/django/signals.py,sha256=u1Is0GuNPh5aS47ocbYXbXhXUtjmkMyiViBW14DuyrE,2389
12
+ photo_objects/django/models.py,sha256=M40ZFSIX3WgyVXfHQY9SXQOY0s3fpFqfCxNV3CBD5M8,5753
13
+ photo_objects/django/objsto.py,sha256=B7DxPWuqFaPFXPLhsHCFlqIzYl7EXLxcHde6zJDe89A,4238
14
+ photo_objects/django/signals.py,sha256=Q_Swjl_9z6B6zP-97D_ep5zGSAEgmQfwUz0utMDY93A,1624
15
15
  photo_objects/django/urls.py,sha256=pCUTxg4xSd3ZD8BZVjULb9QpJQ03Ek6-sKoVnYG3-OY,1975
16
16
  photo_objects/django/api/__init__.py,sha256=BnEHlm3mwyBy-1xhk-NFasgZa4fjCDjtfkBUoH0puPY,62
17
- photo_objects/django/api/album.py,sha256=ALMEt3lnH1xFwIaSzSuMBlLEC6-hUiNDDGILRMvzDO4,2509
17
+ photo_objects/django/api/album.py,sha256=CJDeGLCuYoxGUDcjssZRpFnToxG_KVE9Ii7NduFW2ks,2003
18
18
  photo_objects/django/api/auth.py,sha256=lS0S1tMVH2uN30g4jlixklv3eMnQ2FbQVQvuRXeMGYo,1420
19
19
  photo_objects/django/api/photo.py,sha256=Gc4GHmcW0995_alE7Q6mdn7SMt9f1JJ-krv-p-SJi8Y,3870
20
20
  photo_objects/django/api/utils.py,sha256=kHOdXp-LEaEhL82LCLanQmVpU3jFi8KNSYOgIAv-IVs,5167
@@ -22,16 +22,16 @@ photo_objects/django/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
22
22
  photo_objects/django/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  photo_objects/django/management/commands/clean-scaled-photos.py,sha256=KJY6phgTCxcmbMUsUfCRQjatvCmKyFninM8zT-tB3Kc,2008
24
24
  photo_objects/django/management/commands/create-initial-admin-account.py,sha256=SuDSEP7S7OR99NtXvjXHnIyuawNOBgMiuwoHs7aCj6g,1131
25
- photo_objects/django/management/commands/create-site-albums.py,sha256=qRNg-S2u5ABr7iL9LjQNaBaa7PMSSv39mRBWvh6jp_M,944
26
25
  photo_objects/django/migrations/0001_initial.py,sha256=BLW-EZ38sBgDhOYyprc-h_vuPpRxA11qxt4ZuYNO1Wo,2424
27
26
  photo_objects/django/migrations/0002_created_at_updated_at.py,sha256=7OT2VvDffAkX9XKBHVY-jvzxeIl2yU0Jr1ByCNGcUfw,1039
28
27
  photo_objects/django/migrations/0003_admin_visibility.py,sha256=PdxPOJzr-ViRBlOYUHEEGhe0hLtDysZJdMqvbjKVpEg,529
29
28
  photo_objects/django/migrations/0004_camera_setup_and_settings.py,sha256=CS5xyIHgBE2Y7-PSJ52ffRQeCzs8p899px9upomk4O8,1844
29
+ photo_objects/django/migrations/0005_sitesettings.py,sha256=Ilf5vUwTFQfXVP37zz0NWo5dQdeHDh5e-MV3enm0ZKI,994
30
30
  photo_objects/django/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  photo_objects/django/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- photo_objects/django/templatetags/photo_objects_extras.py,sha256=FIv_Q3Xr6AgLBxx4_6aXks3VAt1tK2N3d9rzpzRtx24,1400
32
+ photo_objects/django/templatetags/photo_objects_extras.py,sha256=1L6wweVA96rTWVXqgyf8gSey1wQKi01h6sqv9kZeTIY,1399
33
33
  photo_objects/django/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- photo_objects/django/tests/test_album.py,sha256=UIwaAPpaE_v6OvHG_JWSytybht9RgWZR7BNOyyoNiH0,13962
34
+ photo_objects/django/tests/test_album.py,sha256=yjxP_M0bddS9Xpg1d1Wk5OyJsmxmkdYuljca4oFJSzc,13316
35
35
  photo_objects/django/tests/test_auth.py,sha256=hgr1UMVLvSI1x5zY7wTEXSBKfM5E_sNMIFlx8mVWYPY,3928
36
36
  photo_objects/django/tests/test_commands.py,sha256=e3lE1ZhFR39WIq2VSKDNcQHUkSJqSWDYuAcAfu29svs,2955
37
37
  photo_objects/django/tests/test_img.py,sha256=HEAWcr5fpTkzePkhoQ4YrWsDO9TvFOr7my_0LqVbaO4,829
@@ -43,16 +43,16 @@ photo_objects/django/views/utils.py,sha256=oP9B6QtHPaukX97rqIlCHxanduktPGFVSCGkG
43
43
  photo_objects/django/views/api/__init__.py,sha256=SxK-b7MgMtD9VRMz46rDV5qtm6AvkRcg3Moa1AWl5pY,108
44
44
  photo_objects/django/views/api/album.py,sha256=EZMOkxYzLSWr9wwXnd4yAO64JtXZq2k3FYohiNMFbGQ,1602
45
45
  photo_objects/django/views/api/auth.py,sha256=EN_ExegzmLN-bhSzu3L9-6UE9qodPd7_ZRLilzrvc8Y,819
46
- photo_objects/django/views/api/photo.py,sha256=FQCqcnPwSaEJ9MVggQF09E4WD1wVFmLZnT_pFFpWGvA,3502
46
+ photo_objects/django/views/api/photo.py,sha256=iWDTBWrzztIKcfW7G6vh1eYTuH2JW129iOLSHjTfXCE,3743
47
47
  photo_objects/django/views/api/utils.py,sha256=uQzKdSKHRAux5OZzqgWQr0gsK_FeweQP0cg_67OWA_Y,264
48
48
  photo_objects/django/views/ui/__init__.py,sha256=N3ro5KggdV-JnfyHwoStX73b3SbVbpcsMuQNlxntVJs,92
49
- photo_objects/django/views/ui/album.py,sha256=xXGYGYUY4WicI16Jy64sSi49RGIHtsY8KIzdXW0Yz7I,5286
50
- photo_objects/django/views/ui/configuration.py,sha256=B6PotwK40CMQywdGqyXbwGYXbJv47n5YQT4_WtCqa1w,5575
49
+ photo_objects/django/views/ui/album.py,sha256=PmVXAmqVjKLAie1NyB-qXO3eLqOmhIA8PTAGJewgxko,4738
50
+ photo_objects/django/views/ui/configuration.py,sha256=oX6SV0TFBpbaxfp4cXIdSL41YJhy_aOy30TkBxOpq0M,5065
51
51
  photo_objects/django/views/ui/photo.py,sha256=Zo-HE-CzMSLFEtDTL1ds1HnXi3ORsIym4iWa_ZMCPpc,6299
52
- photo_objects/django/views/ui/users.py,sha256=9EWxw178kcjPNaDzrzKOpTzO93uJYuhjE0lyBMJPw2o,712
52
+ photo_objects/django/views/ui/users.py,sha256=nb73cnzuV98QkJb0j8F2hqPgOGFIWpUFTFu6dXMeVwM,722
53
53
  photo_objects/django/views/ui/utils.py,sha256=YV_YcUbX-zUkdFnBlezPChR6aPDhZJ9loSOHBSzF6Cc,273
54
- photo_objects-0.4.1.dist-info/licenses/LICENSE,sha256=V3w6hTjXfP65F4r_mejveHcV5igHrblxao3-2RlfVlA,1068
55
- photo_objects-0.4.1.dist-info/METADATA,sha256=8m8l9pdPir6bJmip4Q9kBMXXTakFNMLQaLKRNwvoGao,3605
56
- photo_objects-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- photo_objects-0.4.1.dist-info/top_level.txt,sha256=SZeL8mhf-WMGdhRtTGFvZc3aIRBboQls9O0cFDMGdQ0,14
58
- photo_objects-0.4.1.dist-info/RECORD,,
54
+ photo_objects-0.6.0.dist-info/licenses/LICENSE,sha256=V3w6hTjXfP65F4r_mejveHcV5igHrblxao3-2RlfVlA,1068
55
+ photo_objects-0.6.0.dist-info/METADATA,sha256=zaLGXBdjS2a4m9TXnbLG36LtT1EqCtmEFdloP2Dub_U,3605
56
+ photo_objects-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ photo_objects-0.6.0.dist-info/top_level.txt,sha256=SZeL8mhf-WMGdhRtTGFvZc3aIRBboQls9O0cFDMGdQ0,14
58
+ photo_objects-0.6.0.dist-info/RECORD,,
@@ -1,28 +0,0 @@
1
- # pylint: disable=invalid-name
2
- from django.core.management.base import BaseCommand
3
- from django.contrib.sites.models import Site
4
-
5
- from photo_objects.django.api.album import get_site_album
6
-
7
-
8
- class Command(BaseCommand):
9
- help = "Create albums for configuring site metadata."
10
-
11
- def handle(self, *args, **options):
12
- sites = Site.objects.all()
13
-
14
- for site in sites:
15
- album, created = get_site_album(site)
16
- if created:
17
- self.stdout.write(
18
- self.style.SUCCESS(
19
- f'Album for site {site.domain} created:') +
20
- f'\n Key: {album.key}'
21
- f'\n Title: {album.title}')
22
- else:
23
- self.stdout.write(
24
- self.style.NOTICE(
25
- f'Album creation for site {site.domain} skipped: '
26
- 'Album already exists.'
27
- )
28
- )