Skip to content

Commit 7dddb4f

Browse files
committed
add field FinderFolderField to chose an entire folder
1 parent ef6b8ec commit 7dddb4f

File tree

11 files changed

+126
-41
lines changed

11 files changed

+126
-41
lines changed

demoapp/models.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.db import models
22

3-
from finder.models.fields import FinderFileField
3+
from finder.models.fields import FinderFileField, FinderFolderField
44

55

66
class DemoAppModel(models.Model):
@@ -11,3 +11,9 @@ class DemoAppModel(models.Model):
1111
accept_mime_types=['image/*'],
1212
realm='admin',
1313
)
14+
folder = FinderFolderField(
15+
verbose_name="Demo Folder",
16+
null=True,
17+
blank=True,
18+
realm='admin',
19+
)

finder/admin/file.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib import admin
2+
from django.contrib.staticfiles.storage import staticfiles_storage
23
from django.forms.widgets import Media
34
from django.http.response import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
4-
from django.templatetags.static import static
55
from django.urls import path, reverse
66
from django.utils.html import format_html
77
from django.utils.translation import gettext_lazy as _
@@ -24,7 +24,8 @@ def media(self):
2424
return Media(
2525
css={'all': ['finder/css/finder-admin.css', 'admin/css/forms.css']},
2626
js=[format_html(
27-
'<script type="module" src="{}"></script>', static('finder/js/file-admin.js')
27+
'<script type="module" src="{}"></script>',
28+
staticfiles_storage.url('finder/js/file-admin.js')
2829
)],
2930
)
3031

finder/admin/folder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import json
22

33
from django.contrib import admin
4+
from django.contrib.staticfiles.storage import staticfiles_storage
45
from django.core.exceptions import ObjectDoesNotExist, ValidationError
56
from django.db.models import QuerySet, Subquery
6-
77
from django.forms.widgets import Media
88
from django.http.response import HttpResponse, HttpResponseNotAllowed, HttpResponseNotFound, JsonResponse
9-
from django.templatetags.static import static
109
from django.urls import path, reverse
1110
from django.utils.translation import gettext
1211
from django.utils.html import format_html
@@ -29,7 +28,8 @@ def media(self):
2928
return Media(
3029
css={'all': ['finder/css/finder-admin.css']},
3130
js=[format_html(
32-
'<script type="module" src="{}"></script>', static('finder/js/folder-admin.js')
31+
'<script type="module" src="{}"></script>',
32+
staticfiles_storage.url('finder/js/folder-admin.js')
3333
)],
3434
)
3535

finder/browser/views.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,13 @@ def dispatch(self, request, *args, **kwargs):
4747

4848
def _get_children(cls, open_folders, parent):
4949
children = []
50-
for child in parent.subfolders:
51-
child_id = str(child.id)
52-
if child_id in open_folders:
53-
grandchildren = cls._get_children(open_folders, child)
50+
for subfolder in parent.subfolders:
51+
child = subfolder.as_dict()
52+
if str(subfolder.id) in open_folders:
53+
child.update(children=cls._get_children(open_folders, subfolder), is_open=True)
5454
else:
55-
grandchildren = None
56-
children.append({
57-
'id': child_id,
58-
'name': child.name,
59-
'children': grandchildren,
60-
'is_open': grandchildren is not None,
61-
'has_subfolders': child.subfolders.exists(),
62-
})
55+
child.update(children=None, is_open=False)
56+
children.append(child)
6357
return children
6458

