Show other contents besides a form in FormView? - python

I really dislike django CBV design which makes things without flexibility.
I would like to have a page whose upper part showing the content of objects and lower part has a form to be posted.
CBS formview
class EditStudent(FormView):
template_name = "editstudent.html"
model = models.Student
success_url = "/home"
Update:
I add the method but the error
'NoneType' object is not callable shows up.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['students'] = Student.objects.all()[:5]
return context
How can I retriev objects of studens and show them on the template.
Thanks.

'NoneType' object is not callable: I get this error when I don't specify a form class. Created form class 'StForm' associated with model 'Student'.
In the EditStudent view class, the CreateView class was inherited, since the data was not saved to the database with the FormView.
Replace bboard with the name of the folder where your templates are placed.
I have this: templates/bboard which are in the application folder.
template_name = 'bboard/tam_form.html'
The success_url row specifies a path based on the path name.
success_url = reverse_lazy('student')
The five most recent records are also transmitted in the context.
context['students'] = Student.objects.order_by('-pk')[:5]
In the template, the first five records are displayed on top and a form is displayed below to fill out.
forms.py
from django.forms import ModelForm
from .models import Student
class StForm(ModelForm):
class Meta:
model = Student
fields = '__all__'
views.py
from .models import Student
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .forms import StForm
class EditStudent(CreateView):
template_name = 'bboard/editstudent.html'
form_class = StForm
success_url = reverse_lazy('student')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['students'] = Student.objects.order_by('-pk')[:5]
return context
urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('student/', EditStudent.as_view(), name='student'),
]
editstudent.html
<h4>
{% for aaa in students %}
<p>{{ aaa }}</p>
{% endfor %}
</h4>
<h2>form</h2>
<form method="post" action="{% url 'student' %}">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="adding">
</form>

Related

Django UpdateView generating 'GET' request instead of 'POST'

I'm following along a book called Django for Beginners and creating a project which displays newspaper articles. Part of the functionality is being able to edit those articles. I've followed along as closely as I could but I'm still getting an error when hitting the 'Update' button:
My urls.py
from django.urls import path
from .views import (ArticleListView,
ArticleUpdateView,
ArticleDetailView,
ArticleDeleteView)
urlpatterns = [
path('<int:pk>/edit/', ArticleUpdateView.as_view(), name = 'article_edit'),
path('<int:pk>/', ArticleDetailView.as_view(), name = 'article_detail'),
path('<int:pk>/delete/', ArticleDeleteView.as_view(), name = 'article_delete'),
path('', ArticleListView.as_view(), name = 'article_list'),
]
my views.py
from django.shortcuts import render
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Article
# Create your views here.
class ArticleListView(ListView):
model = Article
template_name = 'article_list.html'
class ArticleDetailView(DetailView):
model = Article
template_name = 'article_detail.html'
class ArticleUpdateView(UpdateView):
model = Article
fields = ('title', 'body')
template_name = 'article_edit.html'
class ArticleDeleteView(DeleteView):
model = Article
template_name = 'article_delete.html'
success_url = reverse_lazy('article_list')
My models.py:
from django.db import models
from django.conf import settings
from django.contrib.auth import get_user_model
from django.urls import reverse
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=225)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
def get_absolute_url(self):
reverse('article_detail', args=[str(self.id)])
My HTML:
<!-- templates/article_edit.html -->
{% extends 'base.html' %}
{% block content %}
<h1>Edit</h1>
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}\
<button class="btn btn-info ml-2" type="submit">Update Article {{article.pk}}</button>
</form>
{% endblock content %}
After hitting the edit button, according to the book the app is supposed to forward me to the 'article_detail' page however that is not happening.
Any assistance would be gratefully received.
Thanks
Andy
In the end it was a simple omission of a return statement in the get_absolute_url function within models.py.

I am using CreateView in django but getting error- The view blog.views.PostCreateView didn't return an HttpResponse object. It returned None instead

