Django Formview Won't Validate - django

Django newbie here. I recently implemented a simple search with Django. I user specifies data in search bar, search is executed. I was able to determine today how to get the search to be null if user clicks on empty search bar. What I'm really trying to accomplish is to implement error logic so that the user has to specify data in the search bar. This has been very straight forward with all of the other views, but the search is a bit trickier, for me anyway. I have done several SO searches today, and determined that my HTML get method is ok for a search, but it may be causing some problems with the FORMVIEW that I am using. I have also tried to override POST and GET for the FORMVIEW, but can't seem to get it to work. I want to stick with CLASS BASED VIEWS, so I am using FORMVIEW. Here is my code....
My HTML
<form method="GET" autocomplete=off action="{% url 'Book:book_request_search_results' %}" >
<div>
<h1 class="title">Book Request Search</h1>
</div>
{{ form.non_field.errors }}
<div class="section">
<input type="search" class="name2" name="q">
</div>
<div class="section">
<input type="submit" id="" class="submit6" value="Search">
</div>
My VIEWS.PY
class BookRequestSearchView(LoginRequiredMixin,FormView):
form_class = BookRequestSearch
template_name = 'store/Book_request_search.html'
success_url = reverse_lazy('store:book_request_search_results')
def form_valid(self, form):
self.kwargs['q'] = form.cleaned_data['q']
if self.request.GET.get('q'):
pass
else:
raise forms.ValidationError("Enter data")
return super(BookRequestSearchView, self).form_valid(form)
My FORMS.PY
class BookRequestSearch(forms.Form):
q = forms.CharField(required=True)
def __init__(self, *args, **kwargs):
super(BookRequestSearch, self).__init__(*args, **kwargs)
I also tried to add a clean method to my form but that doesn't seem to work either...
def clean(self):
search = self.initial['q']
if search:
pass
else:
raise forms.ValidationError("Enter data")
return self.cleaned_data
I've been scanning most of the afternoon and can't seem to find a way to trigger validation on the FORMVIEW to trigger an error on the search bar. Any help would be appreciated. I've seen many different FORMVIEW articles, but none of them have helped me understand what I'm doing wrong and why this isn't working. The way the code is now, it works, but it doesn't prevent user from clicking on search and then triggering essentially an empty query. I'm trying to prevent that by forcing the user to put something in the box if they try to search with no criteria. Thanks in advance for any direction.

I was wrong on saying that it's only possible with javascript.
It's so simple as adding the required attribute to your input form, so the browser catches the empty error.
<form>
<input type="search" required><!-- added the required attribute-->
</form>

Related

Django - how to go back to previous view with parameters

I am relatively new with Django, this must be a common problem.
I have created a view to show a form to input date (using widget that returns separate fields):
when date is inserted, I call a function userPage(request, my_date)
that filters, processes and renders a page (user.html) showing a list of items.
def datePage(request):
user=request.user
context = {}
context['form'] = UserDateForm()
if request.GET:
date_yr = request.GET['food_date_year']
date_mo = request.GET['food_date_month']
date_day = request.GET['food_date_day']
my_date_string = date_yr+'-'+date_mo+'-'+date_day
my_date = datetime.strptime(my_date_string, "%Y-%m-%d").date()
return userPage(request,my_date)
return render(request, "date.html", context)
def userPage(request, my_date):
user=request.user
# process context using user, my_date
context={...:..., 'my_date': my_date}
return render(request,'user.html',context)
In user.html I include a URL to add an item:
</div>
<form action="{% url 'My_ItemCreate' %}" method="POST">
{%csrf_token%}
<button type="submit" class="btn btn-success">
<span class="glyphicon glyphicon-plus"></span>
</button>
</form>
</div>
'My_ItemCreate' points to a django.views.generic CreateView that creates an item.:
path('MyItemCreate/',views.My_ItemCreate.as_view(),name='My_ItemCreate'),
class My_ItemCreate(CreateView):
model = MyItem
fields = ...
After creating the item in the CreateView, how do I go back to the user page
after I inserted the date? I have lost the date in the new URL.
If I use URL resolver to go to userPage, how do I pass a date in the format?
It would be nice that I am able to pass initial values in the CreateView, and
make some fields read-only, how do I modify/override CreateView ?
Many Thanks for your help!
I have found an answer to my problem: using request.session
to store a value and retrieving in other views, it works fine.
I am still curious to know if there are experts who
would provide a more elegant solution, and if someone
could be so kind to answer point 2) regarding CreateView read_only fields
Thanks
D

