I'm trying to test redirect from a form using POST. I'm following the mdn web doc tutorial for Django and thought I would see if I could do some of my own test which are not in the tutorial. I'm try to test the book creation form in the tutorial to see if it redirects to book details page which it should do automatically because it's a class based generic view. It works correctly when I test locally but I cannot get the testcase to pass. Thanks for the help in advance.
This is the error:
> =====================================================================
FAIL: test_redirects_to_book_details_on_success (catalog.tests.test_views.BookCreateView)
> ----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\makor\mypythonscripts\django_projects\locallibrary\catalog\tests\test_views.py", line 399, in test_redirects_to_book_details_on_success
self.assertRedirects(response, reverse('book-detail', kwargs={'pk': 1}))
File "C:\Users\makor\mypythonscripts\django_projects\venv\lib\site-packages\django\test\testcases.py", line 512, in assertRedirects
self.assertEqual(
AssertionError: 200 != 302 : Response didn't redirect as expected: Response code was 200 (expected 302)
----------------------------------------------------------------------
Ran 1 test in 0.341s
FAILED (failures=1)
Destroying test database for alias 'default'...
models.py
from django.db import models
from django.urls import reverse # Used to generate URLs by reversing the URL pattern.
from django.contrib.auth.models import User
from datetime import date
import uuid # Required for unique book instances
class Genre(models.Model):
"""Model representing book genre."""
name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')
def __str__(self):
"""String representation of Model object."""
return self.name
class Language(models.Model):
"""Model representing a langauge."""
LANGUAGES = (
('English', 'English'),
('French', 'French'),
('German', 'German'),
('Spanish', 'Spanish'),
)
language = models.CharField(max_length=20, choices=LANGUAGES, blank=False, default='English', help_text='Availble languages')
def __str__(self):
"""String representation of Model object."""
return self.language
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)."""
title = models.CharField(max_length=200)
# Foreign Key used because book can only have one author, but author can have multiply books.
author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book.')
isbn = models.CharField('ISBN', max_length=13, unique=True,
help_text='13 character ISBN number')
# ManyToManyField used because genre can contain many books. Books can cover many genres.
# Genre class has already been defined, so we can specify the object above.
genre = models.ManyToManyField('Genre', help_text='Select a genre for this book.')
language = models.ManyToManyField('Language', help_text='Select the langauge.')
def __str__(self):
"""String for representing the Model object."""
return self.title
def get_absolute_url(self):
"""Returns the URL to access a detail record for this book."""
return reverse('book-detail', args=[str(self.id)])
def display_genre(self):
"""Create a string for the Genre. This is required to display genre in Admin."""
return ', '.join(genre.name for genre in self.genre.all()[:3])
display_genre.short_description = 'Genre'
class BookInstance(models.Model):
"""Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4,
help_text='Unique ID for this particular book across the whole library.')
book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200, blank=True)
due_back = models.DateField(null=True, blank=True)
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
LOAN_STATUS = (
('m', 'Maintenance'),
('o', 'On loan'),
('a', 'Available'),
('r', 'Reserved'),
)
status = models.CharField(
max_length=1,
choices=LOAN_STATUS,
blank=True,
default='m',
help_text='Book availability',
)
class Meta:
ordering = ['due_back']
permissions = (('can_mark_returned', 'set book as returned'),)
def __str__(self):
"""String representing the Model object."""
return f'{self.id} ({self.book.title})'
#property
def is_overdue(self):
"""Determines if the book is overdue based on due date and current date."""
return bool(self.due_back and date.today() > self.due_back)
class Author(models.Model):
"""Model representing an author."""
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('died', null=True, blank=True)
class Meta:
ordering = ['last_name', 'first_name']
def get_absolute_url(self):
"""Returns the URL to access a particular author instance."""
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
"""String representing the Model object."""
return f'{self.last_name}, {self.first_name}'
views.py
import datetime
from django.views import generic
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse, reverse_lazy
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.decorators import login_required, permission_required
from . models import Book, Author, BookInstance, Genre, Language
from . forms import RenewBookForm
def index(request):
"""View function for home page of site."""
# Generate counts of some main objects.
num_books = Book.objects.all().count()
num_instances = BookInstance.objects.all().count()
# Available books (status = 'a')
num_instances_available = BookInstance.objects.filter(status__exact='a').count()
# The 'all()' is implied by default.
num_authors = Author.objects.count()
# Number of visits to this view, as counted in the session variable.
num_visits = request.session.get('num_visits', 0)
request.session['num_visits'] = num_visits + 1
# Generate counts for genre.
num_genre = Genre.objects.all().count()
# Filter books containing a particular word.
title = 'game of thrones'
num_title = Book.objects.filter(title__contains=title.lower()).count()
context = {
'num_books': num_books,
'num_instances': num_instances,
'num_instances_available': num_instances_available,
'num_authors': num_authors,
'num_genre': num_genre,
'num_title': num_title,
'num_visits': num_visits,
}
# Render the HTML template index.html with the data in the context variable.
return render(request, 'index.html', context=context)
class BookListView(generic.ListView):
model = Book
paginate_by = 10
class BookDetailView(generic.DetailView):
model = Book
class AuthorListView(generic.ListView):
model = Author
paginate_by = 10
class AuthorDetailView(generic.DetailView):
model = Author
class LoanedBooksByUserListView(LoginRequiredMixin, generic.ListView):
"""Generic class-based view listing books on loan to current user."""
model = BookInstance
template_name = 'catalog/bookinstance_list_borrowed_user.html'
paginate_by = 10
def get_queryset(self):
return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')
class BorrowedListView(PermissionRequiredMixin, generic.ListView):
"""Generic class-based view listing all books, requires correct permissions."""
permission_required = 'catalog.can_mark_returned'
model = BookInstance
template_name = 'catalog/borrowed_list.html'
paginate_by = 10
def get_queryset(self):
return BookInstance.objects.filter(status__exact='o').order_by('due_back')
#login_required
#permission_required('catalog.can_mark_returned', raise_exception=True)
def renew_book_librarian(request, pk):
book_instance = get_object_or_404(BookInstance, pk=pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = RenewBookForm(request.POST)
# Check if the form is valid
if form.is_valid():
# Process the data in form.cleaned_data as required (here we just write it to the model due_back field)
book_instance.due_back = form.cleaned_data['renewal_date']
book_instance.save()
# Redirect to a new URL:
return HttpResponseRedirect(reverse('all-borrowed'))
else:
context = {
'form': form,
'book_instance': book_instance,
}
return render(request, 'catalog/book_renew_librarian.html', context)
# If this is a GET (or any other method) create the default form.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookForm(initial={'renewal_date': proposed_renewal_date})
context = {
'form': form,
'book_instance': book_instance,
}
return render(request, 'catalog/book_renew_librarian.html', context)
class AuthorCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
permission_required = 'catalog.can_mark_returned'
model = Author
fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death']
initial = {'date_of_death': '11/06/2022'}
class AuthorUpdate(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
permission_required = 'catalog.can_mark_returned'
model = Author
fields = '__all__' # Not recommended (potential security issue if more fields are added.)
class AuthorDelete(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
permission_required = 'catalog.can_mark_returned'
model = Author
success_url = reverse_lazy('authors')
class BookCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
permission_required = 'catalog.can_mark_returned'
model = Book
fields = ['title', 'author', 'summary', 'isbn', 'genre', 'language']
class BookUpdate(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
permission_required = 'catalog.can_mark_returned'
model = Book
fields = '__all__'
class BookDelete(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
permission_required = 'catalog.can_mark_returned'
model = Book
success_url = reverse_lazy('books')
test.py
class BookCreateView(TestCase):
def setUp(self):
# Create a user
test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD')
test_user1.save()
test_user2.save()
# Give test_user2 permission to create Book.
permission = Permission.objects.get(name='set book as returned')
test_user2.user_permissions.add(permission)
test_user2.save()
test_author = Author.objects.create(first_name='John', last_name='Smith')
test_genre = Genre.objects.create(name='Fantasy')
test_language = Language.objects.create(language='English')
test_author.save()
test_genre.save()
test_language.save()
self.author = Author.objects.get(id=1)
self.genre = Genre.objects.get(id=1)
self.language = Language.objects.get(id=1)
def test_redirect_if_not_logged_in(self):
response = self.client.get(reverse('book-create'))
# Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.url.startswith('/accounts/login/'))
def test_forbidden_if_logged_in_but_not_correct_permission(self):
login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK')
response = self.client.get(reverse('book-create'))
self.assertEqual(response.status_code, 403)
def test_logged_in_with_permission_to_create_book(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('book-create'))
# Check that it lets us login - this is our book and we have the right permissions.
self.assertEqual(response.status_code, 200)
def test_view_uses_correct_template(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.get(reverse('book-create'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'catalog/book_form.html')
def test_redirects_to_book_details_on_success(self):
login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD')
response = self.client.post(reverse('book-create'), {'title': 'Test',
'author': self.author,
'summary': 'It\'s all good',
'isbn': '1234567',
'genre': self.genre,
'language': self.language,
})
self.assertRedirects(response, reverse('book-detail', kwargs={'pk': 1}))
form template
{% extends 'base_generic.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit" />
</form>
{% endblock %}
Screenshot of form
I have been at this for about two days but I think my beginners luck has ran out. I can usually figure it out by myself after a while but not anymore as this is my first post. I think the data i am entering into the client.post is not working and giving the correct response object. Sorry if my lingo is not correct as I am new to all this.
First post here. I followed the project code in the Python Crash Course book, so if the code looks similiar that is why.
In my django project, I want all the users to be able to view everything on the website. However, I want to have only the users who entered information in the entries to be able to edit and delete said entries. I know I need to assign ownership, but I'm having difficulty assigning it. currently I have a default setting enabled. For some reason if I take out the default it messes everything up.
urls.py
"""Defines URL patterns for research_topic_database."""
# research_topic_database
from django.urls import path
from . import views
app_name = 'research_topic_database'
urlpatterns = [
# Home page
path('', views.index, name='index'),
# Page that shows all the research category topics.
path('topics/', views.topics, name='topics'),
# Detail page for a single research entry submission.
path('topics/<int:topic_id>/', views.topic, name='topic'),
# Page for adding a new research entry submission.
path('new_entry/<int:topic_id>/', views.new_entry, name='new_entry'),
# Page for editing an entry.
path('edit_entry/<int:entry_id>/', views.edit_entry, name='edit_entry'),
]
models.py
class Topic(models.Model):
"""A research category for topic submissions to fall under."""
category = models.CharField(max_length=19, choices=CATEGORY_CHOICES, blank=True)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return a string representation of the model."""
return self.category
class Entry(models.Model):
"""Entry information about a research topic."""
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
classification = models.CharField(max_length=10, choices=CLASSIFICTION_CHOICES, blank=True)
# The research_title identifies the entry in the database.
research_title = models.CharField(max_length=200)
research_topic_description = models.CharField(max_length=200)
# Explain the USMC requirements you want the research to fulfill.
extended_topic_description = models.TextField()
desired_objectives = models.TextField()
sponsor = models.CharField(max_length=100)
point_of_contact = models.CharField(max_length=100)
mailing_address = models.CharField(max_length=200)
email_address = models.CharField(max_length=50)
phone = models.CharField(max_length=12)
fax = models.CharField(max_length=12)
desired_completion_date = models.CharField(max_length=12)
available_funding = models.CharField(max_length=50)
comments = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
# connecting research entries to certain Users.
owner = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
# owner = models.ForeignKey(User, on_delete=models.CASCADE)
# owner = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Return a string representation of the model."""
return f"{self.research_title[:100]}..."
views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import Http404
from .models import Topic, Entry
from .forms import EntryForm
def index(request):
"""The home page for research_topic_database."""
return render(request, 'research_topic_database/index.html')
#login_required
def topics(request):
"""Show all research categories from TOPIC class."""
topics = Topic.objects.order_by('date_added')
context = {'topics': topics}
return render(request, 'research_topic_database/topics.html', context)
#login_required
def topic(request, topic_id):
"""Show a single research category from TOPIC class
and all its submission entries."""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by('-date_added')
context = {'topic': topic, 'entries': entries}
return render(request, 'research_topic_database/topic.html', context)
#login_required
def new_entry(request, topic_id):
"""Add a new entry submission under a research category."""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST':
# No data submitted; create a blank form.
form = EntryForm()
else:
# POST data submitted; process data.
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
new_entry.save()
return redirect('research_topic_database:topic', topic_id=topic_id)
# Display a blank or invalid form
context = {'topic': topic, 'form': form}
return render(request, 'research_topic_database/new_entry.html', context)
#login_required
def edit_entry(request, entry_id):
"""Edit an existing research entry submission."""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != 'POST':
# Initial request, pre-fill form with current entry.
form = EntryForm(instance=entry)
else:
# POST data submitted; process data.
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return redirect('research_topic_database:topic', topic_id=topic.id)
context = {'entry': entry, 'topic': topic, 'form': form}
return render(request, 'research_topic_database/edit_entry.html', context)
I'm attempting to save records into a model using the modelform (this part is working as intended) and delete records from the same model using a checkbox (can't figure this piece out). I am creating a comprehensive view so I'm not creating using a def variable(reqeust, id) function and my intention is to have both POST methods redirect back to the same page. How would I go about deleting a model record using a checkbox and POST? I will add an #login_required decorator later. Here is my code:
models.py:
class ReportDirectory(models.Model):
report_name = models.CharField(max_length=300, unique=True, blank=False)
report_desc = models.TextField()
report_type = models.CharField(max_length=300)
report_loc = models.TextField()
slug = models.SlugField(unique=True, max_length=300)
last_update = models.DateTimeField(null=True)
main_tags = models.CharField(max_length=300)
# Renames the item in the admin folder
def __str__(self):
return self.report_name
class Favorite(models.Model):
directory_user = models.ForeignKey(User, on_delete=models.CASCADE)
user_report = models.ForeignKey(ReportDirectory, on_delete=models.CASCADE)
favorited = models.BooleanField()
def __str__(self):
return str(self.directory_user)+" - "+str(self.user_report)
views.py:
from django.shortcuts import render,redirect
from django.views import generic
from .models import ReportDirectory, Favorite
from django.contrib.auth.models import User
from .forms import FavoriteForm
def report_directory(request):
favorite = Favorite.objects.filter(directory_user=request.user.id, favorited=True)
reports = ReportDirectory.objects.exclude(favorite__directory_user=request.user.id, favorite__favorited=True)
favform = FavoriteForm(initial={'directory_user':request.user,},)
context = {
'reports':reports,
'favorite':favorite,
'favform':favform
}
if request.method =='POST' and 'favorited' in request.POST:
form = FavoriteForm(request.POST)
if form.is_valid():
form.save()
return redirect('/report_directory')
else:
form = FavoriteForm()
elif request.method =='POST' and 'deletefav' in request.POST:
Favorite.objects.filter(id=id).delete()
return redirect('/report_directory')
return render(request, 'counter/report_directory.html',context)
I am writing the create method for a post, which belongs to a group and a user. I keep getting this error, although this is how it's done in the documentation and every other source i've looked at. here are the relevant files:
models.py
from django.db import models
from django.contrib.auth.models import User, Group
class Post(models.Model):
title = models.CharField(max_length=255, unique=True)
body = models.TextField()
likes_total = models.IntegerField(default=0)
pub_date = models.DateTimeField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
def __str__(self):
return self.name
def summary(self):
# return the 1st 100 chars
return self.body[:100]
def pub_date_pretty(self):
# strftime is how to break down time
return self.pub_date.strftime('%b %e %Y')
posts.urls.py
from . import views
from django.urls import path
app_name = 'posts'
urlpatterns = [
path('create/<int:group_id>/', views.create, name='create'),
]
posts.views.py
def create(request, group_id):
group = get_object_or_404(Group, pk= group_id)
if request.method == 'POST':
if request.POST['body']:
post = Post()
post.title = request.POST['title']
post.body = request.POST['body']
post.pub_date = timezone.datetime.now()
post.author = request.user
post.group = group
post.save()
return redirect('/groups/' + str(group_id))
else:
return render(request, 'groups/detail.html', {'group':group})
else:
return render(request, 'groups/detail.html', {'group':group})
the error is thrown when I am saving the group to the post (post.group = group), although this the method that ive learned. Hope someone can figure this out. thanks!!
You seem to have mixed up two different Group models. In your model you have:
from django.contrib.auth.models import User, Group
and in your view you have:
from groups.models import Group
These are completely different classes, which is why you get an error telling you that you can't assign groups.models.Group to what should be a auth.models.Group.
I'm not sure which you intend it to be, but you need to use the same class in both places.
I'm confused about how to pull in related information from two different tables.
If I go to localhost:8000/user/username, it should display the users profile and user reviews below. because the username is being passed through the URL into the views function. Is that correct?
Also, is it required that I use foreign key to accomplish this? I've read the docs and I'm still not completely sure how a foreign key would help me accomplish my goal here.
Models
from django.db import models
class User(models.Model):
name = models.CharField(max_length=20)
reviewer = models.CharField(max_length=20)
password = models.TextField()
zipcode = models.Charfield(max_length=100)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
return self.name
class UserReview(models.Model):
name = models.ForeignKey(User)
author = models.CharField(max_length=50)
pub_date = models.DateField()
stars = models.IntegerField(max_length=5)
comment = models.CharField(max_length=100)
def __str__(self): # __unicode__ on Python 2
return self.name
Views
from django.shortcuts import render
def index(request):
profile_info = User.objects.filter(name=username)
context = {‘profile_info’: profile_info}
latest_reviews = UserReview.objects.filter(name=username).order_by('-pub_date')[:5]
context = {‘profile_info’: profile_info, 'latest_reviews': latest_reviews}
return render(request, 'randomtemplate.html', context)
URLS
urlpatterns = patterns('',
url(r'^user/(?P<username>\w+)/', 'index'),
)
There's a couple things you need to do. The first is that you need to pass the parameter into the view function, and then you'll need to display the ratings in the template.
#view
def index(request, username):
profile_info = User.objects.filter(name=username)
latest_reviews = UserReview.objects.filter(name=username).order_by('-pub_date')[:5]
context = {‘profile_info’: profile_info, 'latest_reviews': latest_reviews}
return render(request, 'randomtemplate.html', context)
# randomtemplate.html
{{ username }}
{{ latest_reviews }}