Django multiple field search form - python

I have a query form which gets input from 3 fields and display the related matched content from the database models.
But i am trying to select only one field from the three fields on the form and need to get data from database models related to that field?
views.py
from django.shortcuts import render
from search.forms import ModuleForm
from django.http import HttpResponse
from search.models import Module,Metamodule,Release
def searchview(request):
if request.method == 'GET':
form = ModuleForm(request.GET)
if form.is_valid():
release_num = form.cleaned_data['release_num']
metamodule_name = form.cleaned_data['metamodule_name']
module_name = form.cleaned_data['module_name']
results = Module.objects.filter(metamodule__release__number=release_num).filter(metamodule__name=metamodule_name).filter(name=module_name)
return render(request,'search/search_result.html',{'form': form, 'results': results})
else:
form = ModuleForm()
return render(request, 'search/search_form.html',{'form': form})
forms.py
from django import forms
from search.models import Module,Release,Metamodule
class ModuleForm(forms.Form):
release_num = forms.ModelChoiceField(queryset=Release.objects.all(),empty_label='Pick a Release')
metamodule_name = forms.ModelChoiceField(queryset=Metamodule.objects.all(),empty_label='Pick a Meta module')
module_name = forms.ModelChoiceField(queryset=Module.objects.all(),empty_label='Pick a Module')
def clean_release_number(self):
try:
release_num = self.cleaned_data.get["release_num"]
metamodule_name = int(self.cleaned_data["metamodule_name"])
module_name = int(self.cleaned_data["module_name"])
except:
release_num = None
metamodule_name = None
module_name = None
if release_num and Module.objects.exclude(metamodule__release__number=release_num).exists():
raise forms.ValidationError("Please enter a valid release number.")
else:
return release_num
How to modify the view to accept single input and display the data even though the other two fields are not provided with data?

Consider checking for each field and filtering on that field value individually.
if form.is_valid():
release_num = form.cleaned_data['release_num']
metamodule_name = form.cleaned_data['metamodule_name']
module_name = form.cleaned_data['module_name']
results = Module.objects.all()
if release_num:
results = results.filter(metamodule__release__number=release_num)
if metamodule_name:
result = results.filter(metamodule__name=metamodule_name)
if module_name:
result = results.filter(name=module_name)
return render(request,'search/search_result.html',{'form': form, 'results': results})
It's up to you to validate the input using your clean_fieldname() methods. Consider using def clean(self) to perform multi-field validation (like having at least one field filled in).
Why are you processing your form input on GET? Is there a reason why you're submitting form data with GET and ignoring POST?
Also, while it's good that you're checking form.is_valid() and implementing clean_fieldname methods, you need to add an else clause to if form.is_valid(): that handles form.errors.

Related

How To Update Specific Model Field From Django View Before Saving A Form

So, How can I update some Model Fields automatic, without the user having to input the values?
In Models:
class Url(models.Model):
long_url = models.CharField("Long Url",max_length=600)
short_url = models.CharField("Short Url",max_length=7)
visits = models.IntegerField("Site Visits",null=True)
creator = models.ForeignKey(CurtItUser,on_delete=models.CASCADE,null=True)
def __str__(self):
return self.short_url
In Views:
def home(request):
"""Main Page, Random Code Gen, Appendage Of New Data To The DB"""
global res,final_url
if request.method == 'POST':
form = UrlForm(request.POST)
if form.is_valid():
res = "".join(random.choices(string.ascii_uppercase,k=7))
final_url = f"127.0.0.1:8000/link/{res}"
form.save()
redirect(...)
else:
form = UrlForm
return render(...)
Sow how can for exapmle set from my view the value of short_url to final_url ???
You can get the data you need from the form.
you need to get the specific instance first, then you can use that instance to save values from the form.
And do not forget to save!
url_instance = get_object_or_404(Url, pk=pk)
url_instance.short_url = form.cleaned_data['short_url']
url_instance.long_url = form.cleaned_data['long_url']
url_instance.visits = form.cleaned_data['visits']
url_instance.save()
You can find more detailed infromations in the Django Documentation.

Checking if input from a form is in table django

