I am on Django Framework and implemented an interface where a user can do Insert and Update operations on a MySQL table.
MySQL student table does not allow duplicate entries, so if a user enters a duplicate key an Exception message is displayed. If, a user re-submits a successful query to the database the error message disappears.
What is the current issue:
If a user tries to reload the page after a bad query was sent to the database the error message is still displayed. On a page reload it returns a POST request, so it's submitting the duplicate entry again triggering the Exception
How can I stop this request from being submitting again on a page reload, since it seems on a page reload it re-submits the duplicate entry?
views.py
def generate_student_info(request):
# Retrieve inputted value
current_student_name = request.POST.get('student_name')
current_student_id = request.POST.get('student_id')
# Read the table from the database
connection = db.open_connection(read_only=False)
sql_str = f"""SELECT * FROM {students_table};"""
df = db.read(sql_statement=sql_str, connection=connection)
# creating a formset
insert_formset = formset_factory(StudentInfoForm, extra=0)
formset = insert_formset(request.POST or None, initial=[{'student_id': df['student_id'][i], 'student_name': df['student_name'][i]} for i in range(len(df))])
context = {'formset': formset, 'db_error_message': '', 'display_error_message': False}
if request.method == 'POST':
# Insert MySQL query
if 'student_name' in request.POST:
try:
insert_query = f"""INSERT INTO {students_table} (student_id, student_name) VALUES ({current_student_id},'{current_student_name}');"""
db.write(sql_statement=insert_query, connection=connection)
return HttpResponseRedirect("/student_info/")
except Exception as e:
context['db_error_message'] = e
context['display_error_message'] = True
print(e)
return render(request, 'student_info_table.html', context)
# Update MySQL query
else:
if formset.is_valid():
for form in formset:
if form.has_changed():
print(form.cleaned_data)
try:
update_query = f"""UPDATE {students_table} SET student_name='{form.cleaned_data['student_name']}' WHERE student_id='{form.cleaned_data['student_id']}';"""
db.write(sql_statement=update_query, connection=connection)
return HttpResponseRedirect("/student_info/")
except Exception as e:
context['db_error_message'] = e
context['display_error_message'] = True
print(e)
return render(request, 'student_info_table.html', context)
else:
print('formset is not valid')
context = {'formset': formset, 'db_error_message': '', 'display_error_message': False}
return render(request, 'student_info_table.html', context)
student_info_table.html
<form action='' method='post'>
{% csrf_token %}
<div>
<input type="button" value="Insert" id="id_insert" class="generate">
</div>
</form>
{% if display_error_message %}
<h4>{{db_error_message}}</h4>
{% endif %}
<form method="POST" enctype="multipart/form-data" id="insertion-formset">
<table>...</table>
</form>
Instead of sending error message through context use django messages framework. It was built to achieve what you want. To use this you send message from views as
messages.error(request, 'Display error message.')
You can also add multiple message for the request. It is also removed when page is reloaded. Check out document here.
Related
I have a custom validator which should raise an error on the frontend for the user if the validation fails. However, I am not sure where and how to return this to the frontend. Right now it only shows in the terminal/Django error site on dev server:
The view:
def raise_poller(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = PollersForm(request.POST)
# check whether it's valid:
if form.is_valid():
# Make Validation of Choices Fields
poller_choice_one = form.cleaned_data['poller_choice_one']
poller_choice_two = form.cleaned_data['poller_choice_two']
if ' ' in poller_choice_one or ' ' in poller_choice_two:
raise ValidationError('Limit your choice to one word', code='invalid')
return poller_choice_two # !! IDE says this is unreachable
else:
# process the remaining data in form.cleaned_data as required
poller_nature = form.cleaned_data['poller_nature']
poller_text = form.cleaned_data['poller_text']
poller_categories = form.cleaned_data['poller_categories']
# Get the user
created_by = request.user
# Save the poller to the database
p = Pollers(poller_nature=poller_nature,
poller_text=poller_text,
poller_choice_one=poller_choice_one,
poller_choice_two=poller_choice_two,
created_by=created_by)
p.save()
p.poller_categories.set(poller_categories)
# redirect to a new URL:
return HttpResponseRedirect('/')
# if a GET (or any other method) we'll create a blank form
else:
form = PollersForm()
return render(request, 'pollinator/raise_poller.html', {'form': form})
template
{% block content %}
<div class="poll-container">
<form action="/raisepoller/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
</div>
{% endblock %}
Also where to best structure/implement custom validators? After the if form.is_valid part?
To show the error in the form, you have to raise the ValidationError in the form (instead of the view), because form expects and handles when ValidationError is eventually raised. Read the docs about forms validation here
class PollersForm(forms.Form):
def clean(self):
data = self.cleaned_data
poller_choice_one = data['poller_choice_one']
poller_choice_two = data['poller_choice_two']
if ' ' in poller_choice_one or ' ' in poller_choice_two:
# raising ValidationError here in clean does the trick
raise ValidationError('Limit your choice to one word', code='invalid')
return data
sidenote: if the only thing you are doing with the form is creating a model instance, you should consider using ModelForm instead, which is designed exactly for this purpose. It will save you writing a lot of code.
Flask_wtf's validate_on_submit() is never True on visiting the page for the first time, so it always flashes the else part's (code below) Error message which is always an empty dict.
But the form validation and submission are working properly - the success flash message can be seen on a valid post. And the Error flash doesn't disappear after a valid submission.
Reproducible code:
# necessary import stmts & other stuff
class MyForm(FlaskForm):
sub = StringField(validators=[DataRequired("Choose the title")])
body = TextAreaField(validators=[DataRequired(),Length(min=20)])
subm = SubmitField('Submit')
app.config['SECRET_KEY'] = 'my key'
#app.route('/', methods=['GET','POST'])
def index():
fo = MyForm()
flash('Submitted:'+str(fo.is_submitted())) # False on first time visit
#flash('After Validate:'+str(fo.validate()))
if fo.validate_on_submit():
ex = mytable(bodys = fo.body.data, subs = fo.sub.data)
# DB session add & commit stmt here
flash('Submitted','success')
return redirect(url_for('index'))
else:
flash('After val Errors:'+str(fo.errors))
return render_template('index.html',form=fo)
If I un-comment fo.validate()...it flashes csrf_token': ['The CSRF token is missing.'] and the other data required error msgs but as shown below the html template has form.hidden_tag(). Also used {{ form.csrf_token }} instead of hidden_tag()...no success.
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.sub }}
{{ form.body }}
{{ form.subm }}
</form>
Please help to get rid of the validation error on page load, Thank you
So on initial get you don't need to validate your form because there's no data yet, only do it when it's actually posted, like so:
if request.method == 'POST':
if fo.validate_on_submit():
# DB session add & commit stmt here
flash('Submitted', 'success')
return redirect(url_for('index'))
else:
flash('After val Errors:' + str(fo.errors))
I'm trying to run a redirect after I check to see if the user_settings exist for a user (if they don't exist - the user is taken to the form to input and save them).
I want to redirect the user to the appropriate form and give them the message that they have to 'save their settings', so they know why they are being redirected.
The function looks like this:
def trip_email(request):
try:
user_settings = Settings.objects.get(user_id=request.user.id)
except Exception as e:
messages.error(request, 'Please save your settings before you print mileage!')
return redirect('user_settings')
This function checks user settings and redirects me appropriately - but the message never appears at the top of the template.
You may first think: "Are messages setup in your Django correctly?"
I have other functions where I use messages that are not redirects, the messages display as expected in the template there without issue. Messages are integrated into my template appropriately and work.
Only when I use redirect do I not see the messages I am sending.
If I use render like the following, I see the message (but of course, the URL doesn't change - which I would like to happen).
def trip_email(request):
try:
user_settings = Settings.objects.get(user_id=request.user.id)
except Exception as e:
messages.error(request, 'Please save your settings before you print mileage!')
form = UserSettingsForm(request.POST or None)
return render(request, 'user/settings.html', {'form': form})
I have a few other spots where I need to use a redirect because it functionally makes sense to do so - but I also want to pass messages to those redirects.
The user_settings function looks like this:
def user_settings(request):
try:
user_settings = Settings.objects.get(user_id=request.user.id)
form = UserSettingsForm(request.POST or None, instance=user_settings)
except Settings.DoesNotExist:
form = UserSettingsForm(request.POST or None)
if request.method == 'POST':
settings = form.save(commit=False)
settings.user = request.user
settings.save()
messages.warning(request, 'Your settings have been saved!')
return render(request, 'user/settings.html', {'form': form})
I can't find anything in the documentation saying that you can't send messages with redirects... but I can't figure out how to get them to show.
Edit:
This is how I render the messages in the template:
{% for message in messages %}
<div class="alert {{ message.tags }} alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
{{ message }}
</div>
{% endfor %}
I'm not sure if it matters - but it looks like it's almost calling 'GET' twice from somewhere.
the URL section looks like this for these two URLS:
# ex: /trips/email
url(r'^trips/email/$', views.trip_email, name='trip_email'),
# ex: /user/settings
url(r'^user/settings/$', views.user_settings, name='user_settings'),
Nope I think the best way is to use your sessions.
from django.contrib import messages << this is optional if you choose to use django messaging
Handle your form
def handle_form(request):
...
request.session['form_message'] = "success or fail message here"
redirect('/destination/', {})
def page_to_render_errors():
...
message = None
if( 'form_message' in request.session ):
message = request.session['form_message']
del request.session['form_message']
messages.success(request, message ) #<< rememeber messages.success() along with messages.info() etc... are method calls if you choose to pass it through django's messaging
return render(request,'template_name.html', {'message_disp':message })
You cannot view the error message, because it is not getting rendered, and hence there is nowhere for the message to go. The best way to do something like this would be to render an intermediate page, which would display the error message, and redirect after timeout, something of the sort used by payment gateways while redirecting you to your bank.
You can do something like this:
def trip_email(request):
try:
user_settings = Settings.objects.get(user_id=request.user.id)
except Exception as e:
return render(request, 'no_setting_error.html', {'form': form})
and on the no_setting_error html page, insert Javscript to automatically redirect you after a timeout like:
<script>
function redirect(){
window.location.href = "/user_settings";
}
setTimeout(redirect, 3000);
</script>
This will redirect you to from the error page to the user_settings page automatically after 3 seconds.
OK, so I have a functional auth_login setup. But it's unrelated to my Articles model, and ArticleForm ModelForm. However, when I try to create a new article on the local website, I get an error related to views.auth_login even though auth_login isn't referenced anywhere (to my knowledge) in my Article stuff: The view home.views.auth_login didn't return an HttpResponse object. It returned None instead. Usually, an error like that means that you're not returning an actual response in a view definition, but I do. The real question is why home.views.auth_login is being called instead of home.views.add_article. Here's my code:
home/models.py
class Article(models.Model):
headline = models.CharField(max_length=50)
content = models.CharField(max_length=1024)
def __str__(self):
return self.headline
home/forms.py
from django.contrib.auth.models import User
from .models import Article
class LoginForm(forms.ModelForm):
class Meta:
model = User
fields = ["username", "password"]
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['headline', 'content']
home/views.py
def auth_login(request):
if request.method == "POST":
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
return HttpResponseRedirect('/home/')
else:
# Return an 'invalid login' error message.
return HttpResponse('Invalid username / password. :( Try again? <3')
else:
loginform = LoginForm()
context = {
'loginform': loginform
}
return render(request, 'home/login.html', context)
def add_article(request):
if request.method == "POST":
form = ArticleForm(data=request.POST)
if form.is_valid():
article = form.save()
article.save()
# todo change to view article page
return HttpResponseRedirect('/home/')
else:
return HttpResponse('Invalid Inputs. :( Try again? <3')
else:
form = ArticleForm()
context = {
'form': form,
}
return render(request, 'home/add_article.html', context)
home/urls.py
...
urlpatterns = [
# match to ''
# ex: /polls/
url(r'^$', views.auth_login, name='login'),
url(r'^home/$', views.index, name='index'),
url(r'^articles/add/$', views.add_article, name='add_article')
]
home/templates/home/add_article.html
<h2> Add an Article </h2>
<form action="/" method="post">
{% csrf_token %}
{{ form }}
<br><br>
<input type="submit" value="Submit" name="addArticle" class="btn col2"/>
</form>
Results / Problem
When I go to http://127.0.0.1:8000/articles/add, fill out my simple form, and click submit, I get:
The view home.views.auth_login didn't return an HttpResponse object. It returned None instead.
File "/Users/hills/Desktop/code/django-beanstalk/ebenv/lib/python2.7/site-packages/django/core/handlers/exception.py" in inner
39. response = get_response(request)
File "/Users/hills/Desktop/code/django-beanstalk/ebenv/lib/python2.7/site-packages/django/core/handlers/base.py" in _legacy_get_response
249. response = self._get_response(request)
File "/Users/hills/Desktop/code/django-beanstalk/ebenv/lib/python2.7/site-packages/django/core/handlers/base.py" in _get_response
198. "returned None instead." % (callback.__module__, view_name)
Exception Type: ValueError at /
Exception Value: The view home.views.auth_login didn't return an HttpResponse object. It returned None instead.
But I can't figure out why home.views.auth_login is being called instead of home.views.add_article. I've tried deleting and recreating all db tables (python manage.py flush, then python manage.py makemigrations, then python manage.py migrate), and I've even tried independently writing an independent Article2 model / form / template set, but I get the same error. --> Any idea what's going on?
Well your form is submitting to the root of your website, so that's why it's hitting auth_login instead of add_article.
Change <form action="/" method="post"> to just <form method="POST">. I assume the other error (no HttpResponse object) is just a side effect of POSTing to auth_login.
I have a app called dashboard which is where I redirect all logged in users with an option to add articles by the user.
After the user hits Submit button in the form, the data is sent to /dashboard/article/save URL via POST and after the data is stored, the view returns HttpResponseRedirect to show_dashboard which renders dashboard.html with a session variable result.
In the dashboard template file, I have added a notify.js code to show acknowledgements to user. The problem is if this session var is defined, everytime the dashboard page is showed, the notification is triggered EVEN if the user didn't add an article.
(I'm new to using web frameworks so I do not know how this all works properly)
Some code:
dashboard/models.py:
class Article(models.Model):
id = models.IntegerField(primary_key=True)
ar_title = models.CharField(max_length=25)
ar_data = models.CharField(max_length=500)
user = models.ForeignKey(User,on_delete=models.CASCADE)
def getArticleTitle(self):
return self.title
def getArticleData(self):
return self.title
def getArticleAuthor(self):
return self.user
dashboard/urls.py:
urlpatterns = [
url(r'^$', views.show_dashboard,name='home_dashboard'),
url(r'^profile/save/', views.save_profile,name="save_profile"),
url(r'^newsfeed/', views.get_newsfeed,name="newsfeed",),
url(r'^profile/', views.show_profile,name="show_profile"),
url(r'^article/save/', views.add_new_article,name="add_new_article"),
]
dashboard/views.py:
#login_required
def show_dashboard(request):
return render(request,'dashboard/dashboard.html',{'form':NewArticleForm()})
def add_new_article(request):
if(request.method == 'POST'):
ar_title= request.POST['ar_title']
ar_data = request.POST['ar_data']
user = request.user
form = NewArticleForm(request.POST)
if(form.is_valid()):
Article.objects.create(ar_title=ar_title,ar_data=ar_data,user=user)
request.session["result"] = "add_article_OK"
return HttpResponseRedirect(reverse('home_dashboard'))
dashboard.html:
{% ifequal request.session.result 'add_article_OK' %}
<script>
$.notify("New article added successfully",
{position:"bottom right","className":"success","autoHide":"yes","autoHideDelay":"3000"});
</script>
{% endifequal %}
Now, how do I remove this session value after it has displayed the message? I know del request.session['result'] can be issued but where can I put it in this heirarchy of moves?
Do it in the show_dashboard view.
Instead of getting the value from the session in the template, pop it in the view and pass it to the template; that way you take care of getting and clearing it in one go.
#login_required
def show_dashboard(request):
context = {
'form': NewArticleForm(),
'result': request.session.pop('result', None)
}
return render(request,'dashboard/dashboard.html',context)
...
{% ifequal result 'add_article_OK' %}