Django, Modelform not rendering - python

I created a basic Modelform in django but my form does not render.
tasks.html
<table class="...">
<tr>
<th>Function</th>
</tr>
<tr>
<td>
<form method="POST">
{% csrf_token %}
{{ form }}
<button type="submit">Execute</button>
</form>
</td>
</tr>
</table>
models.py
class Tasks(models.Model):
#Task module
function_name = models.CharField(max_length=30, default='')
script_location = models.CharField(max_length=300, default='')
forms.py
from django.forms import ModelForm
from .models import Tasks
class Tasks(ModelForm):
class Meta:
model = Tasks
fields = ['function_name','script_location']
views.py
class Tasks(View):
def get(self, request):
form = Tasks()
return render(request, '.../tasks.html', {'form': form})
I except to see two text fields, but i only see the 'Execute' button

Short answer: avoid name clashes. Add suffixes to your form and view.
You named the model Tasks, the form Tasks and the view Tasks. Here the Tasks() call in your view will resolve to that view, so indeed:
class Tasks(View):
def get(self, request):
form = Tasks()
return render(request, '.../tasks.html', {'form': form})
Here you thus simply create a new Tasks object (the one that is a subclass of View).
I strongly recommend to rename your classes:
a model usually has no Model suffix, so you can keep this Task (singular);
a form can use a Form suffix, so TaskForm; and
your view can be renamed to TaskView.
After renaming, you thus can rewrite your view to:
class TaskView(View):
def get(self, request):
form = TaskForm()
return render(request, '.../tasks.html', {'form': form})
Here however, it looks that you basically implement a CreateView [Django-doc], like:
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView
class TaskView(CreateView):
model = Task
fields = ('function_name','script_location')
template_name = '.../tasks.html'
success_url = reverse_lazy('certain-view')
This will for a GET request render the template with a form in the context. It will impelement a POST request as well that will create a model object and save it to the database, and then make a redirection to the certain-view. So it will implement a lot of boilerplate code for you.

change your forms.py
from django.forms import ModelForm
from .models import Tasks
class TasksForms(ModelForm):
class Meta:
model = Tasks
fields = ('function_name','script_location',)
add url in your apps urls.py
path('your_url/', views.TaskView, name='task'),
change your views.py
def TaskView(request) :
def get(self, request):
form = TasksForms()
return render(request, '.../tasks.html', {'form': form})
don't forget to add action of your form in tasks.html

Related

Django forms: cannot access local variable 'form' where it is not associated with a value

Condition: I have a model, created an empty table in the database, and I'm trying to create an html form that will fill in the fields of the corresponding columns of the table.
And here's what my app looks like:
models.py
from django.db import models
class Cities(models.Model):
city = models.CharField(max_length=100)
def __str__(self):
return self.state
class Routes(models.Model):
route_name = models.CharField(max_length=50, default='Route')
lvl = models.IntegerField(default=0)
about = models.TextField(max_length=1500)
total_distance = models.IntegerField(default=0)
city = models.ForeignKey(Cities, on_delete=models.CASCADE)
forms.py
from django.forms import ModelForm
from .models import Routes
class RouteForm(ModelForm):
class Meta:
model = Routes
fields = '__all__'
views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponse
from routes_form.forms import RouteForm
def getAbout(request):
if request.method == 'POST':
form = RouteForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'routes_form/form_page.html', {'form': form})
form.html
<form method="post">
{% csrf_token %}
<legend>
<h2>About</h2>
</legend>
{{ form }}
<input type="text" placeholder="Write more about the route: about waypoints, points of interest and warnings.">
<input type="submit" value="Send route">
</form>
I have already tried to do everything as indicated in the Django Forms documentation. But still something is wrong. Even at the moment of starting the server, it writes an error:
cannot access local variable 'form' where it is not associated with a value
It is because you haven't defined form for GET method so:
def getAbout(request):
if request.method == 'POST':
form = RouteForm(request.POST)
if form.is_valid():
form.save()
return redirect('some_view_name_to_redirect')
else:
form=RouteForm()
return render(request, 'routes_form/form_page.html', {'form': form})
Note: Models in Django are written in singular form, as Django itself add s as the suffix, so it is better to name the models as City and Route.
Here you passed form = RouteForm(request.POST) object for POST request you need to pass for GET request so, when def getAbout(request) function called with GET request then renders it like this ...
def getAbout(request):
form=RouteForm() # <---- called at GET request
if request.method == 'POST':
form = RouteForm(request.POST) # <---- called at POST request
if form.is_valid():
form.save()
return redirect("/")
return render(request, 'routes_form/form_page.html', {'form': form})

