Skip to content

Commit 3ec17e6

Browse files
committed
Merge remote-tracking branch 'origin/main' into loosen
# Conflicts: # django_unicorn/static/unicorn/js/unicorn.min.js # pyproject.toml
2 parents c5b6e9b + e37c55d commit 3ec17e6

File tree

30 files changed

+510
-289
lines changed

30 files changed

+510
-289
lines changed

.all-contributorsrc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,25 @@
240240
"contributions": [
241241
"code"
242242
]
243+
},
244+
{
245+
"login": "jacksund",
246+
"name": "Jack Sundberg",
247+
"avatar_url": "https://avatars.githubusercontent.com/u/47992949?v=4",
248+
"profile": "http://jacksund.github.io",
249+
"contributions": [
250+
"code"
251+
]
252+
},
253+
{
254+
"login": "siliconcow",
255+
"name": "siliconcow",
256+
"avatar_url": "https://avatars.githubusercontent.com/u/908362?v=4",
257+
"profile": "https://github.com/siliconcow",
258+
"contributions": [
259+
"code",
260+
"test"
261+
]
243262
}
244263
],
245264
"contributorsPerLine": 7,

.github/workflows/js.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ jobs:
66
runs-on: ubuntu-latest
77

88
steps:
9-
- uses: actions/checkout@v2.3.4
9+
- uses: actions/checkout@v4.1.1
1010
with:
1111
fetch-depth: 1
1212

1313
- name: Set up node
14-
uses: actions/setup-node@v2.1.2
14+
uses: actions/setup-node@v4.0.2
1515

1616
- name: Install node packages
1717
run: npm install

.github/workflows/python.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ jobs:
3030
- name: Install dependencies
3131
run: poetry install
3232

33-
- name: black check
34-
run: poetry run black . --check
35-
3633
- name: ruff check
3734
run: poetry run ruff .
3835

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ Available additional settings that can be set to `UNICORN` dict in settings.py w
2525

2626
## Customization changelog
2727

28+
### 0.60.0.1 - (2024-03-29)
29+
30+
- No customizations, just sync with main package.
31+
2832
### 0.58.1.2 - (2024-01-10)
2933

3034
- add optional `CHECK_CHECKSUM_MATCH` setting which is set by default to True. If turned off, `unicorn` does not perform data checksum check on each request.
@@ -291,6 +295,8 @@ Thanks to the following wonderful people ([emoji key](https://allcontributors.or
291295
<td align="center" valign="top" width="14.28%"><a href="https://digitalpinup.art"><img src="https://avatars.githubusercontent.com/u/1392097?v=4?s=100" width="100px;" alt="Pierre"/><br /><sub><b>Pierre</b></sub></a><br /><a href="https://github.com/adamghill/django-unicorn/commits?author=bloodywing" title="Code">💻</a></td>
292296
<td align="center" valign="top" width="14.28%"><a href="https://roman.pt"><img src="https://avatars.githubusercontent.com/u/75075?v=4?s=100" width="100px;" alt="Roman Imankulov"/><br /><sub><b>Roman Imankulov</b></sub></a><br /><a href="https://github.com/adamghill/django-unicorn/commits?author=imankulov" title="Tests">⚠️</a> <a href="https://github.com/adamghill/django-unicorn/commits?author=imankulov" title="Code">💻</a></td>
293297
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rhymiz"><img src="https://avatars.githubusercontent.com/u/7029352?v=4?s=100" width="100px;" alt="Lemi Boyce"/><br /><sub><b>Lemi Boyce</b></sub></a><br /><a href="https://github.com/adamghill/django-unicorn/commits?author=rhymiz" title="Code">💻</a></td>
298+
<td align="center" valign="top" width="14.28%"><a href="http://jacksund.github.io"><img src="https://avatars.githubusercontent.com/u/47992949?v=4?s=100" width="100px;" alt="Jack Sundberg"/><br /><sub><b>Jack Sundberg</b></sub></a><br /><a href="https://github.com/adamghill/django-unicorn/commits?author=jacksund" title="Code">💻</a></td>
299+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/siliconcow"><img src="https://avatars.githubusercontent.com/u/908362?v=4?s=100" width="100px;" alt="siliconcow"/><br /><sub><b>siliconcow</b></sub></a><br /><a href="https://github.com/adamghill/django-unicorn/commits?author=siliconcow" title="Code">💻</a> <a href="https://github.com/adamghill/django-unicorn/commits?author=siliconcow" title="Tests">⚠️</a></td>
294300
</tr>
295301
</tbody>
296302
</table>

django_unicorn/components/unicorn_template_response.py

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ def is_html_well_formed(html: str) -> bool:
6262

6363

6464
def assert_has_single_wrapper_element(root_element: Tag, component_name: str) -> None:
65+
"""Assert that there is at least one child in the root element. And that there is only
66+
one root element.
67+
"""
68+
6569
# Check that the root element has at least one child
6670
try:
6771
next(root_element.descendants)
@@ -70,8 +74,13 @@ def assert_has_single_wrapper_element(root_element: Tag, component_name: str) ->
7074
f"The '{component_name}' component does not appear to have one root element."
7175
) from None
7276

77+
if "unicorn:view" in root_element.attrs or "u:view" in root_element.attrs:
78+
# If the root element is a direct view, skip the check
79+
return
80+
7381
# Check that there is not more than one root element
7482
parent_element = root_element.parent
83+
7584
tag_count = len([c for c in parent_element.children if isinstance(c, Tag)])
7685

7786
if tag_count > 1:
@@ -80,6 +89,37 @@ def assert_has_single_wrapper_element(root_element: Tag, component_name: str) ->
8089
) from None
8190

