The following code is from an multi-vendor ecommerce portal. We need to display different shipping methods according to the store(or vendor) on the checkout summary page.
However even though I get correct queryset while print i.e Store 1 has Test Rest of World Shipping method and Store 2 has UPC and DHL, the rendered form shows incorrect values -
#########################################################
class ShippingCountryChoiceField(forms.ModelChoiceField):
widget = forms.RadioSelect()
def label_from_instance(self, obj):
price_html = format_price(obj.price.gross, obj.price.currency)
label = mark_safe('%s %s' % (obj.shipping_method, price_html))
return label
class ShippingMethodForm(forms.Form):
def __init__(self, country_code, *args, **kwargs):
stores = kwargs.pop('stores')
super(ShippingMethodForm, self).__init__(*args, **kwargs)
for count, store in enumerate(stores, start=1):
method_field = ShippingCountryChoiceField(
queryset=ShippingMethodCountry.objects.select_related(
'shipping_method').order_by('price').filter(shipping_method__store=store),
label=pgettext_lazy('Shipping method form field label', 'Shipping method for %s' % store),
required=True)
if country_code:
queryset = method_field.queryset
method_field.queryset = queryset.unique_for_country_code(country_code)
if self.initial.get('method') is None:
method_field.initial = method_field.queryset.first()
method_field.empty_label = None
self.fields['method_%d' % count] = method_field
print [q.queryset for q in self.fields.values()]
###################################################
#load_checkout
#validate_voucher
#validate_cart
#validate_is_shipping_required
#validate_shipping_address
#add_voucher_form
def shipping_method_view(request, checkout):
country_code = checkout.shipping_address.country.code
stores = checkout.cart.lines.values_list('variant__product__store', flat=True)
stores = Store.objects.filter(id__in=stores)
print checkout.shipping_method
shipping_method_form = ShippingMethodForm(
country_code, request.POST or None, initial={'method': checkout.shipping_method},
stores=stores)
if shipping_method_form.is_valid():
for count, store in enumerate(stores):
checkout.shipping_method[store] = shipping_method_form.cleaned_data['method_%s' % count]
return redirect('checkout:summary')
print [q.queryset for q in shipping_method_form.fields.values()]
return TemplateResponse(request, 'checkout/shipping_method.html', context={
'shipping_method_form': shipping_method_form, 'checkout': checkout})
##############################################################
{% extends "checkout/details.html" %}
{% load i18n %}
{% load gross from prices_i18n %}
{% load bootstrap_form from bootstrap3 %}
{% block forms %}
<h3>{% trans "Shipping address" context "Checkout shipping address title" %}</h3>
{% include "userprofile/snippets/address-short.html" with address=checkout.shipping_address only %}
<p>{% trans "Select other address" %}</p>
<hr>
<form method="post" novalidate>
{% csrf_token %}
{% bootstrap_form shipping_method_form show_label=True %}
<p class="text-md-right">
<button type="submit" class="btn primary">
{% trans "Continue" context "Checkout shipping method primary action" %}
</button>
</p>
</form>
{% endblock %}
I believe the problem is that you are instantiating the widget in the field definition. This could cause state to be shared between different fields. Try changing it to:
class ShippingCountryChoiceField(forms.ModelChoiceField):
widget = forms.RadioSelect
...
Related
I have the following ModelForm, UpdateView and template but when I click on the 'Save' button and the 'Save and continue editing' button, nothing happens. I tried following the fix in this post but it didn't work. How do I post the data in these forms into their respective tables?
forms.py:
class ProductForm(SEOFormMixin, forms.ModelForm):
FIELD_FACTORIES = {
"text": _attr_text_field,
"richtext": _attr_textarea_field,
"integer": _attr_integer_field,
"boolean": _attr_boolean_field,
"float": _attr_float_field,
"date": _attr_date_field,
"datetime": _attr_datetime_field,
"option": _attr_option_field,
"multi_option": _attr_multi_option_field,
"entity": _attr_entity_field,
"numeric": _attr_numeric_field,
"file": _attr_file_field,
"image": _attr_image_field,
}
class Meta:
model = Product
fields = [
'title', 'upc', 'description', 'is_public', 'is_discountable', 'structure', 'slug', 'meta_title',
'meta_description']
widgets = {
'structure': forms.HiddenInput(),
'meta_description': forms.Textarea(attrs={'class': 'no-widget-init'})
}
def __init__(self, product_class, metal, data=None, parent=None, *args, **kwargs):
self.set_initial(product_class, metal, parent, kwargs)
super().__init__(data, *args, **kwargs)
if parent:
self.instance.parent = parent
# We need to set the correct product structures explicitly to pass
# attribute validation and child product validation. Note that
# those changes are not persisted.
self.instance.structure = Product.CHILD
self.instance.parent.structure = Product.PARENT
self.delete_non_child_fields()
else:
# Only set product class for non-child products
self.instance.product_class = product_class
self.instance.metal = metal
self.add_attribute_fields(product_class, metal, self.instance.is_parent)
if 'slug' in self.fields:
self.fields['slug'].required = False
self.fields['slug'].help_text = _('Leave blank to generate from product title')
if 'title' in self.fields:
self.fields['title'].widget = forms.TextInput(
attrs={'autocomplete': 'off'})
def set_initial(self, product_class, metal, parent, kwargs):
"""
Set initial data for the form. Sets the correct product structure
and fetches initial values for the dynamically constructed attribute
fields.
"""
if 'initial' not in kwargs:
kwargs['initial'] = {}
self.set_initial_attribute_values(product_class, metal, kwargs)
if parent:
kwargs['initial']['structure'] = Product.CHILD
def set_initial_attribute_values(self, product_class, metal, kwargs):
"""
Update the kwargs['initial'] value to have the initial values based on
the product instance's attributes
"""
instance = kwargs.get('instance')
if instance is None:
return
for attribute in product_class.attributes.all():
try:
value = instance.attribute_values.get(
attribute=attribute).value
except exceptions.ObjectDoesNotExist:
pass
else:
kwargs['initial']['attr_%s' % attribute.code] = value
for attribute in metal.product_attribute.all():
try:
value = instance.attribute_values.get(
attribute=attribute).value
except exceptions.ObjectDoesNotExist:
pass
else:
kwargs['initial']['attr_%s' % attribute.code] = value
def add_attribute_fields(self, product_class, metal, is_parent=False):
"""
For each attribute specified by the product class, this method
dynamically adds form fields to the product form.
"""
for attribute in product_class.attributes.all():
field = self.get_attribute_field(attribute)
if field:
self.fields['attr_%s' % attribute.code] = field
# Attributes are not required for a parent product
if is_parent:
self.fields['attr_%s' % attribute.code].required = False
for attribute in metal.product_attribute.all():
field = self.get_attribute_field(attribute)
if field:
self.fields['attr_%s' % attribute.code] = field
# Attributes are not required for a parent product
if is_parent:
self.fields['attr_%s' % attribute.code].required = False
def get_attribute_field(self, attribute):
"""
Gets the correct form field for a given attribute type.
"""
return self.FIELD_FACTORIES[attribute.type](attribute)
def delete_non_child_fields(self):
"""
Deletes any fields not needed for child products. Override this if
you want to e.g. keep the description field.
"""
for field_name in ['description', 'is_discountable']:
if field_name in self.fields:
del self.fields[field_name]
def _post_clean(self):
"""
Set attributes before ModelForm calls the product's clean method
(which it does in _post_clean), which in turn validates attributes.
"""
for attribute in self.instance.attr.get_all_attributes():
field_name = 'attr_%s' % attribute.code
# An empty text field won't show up in cleaned_data.
if field_name in self.cleaned_data:
value = self.cleaned_data[field_name]
setattr(self.instance.attr, attribute.code, value)
super()._post_clean()
views.py:
class ProductCreateUpdateView(PartnerProductFilterMixin, UpdateView):
template_name = 'oscar/dashboard/catalogue/product_update.html'
model = Product
context_object_name = 'product'
form_class = ProductForm
category_formset = ProductCategoryFormSet
image_formset = ProductImageFormSet
recommendations_formset = ProductRecommendationFormSet
stockrecord_formset = StockRecordFormSet
creating = False
parent = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.formsets = {'category_formset': self.category_formset,
'image_formset': self.image_formset,
'recommended_formset': self.recommendations_formset,
'stockrecord_formset': self.stockrecord_formset}
def dispatch(self, request, *args, **kwargs):
resp = super().dispatch(
request, *args, **kwargs)
return self.check_objects_or_redirect() or resp
def check_objects_or_redirect(self):
"""
Allows checking the objects fetched by get_object and redirect
if they don't satisfy our needs.
Is used to redirect when create a new variant and the specified
parent product can't actually be turned into a parent product.
"""
if self.creating and self.parent is not None:
is_valid, reason = self.parent.can_be_parent(give_reason=True)
if not is_valid:
messages.error(self.request, reason)
return redirect('dashboard:catalogue-product-list')
def get_queryset(self):
"""
Filter products that the user doesn't have permission to update
"""
return self.filter_queryset(Product.objects.all())
def get_object(self, queryset=None):
"""
This parts allows generic.UpdateView to handle creating products as
well. The only distinction between an UpdateView and a CreateView
is that self.object is None. We emulate this behavior.
This method is also responsible for setting self.product_class and
self.parent.
"""
self.creating = 'pk' not in self.kwargs
if self.creating:
# Specifying a parent product is only done when creating a child
# product.
parent_pk = self.kwargs.get('parent_pk')
if parent_pk is None:
self.parent = None
# A product class needs to be specified when creating a
# standalone product.
product_class_slug = self.kwargs.get('product_class_slug')
metal_slug = self.kwargs.get('metal_slug')
self.product_class = get_object_or_404(
ProductClass, slug=product_class_slug)
self.metal = get_object_or_404(
Metal, slug=metal_slug)
else:
self.parent = get_object_or_404(Product, pk=parent_pk)
self.product_class = self.parent.product_class
self.metal = self.parent.metal
return None # success
else:
product = super().get_object(queryset)
self.product_class = product.get_product_class()
self.parent = product.parent
self.metal = product.get_metal_type()
return product
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['product_class'] = self.product_class
ctx['metal'] = self.metal
ctx['parent'] = self.parent
ctx['title'] = self.get_page_title()
for ctx_name, formset_class in self.formsets.items():
if ctx_name not in ctx:
ctx[ctx_name] = formset_class(self.product_class,
self.request.user,
instance=self.object)
return ctx
def get_page_title(self):
if self.creating:
if self.parent is None:
return _('Create new %(product_class)s product') % {
'product_class': self.product_class.name}
else:
return _('Create new variant of %(parent_product)s') % {
'parent_product': self.parent.title}
else:
if self.object.title or not self.parent:
return self.object.title
else:
return _('Editing variant of %(parent_product)s') % {
'parent_product': self.parent.title}
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['product_class'] = self.product_class
kwargs['parent'] = self.parent
kwargs['metal'] = self.metal
return kwargs
def process_all_forms(self, form):
"""
Short-circuits the regular logic to have one place to have our
logic to check all forms
"""
# Need to create the product here because the inline forms need it
# can't use commit=False because ProductForm does not support it
if self.creating and form.is_valid():
self.object = form.save()
formsets = {}
for ctx_name, formset_class in self.formsets.items():
formsets[ctx_name] = formset_class(self.product_class,
self.request.user,
self.request.POST,
self.request.FILES,
instance=self.object)
is_valid = form.is_valid() and all([formset.is_valid()
for formset in formsets.values()])
cross_form_validation_result = self.clean(form, formsets)
if is_valid and cross_form_validation_result:
return self.forms_valid(form, formsets)
else:
return self.forms_invalid(form, formsets)
# form_valid and form_invalid are called depending on the validation result
# of just the product form and redisplay the form respectively return a
# redirect to the success URL. In both cases we need to check our formsets
# as well, so both methods do the same. process_all_forms then calls
# forms_valid or forms_invalid respectively, which do the redisplay or
# redirect.
form_valid = form_invalid = process_all_forms
def clean(self, form, formsets):
"""
Perform any cross-form/formset validation. If there are errors, attach
errors to a form or a form field so that they are displayed to the user
and return False. If everything is valid, return True. This method will
be called regardless of whether the individual forms are valid.
"""
return True
def forms_valid(self, form, formsets):
"""
Save all changes and display a success url.
When creating the first child product, this method also sets the new
parent's structure accordingly.
"""
if self.creating:
self.handle_adding_child(self.parent)
else:
# a just created product was already saved in process_all_forms()
self.object = form.save()
# Save formsets
for formset in formsets.values():
formset.save()
for idx, image in enumerate(self.object.images.all()):
image.display_order = idx
image.save()
return HttpResponseRedirect(self.get_success_url())
def handle_adding_child(self, parent):
"""
When creating the first child product, the parent product needs
to be implicitly converted from a standalone product to a
parent product.
"""
# ProductForm eagerly sets the future parent's structure to PARENT to
# pass validation, but it's not persisted in the database. We ensure
# it's persisted by calling save()
if parent is not None:
parent.structure = Product.PARENT
parent.save()
def forms_invalid(self, form, formsets):
# delete the temporary product again
if self.creating and self.object and self.object.pk is not None:
self.object.delete()
self.object = None
messages.error(self.request,
_("Your submitted data was not valid - please "
"correct the errors below"))
ctx = self.get_context_data(form=form, **formsets)
return self.render_to_response(ctx)
def get_url_with_querystring(self, url):
url_parts = [url]
if self.request.GET.urlencode():
url_parts += [self.request.GET.urlencode()]
return "?".join(url_parts)
def get_success_url(self):
"""
Renders a success message and redirects depending on the button:
- Standard case is pressing "Save"; redirects to the product list
- When "Save and continue" is pressed, we stay on the same page
- When "Create (another) child product" is pressed, it redirects
to a new product creation page
"""
msg = render_to_string(
'oscar/dashboard/catalogue/messages/product_saved.html',
{
'product': self.object,
'creating': self.creating,
'request': self.request
})
messages.success(self.request, msg, extra_tags="safe noicon")
action = self.request.POST.get('action')
if action == 'continue':
url = reverse(
'dashboard:catalogue-product', kwargs={"pk": self.object.id})
elif action == 'create-another-child' and self.parent:
url = reverse(
'dashboard:catalogue-product-create-child',
kwargs={'parent_pk': self.parent.pk})
elif action == 'create-child':
url = reverse(
'dashboard:catalogue-product-create-child',
kwargs={'parent_pk': self.object.pk})
else:
url = reverse('dashboard:catalogue-product-list')
return self.get_url_with_querystring(url)
urls.py:
urlpatterns += [
path(
'products/create/',
self.myapps_catalogue_views.ProductCreateRedirectView.as_view(),
name='catalogue-product-create'
),
path(
'products/create/<slug:product_class_slug>/<slug:metal_slug>/<slug:gemstone_slug>',
self.myapps_catalogue_views.ProductCreateUpdateView.as_view(),
name='catalogue-product-create'
),
]
template (selected parts):
<!--templates/oscar/dashboard/catalogue/product_update.html-->
{% extends 'oscar/dashboard/layout.html' %}
{% load form_tags %}
{% load i18n %}
{% block body_class %}{{ block.super }} create-page catalogue{% endblock %}
{% block title %}
{{ title }} | {% trans "Products" %} | {{ block.super }}
{% endblock %}
{% block breadcrumbs %}
#omitted due to space constraints
{% endblock %}
{% block headertext %}{{ title }}{% endblock %}
{% block dashboard_content %}
<form action="{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" method="post" class="form-stacked wysiwyg fixed-actions" enctype="multipart/form-data" data-behaviour="tab-nav-errors" autocomplete="off">
{% csrf_token %}
{% if parent %}
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">
{% url 'dashboard:catalogue-product' pk=parent.id as parent_url %}
{% blocktrans with title=parent.title %}
You are currently editing a product variant of
{{ title }}.
{% endblocktrans %}
</div>
</div>
</div>
{% endif %}
<div class="row">
{% block tab_nav %}
#omitted due to space constraints
{% endblock tab_nav %}
<div class="col-md-9">
<div class="tab-content">
{% block tab_content %}
{% block product_details %}
<div class="tab-pane active" id="product_details">
<div class="table-header">
<h3>{% trans "Product details" %}</h3>
</div>
<div class="card card-body product-details">
{% block product_details_content %}
<span class="error-block">{{ form.non_field_errors }}</span>
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
{% for field in form.primary_form_fields %}
{% if 'attr' not in field.id_for_label %}
{% include 'oscar/dashboard/partials/form_field.html' with field=field %}
{% endif %}
{% endfor %}
{% endblock product_details_content %}
</div>
</div>
{% endblock product_details %}
{% block product_categories %}
#omitted due to space constraints
{% endblock product_categories %}
{% block product_attributes %}
#omitted due to space constraints
{% endblock product_attributes %}
{% block product_images %}
#omitted due to space constraints
{% endblock product_images %}
{% block stockrecords %}
#omitted due to space constraints
{% endblock stockrecords %}
{% block child_products %}
#omitted due to space constraints
{% endblock child_products %}
{% block recommended_products %}
#omitted due to space constraints
{% endblock recommended_products %}
{% block seo %}
#omitted due to space constraints
{% endblock seo %}
{% block metal_attributes %}
<div class="tab-pane" id="metal_attributes">
{% block metal_attributes_content %}
<table class="table table-striped table-bordered">
<caption>
{% trans "Attributes" %}
<span class="badge badge-success">
{% trans "Metal Type:" %} {{ metal }}
</span>
</caption>
{% for field in form %}
{% if 'attr' in field.id_for_label %}
<tr>
<td>
{% include "oscar/dashboard/partials/form_field.html" %}
</td>
</tr>
{% endif %}
{% endfor %}
</table>
{% endblock metal_attributes_content %}
</div>
{% endblock metal_attributes %}
{% endblock tab_content %}
</div>
</div>
</div>
{% block fixed_actions_group %}
<div class="fixed-actions-group">
<div class="form-group">
<div class="float-right">
<a href="{% url 'dashboard:catalogue-product-list' %}">
{% trans "Cancel" %}
</a>
{% trans "or" %}
{% if parent %}
<button class="btn btn-secondary" name="action" type="submit" value="create-another-child" data-loading-text="{% trans 'Saving...' %}">
{% trans "Save and add another variant" %}
</button>
{% endif %}
<button class="btn btn-secondary" name="action" type="submit" value="continue" data-loading-text="{% trans 'Saving...' %}">
{% trans "Save and continue editing" %}
</button>
<button class="btn btn-primary" name="action" type="submit" value="save" data-loading-text="{% trans 'Saving...' %}">
{% trans "Save" %}
</button>
</div>
{% if product %}
<a class="btn btn-success" href="{{ product.get_absolute_url }}">{% trans "View on site" %}</a>
{% endif %}
</div>
</div>
{% endblock fixed_actions_group %}
</form>
{% endblock dashboard_content %}
Previews of the Template:
Just a suggestion: Take a look at the urls.py file, and change the paths order. Remember that Django evaluate them in order and take the first expression that match the pattern.
Also noticed that you're using request.GET.urlencode() in form action, but that will return only the URL GET params. Try to add the correct page URL there instead.
I am trying to add and subtract 1 from the value of a Django model's IntegerField and display it on the webpage, depending on when a button is clicked.
models.py
class User(AbstractUser):
following = models.ManyToManyField("self", related_name="followers", symmetrical=False)
following_num = models.IntegerField(default=0)
In views.py:
To add: following_num = page_visitor.following_num +1
To subtract: following_num = page_visitor.following_num -1
This is displaying in the html where the number should be displaying:
<django.db.models.query_utils.DeferredAttribute object at 0x0000022868D735E0>
entire view:
def username(request, user):
#get user
user = get_object_or_404(User.objects, username=user)
posts = Post.objects.filter(user=user).order_by('-date_and_time')
page_visitor = get_object_or_404(User.objects, username=request.user)
if user == request.user:
followButton = False
else:
followButton = True
if request.method == "POST":
if "follow" in request.POST:
request.user.following.add(user)
following_num = page_visitor.following_num +1
#following_num = F('User.following_num') + 1
elif "unfollow" in request.POST:
request.user.following.remove(user)
following_num = page_visitor.following_num -1
#following_num = F('User.following_num') - 1
followers_num = page_visitor.following_num
following_num = User.following_num
return render(request, "network/username.html",{
"user": user,
"posts": posts,
"followButton": followButton,
"followers": followers_num,
"following": following_num
})
html
{% block body %}
<h1>{{ user }}</h1>
<h4>Followers:{{ followers }}</h4>
<h4>Following:{{ following }}</h4>
<!--follow/unfollow button-->
{% if followButton == True %}
<form action = "{% url 'username' user %}" method = "POST">
{% csrf_token %}
{% if user not in request.user.following.all %}
<input type="submit" value="Follow" name="follow">
{% else %}
<input type="submit" value="Unfollow" name="unfollow">
{% endif %}
</form>
{% endif %}
<!--displays all the posts-->
{% for post in posts %}
<div class = "individual_posts">
<h5 class = "post_user">{{ post.user }}</h5>
<h6 id = "post_itself">{{ post.post }}</h6>
<h6 class = "post_elements">{{ post.date_and_time }}</h6>
<h6 class = "post_elements">{{ post.likes }} Likes</h6>
</div>
{% endfor %}
{% endblock %}
you reference the class (model) and not the object (instance)
following_num = User.following_num
as you alredy passed your user object to the template you can also access the attributes directly
{{user.following_num }} {{request.user.following_num }}
but you better rename the passed user variable to avoid confusion / errors
Get user information in django templates
Without the html that is supposed to display the number I cant tell why it is displaying that, but if you intend to change the following count then you need to call page_visitor.save() after changing a property of page_visitor in order for it to save the new property's value in the database
I'm using a SessionWizardView and I can't understand why the done()method is never called. Instead, after posting my form, in the last step, I can see a POST HTTP 200 on my server, but this does nothing.
The get_form() method works as expected.
I suspect a distraction error since I have the exact same logic for another view, and this works well.
Here is the whole code bellow.
The view
class DiscountsCreateView(PermissionRequiredCanHandleProducts,
ModelInContextMixin,
RestaurantMixin, SubSectionDiscounts,
SessionWizardView):
""" Wizard view to create a discount in 2 steps """
model = Discount # used for model context
form_list = [DiscountForm0, DiscountForm1]
template_name = "discounts/discount_add.html"
def get_form(self, step=None, data=None, files=None):
form = super().get_form(step, data, files)
if step is None:
step = self.steps.current
# step0 - name, kind, tax_rate
# => nothing special to do, always the same form
# step1 - specific fields related to the chosen kind
if step == '1':
step0_data = self.storage.get_step_data('0')
kind = step0_data['0-kind']
# combo => combo, combo_unit_price
if kind == Discount.COMBO:
form.fields['combo'].queryset = Combo.objects.restaurant(self.restaurant)
# NOTE : this is not a scalable way to show/hide fields (exponential)
form.fields['rebate_amount'].widget = forms.HiddenInput()
elif kind == Discount.REBATE:
form.fields['combo'].widget = forms.HiddenInput()
form.fields['combo_unit_price'].widget = forms.HiddenInput()
return form
def done(self, form_list, **kwargs):
data = [form.cleaned_data for form in form_list]
try:
Discount.objects.create(
name=data[0]['name'],
kind=data[0]['kind'],
tax_rate=data[0]['tax_rate'],
rebate_amount=data[1]['rebate_amount'],
combo=data[1]['combo'],
combo_unit_price=data[1]['combo_unit_price']
)
except Exception as e:
messages.add_message(self.request, messages.ERROR, MSG_DISCOUNT_ADD_KO.format(e))
else:
messages.add_message(self.request, messages.SUCCESS, MSG_DISCOUNT_ADD_OK)
return redirect(reverse('bo:discount-list'))
The forms
class DiscountForm0(forms.Form):
name = forms.CharField(
label=verbose_display(Discount, 'name'))
kind = forms.ChoiceField(
label=verbose_display(Discount, 'kind'),
choices=Discount.KIND_CHOICES)
tax_rate = forms.ModelChoiceField(
label=verbose_display(Discount, 'tax_rate'),
queryset=TaxRate.objects.all())
class DiscountForm1(forms.Form):
"""
Contains all the specific fields for all discount kinds.
The goal is to only show the fields related to the right discount kind
"""
# For REBATE kind only
rebate_amount = forms.DecimalField(
label=verbose_display(Discount, 'rebate_amount'),
validators=[MaxValueValidator(0)])
# For COMBO kind only
combo = forms.ModelChoiceField(
label=verbose_display(Discount, 'combo'),
queryset=Combo.objects.none())
combo_unit_price = forms.DecimalField(
label=verbose_display(Discount, 'combo_unit_price'),
validators=[MinValueValidator(0)])
The templates
add_discount.html
{% extends "base_dashboard.html" %}
{% load verbose_name %}
{% block dashboard_title %}
Créer une {% model_name model %} : étape {{ wizard.steps.step1 }} / {{ wizard.steps.count }}
{% endblock dashboard_title %}
{% block dashboard_content %}
<form action='' method='post' novalidate>
{% csrf_token %}
{% include 'includes/_wizard_form_horizontal.html' with wizard=wizard %}
</form>
{% endblock dashboard_content %}
_wizard_form_horizontal.html
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{% include 'includes/_form_horizontal.html' with form=form %}
{% endfor %}
{% else %}
{% include 'includes/_form_horizontal.html' with form=wizard.form %}
{% endif %}
{% if wizard.steps.prev %}
<button class="btn btn-primary" name="wizard_goto_step" type="submit"
value="{{ wizard.steps.prev }}">
« étape précédente
</button>
{% endif %}
<input type="submit" class="btn btn-primary" value="étape suivante »"/>
The done() method is always called if the form submitted in the last step is_valid(). So if it's not, it must mean your form isn't valid.
In your case, you're hiding fields that are required by your DiscountForm1. So you're also hiding the error for these fields. You should make them optional and check in the form's clean() method if the appropriate fields are filled.
I want to use django-import-export with class based views.
In the docs at https://django-import-export.readthedocs.org/en/latest/getting_started.html I see an example of exporting as csv
>>> dataset = BookResource().export()
>>> print dataset.csv
id,name,author,author_email,imported,published,price,categories
2,Some book,1,,0,2012-12-05,8.85,1
but which class based view should I use if I want to return an Excel file? Just View?
Jamgreen,
Based on implementation at https://github.com/bmihelac/django-import-export and considering a model "Country" as example with name and abreviation atributes:
First, define at the end of Country models file the Resource:
class CountryResource(resources.ModelResource):
class Meta:
model = Country
Then, implement the class based views:
class CountryExport(View):
def get(self, *args, **kwargs ):
dataset = CountryResource().export()
response = HttpResponse(dataset.csv, content_type="csv")
response['Content-Disposition'] = 'attachment; filename=filename.csv'
return response
class CountryImport(View):
model = Country
from_encoding = "utf-8"
#: import / export formats
DEFAULT_FORMATS = (
base_formats.CSV,
base_formats.XLS,
base_formats.TSV,
base_formats.ODS,
base_formats.JSON,
base_formats.YAML,
base_formats.HTML,
)
formats = DEFAULT_FORMATS
#: template for import view
import_template_name = 'Country/import.html'
resource_class = None
def get_import_formats(self):
"""
Returns available import formats.
"""
return [f for f in self.formats if f().can_import()]
def get_resource_class(self):
if not self.resource_class:
return modelresource_factory(self.model)
else:
return self.resource_class
def get_import_resource_class(self):
"""
Returns ResourceClass to use for import.
"""
return self.get_resource_class()
def get(self, *args, **kwargs ):
'''
Perform a dry_run of the import to make sure the import will not
result in errors. If there where no error, save the user
uploaded file to a local temp file that will be used by
'process_import' for the actual import.
'''
resource = self.get_import_resource_class()()
context = {}
import_formats = self.get_import_formats()
form = ImportForm(import_formats,
self.request.POST or None,
self.request.FILES or None)
if self.request.POST and form.is_valid():
input_format = import_formats[
int(form.cleaned_data['input_format'])
]()
import_file = form.cleaned_data['import_file']
# first always write the uploaded file to disk as it may be a
# memory file or else based on settings upload handlers
with tempfile.NamedTemporaryFile(delete=False) as uploaded_file:
for chunk in import_file.chunks():
uploaded_file.write(chunk)
# then read the file, using the proper format-specific mode
with open(uploaded_file.name,
input_format.get_read_mode()) as uploaded_import_file:
# warning, big files may exceed memory
data = uploaded_import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)
result = resource.import_data(dataset, dry_run=True,
raise_errors=False)
context['result'] = result
if not result.has_errors():
context['confirm_form'] = ConfirmImportForm(initial={
'import_file_name': os.path.basename(uploaded_file.name),
'input_format': form.cleaned_data['input_format'],
})
context['form'] = form
context['opts'] = self.model._meta
context['fields'] = [f.column_name for f in resource.get_fields()]
return TemplateResponse(self.request, [self.import_template_name], context)
def post(self, *args, **kwargs ):
'''
Perform a dry_run of the import to make sure the import will not
result in errors. If there where no error, save the user
uploaded file to a local temp file that will be used by
'process_import' for the actual import.
'''
resource = self.get_import_resource_class()()
context = {}
import_formats = self.get_import_formats()
form = ImportForm(import_formats,
self.request.POST or None,
self.request.FILES or None)
if self.request.POST and form.is_valid():
input_format = import_formats[
int(form.cleaned_data['input_format'])
]()
import_file = form.cleaned_data['import_file']
# first always write the uploaded file to disk as it may be a
# memory file or else based on settings upload handlers
with tempfile.NamedTemporaryFile(delete=False) as uploaded_file:
for chunk in import_file.chunks():
uploaded_file.write(chunk)
# then read the file, using the proper format-specific mode
with open(uploaded_file.name,
input_format.get_read_mode()) as uploaded_import_file:
# warning, big files may exceed memory
data = uploaded_import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)
result = resource.import_data(dataset, dry_run=True,
raise_errors=False)
context['result'] = result
if not result.has_errors():
context['confirm_form'] = ConfirmImportForm(initial={
'import_file_name': os.path.basename(uploaded_file.name),
'input_format': form.cleaned_data['input_format'],
})
context['form'] = form
context['opts'] = self.model._meta
context['fields'] = [f.column_name for f in resource.get_fields()]
return TemplateResponse(self.request, [self.import_template_name], context)
class CountryProcessImport(View):
model = Country
from_encoding = "utf-8"
#: import / export formats
DEFAULT_FORMATS = (
base_formats.CSV,
base_formats.XLS,
base_formats.TSV,
base_formats.ODS,
base_formats.JSON,
base_formats.YAML,
base_formats.HTML,
)
formats = DEFAULT_FORMATS
#: template for import view
import_template_name = 'Country/import.html'
resource_class = None
def get_import_formats(self):
"""
Returns available import formats.
"""
return [f for f in self.formats if f().can_import()]
def get_resource_class(self):
if not self.resource_class:
return modelresource_factory(self.model)
else:
return self.resource_class
def get_import_resource_class(self):
"""
Returns ResourceClass to use for import.
"""
return self.get_resource_class()
def post(self, *args, **kwargs ):
'''
Perform the actual import action (after the user has confirmed he
wishes to import)
'''
opts = self.model._meta
resource = self.get_import_resource_class()()
confirm_form = ConfirmImportForm(self.request.POST)
if confirm_form.is_valid():
import_formats = self.get_import_formats()
input_format = import_formats[
int(confirm_form.cleaned_data['input_format'])
]()
import_file_name = os.path.join(
tempfile.gettempdir(),
confirm_form.cleaned_data['import_file_name']
)
import_file = open(import_file_name, input_format.get_read_mode())
data = import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)
result = resource.import_data(dataset, dry_run=False,
raise_errors=True)
# Add imported objects to LogEntry
ADDITION = 1
CHANGE = 2
DELETION = 3
logentry_map = {
RowResult.IMPORT_TYPE_NEW: ADDITION,
RowResult.IMPORT_TYPE_UPDATE: CHANGE,
RowResult.IMPORT_TYPE_DELETE: DELETION,
}
content_type_id=ContentType.objects.get_for_model(self.model).pk
'''
for row in result:
LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=content_type_id,
object_id=row.object_id,
object_repr=row.object_repr,
action_flag=logentry_map[row.import_type],
change_message="%s through import_export" % row.import_type,
)
'''
success_message = _('Import finished')
messages.success(self.request, success_message)
import_file.close()
url = reverse('%s_list' % (str(opts.app_label).lower()))
return HttpResponseRedirect(url)
the template import.html has the following code:
<h1>{% trans "Importar" %} {{ opts.app_label }}</h1>
{% if confirm_form %}
<form action="{% url "process_import" %}" method="POST">
{% csrf_token %}
{{ confirm_form.as_p }}
<p>
{% trans "Below is a preview of data to be imported. If you are satisfied with the results, click 'Confirm import'" %}
</p>
<div class="submit-row">
<input type="submit" class="btn" name="confirm" value="{% trans "Confirm import" %}">
</div>
</form>
{% else %}
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form" enctype="multipart/form-data">
{% csrf_token %}
<p>
{% trans "This importer will import the following fields: " %}
{% for f in fields %}
{% if forloop.counter0 %}
,
{% endif %}
<tt>{{ f }}</tt>
{% endfor %}
</p>
<fieldset class="module aligned">
{% for field in form %}
<div class="form-row">
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
{% if field.field.help_text %}
<p class="help">{{ field.field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</fieldset>
<div class="submit-row">
<input type="submit" class="btn" value="{% trans "Submit" %}">
</div>
</form>
{% endif %}
{% if result %}
{% if result.has_errors %}
<h2>{% trans "Errors" %}</h2>
<ul>
{% for error in result.base_errors %}
<li>{{ error.error }}</li>
{% endfor %}
{% for line, errors in result.row_errors %}
{% for error in errors %}
<li>
{% trans "Line number" %}: {{ line }} - {{ error.error }}
<div class="traceback">{{ error.traceback|linebreaks }}</div>
</li>
{% endfor %}
{% endfor %}
</ul>
{% else %}
<h2>
{% trans "Preview" %}
</h2>
<table>
<thead>
<tr>
<th></th>
{% for field in fields %}
<th>{{ field }}</th>
{% endfor %}
</tr>
</thead>
{% for row in result.rows %}
<tr>
<td>
{% if row.import_type == 'new' %}
{% trans "New" %}
{% elif row.import_type == 'skip' %}
{% trans "Skipped" %}
{% elif row.import_type == 'delete' %}
{% trans "Delete" %}
{% elif row.import_type == 'update' %}
{% trans "Update" %}
{% endif %}
</td>
{% for field in row.diff %}
<td>
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endif %}
{% endif %}
and the urls.py should contain:
#export
url(r'export/$', login_required(CountryExport.as_view()), name='country_export'),
#import
url(r'import/$', login_required(CountryImport.as_view()), name='country_import'),
url(r'process_import/$', login_required(CountryProcessImport.as_view()), name='process_import'),
I've done the exact same thing today. I tried to make the view fairly generic and allow values to be passed through in the URL so it is a little more complex than simply returning an XLS file.
But anyway, this was my approach;
class PlaceResource(resources.ModelResource):
class Meta:
model = Place
class ExportPlacesView(View):
model = Place
def get(self, request, *args, **kwargs):
client_name = str(self.client).replace(' ', '_').lower()
if 'query' in kwargs:
if kwargs['query'] == u'complete':
complete_runners = Place.objects.filter(
~models.Q(runner__completed_on=None),
console=self.console).values_list(
'client',
flat=True).distinct()
dataset = PlaceResource().export(
Place.objects.filter(
console=self.console, client=self.client,
runner__in=complete_runners
)
)
filename = '{}_completed_runners'.format(client_name)
else:
dataset = PlaceResource().export(Place.objects.filter(
console=self.console, client=self.client,
))
filename = '{}_Runners'.format(client_name)
export_type = kwargs['format']
_dataset_methods = {
'csv': dataset.csv,
'xls': dataset.xls
}
response = HttpResponse(
_dataset_methods[export_type], content_type=export_type
)
response[
'Content-Disposition'] = 'attachment; filename={filename}.{ext}'.format(
filename=filename,
ext=export_type
)
return response
First of all, I am generically creating my form from the database. Here are my codes:
template:
{% block wizard_form_content %}
<div id="alt-list">
<div id="alt-list-header">
<h4>Grids List</h4>
</div>
<div id="alt-list-data" class="container">
{% for grid in data.grids %}
<input type="checkbox" name="{{ grid.name }}" id="id_{{ grid.name }}" tabindex="{{ forloop.counter}}" size="30">{{ grid.name }}<br>
{% endfor %}
</div>
</div>
{% if wizard.form.errors %}
<div class="form-errors-wrapper">
<div class="error">
{% for error in wizard.form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
</div>
{% endif %}
<input type="hidden" name="num-grids" value="{{ data.grids|length }}" id="num-grids" />
<input type="hidden" name="user" value="{{ data.user }}" id="user" />
{% endblock wizard_form_content %}
And this is the corresponding form:
class WhichGridsForm(forms.Form):
# Override the initialize in order to dynamically add fields to the form in order to be saved,
# the fields are saved only when the user selects 'Next Step'.
def __init__(self, *args, **kwargs):
super(WhichGridsForm, self).__init__(*args, **kwargs)
if len(self.data) > 0:
self.num_grids = self.data['num-grids']
user_name = self.data['user']
user1 = User.objects.filter(username=user_name)
gridtype = Grid.GridType.USER_GRID
templateData = ShowGridsData()
templateData.grids = Grid.objects.filter(user=user1, grid_type=gridtype)
for grid in templateData.grids:
gridName = grid.name
# Every time, alternative fields are added with the name 'alternative..', and this because django
# always adds '1-' % (where 1 the number of the step with zero index) prefix in the name,
# with this the names are kept always the same.
self.fields[gridName] = forms.BooleanField(required=False)
Keeping in the mind that this is step 2, when I am trying to reach this step 2 data from step 3 with this line of code:
elif self.steps.step1 == 3:
try:
grids_data = self.get_cleaned_data_for_step('1')
print grids_data
All of the fields seems "False" even though I checked them all.
{u'Cars': False, u'grid11': False, u'deneme11': False, u'asd': False}
Do you have any idea why this would happen?
Edit:
But If I print the form fields in the 'done' method, I get the correct results:
<MultiValueDict: {u'num-grids': [u'4'], u'deneme11': [u'on'], u'Cars': [u'on'], u'composite_wizard-current_step': [u'1'], u'grid11': [u'on'], u'user': [u'muratayan'], u'asd': [u'on'], u'csrfmiddlewaretoken': [u'JYIT5gHs35ZBvk7rCITfpMIPrFleUYXF']}>
Sorry I gave you the wrong field class. Instead MultipleChoiceField it should be ModelMultipleChoiceField, since you select from models.
Something like this works in my case:
forms.py (form for first step)
class FirstStepForm(forms.Form):
def __init__(self, *args, **kwargs):
super(FirstStepForm, self).__init__(*args, **kwargs)
self.fields['countries'] = forms.ModelMultipleChoiceField(queryset=Country.objects.all())
views.py
class MyWizard(SessionWizardView):
def render(self, form=None, **kwargs):
response = super(MyWizard, self).render(form, **kwargs)
grids_data = self.get_cleaned_data_for_step('0') or {}
print grids_data
return response