The goal is to take in input from a user and see if that input matches information in a table in a database. If there is a match, delete that information from the table. If there isn't a match, clear out the form and say that there isn't a match.
Here is the views file.
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import CheckoutForm, CheckoutRegForm
from .models import Checkout
from books.models import Addbook
def checkout(request):
if request.method == 'POST':
form = CheckoutForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, f'The book has been checked out.')
return redirect('front-page')
else:
form = CheckoutForm()
return render(request, 'checkout/checkout.html', {'form': form})
def checkin(request):
if request.method == 'POST':
form = CheckoutRegForm(request.POST)
if form.is_valid():
title = form.cleaned_data['title']
member_id = form.cleaned_data['member_id']
if title in Checkout.objects.all():
Checkout.objects.filter(title = title).delete()
messages.success(request, f'The book has been checked in.')
return redirect('front-page')
else:
messages.error(request, f'Error: This book is not in the Checkout table.')
return redirect('checkin')
else:
form = CheckoutRegForm()
return render(request, 'checkout/checkin.html', {'form': form})
From my understanding, forms in Django collect user information and store it in a dictionary 'form.cleaned_data'. I tired multiple ways to set up that if statement to see if there is a match with the input from the user and information in the Checkout table. But no matter how I set up the if statement, it seems to always go to the 'else' part whenever I test it out. So basically even when I type a title that I know is in the checkout table, it doesn't get deleted and it gives me the error message of "This book is not in the Checkout table".
Here is my forms.py
from .models import Checkout
from django.forms import ModelForm
from django import forms
class CheckoutForm(ModelForm):
# member_id = forms.IntegerField()
class Meta:
model = Checkout
fields = [
'title',
'member_id',
]
class CheckoutRegForm(forms.Form):
member_id = forms.IntegerField()
title = forms.CharField(max_length = 1000)
# date_checkout = forms.DateTimeField(auto_now_add = True)
Ignore the comments. Here is the modules file if you need that.
from django.db import models
class Checkout(models.Model):
member_id = models.IntegerField(null = True)
title = models.CharField(max_length = 1000)
date_checkout = models.DateTimeField(auto_now_add = True)
Thanks for any help. If you need any other information or not really sure what I'm asking, just let me know.
Your problem is in
if title in Checkout.objects.all():
Checkout.objects.all() will return list of objects, when U compare string data from your form. So it's must be something like that:
for object in Checkout.objects.all():
if object.title == title:
do something...

How to pass data between django views

This questions addresses my question genearally, but I am looking for a more specific explanation.
I would like a user to update a a group of model objects, however, the queryset for these objects will need to be retrieved first. My plan is to do this in two seperate URs/views, getting the query set info from the first, then displaying the model formset to be updated next.
My first view gives a list of all the the "Project"s (One of my models), and retrieves the id of the project selected.
Here is the form:
class ProjectLookupForm(forms.Form):
Project_Name = chosenforms.ChosenModelChoiceField(queryset=Project.objects.all())
and here is the view:
def update_project_filter(request):
project_form = ProjectLookupForm(request.POST or None)
if request.method == 'POST':
if project_form.is_valid():
context = {"project_form":project_form}
# Get project here and share it with the next view.
selected_project_id = project_form.cleaned_data["Project_Name"].id
# Add a new return statement here?
# Or call update project view from here?
# Add a redirect button to html?
else:
errors = project_form.errors
context = {"errors":errors, "project_form":project_form}
else:
context = {"project_form":project_form}
return render(request, 'filter_update_project_form.html', context)
As one can see, I have included some comments brainstorming what my possibilities are. My goal is to send the selected_project_id to this next view, so that it can use that id as a model form query set.
def update_project(request):
UpdateFormset = modelformset_factory(Sample, fields=("sample_name", "extraction_date",
"project", "order", "notebook", "notebook_page"))
if request.method == 'POST':
formset = UpdateFormset(request.POST, request.FILES)
if formset.is_valid():
formset.save()
context = {"formset": formset, "project_form":project_form}
else:
errors = formset.errors
context = {"formset":formset, "errors":errors, "project_form":project_form}
else:
formset = UpdateFormset(queryset=Sample.objects.filter(project=2))
context = {"formset":formset, "project_form":project_form}
return render(request, 'update_project_form.html', context)
One can see here that I have hard coded the queryset like so:
queryset=Sample.objects.filter(project=2)
How can I set "project=" to my selected_project_id? Do I pass this info to the view as an input parameter? Or do I send it to the next URL and take it from there?
Assuming you've activated django.contrib.sessions.middleware.SessionMiddleware; you can pass data between views using request.session dictionary as follows:
def update_project_filter(request):
...
selected_project_id = project_form.cleaned_data["Project_Name"].id
request.session['selected_project_id'] = selected_project_id
...
def update_project(request):
...
selected_project_id = request.session.get('selected_project_id')
...