How do I get the List Filters similar to the ones in admin console in my list views?

I am writing my first django app and in one of my ListView there will be hundreds of records. I want the user to be able to filter the result. The filter widget in the admin console looks intuitive for any kind of user. But the problem is, I am not sure how I can get them in my List Views.
I have briefly looked into the django-filters. But, I am not sure if it can help me achieve similar functionalities.
I think you can try like this:
in Template of the list view:
<form path="{% url 'your_list_view' %}" method="GET">
<label for="search">Search: </label>
<input type="text" name="search">
<input type="submit" value="OK">
</form>
Now use this in ListView:
from django.db.models import Q
class YourListView(ListView):
search_fields = ['name', 'something_else']
...
def get_querset(self, *args, **kwargs):
queryset = super(YourListView, self).get_queryset(*args, **kwargs)
search = request.GET.get('search')
if search:
query = list()
for f in self.search_fields:
query.append(Q(**{f: search}))
return queryset.filter(*query)
return queryset

Django button in html template creates error that I can't solve

my code is simple but I admit I have taken it from the internet.
I want to update a model with a button click. Which model, it is chosen by the variable. So I have this html code:
<form action="{% url 'reception:update_status' slug=name %}" method="POST">
{% csrf_token %}
<input type="submit" value="has arrived">
</form>
This code is in url.py
re_path('update_status/(?P<slug>[-a-zA-Z0-9_]+)$', views.update_status, name='update_status'),
and in views.py
def update_status(request, slug):
if request.method == 'POST':
p = MyModel.objects.filter(name=slug)
p.status = 'is waiting'
p.update()
return redirect(request, 'home')
Now with the code like this it comes back with the following error when I click on the button!
Reverse for '<WSGIRequest: POST '/update_status/Name10'>' not found. '<WSGIRequest: POST '/update_status/Name10'>' is not a valid view function or pattern name.
and I have no clue what this means. Please help?
Thanks
For regex patterns in Django 2.0, you need to use re_path, instead of path for your url.
I solved this problem with pk rather than slugs.
I would recommend everyone who is new to Django to create an id field for every Model. Because that can then be used to pass parameters very easy. Slug is more complicated and makes life unnecessarily difficult!

How can i add several copies of one form and submit them with different data? Flask, WTForms

