1from __future__ import annotations
2
3from functools import cached_property
4from typing import TYPE_CHECKING, Any
5
6if TYPE_CHECKING:
7 from .fields import Field
8 from .forms import BaseForm
9
10__all__ = ["BoundField"]
11
12
13class BoundField:
14 "A Field plus data"
15
16 def __init__(self, form: BaseForm, field: Field, name: str):
17 self._form = form
18 self.field = field
19 self.name = name
20 self.html_name = form.add_prefix(name)
21 self.html_id = form.add_prefix(self._auto_id)
22
23 def __repr__(self) -> str:
24 return f'<{self.__class__.__name__} "{self.html_name}">'
25
26 @property
27 def errors(self) -> list[str]:
28 """
29 Return an error list (empty if there are no errors) for this field.
30 """
31 return self._form.errors.get(self.name, [])
32
33 def value(self) -> Any:
34 """
35 Return the value for this BoundField, using the initial value if
36 the form is not bound or the data otherwise.
37 """
38 data = self.initial
39 if self._form.is_bound:
40 data = self.field.bound_data(
41 self._form._field_data_value(self.field, self.html_name), data
42 )
43 return self.field.prepare_value(data)
44
45 @cached_property
46 def initial(self) -> Any:
47 return self._form.get_initial_for_field(self.field, self.name)
48
49 def _has_changed(self) -> bool:
50 return self.field.has_changed(
51 self.initial, self._form._field_data_value(self.field, self.html_name)
52 )
53
54 @property
55 def _auto_id(self) -> str:
56 """
57 Calculate and return the ID attribute for this BoundField, if the
58 associated Form has specified auto_id. Return an empty string otherwise.
59 """
60 auto_id = self._form._auto_id # Boolean or string
61 if auto_id and isinstance(auto_id, str) and "%s" in auto_id:
62 return auto_id % self.html_name
63 elif auto_id:
64 return self.html_name
65 return ""