I am trying to render a formset with crispy forms in Django, utilizing an add-more forms button. However I can't figure out how to apply the same CSS to the empty form as the original form. The only way I have been successful is creating a new empty form like this solution, but this adds in another <form> tag which is a problem that isn't covered in the solution. How can I apply CSS to the dynamically added formset?
The below image is the expected result once I click the add more ingredients <button>:
forms.py
from .models import Recipe, RecipeIngredient
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Div, Layout, Field
class RecipeIngredientForm(forms.ModelForm):
class Meta:
model = RecipeIngredient
fields = ['name', 'quantity', 'unit', 'description']
labels = {
'name': "Ingredient",
"quantity:": "Ingredient Quantity",
"unit": "Unit",
"description:": "Ingredient Description"}
def __init__(self, *args, **kwargs):
super(RecipeIngredientForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-entryform'
self.helper.form_class = 'form-inline'
self.helper.layout = Layout(
Div(
Div(Field("name", placeholder="Chickpeas"), css_class='col-6 col-lg-4'),
Div(Field("quantity", placeholder="2 x 400"), css_class='col-6 col-md-4'),
Div(Field("unit", placeholder="grams"), css_class='col-5 col-md-4'),
Div(Field("description", placeholder="No added salt tins"), css_class='col-12'),
css_class="row",
),
)
views.py:
def recipe_create_view(request):
form = RecipeForm(request.POST or None)
RecipeIngredientFormset = formset_factory(RecipeIngredientForm, prefix="ingredient")
formset = RecipeIngredientFormset(request.POST or None)
context = {
"form": form,
"formset": formset,
}
if request.method == "POST":
#print(request.POST)
if form.is_valid() and formset.is_valid():
parent = form.save(commit=False)
parent.user = request.user
parent.save()
#recipe ingredients
for form in formset:
child = form.save(commit=False)
if form.instance.name.strip() == '':
pass
else:
child.recipe = parent
child.save()
else:
form = RecipeForm(request.POST or None)
formset = RecipeIngredientFormset()
return render(request, "recipes/create.html", context)
create.html
{% load crispy_forms_tags %}
<form action='/recipes/create/' method="POST" >
<!-- MAIN RECIPE FORM-->
{% csrf_token %}
<div class='row'>
{% for recipe in form %}
<div class="col-6 col-md-3 py-2">
{{ recipe|as_crispy_field }}
</div>
{% endfor %}
<div>
<!--RECIPE INGREDIENTS - WHERE CSS ISSUE IS -->
{% if formset %}
<h3>Ingredients</h3>
{{ formset.management_form|crispy }}
<div id='ingredient-form-list'>
{% for ingredient in formset %}
<div class='ingredient-form'>
{% crispy ingredient %}
</div>
{% endfor %}
</div>
<div id='empty-form' class='hidden'>{% crispy formset.empty_form %}</div>
<button class="btn btn-success" id='add-more' type='button'>Add more ingredients</button>
{% endif %}
</div>
<script>
//ingredients add form
const addMoreBtn = document.getElementById('add-more')
const totalNewForms = document.getElementById('id_ingredient-TOTAL_FORMS')
addMoreBtn.addEventListener('click', add_new_form)
function add_new_form(event) {
if (event) {
event.preventDefault()
}
const currentIngredientForms = document.getElementsByClassName('ingredient-form')
const currentFormCount = currentIngredientForms.length // + 1
const formCopyTarget = document.getElementById('ingredient-form-list')
const copyEmptyFormEl = document.getElementById('empty-form').cloneNode(true)
copyEmptyFormEl.setAttribute('class', 'ingredient-form')
copyEmptyFormEl.setAttribute('id', `ingredient-${currentFormCount}`)
const regex = new RegExp('__prefix__', 'g')
copyEmptyFormEl.innerHTML = copyEmptyFormEl.innerHTML.replace(regex, currentFormCount)
totalNewForms.setAttribute('value', currentFormCount + 1)
// now add new empty form element to our html form
formCopyTarget.append(copyEmptyFormEl)
}
</script>
The problem is shown in the below image, this creates another form rather that just adding the fields:
If I use {{ formset.empty_form|crispy }} instead of {% crispy formset.empty_form %} the script works fine, but the CSS is not attached to the form.
Had to use a combination of both methods for this to work, below is the changes to the questions code:
create.html
<div>
<!--RECIPE INGREDIENTS-->
{% if formset %}
<h3 class="mt-4 mb-3">Ingredients</h3>
{{ formset.management_form|crispy }}
<div id='ingredient-form-list'>
{% for ingredient in formset %}
<div class='ingredient-form'>
{% crispy ingredient %}
</div>
{% endfor %}
</div>
<div id='empty-form' class='hidden'>
<div class="row mt-4">
<div class="col-6">{{ formset.empty_form.name|as_crispy_field }}</div>
<div class="col-6">{{ formset.empty_form.quantity|as_crispy_field }}</div>
<div class="col-6">{{ formset.empty_form.unit|as_crispy_field }}</div>
</div>
</div>
<button class="btn btn-success my-2" id='add-more' type='button'>Add more ingredients</button>
{% endif %}
</div>
<div>
Related
i want to display a form to create posts. i use crispy-form and it currently displays:
with html template:
{% extends 'blog_app/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Create Post</legend>
{{ form.media }}
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Post</button>
</div>
</form>
</div>
{% endblock %}
i want to increase size of the title box and reduce size of the content box so that i fits the content section.
what i tried:
in template, display each field as crispy field with specified css class:
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<div class="form-group col-md-8">
{{ form.title|as_crispy_field }}
</div>
<div class="form-group col-md-8">
{{ form.content|as_crispy_field }}
</div>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Post</button>
</div>
</form>
</div>
{% endblock %}
in form class, set helper and layout:
class PostCreateForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.layout = Layout(
Field('title', id="form-title", css_class="col-md-8", name="title"),
Field('content', id="form-content", css_class="col-md-8", name="title"))
in both ways, nothing changes.
can someone give me a pointer?
update:
for the content box, since i used RichTextField from ckeditor for it, when i add the below config to settings.py, the size of it does change to fit the content section. but i still have no idea how to change size of the title box.
CKEDITOR_CONFIGS = {
'default': {
'height': '100%',
'width': '100%',
},
}
Ensure that you have the correct template pack in your settings.py:
CRISPY_TEMPLATE_PACK = 'bootstrap4'
...and use your columns and rows in the layout (in the form helper, not directly in the form):
from crispy_forms.layout import Layout, HTML, Row, Column
class PostCreateForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Row(
Column(
'title',
css_class='col-md-8'
),
),
Row(
Column(
'content',
css_class='col-md-8'
),
)
)
Next, use the crispy templatetag:
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Create Post</legend>
{{ form.media }}
{% crispy form %}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Post</button>
</div>
</form>
How to send information on my email from django form. Now I can see information only in my console output on my cmd
template:
{% extends 'base.html' %}
{% load widget_tweaks %}
{% block title %}
Contact Us
{% endblock %}
{% block content %}
<h2 class="mt-4 ml-4">Contact Me</h2>
<form method="post">
<div class="container mt-4">
{% csrf_token %}
<div class="col-md-4">
{{ form.subject.label }}
{% render_field form.subject class+="form-control" %}
</div>
<div class="col-md-4">
{{ form.email.label }}
{% render_field form.email type="email" class+="form-control" %}
</div>
<div class="col-md-4">
{{ form.message.label }}
{% render_field form.message class+="form-control" rows="4" cols="6" %}
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary mt-2 ml-3">Send</button>
</div>
</div>
</form>
{% endblock %}
forms.py:
from django import forms
class ContactForm(forms.Form):
email = forms.EmailField(required=True)
subject = forms.CharField(required=True)
message = forms.CharField(widget=forms.Textarea, required=False)
views.py:
from django.core.mail import send_mail, BadHeaderError
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from .forms import ContactForm
def emailView(request):
if request.method == 'GET':
form = ContactForm()
else:
form = ContactForm(request.POST)
if form.is_valid():
subject = form.cleaned_data['subject']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
try:
send_mail(subject, message, email, ['yarik.nashivan#gmail.com'])
except BadHeaderError:
return HttpResponse('Invalid header found.')
return redirect('success')
return render(request, "email.html", {'form': form})
def successView(request):
return HttpResponse('Success! Thank you for your message. <p>You will be redirected to the main page in 3 seconds.</p> <meta http-equiv="refresh" content="3;url=/"> ')
Don't look at it: fgkjdfldfjgndfkgndfskj vdafk kjdsjaf jjjfd jdsaf dj fdsjn dnjndfj jdffjk hdsffdfgfdb sfdf
Well, you are using console backend, that is why you are seeing the message in console. If you use SMTP backend, then you can send this email to your acccount. For that, you need to configure like this:
First you need to update the backend in settings.py:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
Then update the smtp configurations in settings.py:
# this configuration is for gmail
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'test#gmail.com'
EMAIL_HOST_PASSWORD = 'test'
EMAIL_PORT = 587
For details, please check the email documentation for Django.
Django==1.11.7
django-crudbuilder==0.2.5
Using Bootstrap datepicker. Now trying to use the datepicker in a crud form for a date field.
In a normal Django form, this would work:
self.fields['payment_date'].widget.attrs.update({'class': 'datepicker'})
But how can the class:datepicker be set for a particular field in a crud form?
The documentation doesn't seem to mention anything useful on css or html class.
Start with implementing custom templates.
Copy over the instance templates in crudbuilder/templates/crudbuilder/instance/ into your application template directory.
Replace the template location for the included form template in all the copied over instance templates .e.g. create.html
...
{% block main_content %}
<div class='container'>
<h3>Create {{actual_model_name|title}}</h3>
<hr/>
{% include "my_app/widgets/form.html" %}
</div>
{% endblock %}
In my_app/templates/my_app/widgets/form.html, write this instead to set the datepicker class on payment_date. (Original code was copied from django-cruid)
{% load crudbuilder %}
{% include "datepicker.html" %}
<form action="." method="post" enctype="multipart/form-data" class="form-horizontal" novalidate>
{% csrf_token %}
{% for field in form %}
<fieldset class={% if field.errors %} "form-group has-error" {% else %} "form-group" {% endif %} >
{{ field|label_with_class:"col-sm-2 control-label" }}
<div class="col-xs-4">
{% if field.name == 'payment_date' %}
{{ field|input_with_class:"form-control datepicker" }}
{% endif %}
{{ field|input_with_class:"form-control" }}
{{ field.errors }}
</div>
</fieldset>
{% endfor %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-12">
<input type="submit" value="Save" class="btn btn-success" />
</div>
</div>
</form>
Finally, set your crud form to use your custom templates.
class PersonCrud(BaseCrudBuilder):
...
custom_templates = {
'list': 'my_app/list.html',
'create': 'my_app/create.html',
'detail': 'my_app/detail.html',
'update': 'my_app/update.html',
'delete': 'my_app/delete.html'
}
I have following code in my twig file:
<form method ="POST" action="{{path('deleteMessages')}}">
{% for message in pmReceived %}
{% if message.isread == false %}
{% if message.showincoming == true %}
<div class="border">
<div class="pmbox">
<p class="pmsender">{{message.sender}}</p><p class="pmdate">{{message.date|date('d-m-Y H:i:s') }}</p><p class="pmsubject">{{message.subject}}</p><input type="checkbox" name="deletePm" value="{{message.pmid}}">
</div>
<div class="pmcontents">
<p class="titleContent">Contents:</p>
<p>{{message.contents}}</p>
</div>
</div>
{% endif %}
{% endif %}
{% endfor %}
{% for message in pmReceived %}
{% if message.isread == true %}
{% if message.showincoming == true %}
<div class="border">
<div class="pmbox">
<p class="pmsender">{{message.sender}}</p><p class="pmdate">{{message.date|date('d-m-Y H:i:s') }}</p><p class="pmsubject">{{message.subject}}</p><input type="checkbox" name="deletePm" value="{{message.pmid}}">
</div>
<div class="pmcontents">
<p class="titleContent">Contents:</p>
<p>{{message.contents}}</p>
</div>
</div>
{% endif %}
{% endif %}
{% endfor %}
<button class="deleteButton" type="submit">Delete selected messages</button>
</form>
This is my controller code:
public function deleteMessagesAction(Request $request) {
if (false === $this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw $this->createAccessDeniedException('Unable to access this page!');
}
$em = $this->getDoctrine()->getEntityManager();
$pm = $this->getDoctrine()
->getRepository('LoginLoginBundle:Privatemessage')
->findBypmid($request->get('sentValue'));
$deleteMessages = $request->get('deletePm');
var_dump($deleteMessages);
$user = $this->get('security.context')->getToken()->getUser();
$pmReceived = $this->getDoctrine()
->getRepository('LoginLoginBundle:Privatemessage')
->findByReceiver($user->getUsername());
$pmSent = $this->getDoctrine()
->getRepository('LoginLoginBundle:Privatemessage')
->findBySender($user->getUsername());
return $this->render('LoginLoginBundle:Default:pmPage.html.twig', array('pmReceived' => $pmReceived, 'pmSent' => $pmSent, 'toDelete'=>$deleteMessages));
}
Now I want to get the value of all the 'ticked' checkboxes, I supposed $deleteMessages would be an array of values, but it only holds the last checkbox that is ticked.
How can I get an array of all the values in my controller?
U need to alter the name of your inputs.
This way u will receive an array of checked checkboxes instead of only the last checked one:
<input type="checkbox" name="deletePm[]" value="{{message.pmid}}">
This will result in an array equal to :
[
0 => 1,
1 => 10,
2 => ...,
]
U then can just loop this array to delete accordingly :
$deleteMessages = $request->get('deletePm');
foreach($deleteMessages as $deleteMessageId) {
//Do something with the ID
}
I have a Entity Type form where I list all the friends of the current user. This is e.g. used for creating a new Group. Now I want to use the same form for adding people to the same group, so the Choice field should show all the friends of the current user that are not yet in the group. I thought I just use a form event to remove the options(users) that are already in the group.
My listener looks like this:
class FriendSelectListener implements EventSubscriberInterface {
public static function getSubscribedEvents() {
// Tells the dispatcher that we want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event) {
$betRound = $event->getData();
$form = $event->getForm();
$groupForm = $form->get('userGroup');
$usersForm = $groupForm->get('users');
foreach($betRound->getUserGroup()->getUsers() as $user){
if($usersForm->has($user->getId())){
$usersForm->remove($user->getId());
}
}
}
}
But I can't render it, because in my test I removed the user with the id 2 and then I get the following error message while rendering:
Key "2" in object (with ArrayAccess) of type
"Symfony\Component\Form\FormView" does not exist in
/var/lib/stickshift/1ed1210eec124c179c45aac4ad484afd/app-root/data/269891/src/Strego/UserBundle/Resources/views/Form/selectFriends.html.twig
at line 5
Update:
This might be related to my view:
{% for id, choice in choices %}
{% set user = choice.data %}
<label>
<div class="people-list people-choose unselected" style="width:270px;">
<img src="{{ user.profilePic | imagine_filter('profile_thumb') }}" class="img-polaroid" alt="{{ user.nickname }}">
{#<img src="{{ asset('bundles/stregoapp/img/profile-thumb-90x90.png') }}" alt="Profilbild" class="img-polaroid">#}
<p>{{ form_widget(form[id]) }} <span class="label">einladen</span></p>
<h3>{{ user.nickname }}</h3>
<h3><small>{{ user.firstName }} {{ user.lastName }}</small></h3>
</div>
</label>
{% endfor %}
For me it seems like I only removed the form element but not the choice.
I found the problem, it was indeed my view. As soon as I stopped iterating over the options but used the child elemts of my form element it was working:
{% for child in form %}
{% set user = choices[child.vars.value].data %}
<label>
<div class="people-list people-choose unselected" style="width:270px;">
<img src="{{ user.profilePic | imagine_filter('profile_thumb') }}" class="img-polaroid" alt="{{ user.nickname }}">
<p>{{ form_widget(child) }} <span class="label">einladen</span></p>
<h3>{{ user.nickname }}</h3>
<h3><small>{{ user.firstName }} {{ user.lastName }}</small></h3>
</div>
</label>
{% endfor %}