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 having a problem with my form, and it is that when trying to persist my data in the database this is not done correctly, because in my form the developer field is not being validated in the correct way because it does not show an error to the client, but the browser console shows me the following error, an invalid form control with name = 'developer' cannot be focused.
This is my form:
class ProjectForm(ModelForm):
developer = forms.ModelMultipleChoiceField(
queryset=User.objects.filter(groups__name='desarrolladores'),
label='Desarrolladores',
)
class Meta:
model = Project
fields = (
'developer', 'name', 'status'
)
labels = {
'name': 'Nombre de Proyecto',
'status': 'Estado',
}
def clean_developer(self):
developer = self.cleaned_data.get('developer')
if developer is None:
raise forms.ValidationError('El campo es obligatorio')
return developer
This is my view:
class ProjectCreateView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView):
login_url = 'users:login'
template_name = 'projects/add_project.html'
form_class = ProjectForm
permission_required = 'projects.add_project'
success_message = 'Proyecto creado exitosamente'
success_url = reverse_lazy('projects:project')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['formset'] = ProjectFormSet(self.request.POST)
else:
data['formset'] = ProjectFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
formset = context['formset']
with transaction.atomic():
self.object = form.save()
if formset.is_valid():
formset.instance = self.object
formset.save()
return super().form_valid(form)
This is my template:
<form id="form-container" method="post">
{% csrf_token %}
{{ form.as_p }}
<h2>Tareas</h2>
<table>
{{ formset.management_form }}
{% for form in formset %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr id="projects_data">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<button type="submit" class="button success"><span class="mif-add icon"></span> {{ message }}</button>
<span class="mif-cancel icon"></span> Cancelar
</form>
This is error
Can you help me out with this. I hava a model form and I need to raise an error after validate two datetime objects in the clean method of the model form. This is what I have.
Forms
class HorariosDisponibles(forms.ModelForm):
tutor = forms.ModelChoiceField(queryset=Tutor.objects.all(),widget=forms.Select(attrs= {'class': 'input is-small is-rounded ' }),label='TUTOR',)
dia_hor_inicio =forms.DateTimeField(widget=forms.DateTimeInput(attrs= {'class': 'input is-small is-rounded ',}),label='Horario de Inicio', initial=datetime.date.today )
dia_hor_fin= forms.DateTimeField(widget=forms.DateTimeInput(attrs= {'class': 'input is-small is-rounded ' }),label='Horario de FinalizaciĆ³n', initial=datetime.date.today)
class Meta:
model = horarios_disp
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["dia_hor_inicio"].widget = DateTimeInput()
self.fields["dia_hor_inicio"].input_formats = ["%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"]
self.fields["dia_hor_fin"].widget = DateTimeInput()
self.fields["dia_hor_fin"].input_formats = ["%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"]
def clean(self):
cleaned_data = super(HorariosDisponibles, self).clean()
tutor = cleaned_data.get("tutor")
dia_hor_inicio = cleaned_data.get("dia_hor_inicio")
dia_hor_fin = cleaned_data.get("dia_hor_fin")
if dia_hor_inicio and dia_hor_fin:
if dia_hor_inicio.day != dia_hor_fin.day :
msg = 'Las fechas no pueden ser distintas'
self.add_error("dia_hor_inicio", msg)
raise forms.ValidationError("Las fechas no pueden ser distintas")
#NEITHER OF THIS APPROACHES WORKED
return cleaned_data
VIEWS
#login_required
def horario_tutor(request):
context = {
}
if request.method == 'POST':
print(request.POST)
form = HorariosDisponibles(request.POST)
if form.is_valid():
tutor = form.cleaned_data['tutor']
print("adentro")
dia_hor_inicio = form.cleaned_data['dia_hor_inicio']
dia_hor_fin = form.cleaned_data['dia_hor_fin']
tutor_horario = horarios_disp(
tutor=tutor, dia_hor_inicio=dia_hor_inicio, dia_hor_fin=dia_hor_fin)
tutor_horario.save()
context = {
'form': form
}
return redirect("home")
return render(request,"horarios_disponibles.html", context)
else:
form = HorariosDisponibles()
context['form'] = form
return render(request, "horarios_disponibles.html", context)
TEMPLATES
{% extends 'base.html' %}
{% block body %}
<section class="section">
<div class="columns is-vcentered">
<div class="column is-centered is-4 is-offset-2">
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="field">
{% for error in field.errors %}
<p class="help is-danger">{{ error }}</p>
{% endfor %}
<label for="{{field.id_for_label}}" class="label">{{ field.label }}</label>
{{ field }}
{% for non_field_error in form.non_field_errors %}
<p class="help is-danger">{{ non_field_error }}</p>
{% endfor %}
{% if field.help_text %}
<p class="help is-danger">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
<p class="control">
<button class="button is-link" type="submit">
Enviar
</button>
</p>
</form>
</section>
It validates if I put two different dates in the form, but it doesn't enter to is_valid() (because ther form is not valid). Render just the button of the template.
Try this:
if form.is_valid():
tutor = form.cleaned_data['tutor']
dia_hor_inicio = form.cleaned_data['dia_hor_inicio']
dia_hor_fin = form.cleaned_data['dia_hor_fin']
tutor_horario = horarios_disp(
tutor=tutor, dia_hor_inicio=dia_hor_inicio, dia_hor_fin=dia_hor_fin
)
tutor_horario.save()
context = {'form': form}
return redirect("home")
else:
context = {'error': 'whatever error you want to show here'}
return render(request, "horarios_disponibles.html", context)
# and probably some extra handling at the end in case there are errors
As a matter of fact, you won't need to declare the context = {} at the beginning of your code before if request.method == 'POST' because you're going to declare one on in the if-else statement anyways.
When I'm trying to remove a row, the formset send me the following error [{}, {}, {}, {'id': ['This field is required']}].
If I change the following parameter can_delete=True inside the modelformset_factory, I can delete object but rows stays display even after I pressed the remove button.
I tried to add {% if form.instance.pk %}{{ form.DELETE }}{% endif %} in the template, it didn't change anything.
I am using django 2.2 and django-dynamic-formset, the country_fr/en come from modeltranslation.
Views
#login_required
def view_countries(request):
CountryFormSet = modelformset_factory(
Country,
fields=('country_fr', 'country_en'),
formset=BaseCountryFormSet,
can_delete=True)
if request.method == 'POST':
formset = CountryFormSet(request.POST)
if formset.is_valid():
formset.save()
else:
formset = CountryFormSet()
context = {
'formset': formset,
'menu': 'cards',
'menu_cards': 'countries',
'model': _('countries'),
'fields': [_('countries')+' [fr]', _('countries')+' [en]'],
}
return render(request, 'cards/index_formset.html', context)
Html
<form method="post" class="mt-2">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div id="form" class="row_formset d-flex text-center mb-1">
{% if form.instance.pk %}{{ form.DELETE }}{% endif %}
{% for field in form.hidden_fields %}
<div class="invisible">
{{ field }}
</div>
{% endfor %}
{% for field in form.visible_fields %}
<div class="flex-fill field">
{{ field }}
</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-flex mt-2 justify-content-center">
<input type="submit" value="{% trans 'submit'|capfirst %}" class="btn btn-primary"/>
</div>
</form>
Forms
class BaseCountryFormSet(BaseModelFormSet):
def clean(self):
if any(self.errors):
raise forms.ValidationError(
_('Errors : ')+f'{self.errors}'+'.',
code='unknow_error'
)
countries_fr = []
countries_en = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
country_fr = form.cleaned_data['country_fr']
country_en = form.cleaned_data['country_en']
# Check that no row have the same country_fr or country_en
if country_fr and country_en:
if country_fr in countries_fr:
duplicates = True
countries_fr.append(country_fr)
if country_en in countries_en:
duplicates = True
countries_en.append(country_en)
if duplicates:
raise forms.ValidationError(
_('Some entries are duplicated.'),
code='duplicate_row'
)
# Check that all row have both country_fr and country_en
if country_en and not country_fr:
raise forms.ValidationError(
_('Some french entries are missing.'),
code='missing_country_fr'
)
elif country_fr and not country_en:
raise forms.ValidationError(
_('Some english entries are missing.'),
code='missing_country_en'
)
Models
class Country(models.Model):
country = models.CharField(_('country'), max_length=100)
slug = models.SlugField(editable=False, max_length=100)
def save(self, *args, **kwargs):
self.slug_fr = slugify(self.country_fr, allow_unicode=False)
self.slug_en = slugify(self.country_en, allow_unicode=False)
super().save(*args, **kwargs)
So the problem come from bootstrap, the d-flex of each form of the formset contain the attribut !important wich override the display: none; of the django-dynamic-formset-script.
I made my own css class to replace d-flex. I kept can_delete=True, it's obviously useful; and I removed {% if form.instance.pk %}{{ form.DELETE }}{% endif %}.
I am trying to make a voting app in django.
Here the view that display a vote.
class DetailVoteView(View):
"""
Displays the vote
If vote is active allows to vote
If vote is completed shows winners and top 5
"""
vote = None
vote_for_chars = None
page_title = ''
def dispatch(self, request, *args, **kwargs):
self.vote = Vote.objects.get(id=kwargs['id'])
self.vote_for_chars = self.vote.voteforcharacter_set.all()
self.page_title = self.vote.title
return super(DetailVoteView, self).dispatch(request, *args, **kwargs)
def get(self, request, id):
if is_active_vote(self.vote):
nominates = self.vote_for_chars
return render(request, 'vote_detail/active_vote_detail.html', context={
'page_title': self.page_title,
'vote': self.vote,
'votes_name': self.page_title,
'nominates': nominates,
})
else:
winners = []
top_fives = []
if self.vote.voteforcharacter_set.count() != 0:
sorted = self.vote.voteforcharacter_set.order_by('-votes_number')
winner_number_votes = sorted[0].votes_number
winners = self.vote_for_chars.filter(votes_number__exact=winner_number_votes)
top_fives = sorted[winners.count():5 + winners.count()]
return render(request, 'vote_detail/completed_vote_detail.html', context={
'page_title': self.page_title,
'vote': self.vote,
'votes_name': self.page_title,
'winners': winners,
'top_fives': top_fives
})
def post(self, request, id):
vote_for_chars = self.vote_for_chars
id_to_vote = request.POST.get("id_to_vote")
char_to_vote = vote_for_chars.get(character__id=id_to_vote)
char_to_vote.votes_number += 1
char_to_vote.save()
return HttpResponseRedirect('/')
The code is not DRY as I suppose. Because, there is the same context in get reuqest.
I need to use a mixins feature?
How can I improve it? Thanks.
EDITED.
Added code for templates.
acvtive_vite_detail.html
{% extends 'base/base.html' %}
{% block content %}
<form action="." method="POST">{% csrf_token %}
<p>Vote: {{ vote }}.</p>
<p>Choose:</p>
<div>
{% for item in nominates %}
{% include "base/listOutput.html" %}
<input type="radio" id="{{ item.id }}"
name="id_to_vote" value="{{ item.character.id }}" checked>
<label for="{{ item.character.name }}">{{ item.character.name }}
</label>
{% endfor %}
</div>
<div>
<button type="submit">Vote!</button>
</div>
</form>
{% endblock content %}
completed_vote_detail.html
{% extends 'base/base.html' %}
{% block content %}
<p>Vote: {{ vote }}.</p>
<p>Winners: </p>
{% for item in winners %}
<p>{% include "base/listOutput.html" %}</p>
{% endfor %}
<p>Top5: </p>
{% for item in topFives %}
<p>{% include "base/listOutput.html" %}</p>
{% endfor %}
{% endblock content %}
listOfOutput.html
<div style="border-style: solid; width: 25%">
<img src="{{ item.character.image.url }}" alt="img" class="img-responsive img-rounded" width="100%" height=auto>
<p>Name: {{ item.character.name }}</p>
<p>Age: {{ item.character.age }}</p>
<p>Votes number: {{ item.votes_number }}</p>
</div>