Skip to content

Commit f880fb2

Browse files
committedAug 26, 2024·
chore
0 parents  commit f880fb2

23 files changed

+594
-0
lines changed
 

‎.github/workflows/python-publish.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This workflow will upload a Python Package using Twine when a release is created
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3+
4+
# This workflow uses actions that are not certified by GitHub.
5+
# They are provided by a third-party and are governed by
6+
# separate terms of service, privacy policy, and support
7+
# documentation.
8+
9+
name: Upload Python Package
10+
11+
on:
12+
release:
13+
types: [published]
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
deploy:
20+
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
- name: Set up Python
26+
uses: actions/setup-python@v3
27+
with:
28+
python-version: '3.10'
29+
- name: Install dependencies
30+
run: |
31+
python -m pip install poetry
32+
- name: Build package
33+
run: poetry build
34+
- name: Publish package
35+
uses: pypa/gh-action-pypi-publish@release/v1
36+
with:
37+
password: ${{ secrets.PYPI_API_TOKEN }}

‎.github/workflows/runtests.yml

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Runs tests
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
11+
code-style-check:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: '3.10'
19+
cache: pip
20+
- name: Install dependencies
21+
run: |
22+
pip install -r requirements-dev.txt
23+
- name: Check code style
24+
run: |
25+
pre-commit run --all-files
26+
27+
runtests:
28+
runs-on: ubuntu-latest
29+
env:
30+
CHANNELS_REDIS: redis://localhost:6379/0
31+
strategy:
32+
matrix:
33+
python-version: ['3.8', '3.9', '3.10' ]
34+
services:
35+
redis:
36+
image: redis
37+
ports:
38+
- 6379:6379
39+
steps:
40+
- uses: actions/checkout@v4
41+
- name: Set up Python ${{ matrix.python-version }}
42+
uses: actions/setup-python@v5
43+
with:
44+
python-version: ${{ matrix.python-version }}
45+
- name: Install dependencies
46+
run: |
47+
pip install -r requirements-dev.txt
48+
- name: Run tests
49+
run: |
50+
tox

‎.gitignore

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
*.pyc
2+
*.egg
3+
*.egg-info
4+
5+
docs/build
6+
dist
7+
build
8+
9+
*.lock
10+
11+
*.sqlite3
12+
*.db
13+
14+
*.DS_Store
15+
16+
.cache
17+
__pycache__
18+
.mypy_cache/
19+
.pytest_cache/
20+
.vscode/
21+
.coverage
22+
docs/build
23+
24+
node_modules/
25+
26+
*.bak
27+
28+
logs
29+
*log
30+
npm-debug.log*
31+
32+
# Translations
33+
# *.mo
34+
*.pot
35+
36+
# Django media/static dirs
37+
media/
38+
static/dist/
39+
static/dev/
40+
41+
.ipython/
42+
.env
43+
44+
celerybeat.pid
45+
celerybeat-schedule
46+
47+
# Common typos
48+
:w
49+
'
50+
.tox
51+
52+
/venv/

‎.pre-commit-config.yaml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
repos:
2+
- repo: https://github.com/pycqa/isort
3+
rev: 5.12.0
4+
hooks:
5+
- id: isort
6+
- repo: https://github.com/psf/black
7+
rev: 23.10.1
8+
hooks:
9+
- id: black
10+
- repo: https://github.com/PyCQA/flake8
11+
rev: 6.0.0
12+
hooks:
13+
- id: flake8
14+
additional_dependencies:
15+
- flake8-bugbear
16+
- flake8-comprehensions
17+
- flake8-no-pep420
18+
- flake8-print
19+
- flake8-tidy-imports
20+
- flake8-typing-imports
21+
- repo: https://github.com/pre-commit/mirrors-mypy
22+
rev: v1.6.1
23+
hooks:
24+
- id: mypy

‎.readthedocs.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: 2
2+
3+
build:
4+
os: ubuntu-22.04
5+
tools:
6+
python: "3.12"
7+
8+
# Build documentation in the "docs/" directory with Sphinx
9+
sphinx:
10+
configuration: docs/source/conf.py
11+
12+
python:
13+
install:
14+
- requirements: docs/requirements.txt

‎Makefile

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
build:
2+
poetry build
3+
4+
publish:
5+
poetry publish
6+
7+
# poetry config repositories.testpypi https://test.pypi.org/legacy/
8+
publish-test:
9+
poetry publish -r testpypi

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# README

‎pyproject.toml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[tool.poetry]
2+
name = "django-template-simplify"
3+
version = "1.0.1"
4+
description = "A set of tools to help simplify your Django templates."
5+
authors = ["Michael Yin <michaelyin@accordbox.com>"]
6+
license = "MIT"
7+
homepage = "https://github.com/rails-inspire-django/django-template-simplify"
8+
readme = "README.md"
9+
packages = [{ include = "template_simplify", from = "src" }]
10+
11+
[tool.poetry.urls]
12+
Changelog = "https://github.com/rails-inspire-django/django-template-simplify/releases"
13+
14+
[tool.poetry.dependencies]
15+
python = ">=3.8"
16+
django = ">=3.0"
17+
18+
[tool.poetry.dev-dependencies]
19+
20+
[build-system]
21+
requires = ["setuptools", "poetry_core>=1.0"]
22+
build-backend = "poetry.core.masonry.api"

‎requirements-dev.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
pre-commit==2.9.2
2+
tox==4.11.3
3+
tox-gh-actions==3.1.3
4+
5+
django==4.2 # for local tests
6+
typing_extensions
7+
pytest
8+
pytest-django
9+
pytest-xdist
10+
pytest-mock
11+
jinja2