django: unable to save new user

I'm trying to build a view that shows a form for creating a new user.
Next are the template I'm using, urls.py and the code attempts at views.py and forms.py.
user_form.html
<form action="{% url 'app:register' %}" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Register">
</form>
urls.py
urlpatterns = [
...
path('newuser/',views.NewUser.as_view(), name='newuser'),
...
]
First attempt:
views.py
class NewUser(generic.CreateView):
model = User
fields = ['username','email','password']
template_name = 'app/user_form.html'
success_url = reverse_lazy('register')
This one didn't return errors, but was unable to save the new user data.
Second try involved creating a form for the occasion:
forms.py
class NewUserForm(forms.Form):
username = forms.CharField(max_length=100)
email = forms.EmailField()
password = forms.PasswordInput()
views.py
class NewUser(generic.CreateView):
model = User
form_class = NewUserForm
template_name = 'app/user_form.html'
success_url = reverse_lazy('register')
This one returned an error: TypeError at /newuser/ BaseForm.__init__() got an unexpected keyword argument 'instance'
Third:
forms.py
class NewUserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username','email','password']
views.py
class NewUser(generic.CreateView):
model = User
form_class = NewUserForm
template_name = 'app/user_form.html'
success_url = reverse_lazy('cadastro')
Can't save a new User instance either.
I've also noticed that the model = User line in this last try can be removed.
I'm using the User.objects.all() query in the terminal and the admin page to check for new users.
What am I not doing?
You should make the POST request to the newuser view, so:
<form action="{% url 'app:newuser' %}" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Register">
</form>
You can however not use a simple ModelForm: passwords in Django are hashed and the ModelForm will not do that, or at least not automatically.
You can make use of the UserCreationForm [Django-doc] to do the hashing properly, this field also uses two password fields which will be validated.
If you want to implement a custom ModelForm, then you will need to implement the password hashing functionality in the model form:
class NewUserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username','email','password']
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data['password'])
if commit:
user.save()
return user
and then plug this into the CreateView with:
class NewUserView(generic.CreateView):
model = User
form_class = NewUserForm
template_name = 'app/user_form.html'
success_url = reverse_lazy('cadastro')
Note: In Django, class-based views (CBV) often have a …View suffix, to avoid a clash with the model names.
Therefore you might consider renaming the view class to NewUserView, instead of NewUser.
when you use CreateView on a model, it calls create method.
but User model is different. User model doesn't have create, instead have create_user. and CreateView doesn't know about it.
i suggest that you use View class like this:
class UserRegisterView(View):
form_class = UserRegistrationForm
template_name = 'account/register.html'
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated:
return redirect('home:home')
return super().dispatch(request, *args, **kwargs)
def get(self, request):
form = self.form_class()
return render(request, self.template_name, {'form':form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
cd = form.cleaned_data
User.objects.create_user(cd['username'], cd['email'], cd['password1'])
messages.success(request, 'you registered successfully', 'success')
return redirect('home:home')
return render(request, self.template_name, {'form':form})
this snippet totally works.

Django Form in ListView, error on POST of Form

Im trying to implement a chat-function for my website. In order to do that, i followed the following tutorial: https://channels.readthedocs.io/en/latest/tutorial/
I've then changed the code a little bit in order to implement it. Until here, everything works just fine. Now I want to store the form-data inside of a database, and thats where the problem appears.
But first my code:
urls.py:
from django.urls import path
from .views import ChatOverView
urlpatterns = [
path('<int:pk>/', ChatOverView.as_view(), name='chat-explicit'),
path('', ChatOverView.as_view(), name='chat-home'),
]
views.py (theres much code here that is probably not needed for this question, but since i dont know what part of it i can ignore, im just posting the whole file-content):
from django.views.generic import ListView
from django.views.generic.edit import FormMixin, FormView
from django.db.models import Q
from django.urls import resolve
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from .models import Message
from .forms import MessageRegisterForm
class ChatOverView(ListView, FormMixin):
model = Message
template_name = 'chat/home-chat.html'
form_class = MessageRegisterForm
success_url = '/thanks/'
def form_valid(self, form):
form = self.get_form()
if form.is_valid():
data = form.cleaned_data
return super().form_valid(form)
def get_context_data(self, *args, **kwargs):
context = super(ChatOverView, self).get_context_data(*args, **kwargs)
messages_raw = reversed(Message.objects.filter(Q(sender=self.request.user) | Q(receiver=self.request.user)))
messages = {}
for mes in messages_raw:
# i am receiver
if mes.sender != self.request.user:
if mes.sender in messages:
messages[mes.sender].append({"received": mes})
else:
messages.update({mes.sender: [{"received": mes}]})
# i sent
else:
if mes.receiver in messages:
messages[mes.receiver].append({"sent": mes})
else:
messages.update({mes.receiver: [{"sent": mes}]})
active_user = self.get_active_chat(messages)
chatroom_name = self.get_chatroom_name(active_user)
context.update(messages_data=messages, active=active_user, roomname=chatroom_name)
return context
def get_chatroom_name(self, active_chat):
# my convention
ids = [active_chat.id, self.request.user.id]
ids.sort()
return str(ids[0]) + '_' + str(ids[1])
def get_active_chat(self, messages):
url_name = resolve(self.request.path_info).url_name
if url_name == "chat-home":
return list(messages.keys())[0]
else:
pk_user = self.request.build_absolute_uri().split("/")[-2]
user = get_object_or_404(User, pk=pk_user)
return user
home-chat.html:
{% extends "solawi/base.html" %}
{% load define_dictfilters %}
{% block content %}
<form class="bg-light" method="post">
<div class="input-group">
{% csrf_token %}
{{ form }}
<div class="input-group-append">
<button type="submit" id="chat-message-submit" value="enter">send</button>
</div>
</div>
</form>
{% endblock content %}
models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Message(models.Model):
sender = models.ForeignKey(User, related_name="sender", on_delete=models.CASCADE)
receiver = models.ForeignKey(User, related_name="receiver", on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.sender.username + ' to ' + self.receiver.username
The rest of the files/settings entrys are the same as in the tutorial linked above.
Now comes the problem: when submitting the form, i get the following error:
Method Not Allowed (POST): /chat/
Method Not Allowed: /chat/
HTTP POST /chat/ 405 [0.00, 127.0.0.1:54424]
How can i fix it?
Thank you for your Help!!
ListView implements a get() method, but no post() method. You need to implement a post() method in order for the view to allow POST requests.
You could subclass django.views.generic.ProcessFormView to get this, or, if you really need a ListView, then you can add a post() method to the class which handles the form validation and whatever else you need to do. Here is how ProcessFormView implements it:
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)

Django: How to use more than 2 Class Based Generic Views in one template

I was trying to create a web app in which a user can see other posts while updating a post. So, for that, I want to use both ListView And UpdateView together in the same template.
My Views.py:
from django.shortcuts import render
from .models import Entry
from django.views.generic import ListView
from django.views.generic.edit import UpdateView
from django.contrib.auth.models import User
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class index(LoginRequiredMixin, ListView):
template_name = 'diary/index.html'
context_object_name = 'entries'
def get_queryset(self): # def get_queryset(self, request):
return Entry.objects.filter(author=self.request.user)
class EntryUpdate(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Entry
fields = ['title', 'content']
template_name = 'diary/update.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
else:
return False
I don't know if I should create another view, or that there is an inbuilt functionality for this, so, it would be really helpful if you guys can help me out.
Any Help Would Be Appreciated!
EDIT:
My ListView Code In views.py:
class index(LoginRequiredMixin, ListView):
template_name = 'diary/index.html'
context_object_name = 'entries'
def get_queryset(self):
return Entry.objects.filter(author=self.request.user)
My UpdateView in views.py:
class EntryUpdate(LoginRequiredMixin, MultipleObjectMixin,UserPassesTestMixin, UpdateView):
model = Entry
fields = ['title', 'content']
template_name = 'diary/update.html'
def get_queryset(self):
return Entry.objects.filter(author=self.request.user)
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
else:
return False
The Error, I'm getting:
'EntryUpdate' object has no attribute 'object_list'
You could perhaps try a MultipleObjectMixin in your UpdateView.
You can define with this mixin get_queryset() and access object_list in the template. Check out the documentation for more info
EDIT
Sure, here's a short code example:
# demo/models.py
from django.db import models
class Title(models.Model):
title = models.CharField(max_length=100)
# demo/views.py
from django.views.generic import UpdateView
from django.views.generic.list import MultipleObjectMixin
from demo.models import Title
class UpdateWithListView(UpdateView, MultipleObjectMixin):
model = Title
template_name_suffix = '_update_form_with_list'
fields = ['title']
object_list = Title.objects.all()
update_with_list_view = UpdateWithListView.as_view()
# my_project/urls.py
from django.contrib import admin
from django.urls import path
from demo.views import update_with_list_view
urlpatterns = [
path('<int:pk>', update_with_list_view),
path('admin/', admin.site.urls),
]
And the template:
demo/templates/demo/title_update_form_with_list.html
Current title: {{ object.title }}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update">
</form>
<p>All other titles:</p>
{% for title in object_list %}
<p>Title: {{ title.title }}</p>
{% endfor %}
And this is what I got in the template (I had 10 "titles" in my DB, each with random character):
EDIT 2
Regarding your edited question, you are missing the definition of "object_list" in your view, which is required by the MultipleObjectMixin.
Please note that in my code example, in views.py, I'm defining the object_list with the query that would populate the object_list. I believe that the error you receive is because the mixin is expecting to receive the object_list.
Please try adding:
# demo/views.py
# omitted imports
class UpdateWithListView(UpdateView, MultipleObjectMixin):
model = Title
template_name_suffix = '_update_form_with_list'
fields = ['title']
object_list = Title.objects.all() # make sure to define this with your query
update_with_list_view = UpdateWithListView.as_view()
If I'm not mistaken, the get_queryset() method is taking care of the object retrieval for the UpdateView, while object_list is relevant for the ListView.
Please try to add the object_list to your view and check if it solves the issue.

How to save inline formset user field in Django using views

I've been using this great post http://kevindias.com/writing/django-class-based-views-multiple-inline-formsets/ to setup my site. I was wondering how to save the user field automatically to an inline formset in views (I used the blockquote for changes to the original). The RecipeForm in (see also below for context)
self.object = form.save(commit=False)
self.object.owner = self.request.user
self.object.save()
saves nicely automatically but not the
ingredient_form.owner= self.request.user
I know Django suggests using BaseInlineFormSet, but most people suggest saving user field in views.py and not forms or models for many different reasons. I would appreciate any suggestions or answers. Here's the full code:
models.py
from django.db import models
class Recipe(models.Model):
owner = models.ForeignKey(User)
title = models.CharField(max_length=255)
description = models.TextField()
class Ingredient(models.Model):
owner = models.ForeignKey(User)
recipe = models.ForeignKey(Recipe)
description = models.CharField(max_length=255)
class Instruction(models.Model):
recipe = models.ForeignKey(Recipe)
number = models.PositiveSmallIntegerField()
description = models.TextField()
forms.py
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from .models import Recipe, Ingredient, Instruction
class RecipeForm(ModelForm):
class Meta:
model = Recipe
IngredientFormSet = inlineformset_factory(Recipe, Ingredient)
InstructionFormSet = inlineformset_factory(Recipe, Instruction)
views.py
from django.http import HttpResponseRedirect
from django.views.generic import CreateView
from .forms import IngredientFormSet, InstructionFormSet, RecipeForm
from .models import Recipe
class RecipeCreateView(CreateView):
template_name = 'recipe_add.html'
model = Recipe
form_class = RecipeForm
success_url = 'success/'
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates blank versions of the form
and its inline formsets.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet()
instruction_form = InstructionFormSet()
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
instruction_form=instruction_form))
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance and its inline
formsets with the passed POST variables and then checking them for
validity.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet(self.request.POST)
instruction_form = InstructionFormSet(self.request.POST)
if (form.is_valid() and ingredient_form.is_valid() and
instruction_form.is_valid()):
return self.form_valid(form, ingredient_form, instruction_form)
else:
return self.form_invalid(form, ingredient_form, instruction_form)
def form_valid(self, form, ingredient_form, instruction_form):
"""
Called if all forms are valid. Creates a Recipe instance along with
associated Ingredients and Instructions and then redirects to a
success page.
"""
self.object = form.save(commit=False)
self.object.owner = self.request.user
self.object.save()
ingredient_form.instance = self.object
ingredient_form.owner= self.request.user
ingredient_form.save()
instruction_form.instance = self.object
instruction_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, ingredient_form, instruction_form):
"""
Called if a form is invalid. Re-renders the context data with the
data-filled forms and errors.
"""
return self.render_to_response(
self.get_context_data(form=form,
ingredient_form=ingredient_form,
instruction_form=instruction_form))
I did some more research and the solution looks somewhat complex following this guide of how to add custom formset saving but modified for BaseInlineFormset as mentioned above. I realized it will be simpler just to make ModelForms for each Model and then linking them in a view, since I only need one child form at a time in the add a new recipe view and can reuse the ModelForm code.
here's the new code that works great! Feel free to contact if you need more info.
forms.py
from django.forms import ModelForm
from .models import Recipe, Ingredient, Instruction
class RecipeForm(ModelForm):
class Meta:
model = Recipe
exclude = ['owner',]
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
exclude = ['owner','recipe',]
class InstructionForm(ModelForm):
class Meta:
model = Instruction
exclude = ['recipe',]
views.py
from .forms import IngredientForm, InstructionForm, RecipeForm
def add_new_value(request):
rform = RecipeForm(request.POST or None)
iform = IngredientForm(request.POST or None)
cform = InstructionForm(request.POST or None)
if rform.is_valid() and iform.is_valid() and cform.is_valid():
rinstance = rform.save(commit=False)
iinstance = iform.save(commit=False)
cinstance = cform.save(commit=False)
user = request.user
rinstance.owner = user
rinstance.save()
iinstance.owner = user
cinstance.owner = user
iinstance.recipe_id = rinstance.id
cinstance.recipe_id = rinstance.id
iinstance.save()
cinstance.save()
return HttpResponseRedirect('/admin/')
context = {
'rform' : rform,
'iform' : iform,
'cform' : cform,
}
return render(request, "add_new_recipe.html", context)
template: add_new_recipe.html
<!DOCTYPE html>
<html>
<head>
<title>Add Recipe</title>
</head>
<body>
<div>
<h1>Add Recipe</h1>
<form action="" method="post">
{% csrf_token %}
<div>
{{ rform.as_p }}
{{ iform.as_p }}
{{ cform.as_p }}
</div>
<input type="submit" value="Add recipe" class="submit" />
</form>
</div>
</body>
</html>

Categories