I am having a post model with a user having OneToMany Relationship with inbuilt user model for authentication
my urls.py
from django.contrib import admin
from django.urls import path, include
# from views import PostView
from . import views
urlpatterns = [
path('', views.PostView.as_view(), name='blogHome'),
path('post/<int:pk>/', views.PostDetailView.as_view(), name='post-detail'),
path('post/new/', views.PostCreateView.as_view(), name='post-create'),
path('about/', views.about, name='about')
]
my views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Post
from django.views.generic import (
ListView,
DetailView,
CreateView
)
# Create your views here.
def home(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'blog/home.html', context)
def about(request):
return render(request, 'blog/about.html')
class PostView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name = 'posts'
ordering = ['-date_published']
class PostDetailView(DetailView):
model = Post
class PostCreateView(CreateView):
model = Post
fields = ['title', 'body']
#to add author before validation
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
Post Model
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
# Create your models here.
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
body = models.TextField()
date_published = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
I am using post_form.html as the template name
post_form.html
{% extends 'blog/layout.html' %}
{% load crispy_forms_tags %}
{% block body %}
<div class="content-section p-20">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group p-30">
<legend class="border-bottom mb-4">Create new post</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Create</button>
</div>
</form>
</div>
{% endblock %}
I am a beginner in Django , please tell if anything more is needed to solve the problem. Also why this type of error is coming only with Createview and not with other views
Apparently the return of super().form_valid(form) is None and not a valid response. I don't know much of this design pattern in Django but seeing your other methods seems like this view is decorated by some method which returns a valid response. So you should not return something in your implementation. So, drop the return and test again.

I'm getting a NoReverseMatch error with my delete view

I'm getting a NoReverseMatch error with my delete view. I think it is with my success url in my views.py but I don't know what to do I even put a kwargs with a pk key in the reverse lazy but still won't work.
Views.py:
from django.shortcuts import render
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.contrib import messages
from . import forms
from . import models
# Create your views here.
class AnnouncementListView(LoginRequiredMixin, generic.ListView):
model = models.Announcement
class AnnouncementDetailView(LoginRequiredMixin, generic.DetailView ):
model = models.Announcement
class AnnouncementUpdateView(LoginRequiredMixin, generic.UpdateView):
model = models.Announcement
form_class = forms.AnnouncementForm
class AnnouncementCreateView(LoginRequiredMixin, generic.CreateView ):
model = models.Announcement
form_class = forms.AnnouncementForm
class AnnouncementDeleteView(LoginRequiredMixin, generic.DeleteView ):
model = models.Announcement
success_url = reverse_lazy('announcement:single')
def delete(self, *args, **kwargs):
messages.success(self.request, "Post Deleted")
return super().delete(*args, **kwargs)
announcement_confirm_delete.html:
{% extends 'base.html' %}
{% block content %}
<div class="container">
<form method="post" action="{% url 'announcement:destroy' announcement.pk %}">
{% csrf_token %}
<h3>Are you sure you want to delete this?</h3>
<div class="row">
<input class='btn btn-danger' type="submit" value="Delete" />
Cancel
</div>
</form>
</div>
{% endblock %}
As the screenshot on your other question shows, announcement:single requires a pk argument, so you'd need to use a function to support that in a success_url.
Django's generic views have a get_success_url;
def get_success_url(self):
return reverse_lazy('announcement:single', kwargs={'pk': self.object.id})
However that's on a delete view, so on success you can't go to a view detailing the object you've just deleted, so it'd make more sense to go to the list view on deletion success.
Your succes ulr of AnnouncementDeleteView must be changed, now it's:
success_url = reverse_lazy('announcement:single')
I think it must be: success_url = reverse_lazy('announcement:list')
or add primary key: reverse_lazy('announcement:single', kwargs={'pk': desired_id})

Restricting/limiting number of times a model.foreignkey choice can be chosen in python django (fantasy sports website)