8291

92+
def _get_direct_view(tag: Tag):
93+
return tag.find_next(attrs={"unicorn:view": True}) or tag.find_next(attrs={"u:view": True})
94+
95+
96+
def get_root_element(soup: BeautifulSoup) -> Tag:
97+
"""Gets the first tag element for the component or the first element with a `unicorn:view` attribute for a direct
98+
view.
99+
100+
Returns:
101+
BeautifulSoup tag element.
102+
103+
Raises `Exception` if an element cannot be found.
104+
"""
105+
106+
for element in soup.contents:
107+
if isinstance(element, Tag) and element.name:
108+
if element.name == "html":
109+
view_element = _get_direct_view(element)
110+
111+
if not view_element:
112+
raise MissingComponentViewElementError(
113+
"An element with an `unicorn:view` attribute is required for a direct view"
114+
)
115+
116+
return view_element
117+
118+
return element
119+
120+
raise MissingComponentElementError("No root element for the component was found")
121+
122+
83123
class UnsortedAttributes(HTMLFormatter):
84124
"""
85125
Prevent beautifulsoup from re-ordering attributes.
@@ -230,32 +270,3 @@ def render(self):
230270
def _desoupify(soup):
231271
soup.smooth()
232272
return soup.encode(formatter=UnsortedAttributes()).decode("utf-8")
233-
234-
235-
def get_root_element(soup: BeautifulSoup) -> Tag:
236-
"""
237-
Gets the first tag element for the component or the first element with a `unicorn:view` attribute for a direct view.
238-
239-
Returns:
240-
BeautifulSoup tag element.
241-
242-
Raises `Exception` if an element cannot be found.
243-
"""
244-
245-
for element in soup.contents:
246-
if isinstance(element, Tag) and element.name:
247-
if element.name == "html":
248-
view_element = element.find_next(attrs={"unicorn:view": True}) or element.find_next(
249-
attrs={"u:view": True}
250-
)
251-
252-
if not view_element:
253-
raise MissingComponentViewElementError(
254-
"An element with an `unicorn:view` attribute is required for a direct view"
255-
)
256-
257-
return view_element
258-
259-
return element
260-
261-
raise MissingComponentElementError("No root element for the component was found")

django_unicorn/components/unicorn_view.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type
88

99
import shortuuid
10-
from django.apps import AppConfig
11-
from django.conf import settings
10+
from django.apps import apps as django_apps_module
1211
from django.core.exceptions import ImproperlyConfigured
1312
from django.db.models import Model
1413
from django.forms.widgets import CheckboxInput, Select
@@ -106,30 +105,17 @@ def get_locations(component_name: str) -> List[Tuple[str, str]]:
106105
class_name = f"{class_name}View"
107106
module_name = convert_to_snake_case(component_name)
108107

109-
unicorn_apps = get_setting("APPS", settings.INSTALLED_APPS)
108+
# note - app_config.name gives the python path
109+
all_django_apps = [app_config.name for app_config in django_apps_module.get_app_configs()]
110+
unicorn_apps = get_setting("APPS", all_django_apps)
110111

111112
if not is_non_string_sequence(unicorn_apps):
112113
raise AssertionError("APPS is expected to be a list, tuple or set")
113114

114-
for app in unicorn_apps:
115-
# Handle an installed app that actually points to an app config
116-
if ".apps." in app:
117-
is_app_config = True # default to True for backwards compatibility
118-
app_config_idx = app.rindex(".apps.")
115+
locations += [(f"{app}.components.{module_name}", class_name) for app in unicorn_apps]
119116

120-
try:
121-
app_config_module_name = app[: app_config_idx + 5]
122-
app_config_class_name = app[app_config_idx + 6 :]
123-
app_config_module = importlib.import_module(app_config_module_name)
124-
is_app_config = type(getattr(app_config_module, app_config_class_name)) == type(AppConfig)
125-
except ModuleNotFoundError:
126-
pass
127-
128-
if is_app_config:
129-
app = app[:app_config_idx] # noqa: PLW2901
130-
131-
app_module_name = f"{app}.components.{module_name}"
132-
locations.append((app_module_name, class_name))
117+
# Add default directory to the end of the list as a fallback
118+
locations.append((f"components.{module_name}", class_name))
133119

134120
return locations
135121

@@ -393,8 +379,6 @@ def dispatch(self, request, *args, **kwargs): # noqa: ARG002
393379
self.mount()
394380
self.hydrate()
395381

396-
self._cache_component(**kwargs)
397-
398382
return self.render_to_response(
399383
context=self.get_context_data(),
400384
component=self,

0 commit comments

Comments
 (0)