6559
def _get_realm(self, request, slug):
@@ -72,9 +66,8 @@ def _get_realm(self, request, slug):
7266
@method_decorator(require_GET)
7367
def structure(self, request, slug=None):
7468
realm = self._get_realm(request, slug)
75-
root_folder_id = str(realm.root_folder.id)
7669
request.session.setdefault('finder.open_folders', [])
77-
request.session.setdefault('finder.last_folder', root_folder_id)
70+
request.session.setdefault('finder.last_folder', str(realm.root_folder.id))
7871
last_folder_id = request.session['finder.last_folder']
7972
if is_open := realm.root_folder.subfolders.exists():
8073
# direct children of the root folder are open regardless of the `open_folders` session
@@ -93,12 +86,11 @@ def structure(self, request, slug=None):
9386
children = None
9487
return {
9588
'root_folder': {
96-
'id': root_folder_id,
89+
**realm.root_folder.as_dict(),
9790
'name': None, # the root folder has no readable name
9891
'is_root': True,
9992
'is_open': is_open,
10093
'children': children,
101-
'has_subfolders': is_open,
10294
},
10395
'labels': [
10496
{'value': id, 'label': name, 'color': color}
@@ -271,6 +263,7 @@ def change(self, request, file_id):
271263

272264
@method_decorator(require_POST)
273265
def crop(self, request, image_id):
266+
raise NotImplementedError
274267
image = FileModel.objects.get_inode(id=image_id, mime_types=['image/*'], is_folder=False)
275268
width, height = request.POST.get('width'), request.POST.get('height')
276269
width = int(width) if str(width).isdigit() else None

finder/forms/fields.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.forms.fields import UUIDField
22
from django.forms.models import ModelMultipleChoiceField
33

4-
from finder.forms.widgets import FinderFileSelect
4+
from finder.forms.widgets import FinderFileSelect, FinderFolderSelect
55
from finder.models.realm import RealmModel
66

77

@@ -22,6 +22,21 @@ def widget_attrs(self, widget):
2222
return super().widget_attrs(widget)
2323

2424

25+
class FinderFolderField(UUIDField):
26+
widget = FinderFolderSelect
27+
28+
def __init__(self, *args, **kwargs):
29+
self.realm = kwargs.pop('realm', None)
30+
super().__init__(*args, **kwargs)
31+
32+
def widget_attrs(self, widget):
33+
if isinstance(self.realm, RealmModel):
34+
widget.realm = self.realm
35+
else:
36+
widget.realm = RealmModel.objects.get_default(self.realm)
37+
return super().widget_attrs(widget)
38+
39+
2540
class LabelsChoiceField(ModelMultipleChoiceField):
2641
def prepare_value(self, values):
2742
values = super().prepare_value(values)

finder/forms/widgets.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
11
import json
22
import uuid
33

4+
from django.contrib.staticfiles.storage import staticfiles_storage
45
from django.core.serializers.json import DjangoJSONEncoder
56
from django.forms.widgets import TextInput
6-
from django.templatetags.static import static
77
from django.urls import reverse
88
from django.utils.html import format_html
99

1010
from finder.models.file import AbstractFileModel, FileModel
11+
from finder.models.folder import FolderModel
1112

1213

13-
class FinderFileSelect(TextInput):
14-
template_name = 'finder/widgets/finder_file_select.html'
15-
accept_mime_types = None
16-
14+
class FinderInodeSelect(TextInput):
1715
class Media:
1816
css = {'all': ['finder/css/finder-select.css']}
1917
js = [format_html(
2018
'<script type="module" src="{}"></script>',
21-
static('finder/js/finder-select.js')
19+
staticfiles_storage.url('finder/js/finder-select.js')
2220
)]
2321

22+
23+
class FinderFileSelect(FinderInodeSelect):
24+
template_name = 'finder/widgets/finder_file_select.html'
25+
accept_mime_types = None
26+
2427
def get_context(self, name, value, attrs):
2528
attrs = attrs or {}
2629
css_classes = attrs.get('class', '').split()
27-
css_classes.append('finder-file-field')
30+
css_classes.append('finder-hidden-input')
2831
attrs['class'] = ' '.join(css_classes)
2932
if isinstance(value, str):
3033
# file reference has not been stored using a `finder.models.fields.FinderFileField`
@@ -38,15 +41,48 @@ def get_context(self, name, value, attrs):
3841
context.update(
3942
base_url=reverse('finder-api:base-url'),
4043
realm=self.realm.slug,
41-
style_url=static('finder/css/finder-browser.css'),
44+
style_url=staticfiles_storage.url('finder/css/finder-browser.css'),
4245
)
4346
if isinstance(self.accept_mime_types, (list, tuple)) and self.accept_mime_types:
4447
context['mime_types'] = ','.join(self.accept_mime_types)
4548
return context
4649

4750
def format_value(self, value):
48-
if value == "" or value is None:
51+
if value == '' or value is None:
4952
return None
5053
if isinstance(value, AbstractFileModel):
5154
return value.id
5255
return value
56+
57+
58+
class FinderFolderSelect(FinderInodeSelect):
59+
template_name = 'finder/widgets/finder_folder_select.html'
60+
61+
def get_context(self, name, value, attrs):
62+
attrs = attrs or {}
63+
css_classes = attrs.get('class', '').split()
64+
css_classes.append('finder-hidden-input')
65+
attrs['class'] = ' '.join(css_classes)
66+
if isinstance(value, str):
67+
# file reference has not been stored using a `finder.models.fields.FinderFileField`
68+
try:
69+
value = FolderModel.objects.get(id=uuid.UUID(value))
70+
except (ValueError, FolderModel.DoesNotExist):
71+
pass
72+
if isinstance(value, FolderModel):
73+
attrs['data-selected_folder'] = json.dumps(value.as_dict(self.realm), cls=DjangoJSONEncoder)
74+
context = super().get_context(name, value, attrs)
75+
context.update(
76+
base_url=reverse('finder-api:base-url'),
77+
realm=self.realm.slug,
78+
style_url=staticfiles_storage.url('finder/css/finder-browser.css'),
79+
folder_icon_url=staticfiles_storage.url('finder/icons/folder.svg'),
80+
)
81+
return context
82+
83+
def format_value(self, value):
84+
if value == '' or value is None:
85+
return None
86+
if isinstance(value, FolderModel):
87+
return value.id
88+
return value

finder/models/fields.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from django.db.models.fields import UUIDField
44
from django.utils.translation import gettext_lazy as _
55

6-
from finder.forms.fields import FinderFileField as FormFileField
6+
from finder.forms.fields import FinderFileField as FormFileField, FinderFolderField as FormFolderField
77
from finder.models.file import FileModel
8+
from finder.models.folder import FolderModel
89

910

1011
class FinderFileField(UUIDField):
@@ -25,14 +26,35 @@ def formfield(self, **kwargs):
2526
}
2627
)
2728