enter image description hereI am in the process of making a fantasy sports website. The django out-of-the-box User equals a team owner. On my blog/models.py file, I created a class of Player (aka an NBA player) with a foreignkey field of player_owner pointing back to User:
# blog/models.py
class Player(models.Model):
player_full = models.CharField(max_length=50)
player_owner = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
image = models.ImageField(default='default_player.jpg', upload_to='player_pics')
player_unit_value = models.PositiveSmallIntegerField(default=1, validators=[MinValueValidator(1),
MaxValueValidator(1)])
As you can see, I also have a field on the Player class called player_unit_value set permanently to 1. This, i think, will come into play with my question. I have a lot of the website set up: Free Agents Page, Team Dashboard, Player Profile, financials etc all built out with form validation to ensure players are "signed" and "dropped" by the authorized signed-in User (aka team owner).
# blog/views.py
class PlayersUpdateView(LoginRequiredMixin, UpdateView, Player):
model = Player
fields = []
template_name = 'blog/player_form.html' # <app>/<model>_<viewtype>.html
context_object_name = 'guys'
def form_valid(self, form):
form.instance.player_owner = self.request.user
return super().form_valid(form)
Thus, when you click "add player" on my site, that player's player_owner field is updated from "None" to the User. and the "Drop Player" button does the reverse.
So here is my question: How can I create a logic that limits the number of times a User (aka owner) can be selected as the player_owner to 15? Ideally, I'd like there to be some sort of form validation that --when initiated by a User who already has 15 players-- shows an invalid warning saying "Sorry, you already have your max number of players on your roster"
Here is another part that might be of use: I created a field on my Users/models.py Profile class called players_owned:
# users/models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
players_owned = models.PositiveSmallIntegerField(default=0,
validators=[MinValueValidator(0),
MaxValueValidator(15)]
)
I suspect there may be a way to += 1 to the User's players_owned field every time a Player is added to the User's team (aka every time the Player.player_owner field is updated from None to a User (aka Owner). But these two classes are on different model files.
I'm new to python and django, so my apologies in advance if this is a little nuts.
Updated code:
blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.models import User
from django.contrib import messages
from django.db.models import Sum, Q
from django.views.generic import (
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView)
from users.forms import ProfileUpdateForm
from .models import Post, Player
class PlayersUpdateView(LoginRequiredMixin, UpdateView, Player):
model = Player
fields = []
template_name = 'blog/player_form.html' # <app>/<model>_<viewtype>.html
def get(self, request, *args, **kwargs):
form = ProfileUpdateForm()
return render(request=request, template_name=self.template_name, context={
"form": form
})
def post(self, request, *args, **kwargs):
form = ProfileUpdateForm(request.POST)
if form.is_valid():
if request.user.players_owned <= 5:
form.instance.player_owner = self.request.user
request.user.players_owned = request.user.players_owned + 1
request.user.save()
return redirect("players-update")
else:
messages.error(request, "Sorry, you already have the max number of players on your roster")
return render(request, self.template_name, {'form': form})
Questions: Should I import model Profile from users/models.py? Maybe this def doesnt belong on the PlayerUpdateView?
blog/models.py: not changed
blog/player_form.html
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<head>
<script>
{% if messages %}
{% for message in messages %}
alert(message);
{% endfor %}
{% endif %}
</script>
</head>
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Adding {{ object.player_full }}</legend>
<img class="rounded-circle article-img" src="{{ object.image.url }}">
{{ form|crispy }}
<h4>Are you sure you want to add <kbd>{{ object.player_full }}</kbd> to your team?</h4>
</fieldset>
<div class="form-group">
<button class="btn btn-outline-success" type="submit">Add Player</button>
<a class="btn btn-outline-secondary" href="{% url 'players-detail' object.id %}">Cancel</a>
</div>
</form>
</div>
{% endblock content %}
users/models.py
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
players_owned = models.PositiveSmallIntegerField(default=0,
validators [MinValueValidator(0), MaxValueValidator(15)])
users/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import Profile
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
# when submitted creates ...user
model = User
# these are the fields we want on our form
fields = ['username', 'email', 'password1', 'password2']
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
class Meta:
# when submitted updates ...user
model = User
# these are the fields we want on our form
fields = ['username', 'email']
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['image', 'players_owned']
Question: Should I use the UserUpdateForm instead?
BTW, here is the latest error I got:
NoReverseMatch at /players/529/update/
Reverse for 'players-detail' with arguments '('',)' not found. 1
pattern(s) tried: ['players/(?P<pk>[0-9]+)/$']
Request Method: GET
Request URL: http://127.0.0.1:8000/players/529/update/
Django Version: 3.0.7
Exception Type: NoReverseMatch
Exception Value:
Reverse for 'players-detail' with arguments '('',)' not found. 1
pattern(s) tried: ['players/(?P<pk>[0-9]+)/$']
Just modify the form_valid element of your view to check against players_owned:
-----Edit
Here is the way I like to do my class based views, with get and post requests, and then you can access the request object. If this is too different from your original code, and you want to keep the form_valid function let me know and I can try to change it to be closer to what you have currently.
from django.contrib import messages
class PlayersUpdateView(LoginRequiredMixin, UpdateView, Player):
template_name = "template_name.html"
def get(self, request):
form = FORM_NAME()
return render(request=request, template_name=self.template_name, context={
"form": form
})
def post(self, request):
form = FORM_NAME(request.POST)
if form.is_valid():
if request.user.players_owned <= 15:
form.instance.player_owner = self.request.user
request.user.players_owned = request.user.players_owned + 1
request.user.save()
return redirect("wherever you want to go on success")
else:
messages.error(request, "Sorry, you already have your max number of players on your roster")
return render(request, self.template_name, {'form': form})
---end of edit
and then in your template add this to display messages, which is an error in this case. If you want to display the form again, use {{form.as_p}}:
<html>
<head>
<script>
{% if messages %}
{% for message in messages %}
alert(message);
{% endfor %}
{% endif %}
</script>
</head>
<body>
{{form.as_p}}
</body>
</html>

Django, Can't get delete button to work

I have a section of my site where an admin can add a widget. However the delete button to delete any current widgets is not working. I have this code implemented else where on my site and it is working.
I copied the same code from the other section of my site where this is being used. The other section of my site which uses this code is a little different in that a post can only be deleted be the user who created it, and the widgets can be delete by any user with the "access_level" field is equal to "admin". However, I am the only admin so it should still work. The page that the widget stuff is displayed on is only accessible if your "access_level" is equal to admin so I don't need to validate whether or not they have the permission before deleting. Please help.
widget_list.html:
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="content">
<div class="widgets-list">
{% for widget in widget_list %}
<h3>{{ widget.name }}</h3>
<h3>{{ widget.widget_order }}</h3>
<div>
<p>{{ widget.body }}</p>
</div>
{% if user.is_authenticated %}
<a class="auth-user-options" href="{% url 'adminpanel:delete-widget' pk=widget.pk %}">Delete</a>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}
Adminpanel app views.py:
from django.shortcuts import render
from adminpanel.forms import WidgetForm
from adminpanel.models import Widget
from django.utils import timezone
from django.contrib.auth import authenticate,login,logout
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse,reverse_lazy
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from braces.views import SelectRelatedMixin
from django.views.generic import (TemplateView,ListView,
DetailView,CreateView,
UpdateView,DeleteView)
# Create your views here.
class CreateWidgetView(LoginRequiredMixin,CreateView):
login_url = '/login/'
redirect_field_name = 'index.html'
form_class = WidgetForm
model = Widget
def form_valid(self,form):
self.object = form.save(commit=False)
self.object.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('adminpanel:widgets')
class SettingsListView(ListView):
model = Widget
ordering = ['widget_order']
class DeleteWidget(LoginRequiredMixin,SelectRelatedMixin,DeleteView):
model = Widget
select_related = ('Widget',)
success_url = reverse_lazy('adminpanel:widget')
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(user_id=self.request.user.id)
def delete(self,*args,**kwargs):
return super().delete(*args,**kwargs)
Project url spy:
from django.conf.urls import url
from django.contrib import admin
from django.conf.urls import include
from accounts import views
from colorsets import views
from colors import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$',views.home,name='index'),
url(r'^accounts/',include('accounts.urls',namespace='accounts')),
url(r'^colorsets/',include('colorsets.urls',namespace='colorsets')),
url(r'^adminpanel/',include('adminpanel.urls',namespace='adminpanel')),
]
Adminpanel app urls.py:
from django.conf.urls import url
from adminpanel import views
app_name = 'adminpanel'
urlpatterns = [
url(r'^widgets/',views.SettingsListView.as_view(),name='widgets'),
url(r'^new/$',views.CreateWidgetView.as_view(),name='create-widget'),
url(r'^delete/(?P<pk>\d+)/$',views.DeleteWidget.as_view(),name='delete-widget'),
]
EDIT: Here is the error I'm getting I forgot to add it.
FieldError at /adminpanel/delete/10/
Cannot resolve keyword 'user_id' into field. Choices are: body, id, name, widget_order
and the traceback points to this:
/Users/garrettlove/Desktop/colors/adminpanel/views.py in get_queryset
return queryset.filter(user_id=self.request.user.id) ...
▶ Local vars
Adminpanel app models.py (widget model):
from django.db import models
from adminpanel.choices import *
# Create your models here.
class Widget(models.Model):
name = models.CharField(max_length=50)
widget_order = models.IntegerField(blank=False,unique=True)
display_name = models.IntegerField(choices=WIDGET_NAME_CHOICES,default=1)
body = models.TextField(max_length=500)
def __str__(self):
return self.name
As your error is saying:
Cannot resolve keyword 'user_id' into field.
And it points to this line:
return queryset.filter(user_id=self.request.user.id)
It's that your queryset model does not have a user_id field. In your DeleteWidget view you have specified model = Widget
and as you see in your Widget model, you have the following fields: body, id, name, widget_order. There is no user_id field.
You need to change your filtering logic. If you want to have a related User model, you should use ForeignKey relation and then you can filter by the User's id.
In your models.py:
from django.contrib.auth.models import User
class Widget(models.Model):
# ...
user = models.ForeignKey(User, on_delete=models.CASCADE)
And then in your DeleteWidget's get_queryset, you can do the following:
return queryset.filter(user__id=self.request.user.id)
# __________________________^
# Note that there is a double underscore here!
# This is because Django uses double underscore for accessing related models in queries.

Categories

Resources