New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Fixed #34077 -- Made BoundField renderable. #16247
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick initial comment: this should have a major section at the top of the release notes, with a full usage example, I think. (We wanna tell folks about it! 😜)
Nice work picking this up David 🦄
django/forms/boundfield.py
Outdated
def as_field(self, template_name=None, context=None, renderer=None): | ||
""" | ||
Render the field including labels, help text and errors. | ||
""" | ||
renderer = renderer or self.form.renderer | ||
template = template_name or self.field.template_name | ||
context = context or {"field": self} | ||
return mark_safe(renderer.render(template, context)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very close to django.forms.utils.RenderableMixin.render()
, so wondering if we could define:
# django/form/utils.py
class RenderableFieldMixin(RenderableMixin):
def as_field(self):
return self.render(self.template_name_field)
Or something like that. And then have the following here:
class BoundField(RenderableFieldMixin):
...
def get_context(self):
return {"field": self}
We'd also need to set self.renderer
in BoundField.__init__()
. Then we could potentially ditch the @html_safe
and __str__()
, but we'd have to see how we can make that behave still as currently it relies on .as_widget()
which looks a bit different. I think we could overcome that though with some thought... It might be that we then later want to revisit what widgets are doing and see if we need something like a RenderableWidgetMixin
too. It'd be really good to support a consistent API for all of this, especially for long term maintenance 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This highlighted a nice bug I'd likely introduced. Hopefully we'll have one test failure showing this.
We have a circular series of renderables as the form contains its boundfield and the boundfield has a reference back to the form.
While I need to spend more time looking at this I'd also appreciate ideas.
docs/ref/forms/api.txt
Outdated
@@ -1243,6 +1243,14 @@ Attributes of ``BoundField`` | |||
Methods of ``BoundField`` | |||
------------------------- | |||
|
|||
.. method:: BoundField.as_field(template_name=None, context=None, renderer=None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, looking at this, this is the signature of RenderableMixin.render()
, not the various .as_*()
methods.
Would definitely be nice to align this better.
Hey @smithdc1 — Just looking at the discussion here. Can I ask you to ping me when you're ready for a review. Thanks. 🎁 |
@carltongibson -- Can I ask you to put this on your list please? On the circular rendering issue, my current thinking is that it's actually ok for now. The original issue was recurively calling I've changed the test to call The for now reference is that if we ever did want to change |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks David. This is a good next step, but I think we need to think about extracting the field rendering for more than just the div.html
. Really .as_field()
should be .as_div()
and we should add the equivalent for the other tags.
I'll try and have a think through the mess with .as_widget()
and .as_hidden()
in .__str__()
. It feels pretty clunky and looks sort of outdated w.r.t. moving stuff to template rendering. Biggest pain is that they're documented API.
"Subclasses of RenderableFieldMixin must provide an as_widget() method." | ||
) | ||
|
||
def __str__(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth a comment to state that we override this here for backward compatibility, i.e. we can't rely on the default implementation from the parent that uses self.render()
.
# TODO -- I'm not sure how best to structure this as __str__ on boundfield does | ||
# something different to RenderableMixin. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this comment is really related to __str__()
below. I think we can start out with the comment I've mentioned.
I'll try to have a think if there is some backward-compatible way we can improve this. But either way, even if it's not an entirely clean fit we're at least coercing this into a more clear pattern.
@@ -0,0 +1,10 @@ | |||
{% if field.use_fieldset %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice that we're extracting this from the django/forms/div.html
template.. But what about the others?
Maybe this should be django/forms/fields/div.html
and we should also extract the equivalent from p.html
, table.html
and ul.html
. Shouldn't we have .as_div()
, .as_table()
, .as_p()
, and .as_ul()
like at the form level instead of .as_field()
?
It certainly feels like this should mirror what happens at the form level as the field should follow the same templating structure as the form to avoid incorrect output.
if self.field.show_hidden_initial: | ||
return self.as_widget() + self.as_hidden(only_initial=True) | ||
return self.as_widget() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. The more I look into the these and some of the existing helpers, the more I feel that some stuff should be cleaned up and ripped out. I'll need some time to have a play though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mornings thought was I wonder if the first step here would be to see if we can make Widget
inherit from RenderableMixin
or similar.
I don't think we can currently call str()
on a widget to get it to render. That could be nice in a template. 🤔
{{ field.label }}
{{ field.help_text }}
{{ field.widget }}
...
Thinking out loud here!
Thank you for you review and comments as always. Just a couple of initial comments.
We don't recommend the other methods. We stopped short of removing them (that was the suggestion by the accessibility team) due to backward compatibility concerns. So I wasn't sure on adding additional support for them. Maybe more importantly I'm not sure there's that much benefit. How many folk are using exactly the standard layout for those methods and would benefit (e.g im not sure how altering the table layout would help)? As soon as you have something slightly different the advise would be to create a custom template and use that instead? Assign it in the renderer or field or ...
It's also been like this for some time. I had assumed that was too much to be changing at this stage. |
Yes... — if we were starting from scratch now, we wouldn't have the So here… — if we were starting from scratch now, we'd do the same for I don't think we need (or, for the a11y concerns, want) to add
👀 |
Ah, if only, eh? 🙂
If we're to do this - only focus on improving the Not splitting out those cases and advertising this split-out field template as a new feature may be confusing to users of those non-recommended methods. |
Hey @ngnpope. 👍 We already moved the |
I'll come back to this after the feature freeze. 🎅🎄 I've spent a bit of time looking at the comments and it seems to unravel and get more complex the more I think through it. I'm not sure unpicking the current str behaviour is that straight forward. 🤔 |
Hey @smithdc1 — OK. Always wise 💝 — Overall, I think this is a (really) good feature to have, but there's no urgency. 5.0 will be more than perfect. 🎁 |
Hey @carltongibson 👋 Just to say, I'm planning on coming back to this next. I've got one eye on trying to get this in before you move onto new adventures. 😀 |
Cool. Yes. Let's get it in. It's the icing on the cake for the forms changes over the last few releases. (I didn't get to think about it but, did we consider the template tag approach? Do we favour the method on BoundField?) |
I think I originally suggested this as an option on the ticket. I've come round to the idea that it is not the best option here. All of the other ways of rendering a form/field/widget are a method, usually one of To suddenly have a template tag to render a field seems like an odd choice. I think the most difficult question we have to answer is what do we do about calling
I'll think I'll start here, likely as another PR so we can get that in. |
@@ -37,6 +37,70 @@ compatible with Django 5.0. | |||
What's new in Django 5.0 | |||
======================== | |||
|
|||
BoundField becomes renderable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a better title.
Closing in favour of #16574 |
Ticket 43077