Tango With Django Chapter 8 - add_page issue

I am currently going through the Tango with Django tutorial, finishing up Chapter 8:
http://www.tangowithdjango.com/book17/chapters/forms.html
I finally got everything to work, the last issue I have is the add_page form's handling of a url that did not start with 'http://', and I'm not quite sure why. No errors arise with the actual web app, I simply get the "Please enter URL" tool-tip window if I do not include it in the URL, and processes correctly once I add in the 'http://' myself.
These are my files:
#VIEWS.PY
from django.http import HttpResponse
from django.shortcuts import render
from rango.models import Category
from rango.models import Page
from rango.forms import CategoryForm
from rango.forms import PageForm
def index(request):
# Query the database for a list of ALL categories currently stored.
# Order the categories by no. likes in descending order.
# Retrieve the top 5 only - or all if less than 5.
# Place the list in our context_dict and dictionary which will be passed to the template engine.
category_list = Category.objects.order_by('-likes')[:5]
page_list = Page.objects.order_by('-views')[:5]
context_dict = {'categories': category_list, 'pages': page_list}
# Render the response and send it back!
return render(request, 'rango/index.html', context_dict)
def about(request):
context_dict = {'italicmessage': "I am italicised font from the context"}
return render(request, 'rango/about.html', context_dict)
def category(request, category_name_slug):
# Create a context dictionary which we can pass to the template rendering engine
context_dict = {}
try:
# Can we find a category name slug with the given name?
# If we can't, the .get() method raises a DoesNotExist exception.
# So the .get() method returns one model instance or raises an exception.
category = Category.objects.get(slug=category_name_slug)
context_dict['category_name'] = category.name
# Retrieve all the associated pages.
# Note that filter returns >= 1 model instance.
pages = Page.objects.filter(category=category)
# Adds our results list to the template context under name pages.
context_dict['pages'] = pages
# We also add the category object from the database to the context dictionary.
# We'll use this in the template to verify that the category exists.
context_dict['category'] = category
context_dict['category_name_slug'] = category_name_slug
except Category.DoesNotExist:
# We get here if we didn't find the specified category.
# Don't do anything - the template displayes the "no category message for us."
pass
# Go render the response and return it to the client.
return render(request, 'rango/category.html', context_dict)
def add_category(request):
# A HTTP POST?
if request.method == 'POST':
form = CategoryForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# save the new category to the database.
form.save(commit=True)
# Now call the index() view.
# The user will be shown the homepage.
return index(request)
else:
# The supplied form contained errors - just print them to the terminal.
print form.errors
else:
# If the request was not a POST, display the form to enter details.
form = CategoryForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render(request, 'rango/add_category.html', {'form': form})
def add_page(request, category_name_slug):
try:
cat = Category.objects.get(slug=category_name_slug)
except Category.DoesNotExist:
cat = None
if request.method == 'POST':
form = PageForm(request.POST)
if form.is_valid():
if cat:
page = form.save(commit=False)
page.category = cat
page.views = 0
page.save()
return category(request, category_name_slug)
else:
form = PageForm()
context_dict = {'form': form, 'category': cat, 'category_name_slug': category_name_slug}
return render(request, 'rango/add_page.html', context_dict)
And...
# FORMS.PY
from django import forms
from rango.models import Page, Category
class CategoryForm(forms.ModelForm):
name = forms.CharField(max_length=128, help_text="Please enter the category name.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
slug = forms.CharField(widget=forms.HiddenInput(), required=False)
# An inline class to provide additional information on the form.
class Meta:
# Provide an association between the ModelForm and a model
model = Category
fields = ('name',)
class PageForm(forms.ModelForm):
title = forms.CharField(max_length=128, help_text="Please enter the title of the page.")
url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
class Meta:
model = Page
exclude = ('category',)
# or specify the fields to include (.i.e. not include the category field)
#fields = ('title', 'url', 'views')
def clean(self):
cleaned_data = self.cleaned_data
url = cleaned_data.get('url')
# If url is not empty and doesn't start with 'http://', prepend 'http://'.
if url and not url.startswith('http://'):
url = 'http://' + url
cleaned_data['url'] = url
return cleaned_data
I suspect it is something with my PageForm class in forms.py, but I seem to have just replicated the code as shown in the tutorial.
Thanks in advance for your help!
A quick look at the Django source shows why this happens:
in core/validators.py
class URLField(CharField):
default_validators = [validators.URLValidator()]
description = _("URL")
You see the default validator for URLField is the URLValidator which can be found in core/validators.py:
#deconstructible
class URLValidator(RegexValidator):
regex = re.compile(
r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?<!-)\.?)|' # domain...
r'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
message = _('Enter a valid URL.')
schemes = ['http', 'https', 'ftp', 'ftps']
def __init__(self, schemes=None, **kwargs):
super(URLValidator, self).__init__(**kwargs)
if schemes is not None:
self.schemes = schemes
def __call__(self, value):
value = force_text(value)
# Check first if the scheme is valid
scheme = value.split('://')[0].lower()
if scheme not in self.schemes:
raise ValidationError(self.message, code=self.code)
# Then check full URL
try:
super(URLValidator, self).__call__(value)
except ValidationError as e:
# Trivial case failed. Try for possible IDN domain
if value:
scheme, netloc, path, query, fragment = urlsplit(value)
try:
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
except UnicodeError: # invalid domain part
raise e
url = urlunsplit((scheme, netloc, path, query, fragment))
super(URLValidator, self).__call__(url)
else:
raise
else:
url = value
So as you can see here if the url prefix is not found in schemes then your URLField won't validate. Specifically in __call__ it splits the supplied input with scheme = value.split('://')[0].lower() to get the prefix. Your best bet is to create your own custom validation if you don't want to use this one. You might want to read: https://docs.djangoproject.com/en/dev/ref/forms/validation/ for more information.
Thats because you work with that url form field ... If you want pass a URL without http:// then you can take a normal char Form field
Behind that URL field is a validation, you can change that validation when you want to work with an URL field
Hope that helps
An URLField uses URLValidator for validation.
From it's source:
scheme = value.split('://')[0].lower()
if scheme not in self.schemes:
raise ValidationError(self.message, code=self.code)
and per default:
schemes = ['http', 'https', 'ftp', 'ftps']
So obviously, only absolute URLs are allowed.
See also this rejected feature request.