Hellow. I have a document database and flask app that gives me web-based opportunity to see the db's docs, add them and delete. Every doc has only it's number and name.
Usually I add the documents one by one, cause i have the WTForm -
class addDocForm(FlaskForm):
doc_name = StringField('Название документа', validators=[DataRequired()])
doc_number = StringField('Исходящий номер', validators=[DataRequired()])
the .html code -
<form action="" method="post" >
{{ form.hidden_tag() }}
<div class="row">
<label>{{ form.doc_name.label }}</label>
{{ form.doc_name(size=32) }}
</div>
<div class="row">
<label>{{ form.doc_number.label }}</label>
{{ form.doc_number(size=32) }}
</div>
<div class="row">
<button type="submit">Добавить</button>
</div>
</form>
and some /route logic -
#app.route('/add_doc', methods=['GET', 'POST'])
#login_required
def add_doc():
form = addDocForm()
if form.validate_on_submit():
doc = Doc(doc_name=form.doc_name)
if Doc.query.filter_by(doc_name=form.doc_name.data).first() == None:
db.session.add(doc)
db.session.commit()
So I add each document one by one filling this form and submitting it again and again. Now i've been tired. I want to save my energy by reducing number of clicking on submit button. Of course it's a joke, but the question is really about thing like this:
how can i add several copies of this 'addDocForm' on one page, fill the fields of these copies and click submit only once?
Is there any clever way to do that? I want to add for example 5-7 docs at once without the necessity to add them one by one. Let's suppose i've load the page with my form (one form) fill the fields, and than clicked '+' button and there appear another form.. fill the fields-> '+' button .. again. After all click the 'submit' button and all the data from filled fields by turns go to the data base. Is it real? any ideas? p.s. i have an idea on how to make it using clear sql + html + js, without flask-wtforms, sqalchemy and so on.. but i guess this is wrong way cause half of my app is already written using them. ) so many text, don't sure if anyone reach this point.. but still - help me, pls (((((
You could construct a MegaForm using field enclosures.
For example (untested):
from wtforms import StringField, FormField, FieldList
class AddDocForm(FlaskForm):
doc_name = StringField('Название документа', validators=[DataRequired()])
doc_number = StringField('Исходящий номер', validators=[DataRequired()])
class MegaForm(FlaskForm):
documents = FieldList(FormField(AddDocForm), min_entries=7, max_entries=7)
#app.route('/add_doc', methods=['GET', 'POST'])
#login_required
def add_doc():
form = AddDocForm()
if form.validate_on_submit():
for idx, data in enumerate(form.documents.data):
doc = Doc(doc_name=data["doc_name"])
if Doc.query.filter_by(doc_name=data["doc_name"]).first() == None:
db.session.add(doc)
db.session.commit()

Django 1.11 CreateView adding datepicker for datefields

So I am trying to change my form's model Datefield output to the Datepicker similar to DatepickerWidget in CreateView
The forms are generated using a html template:
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">{{ field.error }}</span>
</div>
<label class="control-label col-sm-2">{{field.label_tag}}</label>
<div class="col-sm-10">{{field}}</div>
</div>
{% endfor %}
Here is the Views with what I tried:
class newenv_form(generic.CreateView):
model = Environment
fields =['name', 'description', 'creation_date', 'status','status_update_date']
template_name = 'catalogue/new_env.html'
#Does not work
def get_form(self, form):
form = super(newenv_form, self)
form.fields['creation_date','status_update_date'].widget = forms.DateInput(attrs={'class':'datepicker'})
return form
Here is what worked but it is a dropdown datepicker that is limited in choices
def get_form(self):
'''add date picker in forms'''
from django.forms.extras.widgets import SelectDateWidget
form = super(EnvironmentCreateView, self).get_form()
form.fields['creation_date'].widget = SelectDateWidget()
return form
Note that I remove form_class which was causing problems
UPDATE: On Django 3.1, you can find SelectDateWidget within django.forms.widgets
Try to change the following line in the method get_form:
form = super(newenv_form, self)
to:
form = super(newenv_form, self).get_form(form)
And please follow the conventions and use PascalCase for class names in python.
You could call this class EnvironmentCreateView. Further generic view classes could be called for example EnvironmentListView, EnvironmentDetailView, EnvironmentUpdateView, EnvironmentDeleteView.
Using the same pattern for all your model classes will produce comprehensible code.
EDIT (2017-10-24):
Regarding your comment here is a further explanation. Although it is hard to give a correct remote diagnosis, I'd suggest the following changes:
class EnvironmentCreateView(generic.CreateView):
# class attributes ...
def get_form(self, form_class=None):
form = super(EnvironmentCreateView, self).get_form(form_class)
# further code ...
The essential changes are in bold. The class name is changed to meet the conventions. Also the parameter form is changed to form_class to meet the convetions, too. I emphasise conventions in particular, because it makes the code very comprehensible to other people familiar with the framework.
The important change is that form_class has the initial value None.
That should solve the problem with the error.
In the body of the method you call the parent method with super and write after that your custom code.
Please check the documentation for generic.CreateView. It inherits, among others, from generic.FormMixin. That is the class with the method get_form.