django-bidi-input 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-bidi-input
3
+ Version: 1.0.0
4
+ Summary: Force correct text direction (LTR/RTL) on Django admin and form input fields, regardless of site language.
5
+ Author-email: Omid Shojaee <omid.shojaee@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/omidshojaee/django-bidi-input
8
+ Project-URL: Issues, https://github.com/omidshojaee/django-bidi-input/issues
9
+ Classifier: Framework :: Django
10
+ Classifier: Framework :: Django :: 5.2
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: Django>=5.2
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=7; extra == "test"
24
+ Requires-Dist: pytest-django>=4; extra == "test"
25
+
26
+ # django-bidi-input
27
+
28
+ Django doesn't let you control the text direction of individual form/admin
29
+ input fields — when a site's language is RTL (Farsi, Arabic, Hebrew, ...),
30
+ every input renders RTL, including fields where that's wrong: email, URL,
31
+ slug, UUID, IP address, numbers, passwords, phone numbers, etc. Only free
32
+ text fields should follow the page direction.
33
+
34
+ This has been an accepted Django ticket
35
+ ([#35482](https://code.djangoproject.com/ticket/35482)) for a while with no
36
+ implementation yet. `django-bidi-input` fixes it today, without patching
37
+ Django, for both **admin** and regular **forms**.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pip install django-bidi-input
43
+ ```
44
+
45
+ ```python
46
+ INSTALLED_APPS = [
47
+ ...,
48
+ "django_bidi_input",
49
+ ]
50
+ ```
51
+
52
+ That's it. Every `ModelForm`, plain `Form`, and admin form in your project
53
+ now gets a correct `dir` attribute (and a `bidi-input-ltr`/`bidi-input-rtl`
54
+ CSS class as a fallback) on each field, automatically.
55
+
56
+ ## How direction is decided
57
+
58
+ For each field, in order (first match wins):
59
+
60
+ 1. You already set `dir` on the widget yourself — left untouched.
61
+ 2. `direction` set directly on the model field instance:
62
+ ```python
63
+ phone_number = models.CharField(max_length=32)
64
+ phone_number.direction = "ltr"
65
+ ```
66
+ 3. `BIDI_INPUT_OVERRIDES` setting, for fields you can't edit directly
67
+ (third-party app models, etc.):
68
+ ```python
69
+ BIDI_INPUT_OVERRIDES = {
70
+ "myapp.Contact.phone_number": "ltr",
71
+ }
72
+ ```
73
+ 4. Built-in default, in order:
74
+ - widget `input_type` is `password`, `number`, `tel`, `date`, `time`, etc.
75
+ - widget `autocomplete` is `username`, `email`, `tel`, `new-password`,
76
+ `current-password`, `one-time-code`. This is what makes Django's own
77
+ admin/auth **login form** (`username` + `password`) render LTR, even
78
+ though `UsernameField` is a plain `CharField` — Django sets
79
+ `autocomplete="username"` on it unconditionally, including when a
80
+ custom user model uses `USERNAME_FIELD = "email"`.
81
+ - field class is `EmailField`, `URLField`, `SlugField`, `UUIDField`,
82
+ `GenericIPAddressField`, `FilePathField`, `IntegerField`, `FloatField`,
83
+ `DecimalField`, `DurationField`, `RegexField`.
84
+ 5. Otherwise left alone — inherits the page's direction (correct for plain
85
+ `CharField`/`TextField`).
86
+
87
+ ## Settings
88
+
89
+ | Setting | Default | Purpose |
90
+ |---|---|---|
91
+ | `BIDI_INPUT_ENABLED` | `True` | Turn the whole thing off. |
92
+ | `BIDI_INPUT_OVERRIDES` | `{}` | `"app_label.Model.field": "ltr"/"rtl"` map. |
93
+ | `BIDI_INPUT_LTR_FIELD_CLASSES` | see `conf.py` | Dotted paths of extra form field classes to treat as LTR. |
94
+ | `BIDI_INPUT_LTR_INPUT_TYPES` | see `conf.py` | Extra widget `input_type` values to treat as LTR. |
95
+ | `BIDI_INPUT_LTR_AUTOCOMPLETE_VALUES` | see `conf.py` | Extra widget `autocomplete` values to treat as LTR. |
96
+
97
+ ## Why this works for admin too
98
+
99
+ Django admin builds its forms as ordinary `ModelForm` subclasses via
100
+ `modelform_factory`. This package hooks `BaseForm.__init__` once, so admin
101
+ inherits the behavior automatically — no `ModelAdmin` changes needed.
102
+
103
+ ## Scope
104
+
105
+ This targets the actual pain point: **Django admin and Django forms**.
106
+ It does not attempt to make `direction` a real `Field` kwarg (that requires
107
+ changing Django itself, which is what ticket #35482 is for), so
108
+ introspection-based tools built outside the forms API (DRF serializers,
109
+ etc.) won't see it.
110
+
111
+ ## Running the tests
112
+
113
+ ```bash
114
+ pip install -e ".[test]"
115
+ pytest
116
+ ```
@@ -0,0 +1,91 @@
1
+ # django-bidi-input
2
+
3
+ Django doesn't let you control the text direction of individual form/admin
4
+ input fields — when a site's language is RTL (Farsi, Arabic, Hebrew, ...),
5
+ every input renders RTL, including fields where that's wrong: email, URL,
6
+ slug, UUID, IP address, numbers, passwords, phone numbers, etc. Only free
7
+ text fields should follow the page direction.
8
+
9
+ This has been an accepted Django ticket
10
+ ([#35482](https://code.djangoproject.com/ticket/35482)) for a while with no
11
+ implementation yet. `django-bidi-input` fixes it today, without patching
12
+ Django, for both **admin** and regular **forms**.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install django-bidi-input
18
+ ```
19
+
20
+ ```python
21
+ INSTALLED_APPS = [
22
+ ...,
23
+ "django_bidi_input",
24
+ ]
25
+ ```
26
+
27
+ That's it. Every `ModelForm`, plain `Form`, and admin form in your project
28
+ now gets a correct `dir` attribute (and a `bidi-input-ltr`/`bidi-input-rtl`
29
+ CSS class as a fallback) on each field, automatically.
30
+
31
+ ## How direction is decided
32
+
33
+ For each field, in order (first match wins):
34
+
35
+ 1. You already set `dir` on the widget yourself — left untouched.
36
+ 2. `direction` set directly on the model field instance:
37
+ ```python
38
+ phone_number = models.CharField(max_length=32)
39
+ phone_number.direction = "ltr"
40
+ ```
41
+ 3. `BIDI_INPUT_OVERRIDES` setting, for fields you can't edit directly
42
+ (third-party app models, etc.):
43
+ ```python
44
+ BIDI_INPUT_OVERRIDES = {
45
+ "myapp.Contact.phone_number": "ltr",
46
+ }
47
+ ```
48
+ 4. Built-in default, in order:
49
+ - widget `input_type` is `password`, `number`, `tel`, `date`, `time`, etc.
50
+ - widget `autocomplete` is `username`, `email`, `tel`, `new-password`,
51
+ `current-password`, `one-time-code`. This is what makes Django's own
52
+ admin/auth **login form** (`username` + `password`) render LTR, even
53
+ though `UsernameField` is a plain `CharField` — Django sets
54
+ `autocomplete="username"` on it unconditionally, including when a
55
+ custom user model uses `USERNAME_FIELD = "email"`.
56
+ - field class is `EmailField`, `URLField`, `SlugField`, `UUIDField`,
57
+ `GenericIPAddressField`, `FilePathField`, `IntegerField`, `FloatField`,
58
+ `DecimalField`, `DurationField`, `RegexField`.
59
+ 5. Otherwise left alone — inherits the page's direction (correct for plain
60
+ `CharField`/`TextField`).
61
+
62
+ ## Settings
63
+
64
+ | Setting | Default | Purpose |
65
+ |---|---|---|
66
+ | `BIDI_INPUT_ENABLED` | `True` | Turn the whole thing off. |
67
+ | `BIDI_INPUT_OVERRIDES` | `{}` | `"app_label.Model.field": "ltr"/"rtl"` map. |
68
+ | `BIDI_INPUT_LTR_FIELD_CLASSES` | see `conf.py` | Dotted paths of extra form field classes to treat as LTR. |
69
+ | `BIDI_INPUT_LTR_INPUT_TYPES` | see `conf.py` | Extra widget `input_type` values to treat as LTR. |
70
+ | `BIDI_INPUT_LTR_AUTOCOMPLETE_VALUES` | see `conf.py` | Extra widget `autocomplete` values to treat as LTR. |
71
+
72
+ ## Why this works for admin too
73
+
74
+ Django admin builds its forms as ordinary `ModelForm` subclasses via
75
+ `modelform_factory`. This package hooks `BaseForm.__init__` once, so admin
76
+ inherits the behavior automatically — no `ModelAdmin` changes needed.
77
+
78
+ ## Scope
79
+
80
+ This targets the actual pain point: **Django admin and Django forms**.
81
+ It does not attempt to make `direction` a real `Field` kwarg (that requires
82
+ changing Django itself, which is what ticket #35482 is for), so
83
+ introspection-based tools built outside the forms API (DRF serializers,
84
+ etc.) won't see it.
85
+
86
+ ## Running the tests
87
+
88
+ ```bash
89
+ pip install -e ".[test]"
90
+ pytest
91
+ ```
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "django-bidi-input"
7
+ version = "1.0.0"
8
+ description = "Force correct text direction (LTR/RTL) on Django admin and form input fields, regardless of site language."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Omid Shojaee", email = "omid.shojaee@gmail.com" }]
12
+ requires-python = ">=3.10"
13
+ dependencies = ["Django>=5.2"]
14
+ classifiers = [
15
+ "Framework :: Django",
16
+ "Framework :: Django :: 5.2",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ test = ["pytest>=7", "pytest-django>=4"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/omidshojaee/django-bidi-input"
32
+ Issues = "https://github.com/omidshojaee/django-bidi-input/issues"
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["src"]
36
+
37
+ [tool.pytest.ini_options]
38
+ DJANGO_SETTINGS_MODULE = "tests.settings"
39
+ python_files = ["test_*.py"]
40
+ pythonpath = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,25 @@
1
+ from . import conf
2
+ from .direction import resolve_direction
3
+
4
+
5
+ def apply_directions(form):
6
+ if not conf.is_enabled():
7
+ return
8
+
9
+ model = getattr(getattr(form, "_meta", None), "model", None)
10
+
11
+ for field_name, field in form.fields.items():
12
+ attrs = field.widget.attrs
13
+
14
+ if attrs.get("dir"):
15
+ continue
16
+
17
+ direction = resolve_direction(field, field_name, model=model)
18
+ if direction is None:
19
+ continue
20
+
21
+ attrs["dir"] = direction
22
+ css_class = attrs.get("class", "")
23
+ marker = "bidi-input-{}".format(direction)
24
+ if marker not in css_class.split():
25
+ attrs["class"] = "{} {}".format(css_class, marker).strip()
@@ -0,0 +1,12 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class BidiInputConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "django_bidi_input"
7
+ verbose_name = "Bidi Input"
8
+
9
+ def ready(self):
10
+ from . import patch
11
+
12
+ patch.install()
@@ -0,0 +1,76 @@
1
+ from django.conf import settings
2
+ from django.utils.module_loading import import_string
3
+
4
+ DEFAULT_LTR_FIELD_PATHS = [
5
+ "django.forms.EmailField",
6
+ "django.forms.URLField",
7
+ "django.forms.SlugField",
8
+ "django.forms.UUIDField",
9
+ "django.forms.GenericIPAddressField",
10
+ "django.forms.FilePathField",
11
+ "django.forms.IntegerField",
12
+ "django.forms.FloatField",
13
+ "django.forms.DecimalField",
14
+ "django.forms.DurationField",
15
+ "django.forms.RegexField",
16
+ ]
17
+
18
+ DEFAULT_LTR_INPUT_TYPES = {
19
+ "email",
20
+ "url",
21
+ "number",
22
+ "password",
23
+ "tel",
24
+ "date",
25
+ "time",
26
+ "datetime-local",
27
+ "month",
28
+ "week",
29
+ "color",
30
+ }
31
+
32
+ DEFAULT_LTR_AUTOCOMPLETE_VALUES = {
33
+ "username",
34
+ "email",
35
+ "tel",
36
+ "new-password",
37
+ "current-password",
38
+ "one-time-code",
39
+ }
40
+
41
+
42
+ def _import_all(dotted_paths):
43
+ classes = []
44
+ for path in dotted_paths:
45
+ try:
46
+ classes.append(import_string(path))
47
+ except ImportError:
48
+ continue
49
+ return tuple(classes)
50
+
51
+
52
+ def get_ltr_field_classes():
53
+ paths = getattr(settings, "BIDI_INPUT_LTR_FIELD_CLASSES", DEFAULT_LTR_FIELD_PATHS)
54
+ return _import_all(paths)
55
+
56
+
57
+ def get_ltr_input_types():
58
+ return set(getattr(settings, "BIDI_INPUT_LTR_INPUT_TYPES", DEFAULT_LTR_INPUT_TYPES))
59
+
60
+
61
+ def get_ltr_autocomplete_values():
62
+ return set(
63
+ getattr(
64
+ settings,
65
+ "BIDI_INPUT_LTR_AUTOCOMPLETE_VALUES",
66
+ DEFAULT_LTR_AUTOCOMPLETE_VALUES,
67
+ )
68
+ )
69
+
70
+
71
+ def get_overrides():
72
+ return getattr(settings, "BIDI_INPUT_OVERRIDES", {})
73
+
74
+
75
+ def is_enabled():
76
+ return getattr(settings, "BIDI_INPUT_ENABLED", True)
@@ -0,0 +1,47 @@
1
+ from django.core.exceptions import FieldDoesNotExist
2
+
3
+ from . import conf
4
+
5
+ LTR = "ltr"
6
+ RTL = "rtl"
7
+ VALID_DIRECTIONS = (LTR, RTL)
8
+
9
+
10
+ def _override_key(model, field_name):
11
+ return "{}.{}.{}".format(model._meta.app_label, model._meta.object_name, field_name)
12
+
13
+
14
+ def _model_field_for(model, field_name):
15
+ if model is None:
16
+ return None
17
+ try:
18
+ return model._meta.get_field(field_name)
19
+ except FieldDoesNotExist:
20
+ return None
21
+
22
+
23
+ def resolve_direction(form_field, field_name, model=None):
24
+ model_field = _model_field_for(model, field_name)
25
+
26
+ if model_field is not None:
27
+ direction = getattr(model_field, "direction", None)
28
+ if direction in VALID_DIRECTIONS:
29
+ return direction
30
+
31
+ if model is not None:
32
+ override = conf.get_overrides().get(_override_key(model, field_name))
33
+ if override in VALID_DIRECTIONS:
34
+ return override
35
+
36
+ input_type = getattr(form_field.widget, "input_type", None)
37
+ if input_type in conf.get_ltr_input_types():
38
+ return LTR
39
+
40
+ autocomplete = form_field.widget.attrs.get("autocomplete")
41
+ if autocomplete in conf.get_ltr_autocomplete_values():
42
+ return LTR
43
+
44
+ if isinstance(form_field, conf.get_ltr_field_classes()):
45
+ return LTR
46
+
47
+ return None
@@ -0,0 +1,19 @@
1
+ from django.forms.forms import BaseForm
2
+
3
+ from .apply import apply_directions
4
+
5
+ _PATCHED_ATTR = "_bidi_input_patched"
6
+
7
+
8
+ def install():
9
+ if getattr(BaseForm.__init__, _PATCHED_ATTR, False):
10
+ return
11
+
12
+ original_init = BaseForm.__init__
13
+
14
+ def patched_init(self, *args, **kwargs):
15
+ original_init(self, *args, **kwargs)
16
+ apply_directions(self)
17
+
18
+ setattr(patched_init, _PATCHED_ATTR, True)
19
+ BaseForm.__init__ = patched_init
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-bidi-input
3
+ Version: 1.0.0
4
+ Summary: Force correct text direction (LTR/RTL) on Django admin and form input fields, regardless of site language.
5
+ Author-email: Omid Shojaee <omid.shojaee@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/omidshojaee/django-bidi-input
8
+ Project-URL: Issues, https://github.com/omidshojaee/django-bidi-input/issues
9
+ Classifier: Framework :: Django
10
+ Classifier: Framework :: Django :: 5.2
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: Django>=5.2
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=7; extra == "test"
24
+ Requires-Dist: pytest-django>=4; extra == "test"
25
+
26
+ # django-bidi-input
27
+
28
+ Django doesn't let you control the text direction of individual form/admin
29
+ input fields — when a site's language is RTL (Farsi, Arabic, Hebrew, ...),
30
+ every input renders RTL, including fields where that's wrong: email, URL,
31
+ slug, UUID, IP address, numbers, passwords, phone numbers, etc. Only free
32
+ text fields should follow the page direction.
33
+
34
+ This has been an accepted Django ticket
35
+ ([#35482](https://code.djangoproject.com/ticket/35482)) for a while with no
36
+ implementation yet. `django-bidi-input` fixes it today, without patching
37
+ Django, for both **admin** and regular **forms**.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pip install django-bidi-input
43
+ ```
44
+
45
+ ```python
46
+ INSTALLED_APPS = [
47
+ ...,
48
+ "django_bidi_input",
49
+ ]
50
+ ```
51
+
52
+ That's it. Every `ModelForm`, plain `Form`, and admin form in your project
53
+ now gets a correct `dir` attribute (and a `bidi-input-ltr`/`bidi-input-rtl`
54
+ CSS class as a fallback) on each field, automatically.
55
+
56
+ ## How direction is decided
57
+
58
+ For each field, in order (first match wins):
59
+
60
+ 1. You already set `dir` on the widget yourself — left untouched.
61
+ 2. `direction` set directly on the model field instance:
62
+ ```python
63
+ phone_number = models.CharField(max_length=32)
64
+ phone_number.direction = "ltr"
65
+ ```
66
+ 3. `BIDI_INPUT_OVERRIDES` setting, for fields you can't edit directly
67
+ (third-party app models, etc.):
68
+ ```python
69
+ BIDI_INPUT_OVERRIDES = {
70
+ "myapp.Contact.phone_number": "ltr",
71
+ }
72
+ ```
73
+ 4. Built-in default, in order:
74
+ - widget `input_type` is `password`, `number`, `tel`, `date`, `time`, etc.
75
+ - widget `autocomplete` is `username`, `email`, `tel`, `new-password`,
76
+ `current-password`, `one-time-code`. This is what makes Django's own
77
+ admin/auth **login form** (`username` + `password`) render LTR, even
78
+ though `UsernameField` is a plain `CharField` — Django sets
79
+ `autocomplete="username"` on it unconditionally, including when a
80
+ custom user model uses `USERNAME_FIELD = "email"`.
81
+ - field class is `EmailField`, `URLField`, `SlugField`, `UUIDField`,
82
+ `GenericIPAddressField`, `FilePathField`, `IntegerField`, `FloatField`,
83
+ `DecimalField`, `DurationField`, `RegexField`.
84
+ 5. Otherwise left alone — inherits the page's direction (correct for plain
85
+ `CharField`/`TextField`).
86
+
87
+ ## Settings
88
+
89
+ | Setting | Default | Purpose |
90
+ |---|---|---|
91
+ | `BIDI_INPUT_ENABLED` | `True` | Turn the whole thing off. |
92
+ | `BIDI_INPUT_OVERRIDES` | `{}` | `"app_label.Model.field": "ltr"/"rtl"` map. |
93
+ | `BIDI_INPUT_LTR_FIELD_CLASSES` | see `conf.py` | Dotted paths of extra form field classes to treat as LTR. |
94
+ | `BIDI_INPUT_LTR_INPUT_TYPES` | see `conf.py` | Extra widget `input_type` values to treat as LTR. |
95
+ | `BIDI_INPUT_LTR_AUTOCOMPLETE_VALUES` | see `conf.py` | Extra widget `autocomplete` values to treat as LTR. |
96
+
97
+ ## Why this works for admin too
98
+
99
+ Django admin builds its forms as ordinary `ModelForm` subclasses via
100
+ `modelform_factory`. This package hooks `BaseForm.__init__` once, so admin
101
+ inherits the behavior automatically — no `ModelAdmin` changes needed.
102
+
103
+ ## Scope
104
+
105
+ This targets the actual pain point: **Django admin and Django forms**.
106
+ It does not attempt to make `direction` a real `Field` kwarg (that requires
107
+ changing Django itself, which is what ticket #35482 is for), so
108
+ introspection-based tools built outside the forms API (DRF serializers,
109
+ etc.) won't see it.
110
+
111
+ ## Running the tests
112
+
113
+ ```bash
114
+ pip install -e ".[test]"
115
+ pytest
116
+ ```
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/django_bidi_input/__init__.py
4
+ src/django_bidi_input/apply.py
5
+ src/django_bidi_input/apps.py
6
+ src/django_bidi_input/conf.py
7
+ src/django_bidi_input/direction.py
8
+ src/django_bidi_input/patch.py
9
+ src/django_bidi_input.egg-info/PKG-INFO
10
+ src/django_bidi_input.egg-info/SOURCES.txt
11
+ src/django_bidi_input.egg-info/dependency_links.txt
12
+ src/django_bidi_input.egg-info/requires.txt
13
+ src/django_bidi_input.egg-info/top_level.txt
14
+ tests/test_admin.py
15
+ tests/test_forms.py
@@ -0,0 +1,5 @@
1
+ Django>=5.2
2
+
3
+ [test]
4
+ pytest>=7
5
+ pytest-django>=4
@@ -0,0 +1 @@
1
+ django_bidi_input
@@ -0,0 +1,25 @@
1
+ import pytest
2
+ from django.contrib.admin.sites import AdminSite
3
+
4
+ from tests.testapp.admin import ContactAdmin, ProfileAdmin
5
+ from tests.testapp.models import Contact, Profile
6
+
7
+
8
+ @pytest.mark.django_db
9
+ def test_admin_form_gets_directions_without_any_admin_config():
10
+ admin = ContactAdmin(Contact, AdminSite())
11
+ form_class = admin.get_form(request=None)
12
+ form = form_class()
13
+
14
+ assert "dir" not in form.fields["full_name"].widget.attrs
15
+ assert form.fields["email"].widget.attrs["dir"] == "ltr"
16
+ assert form.fields["notes"].widget.attrs["dir"] == "rtl"
17
+
18
+
19
+ @pytest.mark.django_db
20
+ def test_admin_form_picks_up_settings_override():
21
+ admin = ProfileAdmin(Profile, AdminSite())
22
+ form_class = admin.get_form(request=None)
23
+ form = form_class()
24
+
25
+ assert form.fields["bio"].widget.attrs["dir"] == "ltr"
@@ -0,0 +1,97 @@
1
+ from django import forms
2
+ import pytest
3
+
4
+ from tests.testapp.models import Contact, Profile
5
+
6
+
7
+ class ContactForm(forms.ModelForm):
8
+ class Meta:
9
+ model = Contact
10
+ fields = [
11
+ "full_name",
12
+ "email",
13
+ "website",
14
+ "slug",
15
+ "reference_id",
16
+ "ip_address",
17
+ "age",
18
+ "notes",
19
+ "phone_number",
20
+ ]
21
+
22
+
23
+ class ProfileForm(forms.ModelForm):
24
+ class Meta:
25
+ model = Profile
26
+ fields = ["bio"]
27
+
28
+
29
+ class PlainForm(forms.Form):
30
+ subject = forms.CharField()
31
+ contact_email = forms.EmailField()
32
+ password = forms.CharField(widget=forms.PasswordInput)
33
+
34
+
35
+ @pytest.mark.django_db
36
+ def test_plain_text_field_is_left_alone():
37
+ form = ContactForm()
38
+ assert "dir" not in form.fields["full_name"].widget.attrs
39
+
40
+
41
+ @pytest.mark.django_db
42
+ @pytest.mark.parametrize(
43
+ "field_name",
44
+ ["email", "website", "slug", "reference_id", "ip_address", "age"],
45
+ )
46
+ def test_builtin_heuristic_defaults_to_ltr(field_name):
47
+ form = ContactForm()
48
+ assert form.fields[field_name].widget.attrs["dir"] == "ltr"
49
+
50
+
51
+ @pytest.mark.django_db
52
+ def test_explicit_model_field_override_rtl():
53
+ form = ContactForm()
54
+ assert form.fields["notes"].widget.attrs["dir"] == "rtl"
55
+
56
+
57
+ @pytest.mark.django_db
58
+ def test_explicit_model_field_override_ltr_for_plain_charfield():
59
+ # phone_number is a bare CharField; only the .direction attribute
60
+ # set on the model field in models.py makes it LTR.
61
+ form = ContactForm()
62
+ assert form.fields["phone_number"].widget.attrs["dir"] == "ltr"
63
+
64
+
65
+ @pytest.mark.django_db
66
+ def test_settings_override_map():
67
+ form = ProfileForm()
68
+ assert form.fields["bio"].widget.attrs["dir"] == "ltr"
69
+
70
+
71
+ def test_plain_non_model_form_uses_class_based_heuristic():
72
+ form = PlainForm()
73
+ assert "dir" not in form.fields["subject"].widget.attrs
74
+ assert form.fields["contact_email"].widget.attrs["dir"] == "ltr"
75
+ assert form.fields["password"].widget.attrs["dir"] == "ltr"
76
+
77
+
78
+ def test_admin_login_form_username_and_password_are_ltr():
79
+ from django.contrib.auth.forms import AuthenticationForm
80
+
81
+ form = AuthenticationForm()
82
+ # UsernameField is a plain CharField -- only the autocomplete="username"
83
+ # token (set by Django itself) makes this resolve to LTR. This also
84
+ # covers a custom user model with USERNAME_FIELD = "email": Django's
85
+ # AuthenticationForm always uses UsernameField regardless of the
86
+ # underlying model field type, so the field-class heuristic alone
87
+ # would never catch it.
88
+ assert form.fields["username"].widget.attrs["dir"] == "ltr"
89
+ assert form.fields["password"].widget.attrs["dir"] == "ltr"
90
+
91
+
92
+ def test_developer_set_dir_is_never_overwritten():
93
+ class ExplicitForm(forms.Form):
94
+ note = forms.CharField(widget=forms.TextInput(attrs={"dir": "rtl"}))
95
+
96
+ form = ExplicitForm()
97
+ assert form.fields["note"].widget.attrs["dir"] == "rtl"