‎setup.cfg

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[flake8]
2+
ignore = E203, E266, E501, W503, E231, E701, B950, B907
3+
max-line-length = 88
4+
max-complexity = 18
5+
select = B,C,E,F,W,T4,B9
6+
7+
[isort]
8+
profile = black
9+
10+
[mypy]
11+
python_version = 3.10
12+
check_untyped_defs = False
13+
ignore_missing_imports = True
14+
warn_unused_ignores = False
15+
warn_redundant_casts = False
16+
warn_unused_configs = False
17+
18+
[mypy-*.tests.*]
19+
ignore_errors = True
20+
21+
[mypy-*.migrations.*]
22+
ignore_errors = True

‎src/template_simplify/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .templatetags.template_simplify import dom_id
2+
3+
__all__ = [
4+
"dom_id",
5+
]

‎src/template_simplify/templatetags/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import re
2+
from typing import Any, Optional
3+
4+
from django import template
5+
from django.db.models.base import Model
6+
from django.template import Node, TemplateSyntaxError
7+
8+
register = template.Library()
9+
10+
11+
@register.simple_tag
12+
def dom_id(instance: Any, prefix: Optional[str] = "") -> str:
13+
"""
14+
Generate a unique identifier for a Django model instance, class, or even Python object.
15+
16+
Args:
17+
instance (Any): The instance or class for which the identifier is generated.
18+
prefix (Optional[str]): An optional prefix to prepend to the identifier. Defaults to an empty string.
19+
20+
Returns:
21+
str: The generated identifier.
22+
23+
Raises:
24+
Exception: If the model instance does not have either the `to_key` or `pk` attribute.
25+
26+
Note:
27+
- If `instance` is a Django model instance, the identifier is generated based on the `to_key` or `pk` attribute.
28+
- If `instance` is a Django model class, the identifier is generated as `new_<class_name>`.
29+
- If `instance` is neither a model instance nor a model class, the identifier is generated based on the `to_key`
30+
attribute if available, otherwise it uses the string representation of the instance.
31+
- The `prefix` argument can be used to prepend a prefix to the generated identifier.
32+
"""
33+
if not isinstance(instance, type) and isinstance(instance, Model):
34+
# Django model instance
35+
if hasattr(instance, "to_key") and getattr(instance, "to_key"): # noqa: B009
36+
identifier = f"{instance.__class__.__name__.lower()}_{instance.to_key}"
37+
elif hasattr(instance, "pk") and getattr(instance, "pk"): # noqa: B009
38+
identifier = f"{instance.__class__.__name__.lower()}_{instance.pk}"
39+
else:
40+
raise Exception(
41+
f"Model instance must have either to_key or pk attribute {instance}"
42+
)
43+
elif isinstance(instance, type) and issubclass(instance, Model):
44+
# Django model class
45+
identifier = f"new_{instance.__name__.lower()}"
46+
else:
47+
if hasattr(instance, "to_key") and getattr(instance, "to_key"): # noqa: B009
48+
# Developer can still use to_key property to generate the identifier
49+
identifier = f"{instance.to_key}"
50+
else:
51+
# Use the string representation
52+
identifier = str(instance)
53+
54+
if prefix:
55+
identifier = f"{prefix}_{identifier}"
56+
57+
return identifier
58+
59+
60+
ATTRIBUTE_RE = re.compile(
61+
r"""
62+
(?P<attr>
63+
[@\w:_\.\/-]+
64+
)
65+
(?P<sign>
66+
\+?=
67+
)
68+
(?P<value>
69+
['"]? # start quote
70+
[^"']*
71+
['"]? # end quote
72+
)
73+
""",
74+
re.VERBOSE | re.UNICODE,
75+
)
76+
77+
78+
VALUE_RE = re.compile(
79+
r"""
80+
['"] # start quote (required)
81+
(?P<value>
82+
[^"']* # match any character except quotes
83+
)
84+
['"] # end quote (required)
85+
""",
86+
re.VERBOSE | re.UNICODE,
87+
)
88+
89+
90+
@register.tag
91+
def class_names(parser, token):
92+
error_msg = f"{token.split_contents()[0]!r} tag requires " "a list of css classes"
93+
try:
94+
bits = token.split_contents()
95+
tag_name = bits[0] # noqa
96+
attr_list = bits[1:]
97+
except ValueError as exc:
98+
raise TemplateSyntaxError(error_msg) from exc
99+
100+
css_ls = []
101+
css_dict = {}
102+
for pair in attr_list:
103+
attribute_match = ATTRIBUTE_RE.match(pair) or VALUE_RE.match(pair)
104+
105+
if attribute_match:
106+
dct = attribute_match.groupdict()
107+
attr = dct.get("attr", None)
108+
# sign = dct.get("sign", None)
109+
value = parser.compile_filter(dct["value"])
110+
if attr:
111+
css_dict[attr] = value
112+
else:
113+
css_ls.append(value)
114+
else:
115+
raise TemplateSyntaxError("class_names found supported token: " + f"{pair}")
116+
117+
return ClassNamesNode(css_ls=css_ls, css_dict=css_dict)
118+
119+
120+
class ClassNamesNode(Node):
121+
def __init__(self, css_ls, css_dict):
122+
self.css_ls = css_ls
123+
self.css_dict = css_dict
124+
125+
def render(self, context):
126+
final_css = []
127+
128+
# for common css classes
129+
for value in self.css_ls:
130+
final_css.append(value.token)
131+
132+
# for conditionals
133+
for attr, expression in self.css_dict.items():
134+
real_value = expression.resolve(context)
135+
if real_value:
136+
final_css.append(attr)
137+
138+
return " ".join(final_css)

‎tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)
Please sign in to comment.