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.
Related
I'm having difficulty with displaying custom defined errors on a page when a user submits an invalid form submission. I set novalidate on the form element with the intent of disabling a browser's default form validation messages. At the same time the error messages that are defined on QuestionForm are not displaying on the page.
It's not clear to me why the form error messages aren't showing? Is setting novalidate causing this to occur?
class Page(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['search_form'] = SearchForm()
return context
class AskQuestionPage(Page):
template_name = "posts/ask.html"
extra_context = {
'title': "Ask a public question"
}
def attach_question_tags(self, tags):
question_tags = []
for name in tags:
try:
tag = Tag.objects.get(name=name)
except Tag.DoesNotExist:
tag = Tag.objects.create(name=name)
finally:
question_tags.append(tag)
return question_tags
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = QuestionForm
return context
def post(self, request):
context = self.get_context_data()
form = context['form'](request.POST)
if form.is_valid():
tags = self.attach_question_tags(
[tag.lower() for tag in form.cleaned_data.pop("tags")]
)
try:
question = form.save(commit=False)
question.profile = request.user.profile
question.save()
except IntegrityError:
form.add_error(None, "This post is already posted")
context['form'] = form
else:
question.tags.add(*tags)
form.save_m2m()
return SeeOtherHTTPRedirect(
reverse("posts:question", kwargs={
"question_id": question.id
})
)
return self.render_to_response(context)
<div class="question_posting_form">
<h2>{{ title }}</h2>
{% if form.non_field_errors %}
<ul class="non_field_errors">
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ form.errors }}
{% if form.errors %}
<ul class="non_field_errors">
{% for error in form.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form id="postform" method="post" action="{{ request.path }}" novalidate>
{% csrf_token %}
{% for field in form %}
{% if field.html_name == "body" %}
<div class="content_posting">
{% include "./markdown_help.html" %}
<fieldset class="form_fieldset_widget">
<p>{{ field.label }}<p>
<p>{{ field.help_text }}</p>
{{ field }}
</fieldset>
<div class="post_draft" id="post_preview_question"></div>
</div>
{% elif field.html_name == "tags" %}
<fieldset id="tag_fieldset" class="form_fieldset_widget">
<p>{{ field.label }}</p>
<p>{{ field.help_text }}</p>
<div class="tag_boxes">
{% for field in field.subwidgets %}
{{ field }}
{% endfor %}
</div>
</fieldset>
{% else %}
<fieldset class="form_fieldset_widget">
<p>{{ field.label }}<p>
<p>{{ field.help_text }}</p>
{{ field }}
</fieldset>
{% endif %}
{% endfor %}
{% if request.resolver_match.url_name == "edit" %}
<button id="submit_question" type="submit">Edit Question</button>
{% else %}
<button id="submit_question" type="submit">Ask Question</button>
{% endif %}
</form>
</div>
class MultiTagWidget(MultiWidget):
def __init__(self, *args, **kwargs):
widgets = [TextInput(attrs=kwargs['attrs']) for i in range(4)]
for i, w in enumerate(widgets):
w.attrs.update({"placeholder": f"Tag {i}"})
super().__init__(widgets, *args, **kwargs)
def decompress(self, value):
if not value:
return ["", "", "", ""]
return value
class TagField(MultiValueField):
def __init__(self, *args, **kwargs):
fields = []
for i in range(4):
field = CharField(**{
"min_length": 1, "max_length": 25, "validators":[
RegexValidator("[<>`':;,.\"]", inverse_match=True)
]
})
if i == 0:
field.error_messages = {
'incomplete': "Provide at least 1 tag for your question"
}
field.required = True
else:
field.required = False
fields.append(field)
super().__init__(fields, *args, **kwargs)
def compress(self, data_list):
field_values = list(set([data.strip() for data in data_list if data]))
return field_values
class PostingForm(ModelForm):
body = CharField(
widget=Textarea(
attrs={
"class": "question_input_shade fill_block_width adjust_box"
}
), min_length=50
)
class Meta:
model = None
fields = ['body']
class QuestionForm(PostingForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['body'].help_text = "Clarify your question with as much detail as possible"
self.fields['body'].error_messages={
'required': "Elaborate on your question",
'min_length': "Add more info to your question"
}
title = CharField(
min_length=20, max_length=80,
widget=TextInput({"class": "question_input_shade fill_block_width"}),
error_messages={"max_length": "The title of your question is too long"},
help_text="Concisely describe the issue"
)
tags = TagField(
widget=MultiTagWidget(
attrs={
"min_length": 1, "max_length": 25,
"class": "question_input_shade inline_tag_input"
}
), require_all_fields=False,
help_text="Add up to 4 tags for your question"
)
I've created a django subscription form. I want to show a 'Thank You' message below the form after submitting but if I submit the form, email field is still showing the value. I've tried HttpResponseRedirect('/') but doing so doesn't show 'Thank You' message.
#Views.py
global categories
categories = ['Development','Technology','Science','Lifestyle','Other']
class IndexView(TemplateView):
model = Post
template_name = 'blog/index.html'
def post(self,request,*args,**kwargs):
context = self.get_context_data()
if request.method == 'POST':
form = SubscriberForm(request.POST)
if context["form"].is_valid():
context["email"] = request.POST.get('email')
form.save()
form = SubscriberForm()
return render(request, 'blog/index.html', context=context)
else:
form = SubscriberForm()
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(IndexView,self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['post_list'] = Post.objects.all()
context["categories"] = categories
form = SubscriberForm(self.request.POST or None) # instance= None
context["form"] = form
return context
#sidebar.html
<div class="subscribe">
<form class="" action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button id="subscribe-button" type="submit" name="button"><i class="fa fa-paper-plane" aria-hidden="true"></i></button>
</form>
{% if email %}
<h6>Thank you for Subscribing!</h6>
{% endif %}
<!-- <i class="fa fa-paper-plane" aria-hidden="true"></i> -->
</div>
#models.py
class Subscribe(models.Model):
email = models.EmailField()
subscribed_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-subscribed_on',)
def __str__(self):
return 'Subscribed by {} on {}'.format(self.email, self.subscribed_on)
#forms.py
class Subscribe(models.Model):
email = models.EmailField()
subscribed_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-subscribed_on',)
def __str__(self):
return 'Subscribed by {} on {}'.format(self.email, self.subscribed_on)
Instead of rendering the template you can redirect() after saving the form.
And to render the success message you can use the django messaging framework.
def post(self,request,*args,**kwargs):
context = self.get_context_data()
if request.method == 'POST':
form = SubscriberForm(request.POST)
if context["form"].is_valid():
context["email"] = request.POST.get('email')
form.save()
messages.success(request, 'Thank you for subscribing')
return redirect('/')
else:
form = SubscriberForm()
return render(request, 'blog/index.html', context=context)
Now in the template you can render the messages like this.
<div class="subscribe">
<form class="" action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button id="subscribe-button" type="submit" name="button"><i class="fa fa-paper-plane" aria-hidden="true"></i></button>
</form>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<!-- <i class="fa fa-paper-plane" aria-hidden="true"></i> -->
</div>
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 having a problem in displaying an error message in the HTML page. I am using Django's default UserCreationForm for signup page. It has two password fields - one original and one for confirmation. When the user enters different passwords, I am getting at /signup/ whereas I want the error message to be displayed in the HTML page saying that the passwords didn't match. I have gone through the docs and I have added some related lines in my code, I don't know where I'm going wrong.
Here is my views.py:
def adduser(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
print(request.POST)
if(form.is_valid):
try:
user = employees.objects.get(emp_id=request.POST['username'] )
except employees.DoesNotExist:
user = None
print(user)
if( user != None ):
if request.POST['username'] in employees.objects.values_list('manager_id__emp_id',flat=True):
g = Group.objects.get(name='Managers')
newuser = form.save()
newuser.groups.add(g)
else:
g = Group.objects.get(name='Employees')
newuser = form.save()
newuser.groups.add(g)
return render(request,'login.html',{'form': form})
else:
form = UserCreationForm()
return render(request,'signup.html', {'form': form, 'msg': 'Enter valid employee id'})
else:
form = UserCreationForm()
return render(request,'signup.html', {'form': form})
and here is my signup.html:
<body>
<div class="container">
<div class="page-header">
<h1>Sign-up Here</h1>
</div>
{% block body %}
<form method="post">
{% csrf_token %}
<font color="orange" size="5px"><p> * Enter your Employee id, as username * </p></font>
{{ form.as_p }}
<font color="red"> {{ msg }} </font><br>
<font color="red"> {{ form.password1.errors }} </font><br>
<font color="red"> {{ form.password2.errors }} </font><br>
<br>
<button class="btn btn-success" type="submit"> Go! </button>
</form>
{% endblock %}
</div>
</body>
The problem is in this line :
if(form.is_valid):
This is not the correct way of testing form validation,
instead use:
if form.is_valid():
Also you dont need to declare form multiple times,it can be done single time.
Like this :
def adduser(request):
form = UserCreationForm(request.POST or None)
if request.method == 'POST':
print(request.POST)
if form.is_valid():
try:
user = employees.objects.get(emp_id=request.POST['username'] )
except employees.DoesNotExist:
user = None
print(user)
if( user != None ):
if request.POST['username'] in employees.objects.values_list('manager_id__emp_id',flat=True):
g = Group.objects.get(name='Managers')
newuser = form.save()
newuser.groups.add(g)
else:
g = Group.objects.get(name='Employees')
newuser = form.save()
newuser.groups.add(g)
return render(request,'login.html',{'form': form})
else:
form = UserCreationForm()
return render(request,'signup.html', {'form': form, 'msg': 'Enter valid employee id'})
return render(request,'signup.html', {'form': form})
And there can be diffrenmt type of erros, field and non_field_errors, so use someting like this :
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
I'm creating a page where my users can edit their articles but there is one problem, my form is not saving the modifications when I submit my form. Let's have a look:
views.py
def edit(request, id=None, template_name='article_edit.html'):
if id:
article = get_object_or_404(Article, id=id)
if article.user != request.user:
return HttpResponseForbidden()
else:
article = Article(user=request.user)
if request.POST:
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
save_it = form.save()
save_it.save()
form.save_m2m()
return HttpResponseRedirect("/")
else:
form = ArticleForm(instance=article)
context = {'form': form}
populateContext(request, context)
return render(request, template_name, context)
line 3 to 8 : Is to check if it's your own articles there is no problem with that.
line 10 to 18 : There is a problem between these lines, my form is not saving when I submit my form
forms.py (ModelForm)
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
exclude = ['date', 'rating', 'user']
form = ArticleForm()
(Line 4 : I'm excluding the date, the ratings and the username when saved so that the user can not change these informations.)
I don't have any problem in my template.html and the urls.py don't bother with that I checked over 10 times :)
Any help would be great, I'm blocked on this problem since over 1 month...
******* EDIT *******
Models.py
class Article(models.Model):
user = models.ForeignKey(User)
titre = models.CharField(max_length=100, unique=True)
summary = RichTextField(null=True, max_length=140)
contenu = RichTextField(null=True)
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
image = models.ImageField(upload_to='article', default='article/amazarashi.jpeg')
rating = RatingField(can_change_vote=True)
tags = TaggableManager(through=TaggedItem, blank=True)
def __str__(self):
return self.titre
Template.html
{% block body %}
{% if user.is_authenticated %}
<p>Authentificated as <strong>{{ user.username }}</strong></p>
{% else %}
<p>NOT Authentificated</p>
{% endif %}
<h1>Edit this {{ article.titre }}</h1>
<br>
<div class="col-xs-12 col-sm-8 col-md-8 col-lg-8">
<form enctype='multipart/form-data' action="{% url "article.views.index" %}" method="post" class="form" autocomplete="off" autocorrect="off">
{% csrf_token %}
<div class="form-group">TITRE
{{ form.titre.errors }}
{{ form.titre }}
</div>
<div class="form-group">SUMMARY
{{ form.media }}
{{ form.summary.errors }}
{{ form.summary }}
</div>
<div class="form-group">CONTENU
{{ form.media }}
{{ form.contenu.errors }}
{{ form.contenu }}
</div>
<div class="form-group">
{{ form.image.errors }}
{{ form.image }}
</div>
<div class="form-group">TAGS
{{ form.tags.errors }}
{{ form.tags }}
</div>
<input type="submit" class="btn btn-default" value="Submit"/>
</div>
</form>
{% endblock %}
According to your error message.. Modify your form in templates:
<form enctype='multipart/form-data' ...>
Add request.FILES to your Form creation:
if request.POST:
form = ArticleForm(request.POST, request.FILES, instance=article)
UPDATE:
You have strange url tag parameter. Are you sure you have defined your url like: ('...', your_view, name='article.views.index')? I am in doubt cause url tag takes url name as parameter but not view path and nobody usually uses url names like this.