2024-08-11 18:21:27 +00:00
|
|
|
from random import choices
|
|
|
|
from string import ascii_lowercase
|
2024-09-03 13:25:14 +00:00
|
|
|
from typing import Any, Callable
|
2024-08-08 19:19:43 +00:00
|
|
|
|
2024-08-11 18:21:27 +00:00
|
|
|
from django.template.loader import render_to_string
|
2024-09-03 13:25:14 +00:00
|
|
|
from django.urls import NoReverseMatch, reverse
|
2024-08-11 18:21:27 +00:00
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
|
|
|
|
|
|
|
|
def Popover(
|
|
|
|
wrapped_content: str,
|
|
|
|
popover_content: str = "",
|
|
|
|
) -> str:
|
|
|
|
id = randomid()
|
|
|
|
if popover_content == "":
|
|
|
|
popover_content = wrapped_content
|
|
|
|
content = f"<span data-popover-target={id}>{wrapped_content}</span>"
|
|
|
|
result = mark_safe(
|
|
|
|
str(content)
|
|
|
|
+ render_to_string(
|
2024-09-02 15:43:41 +00:00
|
|
|
"cotton/popover.html",
|
2024-08-11 18:21:27 +00:00
|
|
|
{
|
|
|
|
"id": id,
|
2024-09-02 15:43:41 +00:00
|
|
|
"slot": popover_content,
|
2024-08-11 18:21:27 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return result
|
|
|
|
|
2024-08-08 19:19:43 +00:00
|
|
|
|
2024-09-02 18:04:21 +00:00
|
|
|
HTMLAttribute = tuple[str, str]
|
|
|
|
HTMLTag = str
|
|
|
|
|
|
|
|
|
2024-09-03 13:25:14 +00:00
|
|
|
def Component(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
template: str = "",
|
|
|
|
tag_name: str = "",
|
|
|
|
) -> HTMLTag:
|
|
|
|
if not tag_name and not template:
|
|
|
|
raise ValueError("One of template or tag_name is required.")
|
2024-09-02 18:04:21 +00:00
|
|
|
if isinstance(children, str):
|
|
|
|
children = [children]
|
|
|
|
childrenBlob = "\n".join(children)
|
|
|
|
attributesList = [f'{name} = "{value}"' for name, value in attributes]
|
|
|
|
attributesBlob = " ".join(attributesList)
|
2024-09-03 13:25:14 +00:00
|
|
|
tag: str = ""
|
|
|
|
if tag_name != "":
|
|
|
|
tag = f"<a {attributesBlob}>{childrenBlob}</a>"
|
|
|
|
elif template != "":
|
|
|
|
tag = render_to_string(
|
|
|
|
template,
|
|
|
|
{name: value for name, value in attributes} | {"slot": "\n".join(children)},
|
|
|
|
)
|
2024-09-02 18:04:21 +00:00
|
|
|
return mark_safe(tag)
|
|
|
|
|
|
|
|
|
2024-09-03 13:25:14 +00:00
|
|
|
def A(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
url: str | Callable[..., Any] = "",
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Returns the HTML tag "a".
|
|
|
|
"url" can either be:
|
|
|
|
- URL (string)
|
|
|
|
- path name passed to reverse() (string)
|
|
|
|
- function
|
|
|
|
"""
|
|
|
|
additional_attributes = []
|
|
|
|
if url:
|
|
|
|
if type(url) is str:
|
|
|
|
try:
|
|
|
|
url_result = reverse(url)
|
|
|
|
except NoReverseMatch:
|
|
|
|
url_result = url
|
|
|
|
elif callable(url):
|
|
|
|
url_result = url()
|
|
|
|
else:
|
|
|
|
raise TypeError("'url' is neither str nor function.")
|
|
|
|
additional_attributes = [("href", url_result)]
|
|
|
|
return Component(
|
|
|
|
tag_name="a", attributes=attributes + additional_attributes, children=children
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def Button(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
):
|
|
|
|
return Component(
|
|
|
|
template="cotton/button.html", attributes=attributes, children=children
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-11-09 09:06:14 +00:00
|
|
|
def safe_division(numerator: int | float, denominator: int | float) -> int | float:
|
|
|
|
"""
|
|
|
|
Divides without triggering division by zero exception.
|
|
|
|
Returns 0 if denominator is 0.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return numerator / denominator
|
|
|
|
except ZeroDivisionError:
|
|
|
|
return 0
|
2024-06-03 16:19:11 +00:00
|
|
|
|
|
|
|
|
2024-08-08 19:19:43 +00:00
|
|
|
def safe_getattr(obj: object, attr_chain: str, default: Any | None = None) -> object:
|
2024-05-30 09:15:52 +00:00
|
|
|
"""
|
|
|
|
Safely get the nested attribute from an object.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
obj (object): The object from which to retrieve the attribute.
|
|
|
|
attr_chain (str): The chain of attributes, separated by dots.
|
|
|
|
default: The default value to return if any attribute in the chain does not exist.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The value of the nested attribute if it exists, otherwise the default value.
|
|
|
|
"""
|
2024-06-03 16:19:11 +00:00
|
|
|
attrs = attr_chain.split(".")
|
2024-05-30 09:15:52 +00:00
|
|
|
for attr in attrs:
|
|
|
|
try:
|
|
|
|
obj = getattr(obj, attr)
|
|
|
|
except AttributeError:
|
|
|
|
return default
|
|
|
|
return obj
|
2024-08-11 18:21:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
def truncate(input_string: str, length: int = 30, ellipsis: str = "…") -> str:
|
|
|
|
return (
|
|
|
|
(f"{input_string[:length-len(ellipsis)]}{ellipsis}")
|
|
|
|
if len(input_string) > 30
|
|
|
|
else input_string
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def truncate_with_popover(input_string: str) -> str:
|
|
|
|
if (truncated := truncate(input_string)) != input_string:
|
|
|
|
print(f"Not the same after: {truncated=}")
|
|
|
|
return Popover(wrapped_content=truncated, popover_content=input_string)
|
|
|
|
else:
|
|
|
|
print("Strings are the same!")
|
|
|
|
return input_string
|
|
|
|
|
|
|
|
|
|
|
|
def randomid(seed: str = "", length: int = 10) -> str:
|
|
|
|
return seed + "".join(choices(ascii_lowercase, k=length))
|