Hello, I am writing a small project about a car shop and this is the problem I came up with.
I'm trying to add a new car and everything seems to work, but when I fill out the form and click submit, it just redirects me to products page without errors and without adding a new car to the database.
Here is the code.
views.py
class AddProductView(View):
action = 'Add'
template_name = 'myApp/manipulate_product.html'
context = {
}
form_class = ManipulateProductForm
def get(self, req, *args, **kwargs):
form = self.form_class()
self.context['action'] = self.action
self.context['form'] = form
return render(req, self.template_name, self.context)
def post(self, req, *args, **kwargs):
form = self.form_class(req.POST or None)
if form.is_valid():
form.save()
else:
print(form.errors)
return redirect('products', permanent=True)
models.py
class Car(models.Model):
name = models.CharField(max_length=32)
model = models.CharField(max_length=32, unique=True)
price = models.IntegerField(validators=[
MinValueValidator(0),
])
def __str__(self):
return f'{self.name} {self.model}'
forms.py
class ManipulateProductForm(forms.ModelForm):
def __init__(self, action="Submit", *args, **kwargs):
super().__init__(*args, **kwargs)
self.action = action
self.helper = FormHelper(self)
self.helper.add_input(Submit('submit', self.action, css_class='btn btn-primary'))
class Meta:
model = Car
fields = '__all__'
manipulate_product.html
{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
<div class="product-manipulate-container">
{% crispy form form.helper%}
</div>
{% endblock %}
I'm sure the problem is in Crispy, because if I replace code in forms.py and manipulate_product.html to this
forms.py
class ManipulateProductForm(forms.ModelForm):
class Meta:
model = Car
fields = '__all__'
manipulate_product.html
{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
<div class="product-manipulate-container">
<form action="" method="POST">
{% csrf_token %}
{{ form.as_div }}
<input type="submit" value="Submit">
</form>
</div>
{% endblock %}
Everything is working fine!
I noticed that when I use Crispy in AddProductView post method
is_valid() method returns False but without Crispy it returns True
I have tried everything except one delete the whole project and start over.
I searched on youtube , google , stackoverflow but didn't find anything similar.
Looked at the Crysp documentation, but it's also empty.
I hope someone has come across this problem and can help me.
Thank you!
Try rewriting your form like this:
class ManipulateProductForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ManipulateProductForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_action = 'Submit'
self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-primary'))
class Meta:
model = Car
fields = '__all__'
And in your template you can just do the following, since you used the default name of the helper:
{% crispy form %}
Related
I have a problem, the urls form works but I can't see the records in url/admin, can I ask for help, thank you :D
SOF wants me to add more details otherwise it doesn't transfer, I don't know what more I can add, generally temapals and urls work.
class Note(models.Model):
"""..."""
notes = models.CharField(max_length=100, unique=True)
description = models.TextField()
class Meta:
verbose_name = "Note"
verbose_name_plural = "Notes"
def __str__(self):
return self.notes
class NoteView(View):
def get(self, request):
if request.method == 'POST':
textN = Note.objects.all().order_by('notes')
form = NoteAddForm(request.POST)
if form.is_valid():
form.save()
return redirect('Files/menu')
else:
textN = NoteAddForm()
return render(request, 'Files/note.html', {'textN': textN})
class NoteAddForm(forms.ModelForm):
"""New note add form"""
class Meta:
model = Note
fields = '__all__'
{% extends 'Files/base.html' %}
{% block title %}Notatnik{% endblock %}
<h2>Notatnik Dietetyka/ Zalecenia ręczne </h2>
{% block content %}
<form action="/send/" method="post">
{% csrf_token %}
{{ textN }}
<label>
<input type="text" class="btn btn-second btn-lg">
<button>Wyślij formularz</button>
</label>
</form>
<button type="button" class="btn btn-primary btn-lg">Powrót</button>
{% endblock %}
Within your NoteView class in views.py file is where the issue is.
I see you have an if statement checking for if request.method == 'POST' within the class-based view get(). The get() is equivalent to if request.method == 'GET'. Therefore, what you might want to do is to override the post() on the class instead. For example:
class NoteView(View):
template_name = 'Files/note.html'
# Use the get method to pass the form to the template
def get(self, request, *arg, **kwargs):
textN = NoteAddForm()
return render(request, self.template_name, {'textN': textN})
# Use the post method to handle the form submission
def post(self, request, *arg, **kwargs):
# textN = Note.objects.all().order_by('notes') -> Not sure why you have this here...
form = NoteAddForm(request.POST)
if form.is_valid():
form.save()
# if the path is... i.e: path('success/', SucessView.as_view(), name='success')
return redirect('success') # Redirect upon submission
else:
print(form.errors) # To see the field(s) preventing the form from being submitted
# Passing back the form to the template in the name 'textN'
return render(request, self.template_name, {'textN': form})
Ideally, that should fix the issue you're having.
Updates
On the form, what I'd suggest having is...
# Assuming that this view handles both the get and post request
<form method="POST"> # Therefore, removing the action attribute from the form
{% csrf_token %}
{{ textN }}
# You need to set the type as "submit", this will create a submit button to submit the form
<input type="submit" class="btn btn-second btn-lg" value="Submit">
</form>
I make a site with multiple users, making posts with images and ability to add/remove friends.
So it's easy to make two different pages for post list and creating a new one. But of course it looks better when you can read posts and make new at the same place.
As I understand (learn django for less than a month), I can't connect 2 views to the same url, so the most logical way I see is to join 2 views in one, I also tried to play with template inheriting to render post form by including template, but actually it doesn't work.
Below you can see my views, Post model, and templates. Thank you for attention.
views.py:
from braces.views import SelectRelatedMixin
from . import models
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
class PostList(SelectRelatedMixin, generic.ListView):
model = models.Post
select_related = ('user',)
class CreatePost(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
models.py:
import misaka
class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE, related_name = 'posts')
posted_at = models.DateTimeField(auto_now = True)
post_message = models.TextField()
message_html = models.TextField(editable = False)
post_image = models.ImageField(upload_to = 'postpics', blank = True)
def __str__(self):
return self.post_message
def save(self, *args, **kwargs):
self.message_html = misaka.html(self.post_message)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('posts:all')
class Meta:
ordering = ['-posted_at']
unique_together = ['user', 'post_message']
urls.py:
app_name = 'posts'
urlpatterns = [
path('', views.PostList.as_view(), name = 'all'),
path('new/', views.CreatePost.as_view(), name = 'create'),
]
post_form.html (template, that allows to make a new post, which will be seen in post_list.html):
{% extends 'posts/post_base.html'%}
{% block post_content %}
<div class="post-form">
<form action="{% url 'posts:create' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.post_message }}</p>
<p>{{ form.post_image }}</p>
<input id='post-submit' type="submit" value="Post">
</form>
</div>
{% endblock %}
post_list.html:
{% extends 'posts/post_base.html'%}
{% block post_content %}
<div class="post-container">
{% for post in post_list %}
<div class="current-post-container">
{% include 'posts/_post.html'%}
</div>
{% endfor %}
</div>
{% endblock %}
_post.html(pages, which render by Misaka):
<div class="post-info">
<h5 id='post-owner' >{{post.user.first_name}} {{post.user.last_name}}</h5>
<h6>{{ post.posted_at }}</h6>
<p>{{ post.message_html|safe }}</p>
<div>
<img class='post-image' src="/media/{{ post.post_image }}" alt="">
<div>
{% if user.is_authenticated and post.user == user and not hide_delete %}
<a href="{% url 'posts:delete' pk=post.pk %}" title = 'delete'>Delete</a>
{% endif %}
</div>
</div>
</div>
post_base.html:
{% extends 'base.html' %}
{% block content%}
{% block prepost %}{% endblock %}
{% block post_content %}{% endblock %}
{% block post_post %}{% endblock %}
{% endblock %}
EDIT:
Task was solved. I added two template_name strings to both of my views, so now they look like:
CreatePost in views.py:
class CreatePost(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
PostList in views.py:
class PostList(SelectRelatedMixin, generic.ListView):
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
You can put the post_create_form on the same page as post_list_view there is no need to make a separate view for post creation but You need to make ones for editing and deleting.
You can give all of these views the same HTML page with different URLs.
Using template_name = 'example/example.html' ,in Class_Based_Views.
I hope I understand your problem if not clarify more why you can't join two views in one.
def posts(request):
posts = Post.objects.all()
form = PostForm(request.POST or None, request.FILES or None)
if request.method == "POST":
if form.is_valid():
...
context={
'posts' : page_obj,
'create_or_update_post_form' : form,
}
return render(request, 'post/posts.html',context)
Do you struggle to do this in Class-based-view?
You can do easily with django class based views.
Create views as
from django.views.generic import ListView
from django.views.generic.edit import CreateView
class ModelCreate(CreateView):
model = ModelName
fields = ['field1', 'field2']
template_name = 'same_page.html'
success_url = reverse_lazy('list_view')
class ModelList(CreateView, ListView):
model = ModelName
fields = ['field1', 'field2']
paginate_by = 5
template_name = 'same_page.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['form'] = self.get_form()
return context
# If form post redirect to ModelCreate View
def post(self, request, *args, **kwargs):
return ModelCreate.as_view()(request)
app/urls.py
from django.urls import path
from app import views
path('list', views.ModelList.as_view(), name='list_view'),
path('add', views.ModelCreate.as_view(), name='add_view'),
Finally in templates/same_page.html
<div class="row">
<div class="col-sm-5">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
<button type="submit" value="submit" class="btn btn-primary btn-sm float-right">Submit</button>
</form>
</div>
<div class="col-sm-5">
{% if object_list %}
{% for object in object_list %}
<p>{{object.field1}}</p>
<p>{{object.field2}}</p>
{% endfor %}
{% endif %}
</div>
</div>
Hope, this helps.
I was able to save before without an issue using django.views.generic.CreateView. However, I can't save the same form using View.
Below are my codes:
models.py
class Project(models.Model):
name = models.CharField(max_length=200, verbose_name='Project Name')
user = models.ForeignKey(User,
on_delete=models.CASCADE,
blank=True,
null=True)
client = models.ForeignKey(Client,
on_delete=models.SET_NULL,
blank=True,
null=True)
deadline = models.DateTimeField(blank=True, null=True)
views.py
from django.views.generic import View
class ProjectCreateView(LoginRequiredMixin, View):
form_class = ProjectCreateForm
success_url = reverse_lazy('dashboard')
template_name = 'translation/project_form.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
project_form = self.form_class(request.POST, instance=request.user)
if project_form.is_valid():
project_form.save()
return redirect(self.success_url)
else:
return render(request, self.template_name)
html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container mt-5 py-2 w-50 bg-dark text-white">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-success mt-3">Confirm</button>
</form>
</div>
{% endblock content %}
urls.py
path('project/new', views.ProjectCreateView.as_view(), name='project-add'),
When I submit the form in html, it doesn't render any error message. It simply redirects to the self.success_url and the intended object is not saved.
Any help or advice will be much appreciated. Thank you!
Edit:
Forgot to include the codes for the Form:
forms.py
class ProjectCreateForm(forms.ModelForm):
class Meta:
model = Project
widgets = {
'deadline' : forms.DateInput(attrs={'type':'date'})
}
fields = ['name', 'client', 'deadline']
I've this template in my Django application for adding a training session:
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<h1>New session</h1>
<form action="" method="post">{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-success" type="submit" value="Save" />
</form>
<p />
{% endblock content %}
The form contains a datetime field which appears as follows:
Is it possible to change this so instead of entering the datetime as text it can be selected from a calendar type icon? If so, how is this done?
This is my view:
class SessionCreateView(CreateView):
model = ClubSession
template_name = 'session_new.html'
fields = ['location', 'coach', 'date', 'details']
This is my model:
class ClubSession(models.Model):
location = models.CharField(max_length=200)
coach = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
date = models.DateTimeField(default=now)
details = models.TextField()
def __str__(self):
return self.location
def get_absolute_url(self):
return reverse('session_detail', args=[str(self.id)])
With crispy forms, I think you will need a form for this.
class ClubSessionForm(forms.ModelForm):
class Meta:
model = ClubSession
fields = ['location', 'coach', 'date', 'details']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['date'].widget.attrs.update({'type': 'datetime-local'})
class SessionCreateView(CreateView):
model = ClubSession
form_class = ClubSessionForm
template_name = 'session_new.html'
Docs
Keep in mind that not all browsers support <input type="datetime-local"> - I believe only Chrome and Opera do. If you need it working in all browsers, you'll need a JS solution.
I am getting this error when i visit my page:
Caught AttributeError while rendering: 'WSGIRequest' object has no attribute 'get'
The error kicks in on line "17" of my html, which is the line that outputs form.as_p
The html looks like this:
{% extends "base.htm" %}
{% block content %}
{% if story_list %}
{% for story in story_list %}
<div class="Story">
{{ story.title }}
</div>
{% endfor %}
{% else %}
<p>No stories are present - enter one below</p>
{% endif %}
<h3>Create a new story</h3>
<form action="/addStory" method="post">
{% csrf_token %}
{{ form.as_p }} ***THIS IS LINE 17***
<input type="submit" value="Submit"/>
</form>
{% endblock %}
The problem is i have a view that does two things, and from the django tutorials overrode the get_context_data method to add the second item to the django context. Because, um, that's what i'm meant to do, riiiiiiiiight?
#for showing of the stories!
class StoryShowView(ListView):
model = StoryForm
def get_queryset(self):
return getStoryItemsForUser(self.request)
def get_context_data(self, **kwargs):
context = super(StoryShowView,self).get_context_data(**kwargs)
context['form'] = createNewStoryForm(self.request)
return context
Where, well, the method createNewStoryForm just does this:
def createNewStoryForm(request):
return StoryForm(request)
and StoryForm is just this:
class StoryForm(ModelForm):
class Meta:
model = Story
ordering = ['create_date']
and the Story model is a normal model, that probably isn't part of the problem, but, hey, i am a cutting and a pasting, so here goes!
class Story(models.Model):
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
title = models.CharField(max_length=100)
is_closed = models.BooleanField()
is_random = models.BooleanField() # for uncategorised stories. Only one of these.
result = models.CharField(max_length=20) #how did the relo work out?
create_date = models.DateTimeField('date created')
def __unicode__(self):
return self.title
Any ideas what i am doing wrong?
UPDATE:
ah, it was the line::
return StoryForm(request)
I take it i can either pass in a "request.POST" or nothing, is that it?
Probably you're right and you were passing request instead of request.POST, reqest.GET or request.REQUEST to the constructor of your form. See the doc on how to use forms:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
Two problems that I can see. The easy one being that you can simply replace this line:
context['form'] = createNewStoryForm(self.request)
with
context['form'] = StoryForm(request.POST, request.FILES)
Finally shouldn't this:
class StoryShowView(ListView):
model = StoryForm
Be:
class StoryShowView(ListView):
model = Story