Tango With Django Chapter 8 - add_page issue - python

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.

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 do i query model managers in my function based views for single_page(detail.html)

I'm finding it difficult to query a custom django model manager in my function based detail view. How can i resolve this?
I'm using django 2.2.
in my models.py file, i have this code below, which works perfectly. Querying for the listview is working fine and when i query the detail view using get_object_or_404(Modelname, id), the detail views works too but when i try to query it using my custom model manager, i keep getting this error
"movie_detail() got an unexpected keyword argument 'id'".
i've also tried removing--->
def get_absolute_url(self):
return reverse('core:movie_detail', args=[self.id,])
from my movie model when querying the model manager, but am still get same error
How can i resolve this?
my models.py
class MovieManager(models.Manager):
def all_with_related_persons(self):
qs = self.get_queryset()
qs = qs.select_related('director')
qs = qs.prefetch_related('writers', 'actors')
return qs
class Movie(models.Model):
NOT_RATED = 0
RATED_G = 1
RATED_PG = 2
RATED_R = 3
RATINGS = (
(NOT_RATED, 'NR - Not Rated'),
(RATED_G, 'G - General Audiences'),
(RATED_PG, ' PG - Parental Guidance' 'Suggested'),
(RATED_R, 'R - Restricted'),
)
title = models.CharField(max_length=140)
plot = models.TextField()
year = models.PositiveIntegerField()
rating = models.IntegerField(choices=RATINGS, default=NOT_RATED)
runtime = models.PositiveIntegerField()
website = models.URLField(blank=True)
director = models.ForeignKey(to='Person',
on_delete=models.SET_NULL,
related_name="directed",
null=True,
blank=True)
writer = models.ManyToManyField(to="Person", related_name="writing_credits", blank=True)
actors = models.ManyToManyField(to="Person", through="Role", related_name="acting_credits", blank=True)
objects = MovieManager()
def __str__(self):
return '{} ({})'.format(self.title, self.year)
def get_absolute_url(self):
return reverse('core:movie_detail', args=[self.id,])
class Meta:
ordering = ('-year', 'title',)
<--- Views.py --->
My List view
def movie_list(request):
object_list = Movie.objects.all()
paginator = Paginator(object_list, 12)
page_number = request.GET.get('page', 1)
try:
page = paginator.page(page_number)
except PageNotAnInteger:
# If page is not an integer deliver the first page
page = paginator.page(1)
except EmptyPage:
# If page is out of range deliver last page of results
page = paginator.page(paginator.num_pages)
context = {
'object_list': object_list,
'products': page,
'page': page,
}
template = 'core/movie_list.html'
return render(request, template, context)
DetailView without custom model manager.(This works)
def movie_detail(request, id):
object_list = get_object_or_404(Person, id=id)
context = {'movie': object_list}
template = 'core/person_detail.html'
return render(request, template, context)
DetailView with custom model manager.(This doesn't work. throws an error "movie_detail() got an unexpected keyword argument 'id'")
def movie_detail(request):
object_list = Movie.objects.all_with_related_persons()
context = {'movie': object_list}
template = 'core/movie_detail.html'
return render(request, template, context)
My url path to the detail_view
path('<id>/', views.movie_detail, name="movie_detail"),
I expect detail view to return queries based on what i queried in my custom model manager.
Your url pattern for the movie_detail view is passing an id kwarg to your view, so your view needs to accept this id as argument. Instead of
def movie_detail(request)
you should define
def movie_detail(request, id)
The error you see just says: movie_detail was called with keyword argument id, meaning it was called like this: movie_detail(request=the_request, id=some_id) which can't work if it's defined to only have one argument, request.
But also, since you're making a detail view for one object, you should probably do something with the id to select the specific object:
def movie_detail(request, id):
object_list = Movie.objects.all_with_related_persons()
try:
movie = object_list.get(id=id)
except Movie.DoesNotExist:
raise Http404
context = {'movie': movie}
template = 'core/movie_detail.html'
return render(request, template, context)

Saving forms and redirecting to a new URL in Django

I'm creating a project with django and am having trouble with some of the basics, my aim is to have a homepage with a search box. When a search term is entered, the server gathers data from various api's, saves the data to the database then directs the user to a results page which displays this data.
So far I have managed to create a page that takes the search term and saves the data to the database. However if I try to then redirect to a new page it breaks, specifically the data doesn't save to the database. I'm having real trouble finding information on what to do next.
I have a model:
class film(models.Model):
filmName = models.CharField(primary_key=True, max_length=120)
quotient = models.FloatField()
rating = models.FloatField()
gross = models.IntegerField()
star = models.CharField(max_length=120)
releaseDate = models.DateField()
def __unicode__(self):
return self.filmName
With a form:
class searchFilm(forms.ModelForm):
class Meta:
model = film
fields = ['filmName']
A two view methods:
def home(request):
title = "Culturnomicon"
form = searchFilm(request.POST or None)
if form.is_valid():
searchedFilm = form.save(commit = False)
fixedName = searchedFilm.filmName.replace(' ', '+')
url = 'http://api.themoviedb.org/3/search/movie?api_key=APIKEY&query='+fixedName
urlRequest = Request(url)
try:
reponse = urlopen(urlRequest)
filmData = reponse.read()
parsed_json = json.loads(filmData)
except URLError, e:
print "didnt work"
firstResult = parsed_json['results'][0]
filmId = str(firstResult['id'])
filmData = getFilmData(filmId)
if parsed_json['total_results'] != 0:
searchedFilm = saveFilm(searchedFilm, filmData)
searchedFilm.save()
context = {
"template_title": title,
"form": form,
}
return render(request, "home.html", context)
def results(request):
return render(request, "results.html", {})
and a template:
<form method="POST" action="{% url 'results' %}">
{{form}}
{% csrf_token %}
<input type="submit" value ="Search" class = 'btn'/>
</form>
Any advice on what to do next would be greatly appreciated.
I also have the feeling that having methods to gather the API information may be a bad idea, is it bad to do this and if so how can I fix it?
If all you really want to do is load a different template then you don't need to have a separate view, you just move the return render(..."results.html" inside of the if form.is_valid() statement after you've done all you need with the database.
You may need to extend the render call to include some context data about what results that template should actually display however.
If you do want a separate view, then you need to move the handling of the form to the other view.
def home(request):
title = "Culturnomicon"
form = searchFilm(request.POST or None)
if form.is_valid():
# Your logic
return render(request, "results.html", {})
context = {
"template_title": title,
"form": form,
}
return render(request, "home.html", context)

Django multiple field search form

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.

Categories