I'll address this question using a base setup:
# models.py
class MyModel(models.Model):
required_field = models.CharField("some label", max_length=10)
another_required_field = models.CharField("some label", max_length=10)
checkbox = models.BooleanField("some label")
# forms.py
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
# views.py
class MyView(FormView):
form_class = MyForm
template_name = 'some-template.html'
Now suppose I check the checkbox and fill just one of the required fields. The form obviously doesn't pass validation and gets back with errors and all. Problem is, the value of the checkbox comes back unchecked. This is not a big deal with just one BooleanField, but I'm working on a project where I have tons of checkboxes. Check them all from scratch is rather frustrating.
So I had a check on django's documentation and stumbled upon this paragraph regarding BooleanFields:
Since all Field subclasses have required=True by default, the validation condition here
is important. If you want to include a boolean in your form that can be either True or
False (e.g. a checked or unchecked checkbox), you must remember to pass in
required=False when creating the BooleanField.
And I did this:
# forms.py
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
for field in self.fields:
if isinstance(field, forms.CheckboxInput):
self.fields[field].required = False
class Meta:
model = MyModel
but it didn't work. Again, checkboxes lose their state after the form didn't pass validation, so I guess that was not what I was looking for.
So my question is, is there a way to achieve that? I'm pretty sure there should be one, it would be great if some of you could at least drive me in the right direction. Thanks :-)
EDIT
After a bit of debugging, I solved the issue. Turns out I was using a custom template for crispy forms checkboxes, and I found a little bug in there.
Your view would need to populate the form from the request.POST dictionary as such:
def your_view(request):
form = MyForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.save()
# do whatever esle
return render(request, 'your-template.html', {'form': form})
Unless you pass the request.POST data, and/or an instance of the model you're editing, your form will be un-bound, and therefore not show any values that exist either in the POST data or from your model. If you're editing an instance, it would look like:
def your_view(request, id):
my_model_instance = MyModel.objects.get(pk=id)
form = MyForm(request.POST or None, instance=my_model_instance)
if request.method == 'POST' and form.is_valid():
form.save()
# do whatever esle
return render(request, 'your-template.html', {'form': form})
maybe the problem is on your view:
view exemple:
def view(request):
if request.method == 'POST':#bound the form wit data from request.Post
form = MyForm(request.POST)
if form.is_valid():
#do somthing
form.save()
#if form not valid render the page.html with form that has request.Post data
return render(request,'some-template.html',{'form': form})
else:
form = MyForm()
return render(request, 'some-template.html',{'form': form})
Related
I am doing an online classroom project in Django where I created a model named create_course which is accessible by teachers. Now I am trying to design this as the teacher who creates a class only he can see this after login another teacher shouldn't see his classes and how to add students into that particular class I created
the course model
class course(models.Model):
course_name = models.CharField(max_length=200)
course_id = models.CharField(max_length=10)
course_sec = models.IntegerField()
classroom_id = models.CharField(max_length=50,unique=True)
created_by = models.ForeignKey(User,on_delete=models.CASCADE)
here if I use "the created_by" field in forms it appears to be a drop-down menu where every user is showing but I want to automatically save the user who creates the object
views.py
def teacher_view(request, *args, **kwargs):
form = add_course(request.POST or None)
context = {}
if form.is_valid():
form.save()
return HttpResponse("Class Created Sucessfully")
context['add_courses'] = form
return render(request, 'teacherview.html', context)
forms.py
from django import forms
from .models import course
class add_course(forms.ModelForm):
class Meta:
model = course
fields = ('course_name', 'course_id', 'course_sec', 'classroom_id')
You can inject the logged in user to the .created_by of the .instance in the form, so:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def teacher_view(request, *args, **kwargs):
if request.method == 'POST':
form = add_course(request.POST, request.FILES)
if form.is_valid():
form.instance.created_by = request.user
form.save()
return redirect('name-of-some-view')
else:
form = add_course()
return render(request, 'teacherview.html', {'add_courses': form})
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: Usually a Form or a ModelForm ends with a …Form suffix,
to avoid collisions with the name of the model, and to make it clear that we are
working with a form. Therefore it might be better to use CourseForm instead of
add_course.
Note: Models in Django are written in PascalCase, not snake_case,
so you might want to rename the model from course to Course.
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
In your view use commit=False to stop the form from saving until you add the created_by field.
def teacher_view(request, *args, **kwargs):
form = add_course(request.POST or None)
context = {}
if form.is_valid():
course = form.save(commit=False)
course.created_by = request.user
course.save()
return HttpResponse("Class Created Sucessfully")
context['add_courses'] = form
return render(request, 'teacherview.html', context)
I am writing a django ListView with FormMixin, but it can't handle form errors. The model limits input to 140 characters, when I inspect and change limit to 200 and submit I get
'PostListView' object has no attribute 'object_list'"
Here's the code
class PostListView(FormMixin, generic.ListView):
model = Post
form_class = PostForm
paginate_by = 10
template_name = 'index.html'
def get_success_url(self):
return reverse('index')
def post(self, request, *args, **kwargs):
form = PostForm(request.POST)
if form.is_valid():
form.save()
return super().form_valid(form)
else:
return self.form_invalid(form)
With everything working normally, it saves the data and displays the list. On error, no error, it fails.
EDIT
As #crimsonpython24 has said, ListView is for displaying data. I opted to use a basic view
def index(request):
'''deal with post method first'''
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse('index'))
else:
form = PostForm
posts = Post.objects.all()
return render(request, 'index.html', {'form':form, 'posts':posts})
This allows for invalid form data to be returned for correction, and also allows viewing of posts
The point is that ListView is only supposed to view objects. If you have a form in your view, try to go for one of the edit views, which lets you create, update, and delete (now I'm assuming that you also handle a form in this view).
I can't exactly describe how ListView causes your problem other than it does not fit your purpose and there are better alternatives.
EDIT
Now you're concatenating a FormView and ListView. However, I will still recommend going for a FormView as a ListView doesn't have a form_class attribute. It's easy, though. Let's say you have this FormView class:
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm # Now you can simply do this to your form
success_url = '/thanks/'
def form_valid(self, form):
form.send_email()
return super().form_valid(form)
and then simply pass in context data to make it behave partially like a ListView:
class ContactView(FormView):
# whatever you have here already
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
return context
So now your new view will render both your form and context data. Seeing as both these views don't require a primary key to operate, I don't see any problem implmenting this.
after uploading the image to a form when i submit the form it still shows "the field is required" error
in models.py:
class Members(models.Model):
image=models.ImageField(upload_to="images/",default='')
in forms.py:
class CreateOne(forms.Form):
image=forms.ImageField(required=True)
in create.html:
<form class="form-inline" action="create" method="POST"
enctype="multipart/form-data" novalidate>
in views.py:
def create(request):
member = Members(image=request.FILES['image'])
if request.method == 'POST':
form = forms.CreateOne(request.POST)
if form.is_valid():
member.save()
return redirect('/')
else:
form = CreateOne()
I think you understand the way to use a Form the wrong way. The idea of such Form when you write some_form.save() it makes the changes (and for example create a model instance and save it to the database).
Since most forms (like probably this one) are related to a single model, Django has a ModelForm, which offers extra convenience:
class CreateOne(forms.ModelForm):
image=forms.ImageField(required=True)
class Meta:
model = Members
fields = ['image']
Then we can make a view that will create an instance in case of a valid form with:
def create(request):
if request.method == 'POST':
form = forms.CreateOne(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('/')
else:
form = CreateOne()
return render(request, 'some_template.html', {'form': form}
You probably also do not want to redirect in case the form was not valid, since then you probably want to give the user feedback that the form was not valid (and show the corresponding errors).
I want to create a view function + template that displays a simple form (derived from a user model) and also captures the form submission. How do I do this using generic views in Django?
My user model is:
class User(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
I only need to capture the email field in the form.
I think there must be a simple way to do this using generic forms, however I'm not sure which one to use nor how to do it. The only other ways I know how to do it are:
1) Create UserForm explicitly and a single view function separating POST and GET requests. E.g., :
def contact(request):
if request.method == 'GET':
# display UserForm
....
elif request.method == 'POST':
# process form submission
....
2) Create two views (with seperate URLs) - one using generic view to display form and another view to receive form submission e.g.,:
class contact(generic.DetailView):
# display form from User model
model = User
....
def submit(request):
# process form submission
....
So, my two questions are:
can and how should this be implemented using ONLY a generic view?
which generic view should be used?
First part of the answer: use a single view. If you use a function view (which is by far the simplest solution), the canonical form-handling edit view looks like:
def myview(request, instance_id, ...):
instance = get_object_or_404(pk=instance_id)
if request.method == "POST":
form = MyForm(request.POST, ..., instance=instance)
if form.is_valid():
# assuming a ModelForm
form.save()
return redirect(somewhere)
# invalid forms will be re-rendered with the error messages
else:
form = MyForm(instance=instance)
return render(request, "myapp/mytemplate.html", {"form": form})
For a create view, you just remove all the instance_xxx parts. Or you can use the same view for both create and update making the instance_id optional:
def myview(request, instance_id=None, ...):
if instance_id is not None:
instance = get_object_or_404(pk=instance_id)
else:
instance = None
if request.method == "POST":
form = MyForm(request.POST, ..., instance=instance)
if form.is_valid():
# assuming a ModelForm
form.save()
return redirect(somewhere)
# invalid forms will be re-rendered with the error messages
else:
form = MyForm(instance=instance)
return render(request, "myapp/mytemplate.html", {"form": form})
If you want a class-based generic view they are documented here. I personally don't think there's much to gain from generic class-based views (except eventually headaches when you try to grasp the execution flow scattered amongst half a dozen base classes and mixins) but YMMV.
update
if I want to do some processing on the data (including adding in extra fields) before saving an instance to the DB, where would I do this?
Preferably in the form itself unless you need some other data that you don't want to pass to the form. For all forms you can process data at the validation stage. With a ModelForm you can also override the save() method itself:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('whatever', 'something')
def save(self, commit=True):
""" Save user and create a pro account """
instance = super(MyModelForm, self).save(commit=False)
instance.something_else = 42
if commit:
instance.save()
return instance
CreateView can work perfectly according to your requirements, You only need to create a form of contact models and success_url where user will redirect after form submission. It'll save automatic user data into models
class ContactView(generic.CreateView):
form_class = ContactForm
template_name = 'contact/index.html'
success_url = '/homepage/' . #Mention URL here
This can also be done using only CreateView.Specify email in fields as you need only the email field in the form.You can also process submitted form in form_valid method.
class UserCreate(CreateView):
model = User
fields = ['email']
success_url = '/your_success_url/'
#transaction.atomic
def form_valid(self, form):
new_user = form.save(commit=False)
# process your submitted form here.
# eg. add any extra fields as:
# new_user.something = something
new_user.save()
return super().form_valid(form)
So, the first part is pretty clear.
customer = Customer.objects.get(pk=1)
customer.pk = None
customer.save() # Saved a new instance.
# But i want to modify it
The problem here is that i want to modify that instance, before saving. for that i have to render it on the form in HTML.
How to achieve that?
Suggestions needed.
Greetings.
You modify the pk after the form has been submitted.
You pass customer as instance to a CustomerForm and let the form save a new object. Something like:
class CustomerForm(forms.ModelForm):
class Meta:
model = Customer
def my_view(request):
customer = Customer.objects.get(pk=1)
customer.pk = None
if request.method == 'POST':
form = CustomerForm(instance=customer)
if form.is_valid():
customer = form.save()
return redirect('...')
else:
form = CustomerForm(instance=customer)
return render(request, 'template', {'form': form})