1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
|
from dataclasses import dataclass
from typing import List
from django.template import Template
from django.template.loader import render_to_string
from django.utils.html import conditional_escape
from django.utils.safestring import SafeString
from django.utils.text import slugify
from crispy_forms.utils import TEMPLATE_PACK, flatatt, render_field
@dataclass
class Pointer:
positions: List[int]
name: str
class TemplateNameMixin:
def get_template_name(self, template_pack):
if "%s" in self.template:
template = self.template % template_pack
else:
template = self.template
return template
class LayoutObject(TemplateNameMixin):
def __getitem__(self, slice):
return self.fields[slice]
def __setitem__(self, slice, value):
self.fields[slice] = value
def __delitem__(self, slice):
del self.fields[slice]
def __len__(self):
return len(self.fields)
def __getattr__(self, name):
"""
This allows us to access self.fields list methods like append or insert, without
having to declare them one by one
"""
# Check necessary for unpickling, see #107
if "fields" in self.__dict__ and hasattr(self.fields, name):
return getattr(self.fields, name)
else:
return object.__getattribute__(self, name)
def get_field_names(self, index=None):
"""
Returns a list of Pointers. First parameter is the location of the
field, second one the name of the field. Example::
[
Pointer([0,1,2], 'field_name1'),
Pointer([0,3], 'field_name2'),
]
"""
return self.get_layout_objects(str, index=None, greedy=True)
def get_layout_objects(self, *LayoutClasses, index=None, max_level=0, greedy=False):
"""
Returns a list of Pointers pointing to layout objects of any type matching
`LayoutClasses`::
[
Pointer([0,1,2], 'div']),
Pointer([0,3], 'field_name'),
]
:param max_level: An integer that indicates max level depth to reach when
traversing a layout.
:param greedy: Boolean that indicates whether to be greedy. If set, max_level
is skipped.
"""
pointers = []
if index is not None and not isinstance(index, list):
index = [index]
elif index is None:
index = []
str_class = len(LayoutClasses) == 1 and LayoutClasses[0] == str
for i, layout_object in enumerate(self.fields):
if isinstance(layout_object, LayoutClasses):
if str_class:
pointers.append(Pointer(index + [i], layout_object))
else:
pointers.append(Pointer(index + [i], layout_object.__class__.__name__.lower()))
# If it's a layout object and we haven't reached the max depth limit or greedy
# we recursive call
if hasattr(layout_object, "get_field_names") and (len(index) < max_level or greedy):
new_kwargs = {"index": index + [i], "max_level": max_level, "greedy": greedy}
pointers = pointers + layout_object.get_layout_objects(*LayoutClasses, **new_kwargs)
return pointers
def get_rendered_fields(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
return SafeString(
"".join(render_field(field, form, context, template_pack=template_pack, **kwargs) for field in self.fields)
)
class Layout(LayoutObject):
"""
Form Layout. It is conformed by Layout objects: `Fieldset`, `Row`, `Column`, `MultiField`,
`HTML`, `ButtonHolder`, `Button`, `Hidden`, `Reset`, `Submit` and fields. Form fields
have to be strings.
Layout objects `Fieldset`, `Row`, `Column`, `MultiField` and `ButtonHolder` can hold other
Layout objects within. Though `ButtonHolder` should only hold `HTML` and BaseInput
inherited classes: `Button`, `Hidden`, `Reset` and `Submit`.
Example::
helper.layout = Layout(
Fieldset('Company data',
'is_company'
),
Fieldset(_('Contact details'),
'email',
Row('password1', 'password2'),
'first_name',
'last_name',
HTML('<img src="/media/somepicture.jpg"/>'),
'company'
),
ButtonHolder(
Submit('Save', 'Save', css_class='button white'),
),
)
"""
def __init__(self, *fields):
self.fields = list(fields)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
return self.get_rendered_fields(form, context, template_pack, **kwargs)
class ButtonHolder(LayoutObject):
"""
Layout object. It wraps fields in a <div class="buttonHolder">
This is where you should put Layout objects that render to form buttons
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
Parameters
----------
*fields : HTML or BaseInput
The layout objects to render within the ``ButtonHolder``. It should
only hold `HTML` and `BaseInput` inherited objects.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
Examples
--------
An example using ``ButtonHolder`` in your layout::
ButtonHolder(
HTML(<span style="display: hidden;">Information Saved</span>),
Submit('Save', 'Save')
)
"""
template = "%s/layout/buttonholder.html"
def __init__(self, *fields, css_id=None, css_class=None, template=None):
self.fields = list(fields)
self.css_id = css_id
self.css_class = css_class
self.template = template or self.template
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
html = self.get_rendered_fields(form, context, template_pack, **kwargs)
template = self.get_template_name(template_pack)
context.update({"buttonholder": self, "fields_output": html})
return render_to_string(template, context.flatten())
class BaseInput(TemplateNameMixin):
"""
A base class to reduce the amount of code in the Input classes.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
"""
template = "%s/layout/baseinput.html"
field_classes = ""
def __init__(self, name, value, *, css_id=None, css_class=None, template=None, **kwargs):
self.name = name
self.value = value
if css_id:
self.id = css_id
else:
slug = slugify(self.name)
self.id = f"{self.input_type}-id-{slug}"
self.attrs = {}
if css_class:
self.field_classes += f" {css_class}"
self.template = template or self.template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
"""
Renders an `<input />` if container is used as a Layout object.
Input button value can be a variable in context.
"""
self.value = Template(str(self.value)).render(context)
template = self.get_template_name(template_pack)
context.update({"input": self})
return render_to_string(template, context.flatten())
class Submit(BaseInput):
"""
Used to create a Submit button descriptor for the {% crispy %} template tag.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> submit = Submit('Search the Site', 'search this site')
>>> submit.render("", "", Context())
'<input type="submit" name="search-the-site" value="search this site" '
'class="btn btn-primary" id="submit-id-search-the-site"/>'
>>> submit = Submit('Search the Site', 'search this site', css_id="custom-id",
css_class="custom class", my_attr=True, data="my-data")
>>> submit.render("", "", Context())
'<input type="submit" name="search-the-site" value="search this site" '
'class="btn btn-primary custom class" id="custom-id" data="my-data" my-attr/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Submit('submit', 'Submit'))
"""
input_type = "submit"
field_classes = "btn btn-primary"
class Button(BaseInput):
"""
Used to create a button descriptor for the {% crispy %} template tag.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> button = Button('Button 1', 'Press Me!')
>>> button.render("", "", Context())
'<input type="button" name="button-1" value="Press Me!" '
'class="btn" id="button-id-button-1"/>'
>>> button = Button('Button 1', 'Press Me!', css_id="custom-id",
css_class="custom class", my_attr=True, data="my-data")
>>> button.render("", "", Context())
'<input type="button" name="button-1" value="Press Me!" '
'class="btn custom class" id="custom-id" data="my-data" my-attr/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Button('Button 1', 'Press Me!'))
"""
input_type = "button"
field_classes = "btn"
class Hidden(BaseInput):
"""
Used to create a Hidden input descriptor for the {% crispy %} template tag.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> hidden = Hidden("hidden", "hide-me")
>>> hidden.render("", "", Context())
'<input type="hidden" name="hidden" value="hide-me"/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Hidden("hidden", "hide-me"))
"""
input_type = "hidden"
field_classes = "hidden"
class Reset(BaseInput):
"""
Used to create a reset button descriptor for the {% crispy %} template tag.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> reset = Reset('Reset This Form', 'Revert Me!')
>>> reset.render("", "", Context())
'<input type="reset" name="reset-this-form" value="Revert Me!" '
'class="btn btn-inverse" id="reset-id-reset-this-form"/>'
>>> reset = Reset('Reset This Form', 'Revert Me!', css_id="custom-id",
css_class="custom class", my_attr=True, data="my-data")
>>> reset.render("", "", Context())
'<input type="reset" name="reset-this-form" value="Revert Me!" '
'class="btn btn-inverse custom class" id="custom-id" data="my-data" my-attr/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Reset('Reset This Form', 'Revert Me!'))
"""
input_type = "reset"
field_classes = "btn btn-inverse"
class Fieldset(LayoutObject):
"""
A layout object which wraps fields in a ``<fieldset>``
Parameters
----------
legend : str
The content of the fieldset's ``<legend>``. This text is context
aware, to bring this to life see the examples section.
*fields : str | LayoutObject | HTML
Any number of fields as positional arguments to be rendered within
the ``<fieldset>``
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<fieldset>``.
Examples
--------
The Fieldset Layout object is added to your ``Layout`` for example::
Fieldset("Text for the legend",
"form_field_1",
"form_field_2",
css_id="my-fieldset-id",
css_class="my-fieldset-class",
data="my-data"
)
The above layout will be rendered as::
'''
<fieldset id="fieldset-id" class="my-fieldset-class" data="my-data">
<legend>Text for the legend</legend>
# form fields render here
</fieldset>
'''
The first parameter is the text for the fieldset legend. This text is context aware,
so you can do things like::
Fieldset("Data for {{ user.username }}",
'form_field_1',
'form_field_2'
)
"""
template = "%s/layout/fieldset.html"
def __init__(self, legend, *fields, css_class=None, css_id=None, template=None, **kwargs):
self.fields = list(fields)
self.legend = legend
self.css_class = css_class
self.css_id = css_id
self.template = template or self.template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
fields = self.get_rendered_fields(form, context, template_pack, **kwargs)
if self.legend:
legend = Template(str(self.legend)).render(context)
else:
legend = SafeString("")
template = self.get_template_name(template_pack)
context.update({"fieldset": self, "legend": legend, "fields": fields})
return render_to_string(template, context.flatten())
class MultiField(LayoutObject):
"""
MultiField container for Bootstrap3. Renders to a MultiField <div>.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_template: str
The template which fields will be rendered with.
Parameters
----------
label: str
The label for the multifield.
*fields: str
The fields to be rendered within the multifield.
label_class: str, optional
CSS classes to be added to the multifield label. By default None.
help_text: str, optional
Help text will be available in the context of the multifield template.
This is unused in the bootstrap3 template provided. By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
css_id : str, optional
A DOM id for the layout object which will be added to the wrapping
``<div>`` if provided. By default None.
template : str, optional
Overrides the default template, if provided. By default None.
field_template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the wrapping
``<div>``.
"""
template = "%s/layout/multifield.html"
field_template = "%s/multifield.html"
def __init__(
self,
label,
*fields,
label_class=None,
help_text=None,
css_class=None,
css_id=None,
template=None,
field_template=None,
**kwargs,
):
self.fields = list(fields)
self.label_html = label
self.label_class = label_class or "blockLabel"
self.css_class = css_class or "ctrlHolder"
self.css_id = css_id
self.help_text = help_text
self.template = template or self.template
self.field_template = field_template or self.field_template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
# If a field within MultiField contains errors
if context["form_show_errors"]:
for field in (pointer.name for pointer in self.get_field_names()):
if field in form.errors:
self.css_class += " error"
field_template = self.field_template % template_pack
fields_output = self.get_rendered_fields(
form,
context,
template_pack,
template=field_template,
labelclass=self.label_class,
layout_object=self,
**kwargs,
)
template = self.get_template_name(template_pack)
context.update({"multifield": self, "fields_output": fields_output})
return render_to_string(template, context.flatten())
class Div(LayoutObject):
"""
Layout object. It wraps fields in a ``<div>``.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
css_class : str, optional
CSS classes to be applied to the ``<div>``. By default None.
Parameters
----------
*fields : str, LayoutObject
Any number of fields as positional arguments to be rendered within
the ``<div>``.
css_id : str, optional
A DOM id for the layout object which will be added to the ``<div>`` if
provided. By default None.
css_class : str, optional
Additional CSS classes to be applied in addition to those declared by
the class itself. By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<div>``.
Examples
--------
In your ``Layout`` you can::
Div(
'form_field_1',
'form_field_2',
css_id='div-example',
css_class='divs',
)
It is also possible to nest Layout Objects within a Div::
Div(
Div(
Field('form_field', css_class='field-class'),
css_class='div-class',
),
Div('form_field_2', css_class='div-class'),
)
"""
template = "%s/layout/div.html"
css_class = None
def __init__(self, *fields, css_id=None, css_class=None, template=None, **kwargs):
self.fields = list(fields)
if self.css_class and css_class:
self.css_class += f" {css_class}"
elif css_class:
self.css_class = css_class
self.css_id = css_id
self.template = template or self.template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
fields = self.get_rendered_fields(form, context, template_pack, **kwargs)
template = self.get_template_name(template_pack)
return render_to_string(template, {"div": self, "fields": fields})
class Row(Div):
"""
Layout object. It wraps fields in a ``<div>`` and the template adds the
appropriate class to render the contents in a row. e.g. ``form-row`` when
using the Bootstrap4 template pack.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
css_class : str, optional
CSS classes to be applied to the ``<div>``. By default None.
Parameters
----------
*fields : str, LayoutObject
Any number of fields as positional arguments to be rendered within
the ``<div>``.
css_id : str, optional
A DOM id for the layout object which will be added to the ``<div>`` if
provided. By default None.
css_class : str, optional
Additional CSS classes to be applied in addition to those declared by
the class itself. By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<div>``.
Examples
--------
In your ``Layout`` you can::
Row('form_field_1', 'form_field_2', css_id='row-example')
It is also possible to nest Layout Objects within a Row::
Row(
Div(
Field('form_field', css_class='field-class'),
css_class='div-class',
),
Div('form_field_2', css_class='div-class'),
)
"""
template = "%s/layout/row.html"
class Column(Div):
"""
Layout object. It wraps fields in a ``<div>`` and the template adds the
appropriate class to render the contents in a column. e.g. ``col-md`` when
using the Bootstrap4 template pack.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
css_class : str, optional
CSS classes to be applied to the ``<div>``. By default None.
Parameters
----------
*fields : str, LayoutObject
Any number of fields as positional arguments to be rendered within
the ``<div>``.
css_id : str, optional
A DOM id for the layout object which will be added to the ``<div>`` if
provided. By default None.
css_class : str, optional
Additional CSS classes to be applied in addition to those declared by
the class itself. If using the Bootstrap4 template pack the default
``col-md`` is removed if this string contins another ``col-`` class.
By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<div>``.
Examples
--------
In your ``Layout`` you can::
Column('form_field_1', 'form_field_2', css_id='col-example')
It is also possible to nest Layout Objects within a Row::
Div(
Column(
Field('form_field', css_class='field-class'),
css_class='col-sm,
),
Column('form_field_2', css_class='col-sm'),
)
"""
template = "%s/layout/column.html"
class HTML:
"""
Layout object. It can contain pure HTML and it has access to the whole
context of the page where the form is being rendered.
Examples::
HTML("{% if saved %}Data saved{% endif %}")
HTML('<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />')
"""
def __init__(self, html):
self.html = html
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
return Template(str(self.html)).render(context)
class Field(LayoutObject):
"""
A Layout object, usually containing one field name, where you can add
attributes to it easily.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
attrs : dict
Attributes to be applied to the field. These are converted into html
attributes. e.g. ``data_id: 'test'`` in the attrs dict will become
``data-id='test'`` on the field's ``<input>``.
Parameters
----------
*fields : str
Usually a single field, but can be any number of fields, to be rendered
with the same attributes applied.
css_class : str, optional
CSS classes to be applied to the field. These are added to any classes
included in the ``attrs`` dict. By default ``None``.
wrapper_class: str, optional
CSS classes to be used when rendering the Field. This class is usually
applied to the ``<div>`` which wraps the Field's ``<label>`` and
``<input>`` tags. By default ``None``.
template : str, optional
Overrides the default template, if provided. By default ``None``.
**kwargs : dict, optional
Additional attributes are converted into key="value", pairs. These
attributes are added to the field's ``<input>``.
Examples
--------
Example::
Field('field_name', style="color: #333;", css_class="whatever", id="field_name")
"""
template = "%s/field.html"
attrs = {}
def __init__(self, *fields, css_class=None, wrapper_class=None, template=None, **kwargs):
self.fields = list(fields)
# Make sure shared state is not edited.
self.attrs = self.attrs.copy()
if css_class:
if "class" in self.attrs:
self.attrs["class"] += f" {css_class}"
else:
self.attrs["class"] = css_class
self.wrapper_class = wrapper_class
self.template = template or self.template
# We use kwargs as HTML attributes, turning data_id='test' into data-id='test'
self.attrs.update({k.replace("_", "-"): conditional_escape(v) for k, v in kwargs.items()})
def render(self, form, context, template_pack=TEMPLATE_PACK, extra_context=None, **kwargs):
if extra_context is None:
extra_context = {}
if self.wrapper_class:
extra_context["wrapper_class"] = self.wrapper_class
template = self.get_template_name(template_pack)
return self.get_rendered_fields(
form,
context,
template_pack,
template=template,
attrs=self.attrs,
extra_context=extra_context,
**kwargs,
)
class MultiWidgetField(Field):
"""
Layout object. For fields with :class:`~django.forms.MultiWidget` as
``widget``, you can pass additional attributes to each widget.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
Parameters
----------
*fields : str
Usually a single field, but can be any number of fields, to be rendered
with the same attributes applied.
attrs : str, optional
Additional attrs to be added to each widget. These are added to any
classes included in the ``attrs`` dict. By default ``None``.
wrapper_class: str, optional
CSS classes to be used when rendering the Field. This class is usually
applied to the ``<div>`` which wraps the Field's ``<label>`` and
``<input>`` tags. By default ``None``.
template : str, optional
Overrides the default template, if provided. By default ``None``.
Examples
--------
Example::
MultiWidgetField(
'multiwidget_field_name',
attrs=(
{'style': 'width: 30px;'},
{'class': 'second_widget_class'}
),
)
"""
def __init__(self, *fields, attrs=None, template=None, wrapper_class=None):
self.fields = list(fields)
self.attrs = attrs or {}
self.template = template or self.template
self.wrapper_class = wrapper_class
|