1import datetime 2 3from django import forms 4from django.db.models.fields import BLANK_CHOICE_DASH 5from django.forms.fields import CallableChoiceIterator 6from django.utils.dateparse import parse_date, parse_datetime, parse_time 7from django.utils.encoding import force_str 8from django.utils.functional import cached_property 9from django.utils.html import format_html 10from django.utils.safestring import mark_safe 11from django.utils.translation import gettext as _ 12 13from wagtail.admin.staticfiles import versioned_static 14from wagtail.core.rich_text import RichText, get_text_for_indexing 15from wagtail.core.telepath import Adapter, register 16from wagtail.core.utils import camelcase_to_underscore, resolve_model_string 17 18from .base import Block 19 20 21class FieldBlock(Block): 22 """A block that wraps a Django form field""" 23 24 def id_for_label(self, prefix): 25 return self.field.widget.id_for_label(prefix) 26 27 def value_from_form(self, value): 28 """ 29 The value that we get back from the form field might not be the type 30 that this block works with natively; for example, the block may want to 31 wrap a simple value such as a string in an object that provides a fancy 32 HTML rendering (e.g. EmbedBlock). 33 34 We therefore provide this method to perform any necessary conversion 35 from the form field value to the block's native value. As standard, 36 this returns the form field value unchanged. 37 """ 38 return value 39 40 def value_for_form(self, value): 41 """ 42 Reverse of value_from_form; convert a value of this block's native value type 43 to one that can be rendered by the form field 44 """ 45 return value 46 47 def value_from_datadict(self, data, files, prefix): 48 return self.value_from_form(self.field.widget.value_from_datadict(data, files, prefix)) 49 50 def value_omitted_from_data(self, data, files, prefix): 51 return self.field.widget.value_omitted_from_data(data, files, prefix) 52 53 def clean(self, value): 54 # We need an annoying value_for_form -> value_from_form round trip here to account for 55 # the possibility that the form field is set up to validate a different value type to 56 # the one this block works with natively 57 return self.value_from_form(self.field.clean(self.value_for_form(value))) 58 59 @property 60 def required(self): 61 # a FieldBlock is required if and only if its underlying form field is required 62 return self.field.required 63 64 def get_form_state(self, value): 65 return self.field.widget.format_value(self.field.prepare_value(self.value_for_form(value))) 66 67 class Meta: 68 # No icon specified here, because that depends on the purpose that the 69 # block is being used for. Feel encouraged to specify an icon in your 70 # descendant block type 71 icon = "placeholder" 72 default = None 73 74 75class FieldBlockAdapter(Adapter): 76 js_constructor = 'wagtail.blocks.FieldBlock' 77 78 def js_args(self, block): 79 classname = [ 80 'field', 81 camelcase_to_underscore(block.field.__class__.__name__), 82 'widget-' + camelcase_to_underscore(block.field.widget.__class__.__name__), 83 'fieldname-' + block.name, 84 ] 85 86 form_classname = getattr(block.meta, 'form_classname', '') 87 if form_classname: 88 classname.append(form_classname) 89 90 # Provided for backwards compatibility. Replaced with 'form_classname' 91 legacy_classname = getattr(block.meta, 'classname', '') 92 if legacy_classname: 93 classname.append(legacy_classname) 94 95 meta = { 96 'label': block.label, 97 'required': block.required, 98 'icon': block.meta.icon, 99 'classname': ' '.join(classname), 100 'showAddCommentButton': getattr(block.field.widget, 'show_add_comment_button', True), 101 'strings': { 102 'ADD_COMMENT': _('Add Comment') 103 }, 104 } 105 if block.field.help_text: 106 meta['helpText'] = block.field.help_text 107 108 return [ 109 block.name, 110 block.field.widget, 111 meta, 112 ] 113 114 @cached_property 115 def media(self): 116 return forms.Media(js=[ 117 versioned_static('wagtailadmin/js/telepath/blocks.js'), 118 ]) 119 120 121register(FieldBlockAdapter(), FieldBlock) 122 123 124class CharBlock(FieldBlock): 125 126 def __init__(self, required=True, help_text=None, max_length=None, min_length=None, validators=(), **kwargs): 127 # CharField's 'label' and 'initial' parameters are not exposed, as Block handles that functionality natively 128 # (via 'label' and 'default') 129 self.field = forms.CharField( 130 required=required, 131 help_text=help_text, 132 max_length=max_length, 133 min_length=min_length, 134 validators=validators, 135 ) 136 super().__init__(**kwargs) 137 138 def get_searchable_content(self, value): 139 return [force_str(value)] 140 141 142class TextBlock(FieldBlock): 143 144 def __init__(self, required=True, help_text=None, rows=1, max_length=None, min_length=None, validators=(), **kwargs): 145 self.field_options = { 146 'required': required, 147 'help_text': help_text, 148 'max_length': max_length, 149 'min_length': min_length, 150 'validators': validators, 151 } 152 self.rows = rows 153 super().__init__(**kwargs) 154 155 @cached_property 156 def field(self): 157 from wagtail.admin.widgets import AdminAutoHeightTextInput 158 field_kwargs = {'widget': AdminAutoHeightTextInput(attrs={'rows': self.rows})} 159 field_kwargs.update(self.field_options) 160 return forms.CharField(**field_kwargs) 161 162 def get_searchable_content(self, value): 163 return [force_str(value)] 164 165 class Meta: 166 icon = "pilcrow" 167 168 169class BlockQuoteBlock(TextBlock): 170 171 def render_basic(self, value, context=None): 172 if value: 173 return format_html('<blockquote>{0}</blockquote>', value) 174 else: 175 return '' 176 177 class Meta: 178 icon = "openquote" 179 180 181class FloatBlock(FieldBlock): 182 183 def __init__(self, required=True, max_value=None, min_value=None, validators=(), *args, 184 **kwargs): 185 self.field = forms.FloatField( 186 required=required, 187 max_value=max_value, 188 min_value=min_value, 189 validators=validators, 190 ) 191 super().__init__(*args, **kwargs) 192 193 class Meta: 194 icon = "plus-inverse" 195 196 197class DecimalBlock(FieldBlock): 198 199 def __init__(self, required=True, help_text=None, max_value=None, min_value=None, 200 max_digits=None, decimal_places=None, validators=(), *args, **kwargs): 201 self.field = forms.DecimalField( 202 required=required, 203 help_text=help_text, 204 max_value=max_value, 205 min_value=min_value, 206 max_digits=max_digits, 207 decimal_places=decimal_places, 208 validators=validators, 209 ) 210 super().__init__(*args, **kwargs) 211 212 class Meta: 213 icon = "plus-inverse" 214 215 216class RegexBlock(FieldBlock): 217 218 def __init__(self, regex, required=True, help_text=None, max_length=None, min_length=None, 219 error_messages=None, validators=(), *args, **kwargs): 220 self.field = forms.RegexField( 221 regex=regex, 222 required=required, 223 help_text=help_text, 224 max_length=max_length, 225 min_length=min_length, 226 error_messages=error_messages, 227 validators=validators, 228 ) 229 super().__init__(*args, **kwargs) 230 231 class Meta: 232 icon = "code" 233 234 235class URLBlock(FieldBlock): 236 237 def __init__(self, required=True, help_text=None, max_length=None, min_length=None, validators=(), **kwargs): 238 self.field = forms.URLField( 239 required=required, 240 help_text=help_text, 241 max_length=max_length, 242 min_length=min_length, 243 validators=validators, 244 ) 245 super().__init__(**kwargs) 246 247 class Meta: 248 icon = "site" 249 250 251class BooleanBlock(FieldBlock): 252 253 def __init__(self, required=True, help_text=None, **kwargs): 254 # NOTE: As with forms.BooleanField, the default of required=True means that the checkbox 255 # must be ticked to pass validation (i.e. it's equivalent to an "I agree to the terms and 256 # conditions" box). To get the conventional yes/no behaviour, you must explicitly pass 257 # required=False. 258 self.field = forms.BooleanField(required=required, help_text=help_text) 259 super().__init__(**kwargs) 260 261 def get_form_state(self, value): 262 # Bypass widget.format_value, because CheckboxInput uses that to prepare the "value" 263 # attribute (as distinct from the "checked" attribute that represents the actual checkbox 264 # state, which it handles in get_context). 265 return bool(value) 266 267 class Meta: 268 icon = "tick-inverse" 269 270 271class DateBlock(FieldBlock): 272 273 def __init__(self, required=True, help_text=None, format=None, validators=(), **kwargs): 274 self.field_options = { 275 'required': required, 276 'help_text': help_text, 277 'validators': validators, 278 } 279 try: 280 self.field_options['input_formats'] = kwargs.pop('input_formats') 281 except KeyError: 282 pass 283 self.format = format 284 super().__init__(**kwargs) 285 286 @cached_property 287 def field(self): 288 from wagtail.admin.widgets import AdminDateInput 289 field_kwargs = { 290 'widget': AdminDateInput(format=self.format), 291 } 292 field_kwargs.update(self.field_options) 293 return forms.DateField(**field_kwargs) 294 295 def to_python(self, value): 296 # Serialising to JSON uses DjangoJSONEncoder, which converts date/time objects to strings. 297 # The reverse does not happen on decoding, because there's no way to know which strings 298 # should be decoded; we have to convert strings back to dates here instead. 299 if value is None or isinstance(value, datetime.date): 300 return value 301 else: 302 return parse_date(value) 303 304 class Meta: 305 icon = "date" 306 307 308class TimeBlock(FieldBlock): 309 310 def __init__(self, required=True, help_text=None, format=None, validators=(), **kwargs): 311 self.field_options = { 312 'required': required, 313 'help_text': help_text, 314 'validators': validators 315 } 316 self.format = format 317 super().__init__(**kwargs) 318 319 @cached_property 320 def field(self): 321 from wagtail.admin.widgets import AdminTimeInput 322 field_kwargs = {'widget': AdminTimeInput(format=self.format)} 323 field_kwargs.update(self.field_options) 324 return forms.TimeField(**field_kwargs) 325 326 def to_python(self, value): 327 if value is None or isinstance(value, datetime.time): 328 return value 329 else: 330 return parse_time(value) 331 332 class Meta: 333 icon = "time" 334 335 336class DateTimeBlock(FieldBlock): 337 338 def __init__(self, required=True, help_text=None, format=None, validators=(), **kwargs): 339 self.field_options = { 340 'required': required, 341 'help_text': help_text, 342 'validators': validators, 343 } 344 self.format = format 345 super().__init__(**kwargs) 346 347 @cached_property 348 def field(self): 349 from wagtail.admin.widgets import AdminDateTimeInput 350 field_kwargs = { 351 'widget': AdminDateTimeInput(format=self.format), 352 } 353 field_kwargs.update(self.field_options) 354 return forms.DateTimeField(**field_kwargs) 355 356 def to_python(self, value): 357 if value is None or isinstance(value, datetime.datetime): 358 return value 359 else: 360 return parse_datetime(value) 361 362 class Meta: 363 icon = "date" 364 365 366class EmailBlock(FieldBlock): 367 def __init__(self, required=True, help_text=None, validators=(), **kwargs): 368 self.field = forms.EmailField( 369 required=required, 370 help_text=help_text, 371 validators=validators, 372 ) 373 super().__init__(**kwargs) 374 375 class Meta: 376 icon = "mail" 377 378 379class IntegerBlock(FieldBlock): 380 381 def __init__(self, required=True, help_text=None, min_value=None, 382 max_value=None, validators=(), **kwargs): 383 self.field = forms.IntegerField( 384 required=required, 385 help_text=help_text, 386 min_value=min_value, 387 max_value=max_value, 388 validators=validators, 389 ) 390 super().__init__(**kwargs) 391 392 class Meta: 393 icon = "plus-inverse" 394 395 396class BaseChoiceBlock(FieldBlock): 397 choices = () 398 399 def __init__( 400 self, choices=None, default=None, required=True, 401 help_text=None, widget=None, validators=(), **kwargs): 402 403 self._required = required 404 self._default = default 405 406 if choices is None: 407 # no choices specified, so pick up the choice defined at the class level 408 choices = self.choices 409 410 if callable(choices): 411 # Support of callable choices. Wrap the callable in an iterator so that we can 412 # handle this consistently with ordinary choice lists; 413 # however, the `choices` constructor kwarg as reported by deconstruct() should 414 # remain as the callable 415 choices_for_constructor = choices 416 choices = CallableChoiceIterator(choices) 417 else: 418 # Cast as a list 419 choices_for_constructor = choices = list(choices) 420 421 # keep a copy of all kwargs (including our normalised choices list) for deconstruct() 422 # Note: we omit the `widget` kwarg, as widgets do not provide a serialization method 423 # for migrations, and they are unlikely to be useful within the frozen ORM anyhow 424 self._constructor_kwargs = kwargs.copy() 425 self._constructor_kwargs['choices'] = choices_for_constructor 426 if required is not True: 427 self._constructor_kwargs['required'] = required 428 if help_text is not None: 429 self._constructor_kwargs['help_text'] = help_text 430 431 # We will need to modify the choices list to insert a blank option, if there isn't 432 # one already. We have to do this at render time in the case of callable choices - so rather 433 # than having separate code paths for static vs dynamic lists, we'll _always_ pass a callable 434 # to ChoiceField to perform this step at render time. 435 436 callable_choices = self._get_callable_choices(choices) 437 self.field = self.get_field( 438 choices=callable_choices, 439 required=required, 440 help_text=help_text, 441 validators=validators, 442 widget=widget, 443 ) 444 super().__init__(default=default, **kwargs) 445 446 def _get_callable_choices(self, choices, blank_choice=True): 447 """ 448 Return a callable that we can pass into `forms.ChoiceField`, which will provide the 449 choices list with the addition of a blank choice (if blank_choice=True and one does not 450 already exist). 451 """ 452 def choices_callable(): 453 # Variable choices could be an instance of CallableChoiceIterator, which may be wrapping 454 # something we don't want to evaluate multiple times (e.g. a database query). Cast as a 455 # list now to prevent it getting evaluated twice (once while searching for a blank choice, 456 # once while rendering the final ChoiceField). 457 local_choices = list(choices) 458 459 # If blank_choice=False has been specified, return the choices list as is 460 if not blank_choice: 461 return local_choices 462 463 # Else: if choices does not already contain a blank option, insert one 464 # (to match Django's own behaviour for modelfields: 465 # https://github.com/django/django/blob/1.7.5/django/db/models/fields/__init__.py#L732-744) 466 has_blank_choice = False 467 for v1, v2 in local_choices: 468 if isinstance(v2, (list, tuple)): 469 # this is a named group, and v2 is the value list 470 has_blank_choice = any([value in ('', None) for value, label in v2]) 471 if has_blank_choice: 472 break 473 else: 474 # this is an individual choice; v1 is the value 475 if v1 in ('', None): 476 has_blank_choice = True 477 break 478 479 if not has_blank_choice: 480 return BLANK_CHOICE_DASH + local_choices 481 482 return local_choices 483 return choices_callable 484 485 class Meta: 486 # No icon specified here, because that depends on the purpose that the 487 # block is being used for. Feel encouraged to specify an icon in your 488 # descendant block type 489 icon = "placeholder" 490 491 492class ChoiceBlock(BaseChoiceBlock): 493 def get_field(self, **kwargs): 494 return forms.ChoiceField(**kwargs) 495 496 def _get_callable_choices(self, choices, blank_choice=None): 497 # If we have a default choice and the field is required, we don't need to add a blank option. 498 if blank_choice is None: 499 blank_choice = not(self._default and self._required) 500 return super()._get_callable_choices(choices, blank_choice=blank_choice) 501 502 def deconstruct(self): 503 """ 504 Always deconstruct ChoiceBlock instances as if they were plain ChoiceBlocks with their 505 choice list passed in the constructor, even if they are actually subclasses. This allows 506 users to define subclasses of ChoiceBlock in their models.py, with specific choice lists 507 passed in, without references to those classes ending up frozen into migrations. 508 """ 509 return ('wagtail.core.blocks.ChoiceBlock', [], self._constructor_kwargs) 510 511 def get_searchable_content(self, value): 512 # Return the display value as the searchable value 513 text_value = force_str(value) 514 for k, v in self.field.choices: 515 if isinstance(v, (list, tuple)): 516 # This is an optgroup, so look inside the group for options 517 for k2, v2 in v: 518 if value == k2 or text_value == force_str(k2): 519 return [force_str(k), force_str(v2)] 520 else: 521 if value == k or text_value == force_str(k): 522 return [force_str(v)] 523 return [] # Value was not found in the list of choices 524 525 526class MultipleChoiceBlock(BaseChoiceBlock): 527 def get_field(self, **kwargs): 528 return forms.MultipleChoiceField(**kwargs) 529 530 def _get_callable_choices(self, choices, blank_choice=False): 531 """ Override to default blank choice to False 532 """ 533 return super()._get_callable_choices(choices, blank_choice=blank_choice) 534 535 def deconstruct(self): 536 """ 537 Always deconstruct MultipleChoiceBlock instances as if they were plain 538 MultipleChoiceBlocks with their choice list passed in the constructor, 539 even if they are actually subclasses. This allows users to define 540 subclasses of MultipleChoiceBlock in their models.py, with specific choice 541 lists passed in, without references to those classes ending up frozen 542 into migrations. 543 """ 544 return ('wagtail.core.blocks.MultipleChoiceBlock', [], self._constructor_kwargs) 545 546 def get_searchable_content(self, value): 547 # Return the display value as the searchable value 548 content = [] 549 text_value = force_str(value) 550 for k, v in self.field.choices: 551 if isinstance(v, (list, tuple)): 552 # This is an optgroup, so look inside the group for options 553 for k2, v2 in v: 554 if value == k2 or text_value == force_str(k2): 555 content.append(force_str(k)) 556 content.append(force_str(v2)) 557 else: 558 if value == k or text_value == force_str(k): 559 content.append(force_str(v)) 560 return content 561 562 563class RichTextBlock(FieldBlock): 564 565 def __init__(self, required=True, help_text=None, editor='default', features=None, validators=(), **kwargs): 566 self.field_options = { 567 'required': required, 568 'help_text': help_text, 569 'validators': validators, 570 } 571 self.editor = editor 572 self.features = features 573 super().__init__(**kwargs) 574 575 def get_default(self): 576 if isinstance(self.meta.default, RichText): 577 return self.meta.default 578 else: 579 return RichText(self.meta.default) 580 581 def to_python(self, value): 582 # convert a source-HTML string from the JSONish representation 583 # to a RichText object 584 return RichText(value) 585 586 def get_prep_value(self, value): 587 # convert a RichText object back to a source-HTML string to go into 588 # the JSONish representation 589 return value.source 590 591 @cached_property 592 def field(self): 593 from wagtail.admin.rich_text import get_rich_text_editor_widget 594 return forms.CharField( 595 widget=get_rich_text_editor_widget(self.editor, features=self.features), 596 **self.field_options 597 ) 598 599 def value_for_form(self, value): 600 # Rich text editors take the source-HTML string as input (and takes care 601 # of expanding it for the purposes of the editor) 602 return value.source 603 604 def value_from_form(self, value): 605 # Rich text editors return a source-HTML string; convert to a RichText object 606 return RichText(value) 607 608 def get_searchable_content(self, value): 609 # Strip HTML tags to prevent search backend from indexing them 610 source = force_str(value.source) 611 return [get_text_for_indexing(source)] 612 613 class Meta: 614 icon = "doc-full" 615 616 617class RawHTMLBlock(FieldBlock): 618 619 def __init__(self, required=True, help_text=None, max_length=None, min_length=None, validators=(), **kwargs): 620 self.field = forms.CharField( 621 required=required, help_text=help_text, max_length=max_length, min_length=min_length, 622 validators=validators, 623 widget=forms.Textarea) 624 super().__init__(**kwargs) 625 626 def get_default(self): 627 return mark_safe(self.meta.default or '') 628 629 def to_python(self, value): 630 return mark_safe(value) 631 632 def get_prep_value(self, value): 633 # explicitly convert to a plain string, just in case we're using some serialisation method 634 # that doesn't cope with SafeString values correctly 635 return str(value) + '' 636 637 def value_for_form(self, value): 638 # need to explicitly mark as unsafe, or it'll output unescaped HTML in the textarea 639 return str(value) + '' 640 641 def value_from_form(self, value): 642 return mark_safe(value) 643 644 class Meta: 645 icon = 'code' 646 647 648class ChooserBlock(FieldBlock): 649 650 def __init__(self, required=True, help_text=None, validators=(), **kwargs): 651 self._required = required 652 self._help_text = help_text 653 self._validators = validators 654 super().__init__(**kwargs) 655 656 """Abstract superclass for fields that implement a chooser interface (page, image, snippet etc)""" 657 @cached_property 658 def field(self): 659 return forms.ModelChoiceField( 660 queryset=self.target_model.objects.all(), widget=self.widget, required=self._required, 661 validators=self._validators, 662 help_text=self._help_text) 663 664 def to_python(self, value): 665 # the incoming serialised value should be None or an ID 666 if value is None: 667 return value 668 else: 669 try: 670 return self.target_model.objects.get(pk=value) 671 except self.target_model.DoesNotExist: 672 return None 673 674 def bulk_to_python(self, values): 675 """Return the model instances for the given list of primary keys. 676 677 The instances must be returned in the same order as the values and keep None values. 678 """ 679 objects = self.target_model.objects.in_bulk(values) 680 return [objects.get(id) for id in values] # Keeps the ordering the same as in values. 681 682 def get_prep_value(self, value): 683 # the native value (a model instance or None) should serialise to a PK or None 684 if value is None: 685 return None 686 else: 687 return value.pk 688 689 def value_from_form(self, value): 690 # ModelChoiceField sometimes returns an ID, and sometimes an instance; we want the instance 691 if value is None or isinstance(value, self.target_model): 692 return value 693 else: 694 try: 695 return self.target_model.objects.get(pk=value) 696 except self.target_model.DoesNotExist: 697 return None 698 699 def clean(self, value): 700 # ChooserBlock works natively with model instances as its 'value' type (because that's what you 701 # want to work with when doing front-end templating), but ModelChoiceField.clean expects an ID 702 # as the input value (and returns a model instance as the result). We don't want to bypass 703 # ModelChoiceField.clean entirely (it might be doing relevant validation, such as checking page 704 # type) so we convert our instance back to an ID here. It means we have a wasted round-trip to 705 # the database when ModelChoiceField.clean promptly does its own lookup, but there's no easy way 706 # around that... 707 if isinstance(value, self.target_model): 708 value = value.pk 709 return super().clean(value) 710 711 class Meta: 712 # No icon specified here, because that depends on the purpose that the 713 # block is being used for. Feel encouraged to specify an icon in your 714 # descendant block type 715 icon = "placeholder" 716 717 718class PageChooserBlock(ChooserBlock): 719 def __init__(self, page_type=None, can_choose_root=False, target_model=None, **kwargs): 720 # We cannot simply deprecate 'target_model' in favour of 'page_type' 721 # as it would force developers to update their old migrations. 722 # Mapping the old 'target_model' to the new 'page_type' kwarg instead. 723 if target_model: 724 page_type = target_model 725 726 if page_type: 727 # Convert single string/model into a list 728 if not isinstance(page_type, (list, tuple)): 729 page_type = [page_type] 730 else: 731 page_type = [] 732 733 self.page_type = page_type 734 self.can_choose_root = can_choose_root 735 super().__init__(**kwargs) 736 737 @cached_property 738 def target_model(self): 739 """ 740 Defines the model used by the base ChooserBlock for ID <-> instance 741 conversions. If a single page type is specified in target_model, 742 we can use that to get the more specific instance "for free"; otherwise 743 use the generic Page model. 744 """ 745 if len(self.target_models) == 1: 746 return self.target_models[0] 747 748 return resolve_model_string('wagtailcore.Page') 749 750 @cached_property 751 def target_models(self): 752 target_models = [] 753 754 for target_model in self.page_type: 755 target_models.append( 756 resolve_model_string(target_model) 757 ) 758 759 return target_models 760 761 @cached_property 762 def widget(self): 763 from wagtail.admin.widgets import AdminPageChooser 764 return AdminPageChooser(target_models=self.target_models, 765 can_choose_root=self.can_choose_root) 766 767 def get_form_state(self, value): 768 value_data = self.widget.get_value_data(value) 769 if value_data is None: 770 return None 771 else: 772 return { 773 'id': value_data['id'], 774 'parentId': value_data['parent_id'], 775 'adminTitle': value_data['display_title'], 776 'editUrl': value_data['edit_url'], 777 } 778 779 def render_basic(self, value, context=None): 780 if value: 781 return format_html('<a href="{0}">{1}</a>', value.url, value.title) 782 else: 783 return '' 784 785 def deconstruct(self): 786 name, args, kwargs = super().deconstruct() 787 788 if 'target_model' in kwargs or 'page_type' in kwargs: 789 target_models = [] 790 791 for target_model in self.target_models: 792 opts = target_model._meta 793 target_models.append( 794 '{}.{}'.format(opts.app_label, opts.object_name) 795 ) 796 797 kwargs.pop('target_model', None) 798 kwargs['page_type'] = target_models 799 800 return name, args, kwargs 801 802 class Meta: 803 icon = "redirect" 804 805 806# Ensure that the blocks defined here get deconstructed as wagtailcore.blocks.FooBlock 807# rather than wagtailcore.blocks.field.FooBlock 808block_classes = [ 809 FieldBlock, CharBlock, URLBlock, RichTextBlock, RawHTMLBlock, ChooserBlock, 810 PageChooserBlock, TextBlock, BooleanBlock, DateBlock, TimeBlock, 811 DateTimeBlock, ChoiceBlock, MultipleChoiceBlock, EmailBlock, IntegerBlock, FloatBlock, 812 DecimalBlock, RegexBlock, BlockQuoteBlock 813] 814DECONSTRUCT_ALIASES = { 815 cls: 'wagtail.core.blocks.%s' % cls.__name__ 816 for cls in block_classes 817} 818__all__ = [cls.__name__ for cls in block_classes] 819