I've been struggling to get my form (including the button) in just one line using django-crispy-forms.
I eventually found a solution, but I decided posting the question together with the answer, in case somebody else faces the same problem.
Code in forms.py was as follows:
class SearchForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-inline'
self.helper.layout = Layout(
Field('From', placeholder='From'),
Field('To', placeholder='To'),
Field('Date', placeholder='Date'),
ButtonHolder(Submit('submit', 'Search', css_class='btn btn-primary'))
)
But the button was appearing in a second line.
I tried as alternative
self.helper.form_class = 'form-horizontal'
but no difference.
I found a solution based on this post in github.
That is to adapt the Layout by using a FormActions object:
self.helper.layout = Layout(
Field('From', placeholder='From'),
Field('To', placeholder='To'),
Field('Date', placeholder='Date'),
FormActions(ButtonHolder(Submit('submit', 'Search', css_class='btn btn-primary')))
)
And all fields and the button are perfectly aligned:
Related
crispy_forms Crispy_Forms 1.14.0
crispy_forms_foundation Crispy_Forms_Foundation 0.8.0
Django 3.2.11
I'm trying to migrate from django 2 to django 3, but stuck with empty forms.
class Myform(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset( 'This is shown:' ),
Column('myfield', css_class='large-4'), #hidden
Column('myfield2', css_class='large-4'), #hidden
)
Input fields are not rendered in template (you can see only the Fieldset header text 'This is shown:').
I've discovered that deleting the self.helper.layout = Layout() in the init method, make the fields rendered correctly in the template.
Any idea on how to fix this?
It seems that Column doesn't exist anymore, replace with:
Field('myfield', css_class="black-fields")
How would I get the currently open page in dpymenus, eg page 2 in this example
from discord.ext import commands
from dpymenus import Page, PaginatedMenu
class Demo(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def demo(self, ctx: commands.Context):
page1 = Page(title='Page 1', description='First page test!')
page1.add_field(name='Example A', value='Example B')
page2 = Page(title='Page 2', description='Second page test!')
page2.add_field(name='Example C', value='Example D')
page3 = Page(title='Page 3', description='Third page test!')
page3.add_field(name='Example E', value='Example F')
menu = PaginatedMenu(ctx)
menu.add_pages([page1, page2, page3])
await menu.open()
def setup(client):
client.add_cog(Demo(client))
also what should this be tagged with?
I have discovered the answer:
use menu.page it will return something like Page 1 Title
you can use menu.page.index to get the index of the page item.
I found the answer in a discord server, I was not able to find any info in https://dpymenus.readthedocs.io/en/latest/ if you can please tell me.
I am having trouble using scapry to follow a "next page" link - according to the log it is referring back to itself instead of the "next page" url. Here is the code:
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes2"
start_urls = [
'http://search.jeffersondeeds.com/pdetail.php?instnum=2016230701&year=2016&db=0&cnum=20',
]
def parse(self, response):
for quote in response.xpath('//div'):
yield{
'record' : quote.select(".//span/text()").extract()
}
next_page = response.xpath('//*[#id="nextpage"]/a/#href').extract()
if next_page is not None:
print("GOOOO BUCKS!!")
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse)
else:
print("Ahhh fooey!")
The xpath looks to be correct:
But the url in being captured as next_page is the original url (starts_urls)
next_page isn't None, but it is an empty list.
Now the nextpage link being generated with a javascript inside '//table//script/text()'
you can get it with: response.xpath('//table//script/text()').re_first("href=\\'(pdetail.*)\\'>")
Inspecting the field.html template in the bootstrap3 template pack of django-crispyforms, I noticed an additional context variable, "tag", referenced. You can see this on line 12 and line 41 of the template. How can I specify a value for "tag" in the context used to render the field.html template for a particular form field?
I used palestamp's response as a guide to build a more generic CustomCrispyField. You can pass extra_context as a kwarg to CustomCrispyField. extra_context is just a dictionary that I access in my custom template that I copied from crispy_forms.
from crispy_forms.layout import Field
from crispy_forms.utils import TEMPLATE_PACK
class CustomCrispyField(Field):
extra_context = {}
def __init__(self, *args, **kwargs):
self.extra_context = kwargs.pop('extra_context', self.extra_context)
super(CustomCrispyField, self).__init__(*args, **kwargs)
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, extra_context=None, **kwargs):
if self.extra_context:
extra_context = extra_context.update(self.extra_context) if extra_context else self.extra_context
return super(CustomCrispyField, self).render(form, form_style, context, template_pack, extra_context, **kwargs)
And I would use it like so in my form:
self.helper.layout=Div(CustomCrispyField('my_model_field', css_class="col-xs-3", template='general/custom.html', extra_context={'css_class_extra': 'value1', 'caption': 'value2'})
And my template would have code similar to the following:
{% crispy_field field %}
<button class="btn {{ css_class_extra }}">{{ caption }}</button>
You can just override standard crispy Field like this:
class LinkField(Field):
def __init__(self, *args, **kwargs):
self.view_name = kwargs.pop('view_name')
super(LinkField, self).__init__(*args, **kwargs)
def render(self, form, form_style, context, template_pack=CRISPY_TEMPLATE_PACK):
if hasattr(self, 'wrapper_class'):
context['wrapper_class'] = self.wrapper_class
if hasattr(self, 'view_name'):
context['view_name'] = self.view_name
html = ''
for field in self.fields:
html += render_field(field, form, form_style, context, template=self.template, attrs=self.attrs, template_pack=template_pack)
return html
Then just pass additional variable ('view_name') in overridden template.
In Layout it will look like this:
Layout(
LinkField('field_name', template='path_to_overridden_template',
view_name='variable_to_pass')
)
The tag context variable is set in the template, not the view. If you're using the built-in bootstrap3 template pack it's defined in the template that is including field.html. If the including template does not define tag then it defaults to div.
Ex: table_inline_formset.html line 41.
following bobort's answer, I would like to highlight that you therefor can use any queryset as arguments in the extra_content, which allows you to inject via the custom template an html rendering of anything you want, this becomes very interesting concept on how to transform the crispy forms into super agile. I am currently using forms to render my templates, instead of rendering forms in templates ^^
I need to add a 'Cancel' button to my ModelForm, I am using crispy forms but when I add href='personnel-index' to redirect back to the list view, it does not. I've checked their documentation but no luck nor any luck on Google.
Sometimes the browser's 'Back' button does just as well as a 'Cancel' button in a web form, but if you are absolutely sure you need one, you can always use the HTML object in crispy forms. Something like this would work:
HTML("""<a class="classes-for-styling" href="/personnel/list/">Cancel</a>""")
And, even better, you can include context aware tags to avoid hard-coding urls into your form:
HTML("""<a class="classes-for-styling" href="{% url 'personnel-index' %}">Cancel</a>""")
Then it's just up to you to style your link so it looks like a button.
Not necessarily ideal, but this should handle most situations.
self.helper.add_input(Button('cancel', 'Cancel', css_class='btn-default', onclick="window.history.back()"))
Sorry, I can't comment the existing solution because my reputation isn't good enough. That's why I am adding the code example for HTML (crispy form object) solution that was proposed above:
class MoodForm(forms.ModelForm):
class Meta:
model = Mood
def __init__(self, *args, **kw):
super(MoodForm, self).__init__(*args, **kw)
self.helper = FormHelper()
layout = Layout(
ButtonHolder(
Submit('Save', 'Save', css_class='button white'),
HTML('<a class="btn btn-warning" href={% url "mood:list" %}>Cancel</a>'),
),
)
self.helper.add_layout(layout)
The most important stuff here is:
ButtonHolder(
Submit('Save', 'Save', css_class='button white'),
HTML('<a class="btn btn-warning" href={% url "mood:list" %}>Cancel</a>'),
)
It renders two buttons 'Save' and 'Cancel'.