A django form containing sub forms not showing validation errors for sub forms - django-forms

In my page in django application it has multiple forms such that main form has sub forms .
When submitting I check for all the forms to be valid and if any of the form is invalid , it return the form_invalid function containing the main form .
But the problem is if any of the subform is invalid , it doesn't show any error and the fields are reset as well .
Here is the code when form is submit .
if player_form.is_valid() and stats_form.is_valid() and contacts_form.is_valid() and extra_stats_form.is_valid():
player = player_form.save(commit=False)
stats_form = PlayerStatsForm(request.POST, instance=player.stats)
stats = stats_form.save()
contacts_form = PlayerContactsForm(request.POST, instance=player.contacts)
contacts = contacts_form.save()
extra_stats_form = PlayerExtraStatsForm(request.POST, instance=player.extra_stats)
extra_stats = extra_stats_form.save()
return redirect(self.get_success_url())
else:
return self.form_invalid(player_form)`
The get_context_data function which adds sub_forms into the main player form .
context.update({
'object_title_plural': 'Players',
'list_headings': self.list_headings,
'form_panel_title': 'Add Player',
'save_button_text': 'Add Player',
'search_form': self.form_defaults(PlayerSearchForm()),
'sub_forms': (self.form_defaults(self.get_form(PlayerStatsForm)),
self.form_defaults(self.get_form(PlayerContactsForm)),
self.form_defaults(self.get_form(PlayerExtraStatsForm)))
})
Here is the template in which sub forms are rendered .
{% block display_subforms %}
{% if sub_forms %}
{% include "dashboard/includes/sub_form.html" %}
{% endif %}
{% endblock %}

Related

Free shipping countdown, need helping making it auto refresh

I recently added a minor feature to the slide out shopping cart on a Shopify site to show the customer how many more items they need to add to their cart to receive free shipping. It works just fine, however the issue is that if a customer updates their quantity from the cart, then the feature doesn't automatically update and show that they qualify for free shipping. The customer would have to refresh the page or go to another page to see the update.
How can I make this automatically update?
{% assign free_quantity = 2 %}
{% assign cart_total = cart.item_count %}
{% assign cart_qty_left = free_quantity | minus: cart_total %}
<p class="tt-cart__add-on-title tt-text-2">
{% if cart_qty_left > 0 %}
You are {{ cart_qty_left }} item away from free shipping!
{% else %}
You've got free shipping!
{% endif %}
</p>
The fastest and easiest solution is to make an AJAX request to the current page and replace the content.
For example:
fetch(window.location.href).then(r => r.text()).then(res => {
const html = new DOMParser().parseFromString(res, 'text/html');
const cartText = html.querySelector('.tt-cart__add-on-title').innerText;
document.querySelector('.tt-cart__add-on-title').innerText = cartText
})
Where we make a request to the current URL address with fetch(window.location.href), then we convert the response to text via .then(r => r.text()).
After that we create a DOMParser of the response text:
const html = new DOMParser().parseFromString(res, 'text/html');
We grab the text from the parsed html targeting the proper element:
const cartText = html.querySelector('.tt-cart__add-on-title').innerText;
And finally we replace the text that is present on the page with the text we got from the response with:
document.querySelector('.tt-cart__add-on-title').innerText = cartText
This should happen on a specific event when you add products to the cart or update them.

After browser back button load last search

I have own plugin in October CMS what have a onFilter() function whats return and displays partials with data. When user click on name it redirect him to a detail page. And when user click back button in browser I want to display his last search.
I tried something like Session::push('data', $data) in onFilter() method and Session::get('data') in onInit() but it didnt work. $data is a list of pubs.
Had anyone same problem?
Edit
public function onFilter()
{
$result =
Lounge::filterLounge($categories, $tags, $regions, $paidIds, $price_from, $search);
Session::put('nameFilter', $result);
return [
'#list' => $this->renderPartial('loungelist::list.htm', [
'list_data' => $result])
];
}
public function getNameFilter() {
$nameFilter = Session::get('nameFilter');
return $nameFilter;
}
In partial .htm
{% set list_data = __SELF__.getNameFilter() %}
{% for lounge in list_data %}
{{ lounge.name }}
{% endfor %}
As #mwilson mentions I would use window.history on the front end with the pushstate() function so that when each filter is changed you push state including query strings before firing to php to get the filtered content. I have done this before and works very well.
You should post more code for us to be more helpful. I am assuming you are using a component. Are you using ajax? Session will do the job.
In your component.php put onNameFilter() event you can push the data into the session with Session::put('nameFilter' $data);. I suggest using more specific labels for your events and keys so I chose 'nameFilter'.
You will want to use a method in your component.php to call the session.
public function getNameFilter() {
$nameFilter = Session::get('nameFilter');
return $nameFilter;
}
Now in your partial.htm you can set the name filter data and access it as long as it is in the session:
{% set nameFilterData = __SELF__.getNameFilter() %}
EDIT TO SHOW REFLECTED CODE
I don't understand how it works the first time. What does your CMS Page look like? How do you show the filter the "first time"?
Your CMS Page has {% component 'something' %} right? Then in your default.htm file you have {% partial __SELF__~'::list %}?
In your partial you will need to display the list_data. Does this show anything?
{% for list in list_data %}
{{ list_data.name }}
{% endfor %}

What template variable does django-crispy-forms use for formset forms?

I have the following in my template code:
{% crispy form.meters_formset form.meters_formset.form.helper %}
This renders a formset that is an instance variable of my form. This is how I handle forms with embedded formsets.
In the helper for the formset's form (form.meters_formset.form.helper), I have an HTML element in the layout in which I would like to access the instance attached to that formset form. How would I do this? Crispy forms must be doing a for loop to loop through the formset's forms, but what template variable is it using?
I worked around this problem by replacing
{% crispy form.meters_formset form.meters_formset.form.helper %}
with
{% for subform in form.meters_formset %}
{% crispy subform %}
{% endfor %}
and accessing the instance with {{ subform.instance }}.

When using inline formsets with form wizards, where does formset form validation go?

I am creating an example to learn more about using inline formsets with SessionWizard. Eventually, I want to integrate dynamic formsets in order to add and delete individual forms via the template before submitting. However, when data is absent from the second form, it fails to validate unlike like a regular ModelForm.
Is there a method within SessionWizard that needs to be overridden? Is it something that is inherently handled within Django?
Guidance and examples would be greatly appreciated.
models.py
class Parent(models.Model):
name = models.CharField(max_length=256)
def __unicode__(self):
return name
class Child(models.Model):
name = models.CharField(max_length=256)
parent = models.ForeignKey(Parent)
def __unicode__(self):
return name
urls.py
test_forms = [['parent', ParentForm],['child', ChildFormSet]]
urlpatterns = patterns('example.views',
url(r'^$', TestWizard.as_view(test_forms)),
)
forms.py
class ParentForm(ModelForm):
class Meta:
model = Parent
class ChildForm(ModelForm):
class Meta:
model = Child
exclude = ('parent',)
ChildFormSet = inlineformset_factory(Parent, Child, extra=1)
class TestWizard(SessionWizardView):
"""
This WizardView is used to create multi-page forms and handles all the
storage and validation stuff using sessions.
"""
#template_name = ''
# def get_template_names(self):
# """
# Returns a list of template names to be used for the request.
# Overridden TemplateResponseMixin for specifying template for step.
# """
# return 'survey/forms/wizard_form.html'
#
# def get_context_data(self, form, **kwargs):
# context = super(TestWizard, self).get_context_data(form=form, **kwargs)
# return context
#
# def get_form_initial(self, step):
# """
# Returns dict (list of key, values) for initial form data.
# Useful for populating form fields with data from prior form, with extra
# logic for dealing with formsets.
# """
# return self.initial_dict.get(step, {})
#
# def get_form(self, step=None, data=None, files=None):
# """
# Constructs the form for a given step - overridden to add extra arguments
# """
# form = super(TestWizard, self).get_form(step, data, files)
# return form
def done(self, form_list, **kwargs):
return render_to_response('survey/thanks.html', {
'form_data': [form.cleaned_data for form in form_list],
})
wizard-form.html
{% extends "base.html" %}
{% load i18n %}
{% block head %}
{{ wizard.form.media }}
{% endblock %}
{% block content %}
<p>DEFAULT WIZARD FORM Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
{% endif %}
<input type="submit" value="{% trans "submit" %}"/>
</form>
{% endblock %}

rebuild content of a Div tag in complete function of $.ajaxt

I have a 4 column table of products in template,each item in table has an anchor with onclick like this:
<div id="tablepro">
<table>
<tr>
{% for product in cat.products.all %}
{% if forloop.counter|divisibleby:4 %}
</tr>
<tr>
<td><center>delete</br><img style="width:200px;height:200px;" class="magnify" src="{{product.image.url}}" /></center></td>
{% else %}
<td><center>delete</br><img style="width:200px;height:200px;" class="magnify" src="{{product.image.url}}" /></center></td>
{% endif %}
{% endfor %}
</table>
</div>
in remove function I have :
function remove(id)
{
var URL='{% url CompanyHub.views.catDetails company.webSite,cat.id %}';
URL+='delpro/'+id+'/';
$.ajax({
url:URL,
type:'POST',
complete:function(){
var str='<table><tr>';
{% for product in cat.products.all %}
{% if forloop.counter|divisibleby:4 %}
str+='</tr><tr>';
str+='<td><center>delete</br><img style="width:200px;height:200px;" class="magnify" src="{{product.image.url}}" /></center></td>';
{% else %}
str+='<td><center>delete</br><img style="width:200px;height:200px;" class="magnify" src="{{product.image.url}}" /></center></td>';
{% endif %}
{% endfor %}
str+='</table>';
$('#tablepro').html(str);
},
error:function(){
alert('Error');
}
});
}
in views.py :
def deleteProduct(request,key,cat_id,pro_id):
try:
company=Company.objects.get(webSite__iexact=key)
except Company.DoesNotExist:
Http404
cat=Category.objects.get(pk=cat_id)
if pro_id:
try:
product=Product.objects.get(pk=pro_id)
product.delete()
except Product.DoesNotExist:
Http404
return render_to_response('CompanyHub/Company/%s/cat_details.html'%(company.theme),{'company':company,'cat':cat}, context_instance=RequestContext(request))
as U see I've returned cat object that now a product object has removed from its list,but I can't get my Div updated in template!that sounds like cat object has not been updated in template tag.
any suggestion is appreciated
Template are compiled on server side and the browser renders the HTML.
To update your div after ajax call, you'd need to update it using javascript code in complete method. Your server side view can return JSON, XML, HTML or any other data type and your complete method has to render that data. Here is an example of how your complete method should look like if your server side returns partial html (i.e. just the table):
complete:function(data) {
$('#tablepro').html(data);
},
Remember that templates are compiled on the server side and the resulting HTML is then passed to the client. This means that the template code you have within your complete function (in the ajax call) will always be the same - in other words, every time you delete an element (and remove is called), you are redisplaying all the original categories again, as the HTML generated within the for loop is created on the server side once - not asynchronously

Resources