How to append a new wtforms FormField with initial data as default?

I have a form with wtform, I want to add a new form JobItemForm to my form JobForm using append_entry. JobItemForm has selectField named company. I add choice field data via model like this
form.jobs[0].company.choices = company_list
now I use append_entry without any choices and I recieve an error. So how can I call append_entry with some initial data?
class JobItemForm(Form):
company = SelectField(_('company'), description=_('new company"'))
title = TextField(_('title'), [validators.Length(min=4, max=250)])
date_from = DateField(_("date_from"), format='%Y-%m-%d')
date_to = DateField(_("date_to"), format='%Y-%m-%d')
description = TextAreaField(_('description'))
class JobForm(Form):
jobs = FieldList(FormField(JobItemForm), min_entries=3)
add_job = SubmitField(_('Add job'))
some thing like this
#mod.route('/edit/', methods=['GET', 'POST'])
#login_required
def edit_job():
"""
edit job
"""
company_list = Company.Company_list()
form_title = "Edit job Form"
if request.method != 'POST':
form = JobForm()
form.jobs[0].company.choices = company_list
return render('form.html', form=form, form_title=form_title)
form = JobForm(request.form)
if form.add_job.data:
new_item_job = form.jobs.append_entry()
new_item_job.company.choices = company_list
return render('form.html', form=form, form_title=form_title)
form.validate
if form.errors != dict():
return render('form.html', form=form, form_title=form_title)
# save data
flash(_("Edit successfully!"))
return render('registration/succesful.html')
There is a better way to do this:
form.jobs[0].company.choices = company_list
Wtforms has extensions for GAE,Django and SQLAlchemy which supports orm backed form fields. Documentation of extensions.
For sqlalchemy, it works like this:
from wtforms.ext.sqlalchemy.fields import QuerySelectField
def fill_field():
return Model.query
myfield = QuerySelectField(query_factory=fill_field)
This snippet of code automatically fills the choices for you from the database model.
(I do not have you actual error so I am just guessing here)
The reason you are getting no choices error after add_job because you are filling choices only when it is a GET request. You need to add choices after a Post request too like this:
def your_view():
form = YourForm()
form.fieldname.choices = choice_list
# Here comes your code for GET and POST request both

Categories