28-
def deconstruct(self):
29-
name, path, args, kwargs = super().deconstruct()
30-
return name, path, args, kwargs
31-
3229
def from_db_value(self, value, expression, connection):
3330
if not isinstance(value, uuid.UUID):
3431
return value
3532
try:
3633
return FileModel.objects.get_inode(id=value, is_folder=False, mime_types=self.accept_mime_types)
3734
except FileModel.DoesNotExist:
3835
return
36+
37+
38+
class FinderFolderField(UUIDField):
39+
description = _("Reference to a folder in the finder app.")
40+
41+
def __init__(self, *args, **kwargs):
42+
self.realm = kwargs.pop('realm', None)
43+
super().__init__(*args, **kwargs)
44+
45+
def formfield(self, **kwargs):
46+
return super().formfield(
47+
**{
48+
'form_class': FormFolderField,
49+
'realm': self.realm,
50+
**kwargs,
51+
}
52+
)
53+
54+
def from_db_value(self, value, expression, connection):
55+
if not isinstance(value, uuid.UUID):
56+
return value
57+
try:
58+
return FolderModel.objects.get(id=value)
59+
except FolderModel.DoesNotExist:
60+
return

finder/models/file.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,6 @@ def file_path(self):
146146
def summary(self):
147147
return filesizeformat(self.file_size)
148148

149-
def get_meta_data(self):
150-
return {}
151-
152149
@classmethod
153150
@lru_cache
154151
def mime_types_query(cls):
@@ -173,7 +170,7 @@ def as_dict(self, realm):
173170
'file_size': self.file_size,
174171
'sha1': self.sha1,
175172
'mime_type': self.mime_type,
176-
'last_modified_at': self.last_modified_at,
173+
'last_modified_at': self.last_modified_at.replace(microsecond=0, tzinfo=None),
177174
'summary': self.summary,
178175
'meta_data': self.get_meta_data(),
179176
'folderitem_component': self.folderitem_component,

finder/models/folder.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,17 @@ def summary(self):
160160
ngettext("{} File", "{} Files", num_files).format(num_files),
161161
))
162162

163+
@lru_cache
164+
def as_dict(self, realm=None):
165+
return {
166+
'id': self.id,
167+
'name': self.name,
168+
'has_subfolders': self.subfolders.exists(),
169+
'last_modified_at': self.last_modified_at.replace(microsecond=0, tzinfo=None),
170+
'summary': self.summary,
171+
'meta_data': self.get_meta_data(),
172+
}
173+
163174
def get_download_url(self, realm):
164175
return None
165176

finder/models/inode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ def serializable_value(self, field_name):
287287
return list(self.labels.values('id', 'name', 'color'))
288288
return data
289289

290+
def get_meta_data(self):
291+
return {}
292+
290293

291294
class DiscardedInode(models.Model):
292295
"""

0 commit comments

Comments
 (0)