-
-
Notifications
You must be signed in to change notification settings - Fork 115
[fix] Fixed compatibility of custom theme assets with collectstatic #579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d7cfdb6
81743cb
1765ad7
e476eae
73c4f57
c3720b2
2a7e81d
95b15df
24e0144
441f95d
710cb4e
5a73c39
1510f86
596e2f4
6630ec7
503dd97
fe8abc3
7c10a60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,11 +59,89 @@ def test_wait_for_services(self): | |
|
|
||
|
|
||
| class TestServices(TestUtilities, unittest.TestCase): | ||
| custom_static_token = None | ||
|
|
||
| @property | ||
| def failureException(self): | ||
| TestServices.failed_test = True | ||
| return super().failureException | ||
|
|
||
| @classmethod | ||
| def _execute_docker_compose_command(cls, cmd_args, use_text_mode=False): | ||
| """Execute a docker compose command and log output. | ||
|
|
||
| Args: | ||
| cmd_args: List of command arguments for subprocess.Popen | ||
| use_text_mode: If True, use text mode for subprocess output | ||
|
|
||
| Returns: | ||
| Tuple of (output, error) from command execution | ||
| """ | ||
| kwargs = { | ||
| "stdout": subprocess.PIPE, | ||
| "stderr": subprocess.PIPE, | ||
| "cwd": cls.root_location, | ||
| } | ||
| if use_text_mode: | ||
| kwargs["text"] = True | ||
| cmd = subprocess.run(cmd_args, check=False, **kwargs) | ||
| if use_text_mode: | ||
| output, error = cmd.stdout, cmd.stderr | ||
| else: | ||
| output = cmd.stdout.decode("utf-8", errors="replace") if cmd.stdout else "" | ||
| error = cmd.stderr.decode("utf-8", errors="replace") if cmd.stderr else "" | ||
| output, error = map(str, (cmd.stdout, cmd.stderr)) | ||
| with open(cls.config["logs_file"], "a") as logs_file: | ||
| logs_file.write(output) | ||
| logs_file.write(error) | ||
| if cmd.returncode != 0: | ||
| raise RuntimeError( | ||
| f"docker compose command failed " | ||
| f"({cmd.returncode}): {' '.join(cmd_args)}" | ||
| ) | ||
| return output, error | ||
|
Comment on lines
+69
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider the bytes-to-string conversion for log output. When ♻️ Proposed improvement- cmd = subprocess.run(cmd_args, check=False, **kwargs)
- output, error = map(str, (cmd.stdout, cmd.stderr))
+ cmd = subprocess.run(cmd_args, check=False, **kwargs)
+ if use_text_mode:
+ output, error = cmd.stdout, cmd.stderr
+ else:
+ output = cmd.stdout.decode("utf-8", errors="replace") if cmd.stdout else ""
+ error = cmd.stderr.decode("utf-8", errors="replace") if cmd.stderr else ""🤖 Prompt for AI Agents |
||
|
|
||
| @classmethod | ||
| def _setup_admin_theme_links(cls): | ||
| """Configure admin theme links during tests. | ||
|
|
||
| The default docker-compose setup does not allow injecting | ||
| OPENWISP_ADMIN_THEME_LINKS dynamically, so this method updates | ||
| Django settings inside the running container and reloads uWSGI. | ||
| This enables the Selenium tests to verify that a custom static CSS | ||
| file is served by the admin interface. | ||
| """ | ||
| css_path = os.path.join( | ||
| cls.root_location, | ||
| "customization", | ||
| "theme", | ||
| cls.config["custom_css_filename"], | ||
| ) | ||
| cls.custom_static_token = str(time.time_ns()) | ||
| with open(css_path, "w") as custom_css_file: | ||
| custom_css_file.write( | ||
| f"body{{--openwisp-test: {cls.custom_static_token};}}" | ||
| ) | ||
| script = rf""" | ||
| grep -q OPENWISP_ADMIN_THEME_LINKS /opt/openwisp/openwisp/settings.py || \ | ||
| printf "\nOPENWISP_ADMIN_THEME_LINKS=[{{\"type\":\"text/css\",\"href\":\"/static/admin/css/openwisp.css\",\"rel\":\"stylesheet\",\"media\":\"all\"}},{{\"type\":\"text/css\",\"href\":\"/static/{cls.config["custom_css_filename"]}\",\"rel\":\"stylesheet\",\"media\":\"all\"}},{{\"type\":\"image/x-icon\",\"href\":\"ui/openwisp/images/favicon.png\",\"rel\":\"icon\"}}]\n" >> /opt/openwisp/openwisp/settings.py && | ||
| python collectstatic.py && | ||
| uwsgi --reload uwsgi.pid | ||
| """ # noqa: E501 | ||
| cls._execute_docker_compose_command( | ||
| [ | ||
| "docker", | ||
| "compose", | ||
| "exec", | ||
| "-T", | ||
| "dashboard", | ||
| "bash", | ||
| "-c", | ||
| script, | ||
| ], | ||
| use_text_mode=True, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| cls.failed_test = False | ||
|
|
@@ -76,7 +154,7 @@ def setUpClass(cls): | |
| os.path.dirname(os.path.realpath(__file__)), "data.py" | ||
| ) | ||
| entrypoint = "python manage.py shell --command='import data; data.setup()'" | ||
| cmd = subprocess.Popen( | ||
| cls._execute_docker_compose_command( | ||
| [ | ||
| "docker", | ||
| "compose", | ||
|
|
@@ -87,22 +165,12 @@ def setUpClass(cls): | |
| "--volume", | ||
| f"{test_data_file}:/opt/openwisp/data.py", | ||
| "dashboard", | ||
| ], | ||
| universal_newlines=True, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE, | ||
| cwd=cls.root_location, | ||
| ] | ||
| ) | ||
| output, error = map(str, cmd.communicate()) | ||
| with open(cls.config["logs_file"], "w") as logs_file: | ||
| logs_file.write(output) | ||
| logs_file.write(error) | ||
| subprocess.run( | ||
| cls._execute_docker_compose_command( | ||
| ["docker", "compose", "up", "--detach"], | ||
| stdout=subprocess.DEVNULL, | ||
| stderr=subprocess.DEVNULL, | ||
| cwd=cls.root_location, | ||
| ) | ||
| cls._setup_admin_theme_links() | ||
| # Create base drivers (Firefox) | ||
| if cls.config["driver"] == "firefox": | ||
| cls.base_driver = cls.get_firefox_webdriver() | ||
|
|
@@ -122,6 +190,15 @@ def tearDownClass(cls): | |
| print(f"Unable to delete resource at: {resource_link}") | ||
| cls.second_driver.quit() | ||
| cls.base_driver.quit() | ||
| # Remove the temporary custom CSS file created for testing | ||
| css_path = os.path.join( | ||
| cls.root_location, | ||
| "customization", | ||
| "theme", | ||
| cls.config["custom_css_filename"], | ||
| ) | ||
| if os.path.exists(css_path): | ||
| os.remove(css_path) | ||
| if cls.failed_test and cls.config["logs"]: | ||
| cmd = subprocess.Popen( | ||
| ["docker", "compose", "logs"], | ||
|
|
@@ -156,6 +233,16 @@ def test_admin_login(self): | |
| ) | ||
| self.fail(message) | ||
|
|
||
| def test_custom_static_files_loaded(self): | ||
| self.login() | ||
| self.open("/admin/") | ||
| # Check if the custom CSS variable is applied | ||
| value = self.web_driver.execute_script( | ||
| "return getComputedStyle(document.body)" | ||
| ".getPropertyValue('--openwisp-test');" | ||
| ) | ||
| self.assertEqual(value.strip(), self.custom_static_token) | ||
|
|
||
| def test_device_monitoring_charts(self): | ||
| self.login() | ||
| self.get_resource("test-device", "/admin/config/device/") | ||
|
|
@@ -235,9 +322,17 @@ def test_forgot_password(self): | |
| """Test forgot password to ensure that postfix is working properly.""" | ||
|
|
||
| self.logout() | ||
| try: | ||
| WebDriverWait(self.base_driver, 3).until( | ||
| EC.text_to_be_present_in_element( | ||
| (By.CSS_SELECTOR, ".title-wrapper h1"), "Logged out" | ||
| ) | ||
| ) | ||
| except TimeoutException: | ||
| self.fail("Logout failed.") | ||
|
nemesifier marked this conversation as resolved.
|
||
| self.open("/accounts/password/reset/") | ||
| self.find_element(By.NAME, "email").send_keys("admin@example.com") | ||
| self.find_element(By.XPATH, '//button[@type="submit"]').click() | ||
| self.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click() | ||
| self._wait_until_page_ready() | ||
| self.assertIn( | ||
| "We have sent you an email. If you have not received " | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment explaining why this is needed please.